Fix emulator build error
[platform/framework/web/chromium-efl.git] / v8 / PRESUBMIT.py
1 # 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
30 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
31 for more details about the presubmit API built into gcl.
32 """
33
34 import json
35 import os
36 import re
37 import sys
38
39 # This line is 'magic' in that git-cl looks for it to decide whether to
40 # use Python3 instead of Python2 when running the code in this file.
41 USE_PYTHON3 = True
42
43 _EXCLUDED_PATHS = (
44     r"^test[\\\/].*",
45     r"^testing[\\\/].*",
46     r"^third_party[\\\/].*",
47     r"^tools[\\\/].*",
48 )
49
50 _LICENSE_FILE = (
51     r"LICENSE"
52 )
53
54 # Regular expression that matches code which should not be run through cpplint.
55 _NO_LINT_PATHS = (
56     r'src[\\\/]base[\\\/]export-template\.h',
57 )
58
59
60 # Regular expression that matches code only used for test binaries
61 # (best effort).
62 _TEST_CODE_EXCLUDED_PATHS = (
63     r'.+-unittest\.cc',
64     # Has a method VisitForTest().
65     r'src[\\\/]compiler[\\\/]ast-graph-builder\.cc',
66     # Test extension.
67     r'src[\\\/]extensions[\\\/]gc-extension\.cc',
68     # Runtime functions used for testing.
69     r'src[\\\/]runtime[\\\/]runtime-test\.cc',
70     # Testing helpers.
71     r'src[\\\/]heap[\\\/]cppgc[\\\/]testing\.cc',
72 )
73
74
75 _TEST_ONLY_WARNING = (
76     'You might be calling functions intended only for testing from\n'
77     'production code.  It is OK to ignore this warning if you know what\n'
78     'you are doing, as the heuristics used to detect the situation are\n'
79     'not perfect.  The commit queue will not block on this warning.')
80
81
82 def _V8PresubmitChecks(input_api, output_api):
83   """Runs the V8 presubmit checks."""
84   import sys
85   sys.path.append(input_api.os_path.join(
86         input_api.PresubmitLocalPath(), 'tools'))
87   from v8_presubmit import CppLintProcessor
88   from v8_presubmit import GCMoleProcessor
89   from v8_presubmit import JSLintProcessor
90   from v8_presubmit import TorqueLintProcessor
91   from v8_presubmit import SourceProcessor
92   from v8_presubmit import StatusFilesProcessor
93
94   def FilterFile(affected_file):
95     return input_api.FilterSourceFile(
96       affected_file,
97       files_to_check=None,
98       files_to_skip=_NO_LINT_PATHS)
99
100   def FilterTorqueFile(affected_file):
101     return input_api.FilterSourceFile(
102       affected_file,
103       files_to_check=(r'.+\.tq'))
104
105   def FilterJSFile(affected_file):
106     return input_api.FilterSourceFile(
107       affected_file,
108       files_to_check=(r'.+\.m?js'))
109
110   results = []
111   if not CppLintProcessor().RunOnFiles(
112       input_api.AffectedFiles(file_filter=FilterFile, include_deletes=False)):
113     results.append(output_api.PresubmitError("C++ lint check failed"))
114   if not TorqueLintProcessor().RunOnFiles(
115       input_api.AffectedFiles(file_filter=FilterTorqueFile,
116                               include_deletes=False)):
117     results.append(output_api.PresubmitError("Torque format check failed"))
118   if not JSLintProcessor().RunOnFiles(
119       input_api.AffectedFiles(file_filter=FilterJSFile,
120                               include_deletes=False)):
121     results.append(output_api.PresubmitError("JS format check failed"))
122   if not SourceProcessor().RunOnFiles(
123       input_api.AffectedFiles(include_deletes=False)):
124     results.append(output_api.PresubmitError(
125         "Copyright header, trailing whitespaces and two empty lines " \
126         "between declarations check failed"))
127   if not StatusFilesProcessor().RunOnFiles(
128       input_api.AffectedFiles(include_deletes=True)):
129     results.append(output_api.PresubmitError("Status file check failed"))
130   if not GCMoleProcessor().RunOnFiles(
131       input_api.AffectedFiles(include_deletes=False)):
132     results.append(output_api.PresubmitError("GCMole pattern check failed"))
133   results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
134       input_api, output_api, bot_allowlist=[
135         'v8-ci-autoroll-builder@chops-service-accounts.iam.gserviceaccount.com'
136       ]))
137   return results
138
139
140 def _CheckUnwantedDependencies(input_api, output_api):
141   """Runs checkdeps on #include statements added in this
142   change. Breaking - rules is an error, breaking ! rules is a
143   warning.
144   """
145   # We need to wait until we have an input_api object and use this
146   # roundabout construct to import checkdeps because this file is
147   # eval-ed and thus doesn't have __file__.
148   original_sys_path = sys.path
149   try:
150     sys.path = sys.path + [input_api.os_path.join(
151         input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
152     import checkdeps
153     from cpp_checker import CppChecker
154     from rules import Rule
155   finally:
156     # Restore sys.path to what it was before.
157     sys.path = original_sys_path
158
159   def _FilesImpactedByDepsChange(files):
160     all_files = [f.AbsoluteLocalPath() for f in files]
161     deps_files = [p for p in all_files if IsDepsFile(p)]
162     impacted_files = union([_CollectImpactedFiles(path) for path in deps_files])
163     impacted_file_objs = [ImpactedFile(path) for path in impacted_files]
164     return impacted_file_objs
165
166   def IsDepsFile(p):
167     return os.path.isfile(p) and os.path.basename(p) == 'DEPS'
168
169   def union(list_of_lists):
170     """Ensure no duplicates"""
171     return set(sum(list_of_lists, []))
172
173   def _CollectImpactedFiles(deps_file):
174     # TODO(liviurau): Do not walk paths twice. Then we have no duplicates.
175     # Higher level DEPS changes may dominate lower level DEPS changes.
176     # TODO(liviurau): Check if DEPS changed in the right way.
177     # 'include_rules' impact c++ files but 'vars' or 'deps' do not.
178     # Maybe we just eval both old and new DEPS content and check
179     # if the list are the same.
180     result = []
181     parent_dir = os.path.dirname(deps_file)
182     for relative_f in input_api.change.AllFiles(parent_dir):
183       abs_f = os.path.join(parent_dir, relative_f)
184       if CppChecker.IsCppFile(abs_f):
185         result.append(abs_f)
186     return result
187
188   class ImpactedFile(object):
189     """Duck type version of AffectedFile needed to check files under directories
190     where a DEPS file changed. Extend the interface along the line of
191     AffectedFile if you need it for other checks."""
192
193     def __init__(self, path):
194       self._path = path
195
196     def LocalPath(self):
197       path = self._path.replace(os.sep, '/')
198       return os.path.normpath(path)
199
200     def ChangedContents(self):
201       with open(self._path) as f:
202         # TODO(liviurau): read only '#include' lines
203         lines = f.readlines()
204       return enumerate(lines, start=1)
205
206   def _FilterDuplicates(impacted_files, affected_files):
207     """"We include all impacted files but exclude affected files that are also
208     impacted. Files impacted by DEPS changes take precedence before files
209     affected by direct changes."""
210     result = impacted_files[:]
211     only_paths = set([imf.LocalPath() for imf in impacted_files])
212     for af in affected_files:
213       if not af.LocalPath() in only_paths:
214         result.append(af)
215     return result
216
217   added_includes = []
218   affected_files = input_api.AffectedFiles()
219   impacted_by_deps = _FilesImpactedByDepsChange(affected_files)
220   for f in _FilterDuplicates(impacted_by_deps, affected_files):
221     if not CppChecker.IsCppFile(f.LocalPath()):
222       continue
223
224     changed_lines = [line for line_num, line in f.ChangedContents()]
225     added_includes.append([f.LocalPath(), changed_lines])
226
227   deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
228
229   error_descriptions = []
230   warning_descriptions = []
231   for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
232       added_includes):
233     description_with_path = '{}\n    {}'.format(path, rule_description)
234     if rule_type == Rule.DISALLOW:
235       error_descriptions.append(description_with_path)
236     else:
237       warning_descriptions.append(description_with_path)
238
239   results = []
240   if error_descriptions:
241     results.append(output_api.PresubmitError(
242         'You added one or more #includes that violate checkdeps rules.',
243         error_descriptions))
244   if warning_descriptions:
245     results.append(output_api.PresubmitPromptOrNotify(
246         'You added one or more #includes of files that are temporarily\n'
247         'allowed but being removed. Can you avoid introducing the\n'
248         '#include? See relevant DEPS file(s) for details and contacts.',
249         warning_descriptions))
250   return results
251
252
253 def _CheckHeadersHaveIncludeGuards(input_api, output_api):
254   """Ensures that all header files have include guards."""
255   file_inclusion_pattern = r'src/.+\.h'
256
257   def FilterFile(affected_file):
258     files_to_skip = _EXCLUDED_PATHS + input_api.DEFAULT_FILES_TO_SKIP
259     return input_api.FilterSourceFile(
260       affected_file,
261       files_to_check=(file_inclusion_pattern, ),
262       files_to_skip=files_to_skip)
263
264   leading_src_pattern = input_api.re.compile(r'^src[\\\/]')
265   dash_dot_slash_pattern = input_api.re.compile(r'[-.\\\/]')
266
267   def PathToGuardMacro(path):
268     """Guards should be of the form V8_PATH_TO_FILE_WITHOUT_SRC_H_."""
269     x = input_api.re.sub(leading_src_pattern, 'v8_', path)
270     x = input_api.re.sub(dash_dot_slash_pattern, '_', x)
271     x = x.upper() + "_"
272     return x
273
274   problems = []
275   for f in input_api.AffectedSourceFiles(FilterFile):
276     local_path = f.LocalPath()
277     guard_macro = PathToGuardMacro(local_path)
278     guard_patterns = [
279             input_api.re.compile(r'^#ifndef ' + guard_macro + '$'),
280             input_api.re.compile(r'^#define ' + guard_macro + '$'),
281             input_api.re.compile(r'^#endif  // ' + guard_macro + '$')]
282     skip_check_pattern = input_api.re.compile(
283             r'^// PRESUBMIT_INTENTIONALLY_MISSING_INCLUDE_GUARD')
284     found_patterns = [ False, False, False ]
285     file_omitted = False
286
287     for line in f.NewContents():
288       for i in range(len(guard_patterns)):
289         if guard_patterns[i].match(line):
290           found_patterns[i] = True
291       if skip_check_pattern.match(line):
292         file_omitted = True
293         break
294
295     if not file_omitted and not all(found_patterns):
296       problems.append('{}: Missing include guard \'{}\''.format(
297           local_path, guard_macro))
298
299   if problems:
300     return [output_api.PresubmitError(
301         'You added one or more header files without an appropriate\n'
302         'include guard. Add the include guard {#ifndef,#define,#endif}\n'
303         'triplet or omit the check entirely through the magic comment:\n'
304         '"// PRESUBMIT_INTENTIONALLY_MISSING_INCLUDE_GUARD".', problems)]
305   else:
306     return []
307
308
309 def _CheckNoInlineHeaderIncludesInNormalHeaders(input_api, output_api):
310   """Attempts to prevent inclusion of inline headers into normal header
311   files. This tries to establish a layering where inline headers can be
312   included by other inline headers or compilation units only."""
313   file_inclusion_pattern = r'(?!.+-inl\.h).+\.h'
314   include_directive_pattern = input_api.re.compile(r'#include ".+-inl.h"')
315   include_error = (
316     'You are including an inline header (e.g. foo-inl.h) within a normal\n'
317     'header (e.g. bar.h) file.  This violates layering of dependencies.')
318
319   def FilterFile(affected_file):
320     files_to_skip = _EXCLUDED_PATHS + input_api.DEFAULT_FILES_TO_SKIP
321     return input_api.FilterSourceFile(
322       affected_file,
323       files_to_check=(file_inclusion_pattern, ),
324       files_to_skip=files_to_skip)
325
326   problems = []
327   for f in input_api.AffectedSourceFiles(FilterFile):
328     local_path = f.LocalPath()
329     for line_number, line in f.ChangedContents():
330       if (include_directive_pattern.search(line)):
331         problems.append('{}:{}\n    {}'.format(local_path, line_number,
332                                                line.strip()))
333
334   if problems:
335     return [output_api.PresubmitError(include_error, problems)]
336   else:
337     return []
338
339
340 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
341   """Attempts to prevent use of functions intended only for testing in
342   non-testing code. For now this is just a best-effort implementation
343   that ignores header files and may have some false positives. A
344   better implementation would probably need a proper C++ parser.
345   """
346   # We only scan .cc files, as the declaration of for-testing functions in
347   # header files are hard to distinguish from calls to such functions without a
348   # proper C++ parser.
349   file_inclusion_pattern = r'.+\.cc'
350
351   base_function_pattern = r'[ :]test::[^\s]+|ForTest(ing)?|for_test(ing)?'
352   inclusion_pattern = input_api.re.compile(
353       r'({})\s*\('.format(base_function_pattern))
354   comment_pattern = input_api.re.compile(
355       r'//.*({})'.format(base_function_pattern))
356   exclusion_pattern = input_api.re.compile(
357       r'::[A-Za-z0-9_]+({})|({})[^;]+'.format(base_function_pattern,
358                                               base_function_pattern) + '\{')
359
360   def FilterFile(affected_file):
361     files_to_skip = (_EXCLUDED_PATHS +
362                      _TEST_CODE_EXCLUDED_PATHS +
363                      input_api.DEFAULT_FILES_TO_SKIP)
364     return input_api.FilterSourceFile(
365       affected_file,
366       files_to_check=(file_inclusion_pattern, ),
367       files_to_skip=files_to_skip)
368
369   problems = []
370   for f in input_api.AffectedSourceFiles(FilterFile):
371     local_path = f.LocalPath()
372     for line_number, line in f.ChangedContents():
373       if (inclusion_pattern.search(line) and
374           not comment_pattern.search(line) and
375           not exclusion_pattern.search(line)):
376         problems.append('{}:{}\n    {}'.format(local_path, line_number,
377                                                line.strip()))
378
379   if problems:
380     return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
381   else:
382     return []
383
384
385 def _CheckGenderNeutralInLicenses(input_api, output_api):
386   # License files are taken as is, even if they include gendered pronouns.
387   def LicenseFilter(path):
388     input_api.FilterSourceFile(path, files_to_skip=_LICENSE_FILE)
389
390   return input_api.canned_checks.CheckGenderNeutral(
391     input_api, output_api, source_file_filter=LicenseFilter)
392
393
394 def _RunTestsWithVPythonSpec(input_api, output_api):
395   return input_api.RunTests(
396     input_api.canned_checks.CheckVPythonSpec(input_api, output_api))
397
398
399 def _CommonChecks(input_api, output_api):
400   """Checks common to both upload and commit."""
401   # TODO(machenbach): Replace some of those checks, e.g. owners and copyright,
402   # with the canned PanProjectChecks. Need to make sure that the checks all
403   # pass on all existing files.
404   checks = [
405     input_api.canned_checks.CheckOwnersFormat,
406     input_api.canned_checks.CheckOwners,
407     _CheckCommitMessageBugEntry,
408     input_api.canned_checks.CheckPatchFormatted,
409     _CheckGenderNeutralInLicenses,
410     _V8PresubmitChecks,
411     _CheckUnwantedDependencies,
412     _CheckNoProductionCodeUsingTestOnlyFunctions,
413     _CheckHeadersHaveIncludeGuards,
414     _CheckNoInlineHeaderIncludesInNormalHeaders,
415     _CheckJSONFiles,
416     _CheckNoexceptAnnotations,
417     _RunTestsWithVPythonSpec,
418   ]
419
420   return sum([check(input_api, output_api) for check in checks], [])
421
422
423 def _SkipTreeCheck(input_api, output_api):
424   """Check the env var whether we want to skip tree check.
425      Only skip if include/v8-version.h has been updated."""
426   src_version = 'include/v8-version.h'
427   if not input_api.AffectedSourceFiles(
428       lambda file: file.LocalPath() == src_version):
429     return False
430   return input_api.environ.get('PRESUBMIT_TREE_CHECK') == 'skip'
431
432
433 def _CheckCommitMessageBugEntry(input_api, output_api):
434   """Check that bug entries are well-formed in commit message."""
435   bogus_bug_msg = (
436       'Bogus BUG entry: {}. Please specify prefix:number for v8 or chromium '
437       '(e.g. chromium:12345) or b/number for buganizer.')
438   results = []
439   for bug in (input_api.change.BUG or '').split(','):
440     bug = bug.strip()
441     if 'none'.startswith(bug.lower()):
442       continue
443     if ':' not in bug and not bug.startswith('b/'):
444       try:
445         if int(bug) > 10000000:
446           results.append(
447             'Buganizer entry requires issue tracker prefix b/{}'.format(bug))
448         else:
449           if int(bug) > 200000:
450             prefix_guess = 'chromium'
451           else:
452             prefix_guess = 'v8'
453           results.append(
454               'BUG entry requires issue tracker prefix, e.g. {}:{}'.format(
455                   prefix_guess, bug))
456       except ValueError:
457         results.append(bogus_bug_msg.format(bug))
458     elif not re.match(r'\w+[:\/]\d+', bug):
459       results.append(bogus_bug_msg.format(bug))
460   return [output_api.PresubmitError(r) for r in results]
461
462
463 def _CheckJSONFiles(input_api, output_api):
464   def FilterFile(affected_file):
465     return input_api.FilterSourceFile(
466         affected_file,
467         files_to_check=(r'.+\.json',))
468
469   results = []
470   for f in input_api.AffectedFiles(
471       file_filter=FilterFile, include_deletes=False):
472     with open(f.LocalPath()) as j:
473       try:
474         json.load(j)
475       except Exception as e:
476         results.append('JSON validation failed for {}. Error:\n{}'.format(
477             f.LocalPath(), e))
478
479   return [output_api.PresubmitError(r) for r in results]
480
481
482 def _CheckNoexceptAnnotations(input_api, output_api):
483   """
484   Checks that all user-defined constructors and assignment operators are marked
485   V8_NOEXCEPT.
486
487   This is required for standard containers to pick the right constructors. Our
488   macros (like MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS) add this automatically.
489   Omitting it at some places can result in weird compiler errors if this is
490   mixed with other classes that have the annotation.
491
492   TODO(clemensb): This check should eventually be enabled for all files via
493   tools/presubmit.py (https://crbug.com/v8/8616).
494   """
495
496   def FilterFile(affected_file):
497     files_to_skip = _EXCLUDED_PATHS + (
498         # Skip api.cc since we cannot easily add the 'noexcept' annotation to
499         # public methods.
500         r'src[\\\/]api[\\\/]api\.cc',
501         # Skip src/bigint/ because it's meant to be V8-independent.
502         r'src[\\\/]bigint[\\\/].*',
503     )
504     return input_api.FilterSourceFile(
505         affected_file,
506         files_to_check=(r'src[\\\/].*\.cc', r'src[\\\/].*\.h',
507                         r'test[\\\/].*\.cc', r'test[\\\/].*\.h'),
508         files_to_skip=files_to_skip)
509
510   # matches any class name.
511   class_name = r'\b([A-Z][A-Za-z0-9_:]*)(?:::\1)?'
512   # initial class name is potentially followed by this to declare an assignment
513   # operator.
514   potential_assignment = r'(?:&\s+(?:\1::)?operator=)?\s*'
515   # matches an argument list that contains only a reference to a class named
516   # like the first capture group, potentially const.
517   single_class_ref_arg = r'\(\s*(?:const\s+)?\1(?:::\1)?&&?[^,;)]*\)'
518   # matches anything but a sequence of whitespaces followed by either
519   # V8_NOEXCEPT or "= delete".
520   not_followed_by_noexcept = r'(?!\s+(?:V8_NOEXCEPT|=\s+delete)\b)'
521   full_pattern = r'^.*?' + class_name + potential_assignment + \
522       single_class_ref_arg + not_followed_by_noexcept + '.*?$'
523   regexp = input_api.re.compile(full_pattern, re.MULTILINE)
524
525   errors = []
526   for f in input_api.AffectedFiles(file_filter=FilterFile,
527                                    include_deletes=False):
528     with open(f.LocalPath()) as fh:
529       for match in re.finditer(regexp, fh.read()):
530         errors.append(f'in {f.LocalPath()}: {match.group().strip()}')
531
532   if errors:
533     return [output_api.PresubmitPromptOrNotify(
534         'Copy constructors, move constructors, copy assignment operators and '
535         'move assignment operators should be marked V8_NOEXCEPT.\n'
536         'Please report false positives on https://crbug.com/v8/8616.',
537         errors)]
538   return []
539
540
541 def CheckChangeOnUpload(input_api, output_api):
542   results = []
543   results.extend(_CommonChecks(input_api, output_api))
544   return results
545
546
547 def CheckChangeOnCommit(input_api, output_api):
548   results = []
549   results.extend(_CommonChecks(input_api, output_api))
550   results.extend(input_api.canned_checks.CheckChangeHasDescription(
551       input_api, output_api))
552   if not _SkipTreeCheck(input_api, output_api):
553     results.extend(input_api.canned_checks.CheckTreeIsOpen(
554         input_api, output_api,
555         json_url='http://v8-status.appspot.com/current?format=json'))
556   return results