blob: ce456dd5469a1ec06ecf3d96d81902e6d99b2cbd [file] [log] [blame]
Ben Murdochb8a8cc12014-11-26 15:28:44 +00001# Copyright 2012 the V8 project authors. All rights reserved.
2# Redistribution and use in source and binary forms, with or without
3# modification, are permitted provided that the following conditions are
4# met:
5#
6# * Redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer.
8# * Redistributions in binary form must reproduce the above
9# copyright notice, this list of conditions and the following
10# disclaimer in the documentation and/or other materials provided
11# with the distribution.
12# * Neither the name of Google Inc. nor the names of its
13# contributors may be used to endorse or promote products derived
14# from this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28"""Top-level presubmit script for V8.
29
30See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
31for more details about the presubmit API built into gcl.
32"""
33
Rubin Xu2894c6a2019-02-07 16:01:35 +000034import json
35import re
Ben Murdochb8a8cc12014-11-26 15:28:44 +000036import sys
37
38
39_EXCLUDED_PATHS = (
40 r"^test[\\\/].*",
41 r"^testing[\\\/].*",
42 r"^third_party[\\\/].*",
43 r"^tools[\\\/].*",
44)
45
46
Rubin Xu2894c6a2019-02-07 16:01:35 +000047# Regular expression that matches code which should not be run through cpplint.
48_NO_LINT_PATHS = (
49 r'src[\\\/]base[\\\/]export-template\.h',
50)
51
52
Ben Murdochb8a8cc12014-11-26 15:28:44 +000053# Regular expression that matches code only used for test binaries
54# (best effort).
55_TEST_CODE_EXCLUDED_PATHS = (
56 r'.+-unittest\.cc',
57 # Has a method VisitForTest().
58 r'src[\\\/]compiler[\\\/]ast-graph-builder\.cc',
59 # Test extension.
60 r'src[\\\/]extensions[\\\/]gc-extension\.cc',
61)
62
63
64_TEST_ONLY_WARNING = (
65 'You might be calling functions intended only for testing from\n'
66 'production code. It is OK to ignore this warning if you know what\n'
67 'you are doing, as the heuristics used to detect the situation are\n'
68 'not perfect. The commit queue will not block on this warning.')
69
70
71def _V8PresubmitChecks(input_api, output_api):
72 """Runs the V8 presubmit checks."""
73 import sys
74 sys.path.append(input_api.os_path.join(
75 input_api.PresubmitLocalPath(), 'tools'))
Rubin Xu2894c6a2019-02-07 16:01:35 +000076 from v8_presubmit import CppLintProcessor
77 from v8_presubmit import SourceProcessor
78 from v8_presubmit import StatusFilesProcessor
79
80 def FilterFile(affected_file):
81 return input_api.FilterSourceFile(
82 affected_file,
83 white_list=None,
84 black_list=_NO_LINT_PATHS)
Ben Murdochb8a8cc12014-11-26 15:28:44 +000085
86 results = []
Ben Murdoch62ed6312017-06-06 11:06:27 +010087 if not CppLintProcessor().RunOnFiles(
Rubin Xu2894c6a2019-02-07 16:01:35 +000088 input_api.AffectedFiles(file_filter=FilterFile, include_deletes=False)):
Ben Murdochb8a8cc12014-11-26 15:28:44 +000089 results.append(output_api.PresubmitError("C++ lint check failed"))
Ben Murdoch62ed6312017-06-06 11:06:27 +010090 if not SourceProcessor().RunOnFiles(
91 input_api.AffectedFiles(include_deletes=False)):
Ben Murdochb8a8cc12014-11-26 15:28:44 +000092 results.append(output_api.PresubmitError(
93 "Copyright header, trailing whitespaces and two empty lines " \
94 "between declarations check failed"))
Ben Murdoch62ed6312017-06-06 11:06:27 +010095 if not StatusFilesProcessor().RunOnFiles(
96 input_api.AffectedFiles(include_deletes=True)):
Ben Murdoch014dc512016-03-22 12:00:34 +000097 results.append(output_api.PresubmitError("Status file check failed"))
Ben Murdoch62ed6312017-06-06 11:06:27 +010098 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
Rubin Xu2894c6a2019-02-07 16:01:35 +000099 input_api, output_api, bot_whitelist=[
100 'v8-ci-autoroll-builder@chops-service-accounts.iam.gserviceaccount.com'
101 ]))
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000102 return results
103
104
105def _CheckUnwantedDependencies(input_api, output_api):
106 """Runs checkdeps on #include statements added in this
107 change. Breaking - rules is an error, breaking ! rules is a
108 warning.
109 """
110 # We need to wait until we have an input_api object and use this
111 # roundabout construct to import checkdeps because this file is
112 # eval-ed and thus doesn't have __file__.
113 original_sys_path = sys.path
114 try:
115 sys.path = sys.path + [input_api.os_path.join(
116 input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
117 import checkdeps
118 from cpp_checker import CppChecker
119 from rules import Rule
120 finally:
121 # Restore sys.path to what it was before.
122 sys.path = original_sys_path
123
124 added_includes = []
125 for f in input_api.AffectedFiles():
126 if not CppChecker.IsCppFile(f.LocalPath()):
127 continue
128
129 changed_lines = [line for line_num, line in f.ChangedContents()]
130 added_includes.append([f.LocalPath(), changed_lines])
131
132 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
133
134 error_descriptions = []
135 warning_descriptions = []
136 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
137 added_includes):
138 description_with_path = '%s\n %s' % (path, rule_description)
139 if rule_type == Rule.DISALLOW:
140 error_descriptions.append(description_with_path)
141 else:
142 warning_descriptions.append(description_with_path)
143
144 results = []
145 if error_descriptions:
146 results.append(output_api.PresubmitError(
147 'You added one or more #includes that violate checkdeps rules.',
148 error_descriptions))
149 if warning_descriptions:
150 results.append(output_api.PresubmitPromptOrNotify(
151 'You added one or more #includes of files that are temporarily\n'
152 'allowed but being removed. Can you avoid introducing the\n'
153 '#include? See relevant DEPS file(s) for details and contacts.',
154 warning_descriptions))
155 return results
156
157
Rubin Xu2894c6a2019-02-07 16:01:35 +0000158def _CheckHeadersHaveIncludeGuards(input_api, output_api):
159 """Ensures that all header files have include guards."""
160 file_inclusion_pattern = r'src/.+\.h'
161
162 def FilterFile(affected_file):
163 black_list = (_EXCLUDED_PATHS +
164 input_api.DEFAULT_BLACK_LIST)
165 return input_api.FilterSourceFile(
166 affected_file,
167 white_list=(file_inclusion_pattern, ),
168 black_list=black_list)
169
170 leading_src_pattern = input_api.re.compile(r'^src/')
171 dash_dot_slash_pattern = input_api.re.compile(r'[-./]')
172 def PathToGuardMacro(path):
173 """Guards should be of the form V8_PATH_TO_FILE_WITHOUT_SRC_H_."""
174 x = input_api.re.sub(leading_src_pattern, 'v8_', path)
175 x = input_api.re.sub(dash_dot_slash_pattern, '_', x)
176 x = x.upper() + "_"
177 return x
178
179 problems = []
180 for f in input_api.AffectedSourceFiles(FilterFile):
181 local_path = f.LocalPath()
182 guard_macro = PathToGuardMacro(local_path)
183 guard_patterns = [
184 input_api.re.compile(r'^#ifndef ' + guard_macro + '$'),
185 input_api.re.compile(r'^#define ' + guard_macro + '$'),
186 input_api.re.compile(r'^#endif // ' + guard_macro + '$')]
187 skip_check_pattern = input_api.re.compile(
188 r'^// PRESUBMIT_INTENTIONALLY_MISSING_INCLUDE_GUARD')
189 found_patterns = [ False, False, False ]
190 file_omitted = False
191
192 for line in f.NewContents():
193 for i in range(len(guard_patterns)):
194 if guard_patterns[i].match(line):
195 found_patterns[i] = True
196 if skip_check_pattern.match(line):
197 file_omitted = True
198 break
199
200 if not file_omitted and not all(found_patterns):
201 problems.append(
202 '%s: Missing include guard \'%s\'' % (local_path, guard_macro))
203
204 if problems:
205 return [output_api.PresubmitError(
206 'You added one or more header files without an appropriate\n'
207 'include guard. Add the include guard {#ifndef,#define,#endif}\n'
208 'triplet or omit the check entirely through the magic comment:\n'
209 '"// PRESUBMIT_INTENTIONALLY_MISSING_INCLUDE_GUARD".', problems)]
210 else:
211 return []
212
213
214# TODO(mstarzinger): Similar checking should be made available as part of
215# tools/presubmit.py (note that tools/check-inline-includes.sh exists).
Ben Murdoch014dc512016-03-22 12:00:34 +0000216def _CheckNoInlineHeaderIncludesInNormalHeaders(input_api, output_api):
217 """Attempts to prevent inclusion of inline headers into normal header
218 files. This tries to establish a layering where inline headers can be
219 included by other inline headers or compilation units only."""
220 file_inclusion_pattern = r'(?!.+-inl\.h).+\.h'
221 include_directive_pattern = input_api.re.compile(r'#include ".+-inl.h"')
Rubin Xu2894c6a2019-02-07 16:01:35 +0000222 include_error = (
223 'You are including an inline header (e.g. foo-inl.h) within a normal\n'
224 'header (e.g. bar.h) file. This violates layering of dependencies.')
Ben Murdoch014dc512016-03-22 12:00:34 +0000225
226 def FilterFile(affected_file):
227 black_list = (_EXCLUDED_PATHS +
228 input_api.DEFAULT_BLACK_LIST)
229 return input_api.FilterSourceFile(
230 affected_file,
231 white_list=(file_inclusion_pattern, ),
232 black_list=black_list)
233
234 problems = []
235 for f in input_api.AffectedSourceFiles(FilterFile):
236 local_path = f.LocalPath()
237 for line_number, line in f.ChangedContents():
238 if (include_directive_pattern.search(line)):
239 problems.append(
240 '%s:%d\n %s' % (local_path, line_number, line.strip()))
241
242 if problems:
Rubin Xu2894c6a2019-02-07 16:01:35 +0000243 return [output_api.PresubmitError(include_error, problems)]
Ben Murdoch014dc512016-03-22 12:00:34 +0000244 else:
245 return []
246
247
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000248def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
249 """Attempts to prevent use of functions intended only for testing in
250 non-testing code. For now this is just a best-effort implementation
251 that ignores header files and may have some false positives. A
252 better implementation would probably need a proper C++ parser.
253 """
254 # We only scan .cc files, as the declaration of for-testing functions in
255 # header files are hard to distinguish from calls to such functions without a
256 # proper C++ parser.
257 file_inclusion_pattern = r'.+\.cc'
258
259 base_function_pattern = r'[ :]test::[^\s]+|ForTest(ing)?|for_test(ing)?'
260 inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
261 comment_pattern = input_api.re.compile(r'//.*(%s)' % base_function_pattern)
262 exclusion_pattern = input_api.re.compile(
263 r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
264 base_function_pattern, base_function_pattern))
265
266 def FilterFile(affected_file):
267 black_list = (_EXCLUDED_PATHS +
268 _TEST_CODE_EXCLUDED_PATHS +
269 input_api.DEFAULT_BLACK_LIST)
270 return input_api.FilterSourceFile(
271 affected_file,
272 white_list=(file_inclusion_pattern, ),
273 black_list=black_list)
274
275 problems = []
276 for f in input_api.AffectedSourceFiles(FilterFile):
277 local_path = f.LocalPath()
278 for line_number, line in f.ChangedContents():
279 if (inclusion_pattern.search(line) and
280 not comment_pattern.search(line) and
281 not exclusion_pattern.search(line)):
282 problems.append(
283 '%s:%d\n %s' % (local_path, line_number, line.strip()))
284
285 if problems:
286 return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
287 else:
288 return []
289
290
291def _CommonChecks(input_api, output_api):
292 """Checks common to both upload and commit."""
293 results = []
Rubin Xu2894c6a2019-02-07 16:01:35 +0000294 # TODO(machenbach): Replace some of those checks, e.g. owners and copyright,
295 # with the canned PanProjectChecks. Need to make sure that the checks all
296 # pass on all existing files.
297 results.extend(input_api.canned_checks.CheckOwnersFormat(
298 input_api, output_api))
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000299 results.extend(input_api.canned_checks.CheckOwners(
Rubin Xu2894c6a2019-02-07 16:01:35 +0000300 input_api, output_api))
301 results.extend(_CheckCommitMessageBugEntry(input_api, output_api))
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000302 results.extend(input_api.canned_checks.CheckPatchFormatted(
303 input_api, output_api))
Ben Murdoch13e2dad2016-09-16 13:49:30 +0100304 results.extend(input_api.canned_checks.CheckGenderNeutral(
305 input_api, output_api))
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000306 results.extend(_V8PresubmitChecks(input_api, output_api))
307 results.extend(_CheckUnwantedDependencies(input_api, output_api))
308 results.extend(
309 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
Rubin Xu2894c6a2019-02-07 16:01:35 +0000310 results.extend(_CheckHeadersHaveIncludeGuards(input_api, output_api))
Ben Murdoch014dc512016-03-22 12:00:34 +0000311 results.extend(
312 _CheckNoInlineHeaderIncludesInNormalHeaders(input_api, output_api))
Rubin Xu2894c6a2019-02-07 16:01:35 +0000313 results.extend(_CheckJSONFiles(input_api, output_api))
314 results.extend(_CheckMacroUndefs(input_api, output_api))
315 results.extend(input_api.RunTests(
316 input_api.canned_checks.CheckVPythonSpec(input_api, output_api)))
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000317 return results
318
319
320def _SkipTreeCheck(input_api, output_api):
321 """Check the env var whether we want to skip tree check.
Ben Murdoch014dc512016-03-22 12:00:34 +0000322 Only skip if include/v8-version.h has been updated."""
323 src_version = 'include/v8-version.h'
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000324 if not input_api.AffectedSourceFiles(
325 lambda file: file.LocalPath() == src_version):
326 return False
327 return input_api.environ.get('PRESUBMIT_TREE_CHECK') == 'skip'
328
329
Rubin Xu2894c6a2019-02-07 16:01:35 +0000330def _CheckCommitMessageBugEntry(input_api, output_api):
331 """Check that bug entries are well-formed in commit message."""
332 bogus_bug_msg = (
333 'Bogus BUG entry: %s. Please specify the issue tracker prefix and the '
334 'issue number, separated by a colon, e.g. v8:123 or chromium:12345.')
335 results = []
336 for bug in (input_api.change.BUG or '').split(','):
337 bug = bug.strip()
338 if 'none'.startswith(bug.lower()):
339 continue
340 if ':' not in bug:
341 try:
342 if int(bug) > 100000:
343 # Rough indicator for current chromium bugs.
344 prefix_guess = 'chromium'
345 else:
346 prefix_guess = 'v8'
347 results.append('BUG entry requires issue tracker prefix, e.g. %s:%s' %
348 (prefix_guess, bug))
349 except ValueError:
350 results.append(bogus_bug_msg % bug)
351 elif not re.match(r'\w+:\d+', bug):
352 results.append(bogus_bug_msg % bug)
353 return [output_api.PresubmitError(r) for r in results]
354
355
356def _CheckJSONFiles(input_api, output_api):
357 def FilterFile(affected_file):
358 return input_api.FilterSourceFile(
359 affected_file,
360 white_list=(r'.+\.json',))
361
362 results = []
363 for f in input_api.AffectedFiles(
364 file_filter=FilterFile, include_deletes=False):
365 with open(f.LocalPath()) as j:
366 try:
367 json.load(j)
368 except Exception as e:
369 results.append(
370 'JSON validation failed for %s. Error:\n%s' % (f.LocalPath(), e))
371
372 return [output_api.PresubmitError(r) for r in results]
373
374
375def _CheckMacroUndefs(input_api, output_api):
376 """
377 Checks that each #define in a .cc file is eventually followed by an #undef.
378
379 TODO(clemensh): This check should eventually be enabled for all cc files via
380 tools/presubmit.py (https://crbug.com/v8/6811).
381 """
382 def FilterFile(affected_file):
383 # Skip header files, as they often define type lists which are used in
384 # other files.
385 white_list = (r'.+\.cc',r'.+\.cpp',r'.+\.c')
386 return input_api.FilterSourceFile(affected_file, white_list=white_list)
387
388 def TouchesMacros(f):
389 for line in f.GenerateScmDiff().splitlines():
390 if not line.startswith('+') and not line.startswith('-'):
391 continue
392 if define_pattern.match(line[1:]) or undef_pattern.match(line[1:]):
393 return True
394 return False
395
396 define_pattern = input_api.re.compile(r'#define (\w+)')
397 undef_pattern = input_api.re.compile(r'#undef (\w+)')
398 errors = []
399 for f in input_api.AffectedFiles(
400 file_filter=FilterFile, include_deletes=False):
401 if not TouchesMacros(f):
402 continue
403
404 defined_macros = dict()
405 with open(f.LocalPath()) as fh:
406 line_nr = 0
407 for line in fh:
408 line_nr += 1
409
410 define_match = define_pattern.match(line)
411 if define_match:
412 name = define_match.group(1)
413 defined_macros[name] = line_nr
414
415 undef_match = undef_pattern.match(line)
416 if undef_match:
417 name = undef_match.group(1)
418 if not name in defined_macros:
419 errors.append('{}:{}: Macro named \'{}\' was not defined before.'
420 .format(f.LocalPath(), line_nr, name))
421 else:
422 del defined_macros[name]
423 for name, line_nr in sorted(defined_macros.items(), key=lambda e: e[1]):
424 errors.append('{}:{}: Macro missing #undef: {}'
425 .format(f.LocalPath(), line_nr, name))
426
427 if errors:
428 return [output_api.PresubmitPromptOrNotify(
429 'Detected mismatches in #define / #undef in the file(s) where you '
430 'modified preprocessor macros.',
431 errors)]
432 return []
433
434
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000435def CheckChangeOnUpload(input_api, output_api):
436 results = []
437 results.extend(_CommonChecks(input_api, output_api))
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000438 return results
439
440
441def CheckChangeOnCommit(input_api, output_api):
442 results = []
443 results.extend(_CommonChecks(input_api, output_api))
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000444 results.extend(input_api.canned_checks.CheckChangeHasDescription(
445 input_api, output_api))
446 if not _SkipTreeCheck(input_api, output_api):
447 results.extend(input_api.canned_checks.CheckTreeIsOpen(
448 input_api, output_api,
449 json_url='http://v8-status.appspot.com/current?format=json'))
450 return results
Rubin Xu2894c6a2019-02-07 16:01:35 +0000451
452def PostUploadHook(cl, change, output_api):
453 """git cl upload will call this hook after the issue is created/modified.
454
455 This hook adds a noi18n bot if the patch affects Intl.
456 """
457 def affects_intl(f):
458 return 'intl' in f.LocalPath() or 'test262' in f.LocalPath()
459 if not change.AffectedFiles(file_filter=affects_intl):
460 return []
461 return output_api.EnsureCQIncludeTrybotsAreAdded(
462 cl,
463 [
464 'luci.v8.try:v8_linux_noi18n_rel_ng'
465 ],
466 'Automatically added noi18n trybots to run tests on CQ.')