blob: 606f48df301aaa224286095f5dcb185fabb25439 [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 (
Daniel Hosseiniand7655382021-11-30 18:11:39 +000062 r'/v8::Isolate::(?:|Try)GetCurrent()',
Daniel Hosseinianb3bdbd42021-10-22 17:01:14 +000063 (
Daniel Hosseiniand7655382021-11-30 18:11:39 +000064 'v8::Isolate::GetCurrent() and v8::Isolate::TryGetCurrent() are banned. Hold',
65 'a pointer to the v8::Isolate that was entered. Use v8::Isolate::IsCurrent()',
66 'to check whether a given v8::Isolate is entered.',
Daniel Hosseinianb3bdbd42021-10-22 17:01:14 +000067 ),
Daniel Hosseiniand7655382021-11-30 18:11:39 +000068 True,
Daniel Hosseinianb3bdbd42021-10-22 17:01:14 +000069 (),
70 ),
71)
72
73
74def _CheckNoBannedFunctions(input_api, output_api):
75 """Makes sure that banned functions are not used."""
76 warnings = []
77 errors = []
78
79 def _GetMessageForMatchingType(input_api, affected_file, line_number, line,
80 type_name, message):
81 """Returns an string composed of the name of the file, the line number where
82 the match has been found and the additional text passed as `message` in case
83 the target type name matches the text inside the line passed as parameter.
84 """
85 result = []
86
87 if input_api.re.search(r"^ *//",
88 line): # Ignore comments about banned types.
89 return result
90 if line.endswith(
91 " nocheck"): # A // nocheck comment will bypass this error.
92 return result
93
94 matched = False
95 if type_name[0:1] == '/':
96 regex = type_name[1:]
97 if input_api.re.search(regex, line):
98 matched = True
99 elif type_name in line:
100 matched = True
101
102 if matched:
103 result.append(' %s:%d:' % (affected_file.LocalPath(), line_number))
104 for message_line in message:
105 result.append(' %s' % message_line)
106
107 return result
108
109 def IsExcludedFile(affected_file, excluded_paths):
110 local_path = affected_file.LocalPath()
111 for item in excluded_paths:
112 if input_api.re.match(item, local_path):
113 return True
114 return False
115
116 def CheckForMatch(affected_file, line_num, line, func_name, message, error):
117 problems = _GetMessageForMatchingType(input_api, f, line_num, line,
118 func_name, message)
119 if problems:
120 if error:
121 errors.extend(problems)
122 else:
123 warnings.extend(problems)
124
125 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.cpp', '.h'))
126 for f in input_api.AffectedFiles(file_filter=file_filter):
127 for line_num, line in f.ChangedContents():
128 for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
129 if IsExcludedFile(f, excluded_paths):
130 continue
131 CheckForMatch(f, line_num, line, func_name, message, error)
132
133 result = []
134 if (warnings):
135 result.append(
136 output_api.PresubmitPromptWarning('Banned functions were used.\n' +
137 '\n'.join(warnings)))
138 if (errors):
139 result.append(
140 output_api.PresubmitError('Banned functions were used.\n' +
141 '\n'.join(errors)))
142 return result
143
Lei Zhang71e26732020-05-21 21:03:05 +0000144
Dan Sinclair544bbc62016-03-14 15:07:39 -0400145def _CheckUnwantedDependencies(input_api, output_api):
146 """Runs checkdeps on #include statements added in this
147 change. Breaking - rules is an error, breaking ! rules is a
148 warning.
149 """
150 import sys
151 # We need to wait until we have an input_api object and use this
152 # roundabout construct to import checkdeps because this file is
153 # eval-ed and thus doesn't have __file__.
154 original_sys_path = sys.path
155 try:
Lei Zhangb1d78722018-02-02 22:17:58 +0000156 def GenerateCheckdepsPath(base_path):
157 return input_api.os_path.join(base_path, 'buildtools', 'checkdeps')
158
159 presubmit_path = input_api.PresubmitLocalPath()
160 presubmit_parent_path = input_api.os_path.dirname(presubmit_path)
161 not_standalone_pdfium = \
162 input_api.os_path.basename(presubmit_parent_path) == "third_party" and \
163 input_api.os_path.basename(presubmit_path) == "pdfium"
164
165 sys.path.append(GenerateCheckdepsPath(presubmit_path))
166 if not_standalone_pdfium:
167 presubmit_grandparent_path = input_api.os_path.dirname(
168 presubmit_parent_path)
169 sys.path.append(GenerateCheckdepsPath(presubmit_grandparent_path))
170
Dan Sinclair544bbc62016-03-14 15:07:39 -0400171 import checkdeps
172 from cpp_checker import CppChecker
173 from rules import Rule
dsinclair4cd49e12016-04-05 10:28:48 -0700174 except ImportError:
175 return [output_api.PresubmitError(
176 'Unable to run checkdeps, does pdfium/buildtools/checkdeps exist?')]
Dan Sinclair544bbc62016-03-14 15:07:39 -0400177 finally:
178 # Restore sys.path to what it was before.
179 sys.path = original_sys_path
180
181 added_includes = []
182 for f in input_api.AffectedFiles():
183 if not CppChecker.IsCppFile(f.LocalPath()):
184 continue
185
186 changed_lines = [line for line_num, line in f.ChangedContents()]
187 added_includes.append([f.LocalPath(), changed_lines])
188
189 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
190
191 error_descriptions = []
192 warning_descriptions = []
193 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
194 added_includes):
195 description_with_path = '%s\n %s' % (path, rule_description)
196 if rule_type == Rule.DISALLOW:
197 error_descriptions.append(description_with_path)
198 else:
199 warning_descriptions.append(description_with_path)
200
201 results = []
202 if error_descriptions:
203 results.append(output_api.PresubmitError(
204 'You added one or more #includes that violate checkdeps rules.',
205 error_descriptions))
206 if warning_descriptions:
207 results.append(output_api.PresubmitPromptOrNotify(
208 'You added one or more #includes of files that are temporarily\n'
209 'allowed but being removed. Can you avoid introducing the\n'
210 '#include? See relevant DEPS file(s) for details and contacts.',
211 warning_descriptions))
212 return results
213
214
dsinclair2ca2da52016-09-13 18:10:34 -0700215def _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums):
216 """Checks that the lines in scope occur in the right order.
217
218 1. C system files in alphabetical order
219 2. C++ system files in alphabetical order
220 3. Project's .h files
221 """
222
223 c_system_include_pattern = input_api.re.compile(r'\s*#include <.*\.h>')
224 cpp_system_include_pattern = input_api.re.compile(r'\s*#include <.*>')
225 custom_include_pattern = input_api.re.compile(r'\s*#include ".*')
226
227 C_SYSTEM_INCLUDES, CPP_SYSTEM_INCLUDES, CUSTOM_INCLUDES = range(3)
228
229 state = C_SYSTEM_INCLUDES
230
231 previous_line = ''
232 previous_line_num = 0
233 problem_linenums = []
234 out_of_order = " - line belongs before previous line"
235 for line_num, line in scope:
236 if c_system_include_pattern.match(line):
237 if state != C_SYSTEM_INCLUDES:
238 problem_linenums.append((line_num, previous_line_num,
239 " - C system include file in wrong block"))
240 elif previous_line and previous_line > line:
241 problem_linenums.append((line_num, previous_line_num,
242 out_of_order))
243 elif cpp_system_include_pattern.match(line):
244 if state == C_SYSTEM_INCLUDES:
245 state = CPP_SYSTEM_INCLUDES
246 elif state == CUSTOM_INCLUDES:
247 problem_linenums.append((line_num, previous_line_num,
248 " - c++ system include file in wrong block"))
249 elif previous_line and previous_line > line:
250 problem_linenums.append((line_num, previous_line_num, out_of_order))
251 elif custom_include_pattern.match(line):
252 if state != CUSTOM_INCLUDES:
253 state = CUSTOM_INCLUDES
254 elif previous_line and previous_line > line:
255 problem_linenums.append((line_num, previous_line_num, out_of_order))
256 else:
257 problem_linenums.append((line_num, previous_line_num,
258 "Unknown include type"))
259 previous_line = line
260 previous_line_num = line_num
261
262 warnings = []
263 for (line_num, previous_line_num, failure_type) in problem_linenums:
264 if line_num in changed_linenums or previous_line_num in changed_linenums:
265 warnings.append(' %s:%d:%s' % (file_path, line_num, failure_type))
266 return warnings
267
268
269def _CheckIncludeOrderInFile(input_api, f, changed_linenums):
270 """Checks the #include order for the given file f."""
271
272 system_include_pattern = input_api.re.compile(r'\s*#include \<.*')
273 # Exclude the following includes from the check:
274 # 1) #include <.../...>, e.g., <sys/...> includes often need to appear in a
275 # specific order.
276 # 2) <atlbase.h>, "build/build_config.h"
277 excluded_include_pattern = input_api.re.compile(
278 r'\s*#include (\<.*/.*|\<atlbase\.h\>|"build/build_config.h")')
279 custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"')
280 # Match the final or penultimate token if it is xxxtest so we can ignore it
281 # when considering the special first include.
282 test_file_tag_pattern = input_api.re.compile(
283 r'_[a-z]+test(?=(_[a-zA-Z0-9]+)?\.)')
284 if_pattern = input_api.re.compile(
285 r'\s*#\s*(if|elif|else|endif|define|undef).*')
286 # Some files need specialized order of includes; exclude such files from this
287 # check.
288 uncheckable_includes_pattern = input_api.re.compile(
289 r'\s*#include '
290 '("ipc/.*macros\.h"|<windows\.h>|".*gl.*autogen.h")\s*')
291
292 contents = f.NewContents()
293 warnings = []
294 line_num = 0
295
296 # Handle the special first include. If the first include file is
297 # some/path/file.h, the corresponding including file can be some/path/file.cc,
298 # some/other/path/file.cc, some/path/file_platform.cc, some/path/file-suffix.h
299 # etc. It's also possible that no special first include exists.
300 # If the included file is some/path/file_platform.h the including file could
301 # also be some/path/file_xxxtest_platform.h.
302 including_file_base_name = test_file_tag_pattern.sub(
303 '', input_api.os_path.basename(f.LocalPath()))
304
305 for line in contents:
306 line_num += 1
307 if system_include_pattern.match(line):
308 # No special first include -> process the line again along with normal
309 # includes.
310 line_num -= 1
311 break
312 match = custom_include_pattern.match(line)
313 if match:
314 match_dict = match.groupdict()
315 header_basename = test_file_tag_pattern.sub(
316 '', input_api.os_path.basename(match_dict['FILE'])).replace('.h', '')
317
318 if header_basename not in including_file_base_name:
319 # No special first include -> process the line again along with normal
320 # includes.
321 line_num -= 1
322 break
323
324 # Split into scopes: Each region between #if and #endif is its own scope.
325 scopes = []
326 current_scope = []
327 for line in contents[line_num:]:
328 line_num += 1
329 if uncheckable_includes_pattern.match(line):
330 continue
331 if if_pattern.match(line):
332 scopes.append(current_scope)
333 current_scope = []
334 elif ((system_include_pattern.match(line) or
335 custom_include_pattern.match(line)) and
336 not excluded_include_pattern.match(line)):
337 current_scope.append((line_num, line))
338 scopes.append(current_scope)
339
340 for scope in scopes:
341 warnings.extend(_CheckIncludeOrderForScope(scope, input_api, f.LocalPath(),
342 changed_linenums))
343 return warnings
344
345
346def _CheckIncludeOrder(input_api, output_api):
347 """Checks that the #include order is correct.
348
349 1. The corresponding header for source files.
350 2. C system files in alphabetical order
351 3. C++ system files in alphabetical order
352 4. Project's .h files in alphabetical order
353
354 Each region separated by #if, #elif, #else, #endif, #define and #undef follows
355 these rules separately.
356 """
dsinclair2ca2da52016-09-13 18:10:34 -0700357 warnings = []
Lei Zhangf98bc4a2020-08-24 23:47:16 +0000358 for f in input_api.AffectedFiles(file_filter=input_api.FilterSourceFile):
dsinclaird33c8e32016-11-21 13:31:16 -0800359 if f.LocalPath().endswith(('.cc', '.cpp', '.h', '.mm')):
dsinclair2ca2da52016-09-13 18:10:34 -0700360 changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
361 warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
362
363 results = []
364 if warnings:
365 results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_ORDER_WARNING,
366 warnings))
367 return results
368
Lei Zhangf98bc4a2020-08-24 23:47:16 +0000369
Nicolas Penad824a902017-05-19 15:59:49 -0400370def _CheckTestDuplicates(input_api, output_api):
371 """Checks that pixel and javascript tests don't contain duplicates.
372 We use .in and .pdf files, having both can cause race conditions on the bots,
373 which run the tests in parallel.
374 """
375 tests_added = []
376 results = []
377 for f in input_api.AffectedFiles():
Lei Zhang567039b2019-09-17 00:45:23 +0000378 if f.Action() == 'D':
379 continue
Nicolas Penad824a902017-05-19 15:59:49 -0400380 if not f.LocalPath().startswith(('testing/resources/pixel/',
381 'testing/resources/javascript/')):
382 continue
383 end_len = 0
384 if f.LocalPath().endswith('.in'):
385 end_len = 3
386 elif f.LocalPath().endswith('.pdf'):
387 end_len = 4
388 else:
389 continue
Nicolas Penac4479c52017-06-26 11:45:06 -0400390 path = f.LocalPath()[:-end_len]
Nicolas Penad824a902017-05-19 15:59:49 -0400391 if path in tests_added:
392 results.append(output_api.PresubmitError(
393 'Remove %s to prevent shadowing %s' % (path + '.pdf',
394 path + '.in')))
395 else:
396 tests_added.append(path)
397 return results
398
Lei Zhang624fba82021-06-15 20:11:39 +0000399
Nicolas Penac4479c52017-06-26 11:45:06 -0400400def _CheckPNGFormat(input_api, output_api):
401 """Checks that .png files have a format that will be considered valid by our
402 test runners. If a file ends with .png, then it must be of the form
Hui Yingst5d5273f2021-10-01 17:58:33 +0000403 NAME_expected(_(skia|skiapaths))?(_(win|mac|linux))?.pdf.#.png
404 The expected format used by _CheckPngNames() in testing/corpus/PRESUBMIT.py
405 must be the same as this one.
406 """
Nicolas Penac4479c52017-06-26 11:45:06 -0400407 expected_pattern = input_api.re.compile(
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000408 r'.+_expected(_(skia|skiapaths))?(_(win|mac|linux))?\.pdf\.\d+.png')
Nicolas Penac4479c52017-06-26 11:45:06 -0400409 results = []
Nicolas Penad261b062017-06-26 16:54:43 -0400410 for f in input_api.AffectedFiles(include_deletes=False):
Nicolas Penac4479c52017-06-26 11:45:06 -0400411 if not f.LocalPath().endswith('.png'):
412 continue
413 if expected_pattern.match(f.LocalPath()):
414 continue
415 results.append(output_api.PresubmitError(
416 'PNG file %s does not have the correct format' % f.LocalPath()))
417 return results
dsinclair2ca2da52016-09-13 18:10:34 -0700418
Lei Zhang624fba82021-06-15 20:11:39 +0000419
420def _CheckUselessForwardDeclarations(input_api, output_api):
421 """Checks that added or removed lines in non third party affected
422 header files do not lead to new useless class or struct forward
423 declaration.
424 """
425 results = []
426 class_pattern = input_api.re.compile(r'^class\s+(\w+);$',
427 input_api.re.MULTILINE)
428 struct_pattern = input_api.re.compile(r'^struct\s+(\w+);$',
429 input_api.re.MULTILINE)
430 for f in input_api.AffectedFiles(include_deletes=False):
431 if f.LocalPath().startswith('third_party'):
432 continue
433
434 if not f.LocalPath().endswith('.h'):
435 continue
436
437 contents = input_api.ReadFile(f)
438 fwd_decls = input_api.re.findall(class_pattern, contents)
439 fwd_decls.extend(input_api.re.findall(struct_pattern, contents))
440
441 useless_fwd_decls = []
442 for decl in fwd_decls:
443 count = sum(
444 1
445 for _ in input_api.re.finditer(r'\b%s\b' %
446 input_api.re.escape(decl), contents))
447 if count == 1:
448 useless_fwd_decls.append(decl)
449
450 if not useless_fwd_decls:
451 continue
452
453 for line in f.GenerateScmDiff().splitlines():
454 if (line.startswith('-') and not line.startswith('--') or
455 line.startswith('+') and not line.startswith('++')):
456 for decl in useless_fwd_decls:
457 if input_api.re.search(r'\b%s\b' % decl, line[1:]):
458 results.append(
459 output_api.PresubmitPromptWarning(
460 '%s: %s forward declaration is no longer needed' %
461 (f.LocalPath(), decl)))
462 useless_fwd_decls.remove(decl)
463
464 return results
465
466
Nico Weber077f1a32015-08-06 15:08:57 -0700467def CheckChangeOnUpload(input_api, output_api):
468 results = []
Daniel Hosseinianb3bdbd42021-10-22 17:01:14 +0000469 results.extend(_CheckNoBannedFunctions(input_api, output_api))
Lei Zhang804a0e32020-05-21 20:57:16 +0000470 results.extend(_CheckUnwantedDependencies(input_api, output_api))
471 results.extend(
472 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
473 results.extend(
Lei Zhang4c472552021-05-24 21:44:47 +0000474 input_api.canned_checks.CheckChangeLintsClean(
475 input_api, output_api, lint_filters=LINT_FILTERS))
Lei Zhang804a0e32020-05-21 20:57:16 +0000476 results.extend(_CheckIncludeOrder(input_api, output_api))
477 results.extend(_CheckTestDuplicates(input_api, output_api))
478 results.extend(_CheckPNGFormat(input_api, output_api))
Lei Zhang624fba82021-06-15 20:11:39 +0000479 results.extend(_CheckUselessForwardDeclarations(input_api, output_api))
Dan Sinclair544bbc62016-03-14 15:07:39 -0400480
Lei Zhang71e26732020-05-21 21:03:05 +0000481 author = input_api.change.author_email
482 if author and author not in _KNOWN_ROBOTS:
483 results.extend(
484 input_api.canned_checks.CheckAuthorizedAuthor(input_api, output_api))
485
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000486 for f in input_api.AffectedFiles():
487 path, name = input_api.os_path.split(f.LocalPath())
488 if name == 'PRESUBMIT.py':
489 full_path = input_api.os_path.join(input_api.PresubmitLocalPath(), path)
490 test_file = input_api.os_path.join(path, 'PRESUBMIT_test.py')
491 if f.Action() != 'D' and input_api.os_path.exists(test_file):
492 # The PRESUBMIT.py file (and the directory containing it) might
493 # have been affected by being moved or removed, so only try to
494 # run the tests if they still exist.
495 results.extend(
496 input_api.canned_checks.RunUnitTestsInDirectory(
497 input_api,
498 output_api,
499 full_path,
Lingqi Chied293672021-11-24 20:53:01 +0000500 files_to_check=[r'^PRESUBMIT_test\.py$'],
501 run_on_python2=not USE_PYTHON3,
502 run_on_python3=USE_PYTHON3,
503 skip_shebang_check=True))
Hui Yingst2aa0a4a2020-04-09 19:04:21 +0000504
Nico Weber077f1a32015-08-06 15:08:57 -0700505 return results