blob: fa7241f29e5cc43e29dbab2ee3ea5eafcfb41fc8 [file] [log] [blame]
Nico Weber077f1a32015-08-06 15:08:57 -07001# Copyright 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Presubmit script for pdfium.
6
7See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8for more details about the presubmit API built into depot_tools.
9"""
10
Lei Zhangac238df2021-06-15 21:44:05 +000011USE_PYTHON3 = True
12
Dan Sinclair22d66072016-02-22 11:56:05 -050013LINT_FILTERS = [
Dan Sinclair3ebd1212016-03-09 09:59:23 -050014 # Rvalue ref checks are unreliable.
dan sinclaird2019df2016-02-22 22:32:03 -050015 '-build/c++11',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050016 # Need to fix header names not matching cpp names.
dan sinclaird2019df2016-02-22 22:32:03 -050017 '-build/include_order',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050018 # Too many to fix at the moment.
dan sinclaird2019df2016-02-22 22:32:03 -050019 '-readability/casting',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050020 # Need to refactor large methods to fix.
dan sinclaird2019df2016-02-22 22:32:03 -050021 '-readability/fn_size',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050022 # Lots of usage to fix first.
dan sinclaird2019df2016-02-22 22:32:03 -050023 '-runtime/int',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050024 # Lots of non-const references need to be fixed
dan sinclaird2019df2016-02-22 22:32:03 -050025 '-runtime/references',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050026 # We are not thread safe, so this will never pass.
dan sinclaird2019df2016-02-22 22:32:03 -050027 '-runtime/threadsafe_fn',
Dan Sinclair3ebd1212016-03-09 09:59:23 -050028 # Figure out how to deal with #defines that git cl format creates.
dan sinclaird2019df2016-02-22 22:32:03 -050029 '-whitespace/indent',
Dan Sinclair22d66072016-02-22 11:56:05 -050030]
31
Dan Sinclair544bbc62016-03-14 15:07:39 -040032
dsinclair2ca2da52016-09-13 18:10:34 -070033_INCLUDE_ORDER_WARNING = (
34 'Your #include order seems to be broken. Remember to use the right '
35 'collation (LC_COLLATE=C) and check\nhttps://google.github.io/styleguide/'
36 'cppguide.html#Names_and_Order_of_Includes')
37
38
Lei Zhang71e26732020-05-21 21:03:05 +000039# Bypass the AUTHORS check for these accounts.
40_KNOWN_ROBOTS = set() | set(
41 '%s@skia-public.iam.gserviceaccount.com' % s for s in ('pdfium-autoroll',))
42
Daniel Hosseinianb3bdbd42021-10-22 17:01:14 +000043_THIRD_PARTY = 'third_party/'
44
45# Format: Sequence of tuples containing:
46# * String pattern or, if starting with a slash, a regular expression.
47# * Sequence of strings to show when the pattern matches.
48# * Error flag. True if a match is a presubmit error, otherwise it's a warning.
49# * Sequence of paths to *not* check (regexps).
50_BANNED_CPP_FUNCTIONS = (
51 (
52 r'/\busing namespace ',
53 (
54 'Using directives ("using namespace x") are banned by the Google Style',
55 'Guide ( https://google.github.io/styleguide/cppguide.html#Namespaces ).',
56 'Explicitly qualify symbols or use using declarations ("using x::foo").',
57 ),
58 True,
59 [_THIRD_PARTY],
60 ),
61 (
62 'v8::Isolate::GetCurrent()',
63 (
64 'Avoid uses of v8::Isolate::GetCurrent(). Prefer holding a pointer to',
65 'the v8::Isolate that was entered.',
66 ),
67 False,
68 (),
69 ),
70)
71
72
73def _CheckNoBannedFunctions(input_api, output_api):
74 """Makes sure that banned functions are not used."""
75 warnings = []
76 errors = []
77
78 def _GetMessageForMatchingType(input_api, affected_file, line_number, line,
79 type_name, message):
80 """Returns an string composed of the name of the file, the line number where
81 the match has been found and the additional text passed as `message` in case
82 the target type name matches the text inside the line passed as parameter.
83 """
84 result = []
85
86 if input_api.re.search(r"^ *//",
87 line): # Ignore comments about banned types.
88 return result
89 if line.endswith(
90 " nocheck"): # A // nocheck comment will bypass this error.
91 return result
92
93 matched = False
94 if type_name[0:1] == '/':
95 regex = type_name[1:]
96 if input_api.re.search(regex, line):
97 matched = True
98 elif type_name in line:
99 matched = True
100
101 if matched:
102 result.append(' %s:%d:' % (affected_file.LocalPath(), line_number))
103 for message_line in message:
104 result.append(' %s' % message_line)
105
106 return result
107
108 def IsExcludedFile(affected_file, excluded_paths):
109 local_path = affected_file.LocalPath()
110 for item in excluded_paths:
111 if input_api.re.match(item, local_path):
112 return True
113 return False
114
115 def CheckForMatch(affected_file, line_num, line, func_name, message, error):
116 problems = _GetMessageForMatchingType(input_api, f, line_num, line,
117 func_name, message)
118 if problems:
119 if error:
120 errors.extend(problems)
121 else:
122 warnings.extend(problems)
123
124 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.cpp', '.h'))
125 for f in input_api.AffectedFiles(file_filter=file_filter):
126 for line_num, line in f.ChangedContents():
127 for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
128 if IsExcludedFile(f, excluded_paths):
129 continue
130 CheckForMatch(f, line_num, line, func_name, message, error)
131
132 result = []
133 if (warnings):
134 result.append(
135 output_api.PresubmitPromptWarning('Banned functions were used.\n' +
136 '\n'.join(warnings)))
137 if (errors):
138 result.append(
139 output_api.PresubmitError('Banned functions were used.\n' +
140 '\n'.join(errors)))
141 return result
142
Lei Zhang71e26732020-05-21 21:03:05 +0000143
Dan Sinclair544bbc62016-03-14 15:07:39 -0400144def _CheckUnwantedDependencies(input_api, output_api):
145 """Runs checkdeps on #include statements added in this
146 change. Breaking - rules is an error, breaking ! rules is a
147 warning.
148 """
149 import sys
150 # We need to wait until we have an input_api object and use this
151 # roundabout construct to import checkdeps because this file is
152 # eval-ed and thus doesn't have __file__.
153 original_sys_path = sys.path
154 try:
Lei Zhangb1d78722018-02-02 22:17:58 +0000155 def GenerateCheckdepsPath(base_path):
156 return input_api.os_path.join(base_path, 'buildtools', 'checkdeps')
157
158 presubmit_path = input_api.PresubmitLocalPath()
159 presubmit_parent_path = input_api.os_path.dirname(presubmit_path)
160 not_standalone_pdfium = \
161 input_api.os_path.basename(presubmit_parent_path) == "third_party" and \
162 input_api.os_path.basename(presubmit_path) == "pdfium"
163
164 sys.path.append(GenerateCheckdepsPath(presubmit_path))
165 if not_standalone_pdfium:
166 presubmit_grandparent_path = input_api.os_path.dirname(
167 presubmit_parent_path)
168 sys.path.append(GenerateCheckdepsPath(presubmit_grandparent_path))
169
Dan Sinclair544bbc62016-03-14 15:07:39 -0400170 import checkdeps
171 from cpp_checker import CppChecker
172 from rules import Rule
dsinclair4cd49e12016-04-05 10:28:48 -0700173 except ImportError:
174 return [output_api.PresubmitError(
175 'Unable to run checkdeps, does pdfium/buildtools/checkdeps exist?')]
Dan Sinclair544bbc62016-03-14 15:07:39 -0400176 finally:
177 # Restore sys.path to what it was before.
178 sys.path = original_sys_path
179
180 added_includes = []
181 for f in input_api.AffectedFiles():
182 if not CppChecker.IsCppFile(f.LocalPath()):
183 continue
184
185 changed_lines = [line for line_num, line in f.ChangedContents()]
186 added_includes.append([f.LocalPath(), changed_lines])
187
188 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
189
190 error_descriptions = []
191 warning_descriptions = []
192 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
193 added_includes):
194 description_with_path = '%s\n %s' % (path, rule_description)
195 if rule_type == Rule.DISALLOW:
196 error_descriptions.append(description_with_path)
197 else:
198 warning_descriptions.append(description_with_path)
199
200 results = []
201 if error_descriptions:
202 results.append(output_api.PresubmitError(
203 'You added one or more #includes that violate checkdeps rules.',
204 error_descriptions))
205 if warning_descriptions:
206 results.append(output_api.PresubmitPromptOrNotify(
207 'You added one or more #includes of files that are temporarily\n'
208 'allowed but being removed. Can you avoid introducing the\n'
209 '#include? See relevant DEPS file(s) for details and contacts.',
210 warning_descriptions))
211 return results
212
213
dsinclair2ca2da52016-09-13 18:10:34 -0700214def _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums):
215 """Checks that the lines in scope occur in the right order.
216
217 1. C system files in alphabetical order
218 2. C++ system files in alphabetical order
219 3. Project's .h files
220 """
221
222 c_system_include_pattern = input_api.re.compile(r'\s*#include <.*\.h>')
223 cpp_system_include_pattern = input_api.re.compile(r'\s*#include <.*>')
224 custom_include_pattern = input_api.re.compile(r'\s*#include ".*')
225
226 C_SYSTEM_INCLUDES, CPP_SYSTEM_INCLUDES, CUSTOM_INCLUDES = range(3)
227
228 state = C_SYSTEM_INCLUDES
229
230 previous_line = ''
231 previous_line_num = 0
232 problem_linenums = []
233 out_of_order = " - line belongs before previous line"
234 for line_num, line in scope:
235 if c_system_include_pattern.match(line):
236 if state != C_SYSTEM_INCLUDES:
237 problem_linenums.append((line_num, previous_line_num,
238 " - C system include file in wrong block"))
239 elif previous_line and previous_line > line:
240 problem_linenums.append((line_num, previous_line_num,
241 out_of_order))
242 elif cpp_system_include_pattern.match(line):
243 if state == C_SYSTEM_INCLUDES:
244 state = CPP_SYSTEM_INCLUDES
245 elif state == CUSTOM_INCLUDES:
246 problem_linenums.append((line_num, previous_line_num,
247 " - c++ system include file in wrong block"))
248 elif previous_line and previous_line > line:
249 problem_linenums.append((line_num, previous_line_num, out_of_order))
250 elif custom_include_pattern.match(line):
251 if state != CUSTOM_INCLUDES:
252 state = CUSTOM_INCLUDES
253 elif previous_line and previous_line > line:
254 problem_linenums.append((line_num, previous_line_num, out_of_order))
255 else:
256 problem_linenums.append((line_num, previous_line_num,
257 "Unknown include type"))
258 previous_line = line
259 previous_line_num = line_num
260
261 warnings = []
262 for (line_num, previous_line_num, failure_type) in problem_linenums:
263 if line_num in changed_linenums or previous_line_num in changed_linenums:
264 warnings.append(' %s:%d:%s' % (file_path, line_num, failure_type))
265 return warnings
266
267
268def _CheckIncludeOrderInFile(input_api, f, changed_linenums):
269 """Checks the #include order for the given file f."""
270
271 system_include_pattern = input_api.re.compile(r'\s*#include \<.*')
272 # Exclude the following includes from the check:
273 # 1) #include <.../...>, e.g., <sys/...> includes often need to appear in a
274 # specific order.
275 # 2) <atlbase.h>, "build/build_config.h"
276 excluded_include_pattern = input_api.re.compile(
277 r'\s*#include (\<.*/.*|\<atlbase\.h\>|"build/build_config.h")')
278 custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"')
279 # Match the final or penultimate token if it is xxxtest so we can ignore it
280 # when considering the special first include.
281 test_file_tag_pattern = input_api.re.compile(
282 r'_[a-z]+test(?=(_[a-zA-Z0-9]+)?\.)')
283 if_pattern = input_api.re.compile(
284 r'\s*#\s*(if|elif|else|endif|define|undef).*')
285 # Some files need specialized order of includes; exclude such files from this
286 # check.
287 uncheckable_includes_pattern = input_api.re.compile(
288 r'\s*#include '
289 '("ipc/.*macros\.h"|<windows\.h>|".*gl.*autogen.h")\s*')
290
291 contents = f.NewContents()
292 warnings = []
293 line_num = 0
294
295 # Handle the special first include. If the first include file is
296 # some/path/file.h, the corresponding including file can be some/path/file.cc,
297 # some/other/path/file.cc, some/path/file_platform.cc, some/path/file-suffix.h
298 # etc. It's also possible that no special first include exists.
299 # If the included file is some/path/file_platform.h the including file could
300 # also be some/path/file_xxxtest_platform.h.
301 including_file_base_name = test_file_tag_pattern.sub(
302 '', input_api.os_path.basename(f.LocalPath()))
303
304 for line in contents:
305 line_num += 1
306 if system_include_pattern.match(line):
307 # No special first include -> process the line again along with normal
308 # includes.
309 line_num -= 1
310 break
311 match = custom_include_pattern.match(line)
312 if match:
313 match_dict = match.groupdict()
314 header_basename = test_file_tag_pattern.sub(
315 '', input_api.os_path.basename(match_dict['FILE'])).replace('.h', '')
316
317 if header_basename not in including_file_base_name:
318 # No special first include -> process the line again along with normal
319 # includes.
320 line_num -= 1
321 break
322
323 # Split into scopes: Each region between #if and #endif is its own scope.
324 scopes = []
325 current_scope = []
326 for line in contents[line_num:]:
327 line_num += 1
328 if uncheckable_includes_pattern.match(line):
329 continue
330 if if_pattern.match(line):
331 scopes.append(current_scope)
332 current_scope = []
333 elif ((system_include_pattern.match(line) or
334 custom_include_pattern.match(line)) and
335 not excluded_include_pattern.match(line)):
336 current_scope.append((line_num, line))
337 scopes.append(current_scope)
338
339 for scope in scopes:
340 warnings.extend(_CheckIncludeOrderForScope(scope, input_api, f.LocalPath(),
341 changed_linenums))
342 return warnings
343
344
345def _CheckIncludeOrder(input_api, output_api):
346 """Checks that the #include order is correct.
347
348 1. The corresponding header for source files.
349 2. C system files in alphabetical order
350 3. C++ system files in alphabetical order
351 4. Project's .h files in alphabetical order
352
353 Each region separated by #if, #elif, #else, #endif, #define and #undef follows
354 these rules separately.
355 """
dsinclair2ca2da52016-09-13 18:10:34 -0700356 warnings = []
Lei Zhangf98bc4a2020-08-24 23:47:16 +0000357 for f in input_api.AffectedFiles(file_filter=input_api.FilterSourceFile):
dsinclaird33c8e32016-11-21 13:31:16 -0800358 if f.LocalPath().endswith(('.cc', '.cpp', '.h', '.mm')):
dsinclair2ca2da52016-09-13 18:10:34 -0700359 changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
360 warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
361
362 results = []
363 if warnings:
364 results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_ORDER_WARNING,
365 warnings))
366 return results
367
Lei Zhangf98bc4a2020-08-24 23:47:16 +0000368
Nicolas Penad824a902017-05-19 15:59:49 -0400369def _CheckTestDuplicates(input_api, output_api):
370 """Checks that pixel and javascript tests don't contain duplicates.
371 We use .in and .pdf files, having both can cause race conditions on the bots,
372 which run the tests in parallel.
373 """
374 tests_added = []
375 results = []
376 for f in input_api.AffectedFiles():
Lei Zhang567039b2019-09-17 00:45:23 +0000377 if f.Action() == 'D':
378 continue
Nicolas Penad824a902017-05-19 15:59:49 -0400379 if not f.LocalPath().startswith(('testing/resources/pixel/',
380 'testing/resources/javascript/')):
381 continue
382 end_len = 0
383 if f.LocalPath().endswith('.in'):
384 end_len = 3
385 elif f.LocalPath().endswith('.pdf'):
386 end_len = 4
387 else:
388 continue
Nicolas Penac4479c52017-06-26 11:45:06 -0400389 path = f.LocalPath()[:-end_len]
Nicolas Penad824a902017-05-19 15:59:49 -0400390 if path in tests_added:
391 results.append(output_api.PresubmitError(
392 'Remove %s to prevent shadowing %s' % (path + '.pdf',
393 path + '.in')))
394 else:
395 tests_added.append(path)
396 return results
397
Lei Zhang624fba82021-06-15 20:11:39 +0000398
Nicolas Penac4479c52017-06-26 11:45:06 -0400399def _CheckPNGFormat(input_api, output_api):
400 """Checks that .png files have a format that will be considered valid by our
401 test runners. If a file ends with .png, then it must be of the form
Hui Yingst5d5273f2021-10-01 17:58:33 +0000402 NAME_expected(_(skia|skiapaths))?(_(win|mac|linux))?.pdf.#.png
403 The expected format used by _CheckPngNames() in testing/corpus/PRESUBMIT.py
404 must be the same as this one.
405 """
Nicolas Penac4479c52017-06-26 11:45:06 -0400406 expected_pattern = input_api.re.compile(
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000407 r'.+_expected(_(skia|skiapaths))?(_(win|mac|linux))?\.pdf\.\d+.png')
Nicolas Penac4479c52017-06-26 11:45:06 -0400408 results = []
Nicolas Penad261b062017-06-26 16:54:43 -0400409 for f in input_api.AffectedFiles(include_deletes=False):
Nicolas Penac4479c52017-06-26 11:45:06 -0400410 if not f.LocalPath().endswith('.png'):
411 continue
412 if expected_pattern.match(f.LocalPath()):
413 continue
414 results.append(output_api.PresubmitError(
415 'PNG file %s does not have the correct format' % f.LocalPath()))
416 return results
dsinclair2ca2da52016-09-13 18:10:34 -0700417
Lei Zhang624fba82021-06-15 20:11:39 +0000418
419def _CheckUselessForwardDeclarations(input_api, output_api):
420 """Checks that added or removed lines in non third party affected
421 header files do not lead to new useless class or struct forward
422 declaration.
423 """
424 results = []
425 class_pattern = input_api.re.compile(r'^class\s+(\w+);$',
426 input_api.re.MULTILINE)
427 struct_pattern = input_api.re.compile(r'^struct\s+(\w+);$',
428 input_api.re.MULTILINE)
429 for f in input_api.AffectedFiles(include_deletes=False):
430 if f.LocalPath().startswith('third_party'):
431 continue
432
433 if not f.LocalPath().endswith('.h'):
434 continue
435
436 contents = input_api.ReadFile(f)
437 fwd_decls = input_api.re.findall(class_pattern, contents)
438 fwd_decls.extend(input_api.re.findall(struct_pattern, contents))
439
440 useless_fwd_decls = []
441 for decl in fwd_decls:
442 count = sum(
443 1
444 for _ in input_api.re.finditer(r'\b%s\b' %
445 input_api.re.escape(decl), contents))
446 if count == 1:
447 useless_fwd_decls.append(decl)
448
449 if not useless_fwd_decls:
450 continue
451
452 for line in f.GenerateScmDiff().splitlines():
453 if (line.startswith('-') and not line.startswith('--') or
454 line.startswith('+') and not line.startswith('++')):
455 for decl in useless_fwd_decls:
456 if input_api.re.search(r'\b%s\b' % decl, line[1:]):
457 results.append(
458 output_api.PresubmitPromptWarning(
459 '%s: %s forward declaration is no longer needed' %
460 (f.LocalPath(), decl)))
461 useless_fwd_decls.remove(decl)
462
463 return results
464
465
Nico Weber077f1a32015-08-06 15:08:57 -0700466def CheckChangeOnUpload(input_api, output_api):
467 results = []
Daniel Hosseinianb3bdbd42021-10-22 17:01:14 +0000468 results.extend(_CheckNoBannedFunctions(input_api, output_api))
Lei Zhang804a0e32020-05-21 20:57:16 +0000469 results.extend(_CheckUnwantedDependencies(input_api, output_api))
470 results.extend(
471 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
472 results.extend(
Lei Zhang4c472552021-05-24 21:44:47 +0000473 input_api.canned_checks.CheckChangeLintsClean(
474 input_api, output_api, lint_filters=LINT_FILTERS))
Lei Zhang804a0e32020-05-21 20:57:16 +0000475 results.extend(_CheckIncludeOrder(input_api, output_api))
476 results.extend(_CheckTestDuplicates(input_api, output_api))
477 results.extend(_CheckPNGFormat(input_api, output_api))
Lei Zhang624fba82021-06-15 20:11:39 +0000478 results.extend(_CheckUselessForwardDeclarations(input_api, output_api))
Dan Sinclair544bbc62016-03-14 15:07:39 -0400479
Lei Zhang71e26732020-05-21 21:03:05 +0000480 author = input_api.change.author_email
481 if author and author not in _KNOWN_ROBOTS:
482 results.extend(
483 input_api.canned_checks.CheckAuthorizedAuthor(input_api, output_api))
484
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000485 for f in input_api.AffectedFiles():
486 path, name = input_api.os_path.split(f.LocalPath())
487 if name == 'PRESUBMIT.py':
488 full_path = input_api.os_path.join(input_api.PresubmitLocalPath(), path)
489 test_file = input_api.os_path.join(path, 'PRESUBMIT_test.py')
490 if f.Action() != 'D' and input_api.os_path.exists(test_file):
491 # The PRESUBMIT.py file (and the directory containing it) might
492 # have been affected by being moved or removed, so only try to
493 # run the tests if they still exist.
494 results.extend(
495 input_api.canned_checks.RunUnitTestsInDirectory(
496 input_api,
497 output_api,
498 full_path,
Lingqi Chied293672021-11-24 20:53:01 +0000499 files_to_check=[r'^PRESUBMIT_test\.py$'],
500 run_on_python2=not USE_PYTHON3,
501 run_on_python3=USE_PYTHON3,
502 skip_shebang_check=True))
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000503
Nico Weber077f1a32015-08-06 15:08:57 -0700504 return results