1 # Copyright (c) 2012 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.
5 """Top-level presubmit script for Chromium.
7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8 for more details about the presubmit API built into depot_tools.
13 r"^native_client_sdk[\\\/]src[\\\/]build_tools[\\\/]make_rules.py",
14 r"^native_client_sdk[\\\/]src[\\\/]build_tools[\\\/]make_simple.py",
15 r"^native_client_sdk[\\\/]src[\\\/]tools[\\\/].*.mk",
16 r"^net[\\\/]tools[\\\/]spdyshark[\\\/].*",
18 r"^third_party[\\\/](WebKit|blink)[\\\/].*",
19 r"^third_party[\\\/]breakpad[\\\/].*",
23 r".+[\\\/]pnacl_shim\.c$",
24 r"^gpu[\\\/]config[\\\/].*_list_json\.cc$",
25 r"^chrome[\\\/]browser[\\\/]resources[\\\/]pdf[\\\/]index.js",
26 r"tools[\\\/]md_browser[\\\/].*\.css$",
27 # Test pages for Maps telemetry tests.
28 r"tools[\\\/]perf[\\\/]page_sets[\\\/]maps_perf_test.*",
29 # Test pages for WebRTC telemetry tests.
30 r"tools[\\\/]perf[\\\/]page_sets[\\\/]webrtc_cases.*",
34 # Fragment of a regular expression that matches C++ and Objective-C++
35 # implementation files.
36 _IMPLEMENTATION_EXTENSIONS = r'\.(cc|cpp|cxx|mm)$'
39 # Regular expression that matches code only used for test binaries
41 _TEST_CODE_EXCLUDED_PATHS = (
42 r'.*[\\\/](fake_|test_|mock_).+%s' % _IMPLEMENTATION_EXTENSIONS,
43 r'.+_test_(base|support|util)%s' % _IMPLEMENTATION_EXTENSIONS,
44 r'.+_(api|browser|eg|int|perf|pixel|unit|ui)?test(_[a-z]+)?%s' %
45 _IMPLEMENTATION_EXTENSIONS,
46 r'.+profile_sync_service_harness%s' % _IMPLEMENTATION_EXTENSIONS,
47 r'.*[\\\/](test|tool(s)?)[\\\/].*',
48 # content_shell is used for running layout tests.
49 r'content[\\\/]shell[\\\/].*',
50 # Non-production example code.
51 r'mojo[\\\/]examples[\\\/].*',
52 # Launcher for running iOS tests on the simulator.
53 r'testing[\\\/]iossim[\\\/]iossim\.mm$',
57 _TEST_ONLY_WARNING = (
58 'You might be calling functions intended only for testing from\n'
59 'production code. It is OK to ignore this warning if you know what\n'
60 'you are doing, as the heuristics used to detect the situation are\n'
61 'not perfect. The commit queue will not block on this warning.')
64 _INCLUDE_ORDER_WARNING = (
65 'Your #include order seems to be broken. Remember to use the right '
66 'collation (LC_COLLATE=C) and check\nhttps://google.github.io/styleguide/'
67 'cppguide.html#Names_and_Order_of_Includes')
70 _BANNED_JAVA_FUNCTIONS = (
72 'StrictMode.allowThreadDiskReads()',
74 'Prefer using StrictModeContext.allowDiskReads() to using StrictMode '
80 'StrictMode.allowThreadDiskWrites()',
82 'Prefer using StrictModeContext.allowDiskWrites() to using StrictMode '
89 _BANNED_OBJC_FUNCTIONS = (
93 'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
94 'prohibited. Please use CrTrackingArea instead.',
95 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
100 r'/NSTrackingArea\W',
102 'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
104 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
109 'convertPointFromBase:',
111 'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
112 'Please use |convertPoint:(point) fromView:nil| instead.',
113 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
118 'convertPointToBase:',
120 'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
121 'Please use |convertPoint:(point) toView:nil| instead.',
122 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
127 'convertRectFromBase:',
129 'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
130 'Please use |convertRect:(point) fromView:nil| instead.',
131 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
136 'convertRectToBase:',
138 'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
139 'Please use |convertRect:(point) toView:nil| instead.',
140 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
145 'convertSizeFromBase:',
147 'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
148 'Please use |convertSize:(point) fromView:nil| instead.',
149 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
154 'convertSizeToBase:',
156 'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
157 'Please use |convertSize:(point) toView:nil| instead.',
158 'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
163 r"/\s+UTF8String\s*]",
165 'The use of -[NSString UTF8String] is dangerous as it can return null',
166 'even if |canBeConvertedToEncoding:NSUTF8StringEncoding| returns YES.',
167 'Please use |SysNSStringToUTF8| instead.',
172 r'__unsafe_unretained',
174 'The use of __unsafe_unretained is almost certainly wrong, unless',
175 'when interacting with NSFastEnumeration or NSInvocation.',
176 'Please use __weak in files build with ARC, nothing otherwise.',
182 _BANNED_IOS_OBJC_FUNCTIONS = (
186 'TEST() macro should not be used in Objective-C++ code as it does not ',
187 'drain the autorelease pool at the end of the test. Use TEST_F() ',
188 'macro instead with a fixture inheriting from PlatformTest (or a ',
194 r'/\btesting::Test\b',
196 'testing::Test should not be used in Objective-C++ code as it does ',
197 'not drain the autorelease pool at the end of the test. Use ',
198 'PlatformTest instead.'
205 _BANNED_CPP_FUNCTIONS = (
206 # Make sure that gtest's FRIEND_TEST() macro is not used; the
207 # FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be
208 # used instead since that allows for FLAKY_ and DISABLED_ prefixes.
212 'New code should not use NULL. Use nullptr instead.',
220 'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
221 'base/gtest_prod_util.h and use FRIEND_TEST_ALL_PREFIXES() instead.',
227 r'XSelectInput|CWEventMask|XCB_CW_EVENT_MASK',
229 'Chrome clients wishing to select events on X windows should use',
230 'ui::XScopedEventSelector. It is safe to ignore this warning only if',
231 'you are selecting events from the GPU process, or if you are using',
232 'an XDisplay other than gfx::GetXDisplay().',
236 r"^ui[\\\/]gl[\\\/].*\.cc$",
237 r"^media[\\\/]gpu[\\\/].*\.cc$",
238 r"^gpu[\\\/].*\.cc$",
242 r'XInternAtom|xcb_intern_atom',
244 'Use gfx::GetAtom() instead of interning atoms directly.',
248 r"^gpu[\\\/]ipc[\\\/]service[\\\/]gpu_watchdog_thread\.cc$",
249 r"^remoting[\\\/]host[\\\/]linux[\\\/]x_server_clipboard\.cc$",
250 r"^ui[\\\/]gfx[\\\/]x[\\\/]x11_atom_cache\.cc$",
256 'Overriding setMatrixClip() is prohibited; ',
257 'the base function is deprecated. ',
265 'The use of SkRefPtr is prohibited. ',
266 'Please use sk_sp<> instead.'
274 'The indirect use of SkRefPtr via SkAutoRef is prohibited. ',
275 'Please use sk_sp<> instead.'
283 'The use of SkAutoTUnref is dangerous because it implicitly ',
284 'converts to a raw pointer. Please use sk_sp<> instead.'
292 'The indirect use of SkAutoTUnref through SkAutoUnref is dangerous ',
293 'because it implicitly converts to a raw pointer. ',
294 'Please use sk_sp<> instead.'
300 r'/HANDLE_EINTR\(.*close',
302 'HANDLE_EINTR(close) is invalid. If close fails with EINTR, the file',
303 'descriptor will be closed, and it is incorrect to retry the close.',
304 'Either call close directly and ignore its return value, or wrap close',
305 'in IGNORE_EINTR to use its return value. See http://crbug.com/269623'
311 r'/IGNORE_EINTR\((?!.*close)',
313 'IGNORE_EINTR is only valid when wrapping close. To wrap other system',
314 'calls, use HANDLE_EINTR. See http://crbug.com/269623',
318 # Files that #define IGNORE_EINTR.
319 r'^base[\\\/]posix[\\\/]eintr_wrapper\.h$',
320 r'^ppapi[\\\/]tests[\\\/]test_broker\.cc$',
326 'Do not introduce new v8::Extensions into the code base, use',
327 'gin::Wrappable instead. See http://crbug.com/334679',
331 r'extensions[\\\/]renderer[\\\/]safe_builtins\.*',
335 '#pragma comment(lib,',
337 'Specify libraries to link with in build files and not in the source.',
341 r'^third_party[\\\/]abseil-cpp[\\\/].*',
345 'base::SequenceChecker',
347 'Consider using SEQUENCE_CHECKER macros instead of the class directly.',
353 'base::ThreadChecker',
355 'Consider using THREAD_CHECKER macros instead of the class directly.',
361 r'/(Time(|Delta|Ticks)|ThreadTicks)::FromInternalValue|ToInternalValue',
363 'base::TimeXXX::FromInternalValue() and ToInternalValue() are',
364 'deprecated (http://crbug.com/634507). Please avoid converting away',
365 'from the Time types in Chromium code, especially if any math is',
366 'being done on time values. For interfacing with platform/library',
367 'APIs, use FromMicroseconds() or InMicroseconds(), or one of the other',
368 'type converter methods instead. For faking TimeXXX values (for unit',
369 'testing only), use TimeXXX() + TimeDelta::FromMicroseconds(N). For',
370 'other use cases, please contact base/time/OWNERS.',
376 'CallJavascriptFunctionUnsafe',
378 "Don't use CallJavascriptFunctionUnsafe() in new code. Instead, use",
379 'AllowJavascript(), OnJavascriptAllowed()/OnJavascriptDisallowed(),',
380 'and CallJavascriptFunction(). See https://goo.gl/qivavq.',
384 r'^content[\\\/]browser[\\\/]webui[\\\/]web_ui_impl\.(cc|h)$',
385 r'^content[\\\/]public[\\\/]browser[\\\/]web_ui\.h$',
386 r'^content[\\\/]public[\\\/]test[\\\/]test_web_ui\.(cc|h)$',
392 'Instead of leveldb::DB::Open() use leveldb_env::OpenDB() from',
393 'third_party/leveldatabase/env_chromium.h. It exposes databases to',
394 "Chrome's tracing, making their memory usage visible.",
398 r'^third_party/leveldatabase/.*\.(cc|h)$',
402 'leveldb::NewMemEnv',
404 'Instead of leveldb::NewMemEnv() use leveldb_chrome::NewMemEnv() from',
405 'third_party/leveldatabase/leveldb_chrome.h.',
409 r'^third_party/leveldatabase/.*\.(cc|h)$',
413 'MessageLoop::QuitWhenIdleClosure',
415 'MessageLoop::QuitWhenIdleClosure is deprecated. Please use a',
416 'QuitWhenIdleClosure obtained from a specific RunLoop instance.',
422 'RunLoop::QuitCurrent',
424 'Please migrate away from RunLoop::QuitCurrent*() methods. Use member',
425 'methods of a specific RunLoop instance instead.',
431 'base::ScopedMockTimeMessageLoopTaskRunner',
433 'ScopedMockTimeMessageLoopTaskRunner is deprecated.',
441 'Using std::regex adds unnecessary binary size to Chrome. Please use',
442 're2::RE2 instead (crbug.com/755321)',
448 (r'/base::ThreadRestrictions::(ScopedAllowIO|AssertIOAllowed|'
449 r'DisallowWaiting|AssertWaitAllowed|SetWaitAllowed|ScopedAllowWait)'),
451 'Use the new API in base/threading/thread_restrictions.h.',
459 'Please consider using base::Bind{Once,Repeating} instead',
460 'of base::Bind. (crbug.com/714018)',
466 r'/\bbase::Callback<',
468 'Please consider using base::{Once,Repeating}Callback instead',
469 'of base::Callback. (crbug.com/714018)',
475 r'/\bbase::Closure\b',
477 'Please consider using base::{Once,Repeating}Closure instead',
478 'of base::Closure. (crbug.com/714018)',
486 'RunMessageLoop is deprecated, use RunLoop instead.',
494 'RunThisRunLoop is deprecated, use RunLoop directly instead.',
500 r'RunAllPendingInMessageLoop()',
502 "Prefer RunLoop over RunAllPendingInMessageLoop, please contact gab@",
503 "if you're convinced you need this.",
509 r'RunAllPendingInMessageLoop(BrowserThread',
511 'RunAllPendingInMessageLoop is deprecated. Use RunLoop for',
512 'BrowserThread::UI, TestBrowserThreadBundle::RunIOThreadUntilIdle',
513 'for BrowserThread::IO, and prefer RunLoop::QuitClosure to observe',
514 'async events instead of flushing threads.',
520 r'MessageLoopRunner',
522 'MessageLoopRunner is deprecated, use RunLoop instead.',
528 r'GetDeferredQuitTaskForRunLoop',
530 "GetDeferredQuitTaskForRunLoop shouldn't be needed, please contact",
531 "gab@ if you found a use case where this is the only solution.",
537 'sqlite3_initialize',
539 'Instead of sqlite3_initialize, depend on //sql, ',
540 '#include "sql/initialize.h" and use sql::EnsureSqliteInitialized().',
544 r'^sql/initialization\.(cc|h)$',
545 r'^third_party/sqlite/.*\.(c|cc|h)$',
551 'net::URLFetcher should no longer be used in content embedders. ',
552 'Instead, use network::SimpleURLLoader instead, which supports ',
553 'an out-of-process network stack. ',
554 'net::URLFetcher may still be used in binaries that do not embed',
559 r'^ios[\\\/].*\.(cc|h)$',
560 r'.*[\\\/]ios[\\\/].*\.(cc|h)$',
562 r'^net[\\\/].*\.(cc|h)$',
563 r'.*[\\\/]tools[\\\/].*\.(cc|h)$',
569 _IPC_ENUM_TRAITS_DEPRECATED = (
570 'You are using IPC_ENUM_TRAITS() in your code. It has been deprecated.\n'
571 'See http://www.chromium.org/Home/chromium-security/education/'
572 'security-tips-for-ipc')
574 _JAVA_MULTIPLE_DEFINITION_EXCLUDED_PATHS = [
575 r".*[\\\/]BuildHooksAndroidImpl\.java",
576 r".*[\\\/]LicenseContentProvider\.java",
579 # These paths contain test data and other known invalid JSON files.
580 _KNOWN_INVALID_JSON_FILE_PATTERNS = [
581 r'test[\\\/]data[\\\/]',
582 r'^components[\\\/]policy[\\\/]resources[\\\/]policy_templates\.json$',
583 r'^third_party[\\\/]protobuf[\\\/]',
584 r'^third_party[\\\/]WebKit[\\\/]LayoutTests[\\\/]external[\\\/]wpt[\\\/]',
589 # Please keep sorted.
594 'OS_CAT', # For testing.
613 _ANDROID_SPECIFIC_PYDEPS_FILES = [
614 'build/android/test_runner.pydeps',
615 'build/android/test_wrapper/logdog_wrapper.pydeps',
616 'build/secondary/third_party/android_platform/'
617 'development/scripts/stack.pydeps',
618 'net/tools/testserver/testserver.pydeps',
622 _GENERIC_PYDEPS_FILES = [
623 'chrome/test/chromedriver/test/run_py_tests.pydeps',
627 _ALL_PYDEPS_FILES = _ANDROID_SPECIFIC_PYDEPS_FILES + _GENERIC_PYDEPS_FILES
630 # Bypass the AUTHORS check for these accounts.
632 '%s-chromium-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com' % s
633 for s in ('afdo', 'angle', 'catapult', 'chromite', 'depot-tools',
634 'fuchsia-sdk', 'nacl', 'pdfium', 'skia', 'src-internal', 'webrtc')
635 ) | set('%s@appspot.gserviceaccount.com' % s for s in ('findit-for-me',))
638 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
639 """Attempts to prevent use of functions intended only for testing in
640 non-testing code. For now this is just a best-effort implementation
641 that ignores header files and may have some false positives. A
642 better implementation would probably need a proper C++ parser.
644 # We only scan .cc files and the like, as the declaration of
645 # for-testing functions in header files are hard to distinguish from
646 # calls to such functions without a proper C++ parser.
647 file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
649 base_function_pattern = r'[ :]test::[^\s]+|ForTest(s|ing)?|for_test(s|ing)?'
650 inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
651 comment_pattern = input_api.re.compile(r'//.*(%s)' % base_function_pattern)
652 exclusion_pattern = input_api.re.compile(
653 r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
654 base_function_pattern, base_function_pattern))
656 def FilterFile(affected_file):
657 black_list = (_EXCLUDED_PATHS +
658 _TEST_CODE_EXCLUDED_PATHS +
659 input_api.DEFAULT_BLACK_LIST)
660 return input_api.FilterSourceFile(
662 white_list=(file_inclusion_pattern, ),
663 black_list=black_list)
666 for f in input_api.AffectedSourceFiles(FilterFile):
667 local_path = f.LocalPath()
668 for line_number, line in f.ChangedContents():
669 if (inclusion_pattern.search(line) and
670 not comment_pattern.search(line) and
671 not exclusion_pattern.search(line)):
673 '%s:%d\n %s' % (local_path, line_number, line.strip()))
676 return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
681 def _CheckNoProductionCodeUsingTestOnlyFunctionsJava(input_api, output_api):
682 """This is a simplified version of
683 _CheckNoProductionCodeUsingTestOnlyFunctions for Java files.
685 javadoc_start_re = input_api.re.compile(r'^\s*/\*\*')
686 javadoc_end_re = input_api.re.compile(r'^\s*\*/')
687 name_pattern = r'ForTest(s|ing)?'
688 # Describes an occurrence of "ForTest*" inside a // comment.
689 comment_re = input_api.re.compile(r'//.*%s' % name_pattern)
691 inclusion_re = input_api.re.compile(r'(%s)\s*\(' % name_pattern)
692 # Ignore definitions. (Comments are ignored separately.)
693 exclusion_re = input_api.re.compile(r'(%s)[^;]+\{' % name_pattern)
696 sources = lambda x: input_api.FilterSourceFile(
698 black_list=(('(?i).*test', r'.*\/junit\/')
699 + input_api.DEFAULT_BLACK_LIST),
700 white_list=(r'.*\.java$',)
702 for f in input_api.AffectedFiles(include_deletes=False, file_filter=sources):
703 local_path = f.LocalPath()
704 is_inside_javadoc = False
705 for line_number, line in f.ChangedContents():
706 if is_inside_javadoc and javadoc_end_re.search(line):
707 is_inside_javadoc = False
708 if not is_inside_javadoc and javadoc_start_re.search(line):
709 is_inside_javadoc = True
710 if is_inside_javadoc:
712 if (inclusion_re.search(line) and
713 not comment_re.search(line) and
714 not exclusion_re.search(line)):
716 '%s:%d\n %s' % (local_path, line_number, line.strip()))
719 return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
724 def _CheckNoIOStreamInHeaders(input_api, output_api):
725 """Checks to make sure no .h files include <iostream>."""
727 pattern = input_api.re.compile(r'^#include\s*<iostream>',
728 input_api.re.MULTILINE)
729 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
730 if not f.LocalPath().endswith('.h'):
732 contents = input_api.ReadFile(f)
733 if pattern.search(contents):
737 return [output_api.PresubmitError(
738 'Do not #include <iostream> in header files, since it inserts static '
739 'initialization into every file including the header. Instead, '
740 '#include <ostream>. See http://crbug.com/94794',
745 def _CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
746 """Checks to make sure no source files use UNIT_TEST."""
748 for f in input_api.AffectedFiles():
749 if (not f.LocalPath().endswith(('.cc', '.mm'))):
752 for line_num, line in f.ChangedContents():
753 if 'UNIT_TEST ' in line or line.endswith('UNIT_TEST'):
754 problems.append(' %s:%d' % (f.LocalPath(), line_num))
758 return [output_api.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
759 '\n'.join(problems))]
762 def _CheckDCHECK_IS_ONHasBraces(input_api, output_api):
763 """Checks to make sure DCHECK_IS_ON() does not skip the parentheses."""
765 pattern = input_api.re.compile(r'DCHECK_IS_ON(?!\(\))',
766 input_api.re.MULTILINE)
767 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
768 if (not f.LocalPath().endswith(('.cc', '.mm', '.h'))):
770 for lnum, line in f.ChangedContents():
771 if input_api.re.search(pattern, line):
772 errors.append(output_api.PresubmitError(
773 ('%s:%d: Use of DCHECK_IS_ON() must be written as "#if ' +
774 'DCHECK_IS_ON()", not forgetting the parentheses.')
775 % (f.LocalPath(), lnum)))
779 def _FindHistogramNameInLine(histogram_name, line):
780 """Tries to find a histogram name or prefix in a line."""
781 if not "affected-histogram" in line:
782 return histogram_name in line
783 # A histogram_suffixes tag type has an affected-histogram name as a prefix of
784 # the histogram_name.
787 histogram_prefix = line.split('\"')[1]
788 return histogram_prefix in histogram_name
791 def _CheckUmaHistogramChanges(input_api, output_api):
792 """Check that UMA histogram names in touched lines can still be found in other
793 lines of the patch or in histograms.xml. Note that this check would not catch
794 the reverse: changes in histograms.xml not matched in the code itself."""
795 touched_histograms = []
796 histograms_xml_modifications = []
797 call_pattern_c = r'\bUMA_HISTOGRAM.*\('
798 call_pattern_java = r'\bRecordHistogram\.record[a-zA-Z]+Histogram\('
799 name_pattern = r'"(.*?)"'
800 single_line_c_re = input_api.re.compile(call_pattern_c + name_pattern)
801 single_line_java_re = input_api.re.compile(call_pattern_java + name_pattern)
802 split_line_c_prefix_re = input_api.re.compile(call_pattern_c)
803 split_line_java_prefix_re = input_api.re.compile(call_pattern_java)
804 split_line_suffix_re = input_api.re.compile(r'^\s*' + name_pattern)
805 last_line_matched_prefix = False
806 for f in input_api.AffectedFiles():
807 # If histograms.xml itself is modified, keep the modified lines for later.
808 if f.LocalPath().endswith(('histograms.xml')):
809 histograms_xml_modifications = f.ChangedContents()
811 if f.LocalPath().endswith(('cc', 'mm', 'cpp')):
812 single_line_re = single_line_c_re
813 split_line_prefix_re = split_line_c_prefix_re
814 elif f.LocalPath().endswith(('java')):
815 single_line_re = single_line_java_re
816 split_line_prefix_re = split_line_java_prefix_re
819 for line_num, line in f.ChangedContents():
820 if last_line_matched_prefix:
821 suffix_found = split_line_suffix_re.search(line)
823 touched_histograms.append([suffix_found.group(1), f, line_num])
824 last_line_matched_prefix = False
826 found = single_line_re.search(line)
828 touched_histograms.append([found.group(1), f, line_num])
830 last_line_matched_prefix = split_line_prefix_re.search(line)
832 # Search for the touched histogram names in the local modifications to
833 # histograms.xml, and, if not found, on the base histograms.xml file.
834 unmatched_histograms = []
835 for histogram_info in touched_histograms:
836 histogram_name_found = False
837 for line_num, line in histograms_xml_modifications:
838 histogram_name_found = _FindHistogramNameInLine(histogram_info[0], line)
839 if histogram_name_found:
841 if not histogram_name_found:
842 unmatched_histograms.append(histogram_info)
844 histograms_xml_path = 'tools/metrics/histograms/histograms.xml'
846 if unmatched_histograms:
847 with open(histograms_xml_path) as histograms_xml:
848 for histogram_name, f, line_num in unmatched_histograms:
849 histograms_xml.seek(0)
850 histogram_name_found = False
851 for line in histograms_xml:
852 histogram_name_found = _FindHistogramNameInLine(histogram_name, line)
853 if histogram_name_found:
855 if not histogram_name_found:
856 problems.append(' [%s:%d] %s' %
857 (f.LocalPath(), line_num, histogram_name))
861 return [output_api.PresubmitPromptWarning('Some UMA_HISTOGRAM lines have '
862 'been modified and the associated histogram name has no match in either '
863 '%s or the modifications of it:' % (histograms_xml_path), problems)]
866 def _CheckFlakyTestUsage(input_api, output_api):
867 """Check that FlakyTest annotation is our own instead of the android one"""
868 pattern = input_api.re.compile(r'import android.test.FlakyTest;')
870 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
871 if f.LocalPath().endswith('Test.java'):
872 if pattern.search(input_api.ReadFile(f)):
875 return [output_api.PresubmitError(
876 'Use org.chromium.base.test.util.FlakyTest instead of '
877 'android.test.FlakyTest',
882 def _CheckNoNewWStrings(input_api, output_api):
883 """Checks to make sure we don't introduce use of wstrings."""
885 for f in input_api.AffectedFiles():
886 if (not f.LocalPath().endswith(('.cc', '.h')) or
887 f.LocalPath().endswith(('test.cc', '_win.cc', '_win.h')) or
888 '/win/' in f.LocalPath() or
889 'chrome_elf' in f.LocalPath() or
890 'install_static' in f.LocalPath()):
894 for line_num, line in f.ChangedContents():
895 if 'presubmit: allow wstring' in line:
897 elif not allowWString and 'wstring' in line:
898 problems.append(' %s:%d' % (f.LocalPath(), line_num))
905 return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
906 ' If you are calling a cross-platform API that accepts a wstring, '
908 '\n'.join(problems))]
911 def _CheckNoDEPSGIT(input_api, output_api):
912 """Make sure .DEPS.git is never modified manually."""
913 if any(f.LocalPath().endswith('.DEPS.git') for f in
914 input_api.AffectedFiles()):
915 return [output_api.PresubmitError(
916 'Never commit changes to .DEPS.git. This file is maintained by an\n'
917 'automated system based on what\'s in DEPS and your changes will be\n'
919 'See https://sites.google.com/a/chromium.org/dev/developers/how-tos/'
920 'get-the-code#Rolling_DEPS\n'
921 'for more information')]
925 def _CheckValidHostsInDEPS(input_api, output_api):
926 """Checks that DEPS file deps are from allowed_hosts."""
927 # Run only if DEPS file has been modified to annoy fewer bystanders.
928 if all(f.LocalPath() != 'DEPS' for f in input_api.AffectedFiles()):
930 # Outsource work to gclient verify
932 input_api.subprocess.check_output(['gclient', 'verify'])
934 except input_api.subprocess.CalledProcessError, error:
935 return [output_api.PresubmitError(
936 'DEPS file must have only git dependencies.',
937 long_text=error.output)]
940 def _CheckNoBannedFunctions(input_api, output_api):
941 """Make sure that banned functions are not used."""
945 def IsBlacklisted(affected_file, blacklist):
946 local_path = affected_file.LocalPath()
947 for item in blacklist:
948 if input_api.re.match(item, local_path):
952 def IsIosObcjFile(affected_file):
953 local_path = affected_file.LocalPath()
954 if input_api.os_path.splitext(local_path)[-1] not in ('.mm', '.m', '.h'):
956 basename = input_api.os_path.basename(local_path)
957 if 'ios' in basename.split('_'):
959 for sep in (input_api.os_path.sep, input_api.os_path.altsep):
960 if sep and 'ios' in local_path.split(sep):
964 def CheckForMatch(affected_file, line_num, line, func_name, message, error):
966 if func_name[0:1] == '/':
967 regex = func_name[1:]
968 if input_api.re.search(regex, line):
970 elif func_name in line:
976 problems.append(' %s:%d:' % (affected_file.LocalPath(), line_num))
977 for message_line in message:
978 problems.append(' %s' % message_line)
980 file_filter = lambda f: f.LocalPath().endswith(('.java'))
981 for f in input_api.AffectedFiles(file_filter=file_filter):
982 for line_num, line in f.ChangedContents():
983 for func_name, message, error in _BANNED_JAVA_FUNCTIONS:
984 CheckForMatch(f, line_num, line, func_name, message, error)
986 file_filter = lambda f: f.LocalPath().endswith(('.mm', '.m', '.h'))
987 for f in input_api.AffectedFiles(file_filter=file_filter):
988 for line_num, line in f.ChangedContents():
989 for func_name, message, error in _BANNED_OBJC_FUNCTIONS:
990 CheckForMatch(f, line_num, line, func_name, message, error)
992 for f in input_api.AffectedFiles(file_filter=IsIosObcjFile):
993 for line_num, line in f.ChangedContents():
994 for func_name, message, error in _BANNED_IOS_OBJC_FUNCTIONS:
995 CheckForMatch(f, line_num, line, func_name, message, error)
997 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm', '.h'))
998 for f in input_api.AffectedFiles(file_filter=file_filter):
999 for line_num, line in f.ChangedContents():
1000 for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
1001 if IsBlacklisted(f, excluded_paths):
1003 CheckForMatch(f, line_num, line, func_name, message, error)
1007 result.append(output_api.PresubmitPromptWarning(
1008 'Banned functions were used.\n' + '\n'.join(warnings)))
1010 result.append(output_api.PresubmitError(
1011 'Banned functions were used.\n' + '\n'.join(errors)))
1015 def _CheckNoPragmaOnce(input_api, output_api):
1016 """Make sure that banned functions are not used."""
1018 pattern = input_api.re.compile(r'^#pragma\s+once',
1019 input_api.re.MULTILINE)
1020 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
1021 if not f.LocalPath().endswith('.h'):
1023 contents = input_api.ReadFile(f)
1024 if pattern.search(contents):
1028 return [output_api.PresubmitError(
1029 'Do not use #pragma once in header files.\n'
1030 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
1035 def _CheckNoTrinaryTrueFalse(input_api, output_api):
1036 """Checks to make sure we don't introduce use of foo ? true : false."""
1038 pattern = input_api.re.compile(r'\?\s*(true|false)\s*:\s*(true|false)')
1039 for f in input_api.AffectedFiles():
1040 if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
1043 for line_num, line in f.ChangedContents():
1044 if pattern.match(line):
1045 problems.append(' %s:%d' % (f.LocalPath(), line_num))
1049 return [output_api.PresubmitPromptWarning(
1050 'Please consider avoiding the "? true : false" pattern if possible.\n' +
1051 '\n'.join(problems))]
1054 def _CheckUnwantedDependencies(input_api, output_api):
1055 """Runs checkdeps on #include and import statements added in this
1056 change. Breaking - rules is an error, breaking ! rules is a
1060 # We need to wait until we have an input_api object and use this
1061 # roundabout construct to import checkdeps because this file is
1062 # eval-ed and thus doesn't have __file__.
1063 original_sys_path = sys.path
1065 sys.path = sys.path + [input_api.os_path.join(
1066 input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
1068 from cpp_checker import CppChecker
1069 from java_checker import JavaChecker
1070 from proto_checker import ProtoChecker
1071 from rules import Rule
1073 # Restore sys.path to what it was before.
1074 sys.path = original_sys_path
1078 added_java_imports = []
1079 for f in input_api.AffectedFiles():
1080 if CppChecker.IsCppFile(f.LocalPath()):
1081 changed_lines = [line for _, line in f.ChangedContents()]
1082 added_includes.append([f.AbsoluteLocalPath(), changed_lines])
1083 elif ProtoChecker.IsProtoFile(f.LocalPath()):
1084 changed_lines = [line for _, line in f.ChangedContents()]
1085 added_imports.append([f.AbsoluteLocalPath(), changed_lines])
1086 elif JavaChecker.IsJavaFile(f.LocalPath()):
1087 changed_lines = [line for _, line in f.ChangedContents()]
1088 added_java_imports.append([f.AbsoluteLocalPath(), changed_lines])
1090 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
1092 error_descriptions = []
1093 warning_descriptions = []
1094 error_subjects = set()
1095 warning_subjects = set()
1096 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
1098 path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
1099 description_with_path = '%s\n %s' % (path, rule_description)
1100 if rule_type == Rule.DISALLOW:
1101 error_descriptions.append(description_with_path)
1102 error_subjects.add("#includes")
1104 warning_descriptions.append(description_with_path)
1105 warning_subjects.add("#includes")
1107 for path, rule_type, rule_description in deps_checker.CheckAddedProtoImports(
1109 path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
1110 description_with_path = '%s\n %s' % (path, rule_description)
1111 if rule_type == Rule.DISALLOW:
1112 error_descriptions.append(description_with_path)
1113 error_subjects.add("imports")
1115 warning_descriptions.append(description_with_path)
1116 warning_subjects.add("imports")
1118 for path, rule_type, rule_description in deps_checker.CheckAddedJavaImports(
1119 added_java_imports, _JAVA_MULTIPLE_DEFINITION_EXCLUDED_PATHS):
1120 path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
1121 description_with_path = '%s\n %s' % (path, rule_description)
1122 if rule_type == Rule.DISALLOW:
1123 error_descriptions.append(description_with_path)
1124 error_subjects.add("imports")
1126 warning_descriptions.append(description_with_path)
1127 warning_subjects.add("imports")
1130 if error_descriptions:
1131 results.append(output_api.PresubmitError(
1132 'You added one or more %s that violate checkdeps rules.'
1133 % " and ".join(error_subjects),
1134 error_descriptions))
1135 if warning_descriptions:
1136 results.append(output_api.PresubmitPromptOrNotify(
1137 'You added one or more %s of files that are temporarily\n'
1138 'allowed but being removed. Can you avoid introducing the\n'
1139 '%s? See relevant DEPS file(s) for details and contacts.' %
1140 (" and ".join(warning_subjects), "/".join(warning_subjects)),
1141 warning_descriptions))
1145 def _CheckFilePermissions(input_api, output_api):
1146 """Check that all files have their permissions properly set."""
1147 if input_api.platform == 'win32':
1149 checkperms_tool = input_api.os_path.join(
1150 input_api.PresubmitLocalPath(),
1151 'tools', 'checkperms', 'checkperms.py')
1152 args = [input_api.python_executable, checkperms_tool,
1153 '--root', input_api.change.RepositoryRoot()]
1154 with input_api.CreateTemporaryFile() as file_list:
1155 for f in input_api.AffectedFiles():
1156 # checkperms.py file/directory arguments must be relative to the
1158 file_list.write(f.LocalPath() + '\n')
1160 args += ['--file-list', file_list.name]
1162 input_api.subprocess.check_output(args)
1164 except input_api.subprocess.CalledProcessError as error:
1165 return [output_api.PresubmitError(
1166 'checkperms.py failed:',
1167 long_text=error.output)]
1170 def _CheckTeamTags(input_api, output_api):
1171 """Checks that OWNERS files have consistent TEAM and COMPONENT tags."""
1172 checkteamtags_tool = input_api.os_path.join(
1173 input_api.PresubmitLocalPath(),
1174 'tools', 'checkteamtags', 'checkteamtags.py')
1175 args = [input_api.python_executable, checkteamtags_tool,
1176 '--root', input_api.change.RepositoryRoot()]
1177 files = [f.LocalPath() for f in input_api.AffectedFiles(include_deletes=False)
1178 if input_api.os_path.basename(f.AbsoluteLocalPath()).upper() ==
1182 input_api.subprocess.check_output(args + files)
1184 except input_api.subprocess.CalledProcessError as error:
1185 return [output_api.PresubmitError(
1186 'checkteamtags.py failed:',
1187 long_text=error.output)]
1190 def _CheckNoAuraWindowPropertyHInHeaders(input_api, output_api):
1191 """Makes sure we don't include ui/aura/window_property.h
1194 pattern = input_api.re.compile(r'^#include\s*"ui/aura/window_property.h"')
1196 for f in input_api.AffectedFiles():
1197 if not f.LocalPath().endswith('.h'):
1199 for line_num, line in f.ChangedContents():
1200 if pattern.match(line):
1201 errors.append(' %s:%d' % (f.LocalPath(), line_num))
1205 results.append(output_api.PresubmitError(
1206 'Header files should not include ui/aura/window_property.h', errors))
1210 def _CheckForVersionControlConflictsInFile(input_api, f):
1211 pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
1213 for line_num, line in f.ChangedContents():
1214 if f.LocalPath().endswith('.md'):
1215 # First-level headers in markdown look a lot like version control
1216 # conflict markers. http://daringfireball.net/projects/markdown/basics
1218 if pattern.match(line):
1219 errors.append(' %s:%d %s' % (f.LocalPath(), line_num, line))
1223 def _CheckForVersionControlConflicts(input_api, output_api):
1224 """Usually this is not intentional and will cause a compile failure."""
1226 for f in input_api.AffectedFiles():
1227 errors.extend(_CheckForVersionControlConflictsInFile(input_api, f))
1231 results.append(output_api.PresubmitError(
1232 'Version control conflict markers found, please resolve.', errors))
1235 def _CheckGoogleSupportAnswerUrl(input_api, output_api):
1236 pattern = input_api.re.compile('support\.google\.com\/chrome.*/answer')
1238 for f in input_api.AffectedFiles():
1239 for line_num, line in f.ChangedContents():
1240 if pattern.search(line):
1241 errors.append(' %s:%d %s' % (f.LocalPath(), line_num, line))
1245 results.append(output_api.PresubmitPromptWarning(
1246 'Found Google support URL addressed by answer number. Please replace '
1247 'with a p= identifier instead. See crbug.com/679462\n', errors))
1251 def _CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api):
1252 def FilterFile(affected_file):
1253 """Filter function for use with input_api.AffectedSourceFiles,
1254 below. This filters out everything except non-test files from
1255 top-level directories that generally speaking should not hard-code
1256 service URLs (e.g. src/android_webview/, src/content/ and others).
1258 return input_api.FilterSourceFile(
1260 white_list=(r'^(android_webview|base|content|net)[\\\/].*', ),
1261 black_list=(_EXCLUDED_PATHS +
1262 _TEST_CODE_EXCLUDED_PATHS +
1263 input_api.DEFAULT_BLACK_LIST))
1265 base_pattern = ('"[^"]*(google|googleapis|googlezip|googledrive|appspot)'
1266 '\.(com|net)[^"]*"')
1267 comment_pattern = input_api.re.compile('//.*%s' % base_pattern)
1268 pattern = input_api.re.compile(base_pattern)
1269 problems = [] # items are (filename, line_number, line)
1270 for f in input_api.AffectedSourceFiles(FilterFile):
1271 for line_num, line in f.ChangedContents():
1272 if not comment_pattern.search(line) and pattern.search(line):
1273 problems.append((f.LocalPath(), line_num, line))
1276 return [output_api.PresubmitPromptOrNotify(
1277 'Most layers below src/chrome/ should not hardcode service URLs.\n'
1278 'Are you sure this is correct?',
1280 problem[0], problem[1], problem[2]) for problem in problems])]
1285 def _CheckNoAbbreviationInPngFileName(input_api, output_api):
1286 """Makes sure there are no abbreviations in the name of PNG files.
1287 The native_client_sdk directory is excluded because it has auto-generated PNG
1288 files for documentation.
1291 white_list = (r'.*_[a-z]_.*\.png$|.*_[a-z]\.png$',)
1292 black_list = (r'^native_client_sdk[\\\/]',)
1293 file_filter = lambda f: input_api.FilterSourceFile(
1294 f, white_list=white_list, black_list=black_list)
1295 for f in input_api.AffectedFiles(include_deletes=False,
1296 file_filter=file_filter):
1297 errors.append(' %s' % f.LocalPath())
1301 results.append(output_api.PresubmitError(
1302 'The name of PNG files should not have abbreviations. \n'
1303 'Use _hover.png, _center.png, instead of _h.png, _c.png.\n'
1304 'Contact oshima@chromium.org if you have questions.', errors))
1308 def _ExtractAddRulesFromParsedDeps(parsed_deps):
1309 """Extract the rules that add dependencies from a parsed DEPS file.
1312 parsed_deps: the locals dictionary from evaluating the DEPS file."""
1315 rule[1:] for rule in parsed_deps.get('include_rules', [])
1316 if rule.startswith('+') or rule.startswith('!')
1318 for _, rules in parsed_deps.get('specific_include_rules',
1321 rule[1:] for rule in rules
1322 if rule.startswith('+') or rule.startswith('!')
1327 def _ParseDeps(contents):
1328 """Simple helper for parsing DEPS files."""
1329 # Stubs for handling special syntax in the root DEPS file.
1332 def __init__(self, local_scope):
1333 self._local_scope = local_scope
1335 def Lookup(self, var_name):
1336 """Implements the Var syntax."""
1338 return self._local_scope['vars'][var_name]
1340 raise Exception('Var is not defined: %s' % var_name)
1344 'Var': _VarImpl(local_scope).Lookup,
1346 exec contents in global_scope, local_scope
1350 def _CalculateAddedDeps(os_path, old_contents, new_contents):
1351 """Helper method for _CheckAddedDepsHaveTargetApprovals. Returns
1352 a set of DEPS entries that we should look up.
1354 For a directory (rather than a specific filename) we fake a path to
1355 a specific filename by adding /DEPS. This is chosen as a file that
1356 will seldom or never be subject to per-file include_rules.
1358 # We ignore deps entries on auto-generated directories.
1359 AUTO_GENERATED_DIRS = ['grit', 'jni']
1361 old_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(old_contents))
1362 new_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(new_contents))
1364 added_deps = new_deps.difference(old_deps)
1367 for added_dep in added_deps:
1368 if added_dep.split('/')[0] in AUTO_GENERATED_DIRS:
1370 # Assume that a rule that ends in .h is a rule for a specific file.
1371 if added_dep.endswith('.h'):
1372 results.add(added_dep)
1374 results.add(os_path.join(added_dep, 'DEPS'))
1378 def _CheckAddedDepsHaveTargetApprovals(input_api, output_api):
1379 """When a dependency prefixed with + is added to a DEPS file, we
1380 want to make sure that the change is reviewed by an OWNER of the
1381 target file or directory, to avoid layering violations from being
1382 introduced. This check verifies that this happens.
1384 virtual_depended_on_files = set()
1386 file_filter = lambda f: not input_api.re.match(
1387 r"^third_party[\\\/](WebKit|blink)[\\\/].*", f.LocalPath())
1388 for f in input_api.AffectedFiles(include_deletes=False,
1389 file_filter=file_filter):
1390 filename = input_api.os_path.basename(f.LocalPath())
1391 if filename == 'DEPS':
1392 virtual_depended_on_files.update(_CalculateAddedDeps(
1394 '\n'.join(f.OldContents()),
1395 '\n'.join(f.NewContents())))
1397 if not virtual_depended_on_files:
1400 if input_api.is_committing:
1402 return [output_api.PresubmitNotifyResult(
1403 '--tbr was specified, skipping OWNERS check for DEPS additions')]
1404 if input_api.dry_run:
1405 return [output_api.PresubmitNotifyResult(
1406 'This is a dry run, skipping OWNERS check for DEPS additions')]
1407 if not input_api.change.issue:
1408 return [output_api.PresubmitError(
1409 "DEPS approval by OWNERS check failed: this change has "
1410 "no change number, so we can't check it for approvals.")]
1411 output = output_api.PresubmitError
1413 output = output_api.PresubmitNotifyResult
1415 owners_db = input_api.owners_db
1416 owner_email, reviewers = (
1417 input_api.canned_checks.GetCodereviewOwnerAndReviewers(
1419 owners_db.email_regexp,
1420 approval_needed=input_api.is_committing))
1422 owner_email = owner_email or input_api.change.author_email
1424 reviewers_plus_owner = set(reviewers)
1426 reviewers_plus_owner.add(owner_email)
1427 missing_files = owners_db.files_not_covered_by(virtual_depended_on_files,
1428 reviewers_plus_owner)
1430 # We strip the /DEPS part that was added by
1431 # _FilesToCheckForIncomingDeps to fake a path to a file in a
1433 def StripDeps(path):
1434 start_deps = path.rfind('/DEPS')
1435 if start_deps != -1:
1436 return path[:start_deps]
1439 unapproved_dependencies = ["'+%s'," % StripDeps(path)
1440 for path in missing_files]
1442 if unapproved_dependencies:
1444 output('You need LGTM from owners of depends-on paths in DEPS that were '
1445 'modified in this CL:\n %s' %
1446 '\n '.join(sorted(unapproved_dependencies)))]
1447 suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
1448 output_list.append(output(
1449 'Suggested missing target path OWNERS:\n %s' %
1450 '\n '.join(suggested_owners or [])))
1456 def _CheckSpamLogging(input_api, output_api):
1457 file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
1458 black_list = (_EXCLUDED_PATHS +
1459 _TEST_CODE_EXCLUDED_PATHS +
1460 input_api.DEFAULT_BLACK_LIST +
1461 (r"^base[\\\/]logging\.h$",
1462 r"^base[\\\/]logging\.cc$",
1463 r"^chrome[\\\/]app[\\\/]chrome_main_delegate\.cc$",
1464 r"^chrome[\\\/]browser[\\\/]chrome_browser_main\.cc$",
1465 r"^chrome[\\\/]browser[\\\/]ui[\\\/]startup[\\\/]"
1466 r"startup_browser_creator\.cc$",
1467 r"^chrome[\\\/]installer[\\\/]setup[\\\/].*",
1468 r"chrome[\\\/]browser[\\\/]diagnostics[\\\/]" +
1469 r"diagnostics_writer\.cc$",
1470 r"^chrome_elf[\\\/]dll_hash[\\\/]dll_hash_main\.cc$",
1471 r"^chromecast[\\\/]",
1472 r"^cloud_print[\\\/]",
1473 r"^components[\\\/]browser_watcher[\\\/]"
1474 r"dump_stability_report_main_win.cc$",
1475 r"^components[\\\/]html_viewer[\\\/]"
1476 r"web_test_delegate_impl\.cc$",
1477 r"^components[\\\/]zucchini[\\\/].*",
1478 # TODO(peter): Remove this exception. https://crbug.com/534537
1479 r"^content[\\\/]browser[\\\/]notifications[\\\/]"
1480 r"notification_event_dispatcher_impl\.cc$",
1481 r"^content[\\\/]common[\\\/]gpu[\\\/]client[\\\/]"
1482 r"gl_helper_benchmark\.cc$",
1483 r"^courgette[\\\/]courgette_minimal_tool\.cc$",
1484 r"^courgette[\\\/]courgette_tool\.cc$",
1485 r"^extensions[\\\/]renderer[\\\/]logging_native_handler\.cc$",
1486 r"^ipc[\\\/]ipc_logging\.cc$",
1487 r"^native_client_sdk[\\\/]",
1488 r"^remoting[\\\/]base[\\\/]logging\.h$",
1489 r"^remoting[\\\/]host[\\\/].*",
1490 r"^sandbox[\\\/]linux[\\\/].*",
1492 r"^ui[\\\/]base[\\\/]resource[\\\/]data_pack.cc$",
1493 r"^ui[\\\/]aura[\\\/]bench[\\\/]bench_main\.cc$",
1494 r"^ui[\\\/]ozone[\\\/]platform[\\\/]cast[\\\/]",
1495 r"^storage[\\\/]browser[\\\/]fileapi[\\\/]" +
1496 r"dump_file_system.cc$",
1497 r"^headless[\\\/]app[\\\/]headless_shell\.cc$"))
1498 source_file_filter = lambda x: input_api.FilterSourceFile(
1499 x, white_list=(file_inclusion_pattern,), black_list=black_list)
1504 for f in input_api.AffectedSourceFiles(source_file_filter):
1505 for _, line in f.ChangedContents():
1506 if input_api.re.search(r"\bD?LOG\s*\(\s*INFO\s*\)", line):
1507 log_info.add(f.LocalPath())
1508 elif input_api.re.search(r"\bD?LOG_IF\s*\(\s*INFO\s*,", line):
1509 log_info.add(f.LocalPath())
1511 if input_api.re.search(r"\bprintf\(", line):
1512 printf.add(f.LocalPath())
1513 elif input_api.re.search(r"\bfprintf\((stdout|stderr)", line):
1514 printf.add(f.LocalPath())
1517 return [output_api.PresubmitError(
1518 'These files spam the console log with LOG(INFO):',
1521 return [output_api.PresubmitError(
1522 'These files spam the console log with printf/fprintf:',
1527 def _CheckForAnonymousVariables(input_api, output_api):
1528 """These types are all expected to hold locks while in scope and
1529 so should never be anonymous (which causes them to be immediately
1531 they_who_must_be_named = [
1535 'SkAutoAlphaRestore',
1536 'SkAutoBitmapShaderInstall',
1537 'SkAutoBlitterChoose',
1538 'SkAutoBounderCommit',
1540 'SkAutoCanvasRestore',
1541 'SkAutoCommentBlock',
1543 'SkAutoDisableDirectionCheck',
1544 'SkAutoDisableOvalCheck',
1551 'SkAutoMaskFreeImage',
1552 'SkAutoMutexAcquire',
1553 'SkAutoPathBoundsUpdate',
1555 'SkAutoRasterClipValidate',
1561 anonymous = r'(%s)\s*[({]' % '|'.join(they_who_must_be_named)
1562 # bad: base::AutoLock(lock.get());
1563 # not bad: base::AutoLock lock(lock.get());
1564 bad_pattern = input_api.re.compile(anonymous)
1565 # good: new base::AutoLock(lock.get())
1566 good_pattern = input_api.re.compile(r'\bnew\s*' + anonymous)
1569 for f in input_api.AffectedFiles():
1570 if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
1572 for linenum, line in f.ChangedContents():
1573 if bad_pattern.search(line) and not good_pattern.search(line):
1574 errors.append('%s:%d' % (f.LocalPath(), linenum))
1577 return [output_api.PresubmitError(
1578 'These lines create anonymous variables that need to be named:',
1583 def _CheckUniquePtr(input_api, output_api):
1584 file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
1585 sources = lambda affected_file: input_api.FilterSourceFile(
1587 black_list=(_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
1588 input_api.DEFAULT_BLACK_LIST),
1589 white_list=(file_inclusion_pattern,))
1591 # Pattern to capture a single "<...>" block of template arguments. It can
1592 # handle linearly nested blocks, such as "<std::vector<std::set<T>>>", but
1593 # cannot handle branching structures, such as "<pair<set<T>,set<U>>". The
1594 # latter would likely require counting that < and > match, which is not
1595 # expressible in regular languages. Should the need arise, one can introduce
1596 # limited counting (matching up to a total number of nesting depth), which
1597 # should cover all practical cases for already a low nesting limit.
1598 template_arg_pattern = (
1599 r'<[^>]*' # Opening block of <.
1600 r'>([^<]*>)?') # Closing block of >.
1601 # Prefix expressing that whatever follows is not already inside a <...>
1603 not_inside_template_arg_pattern = r'(^|[^<,\s]\s*)'
1604 null_construct_pattern = input_api.re.compile(
1605 not_inside_template_arg_pattern
1606 + r'\bstd::unique_ptr'
1607 + template_arg_pattern
1610 # Same as template_arg_pattern, but excluding type arrays, e.g., <T[]>.
1611 template_arg_no_array_pattern = (
1612 r'<[^>]*[^]]' # Opening block of <.
1613 r'>([^(<]*[^]]>)?') # Closing block of >.
1614 # Prefix saying that what follows is the start of an expression.
1615 start_of_expr_pattern = r'(=|\breturn|^)\s*'
1616 # Suffix saying that what follows are call parentheses with a non-empty list
1618 nonempty_arg_list_pattern = r'\(([^)]|$)'
1619 return_construct_pattern = input_api.re.compile(
1620 start_of_expr_pattern
1621 + r'std::unique_ptr'
1622 + template_arg_no_array_pattern
1623 + nonempty_arg_list_pattern)
1625 problems_constructor = []
1626 problems_nullptr = []
1627 for f in input_api.AffectedSourceFiles(sources):
1628 for line_number, line in f.ChangedContents():
1630 # return std::unique_ptr<T>(foo);
1631 # bar = std::unique_ptr<T>(foo);
1633 # return std::unique_ptr<T[]>(foo);
1634 # bar = std::unique_ptr<T[]>(foo);
1635 local_path = f.LocalPath()
1636 if return_construct_pattern.search(line):
1637 problems_constructor.append(
1638 '%s:%d\n %s' % (local_path, line_number, line.strip()))
1640 # std::unique_ptr<T>()
1641 if null_construct_pattern.search(line):
1642 problems_nullptr.append(
1643 '%s:%d\n %s' % (local_path, line_number, line.strip()))
1646 if problems_nullptr:
1647 errors.append(output_api.PresubmitError(
1648 'The following files use std::unique_ptr<T>(). Use nullptr instead.',
1650 if problems_constructor:
1651 errors.append(output_api.PresubmitError(
1652 'The following files use explicit std::unique_ptr constructor.'
1653 'Use std::make_unique<T>() instead.',
1654 problems_constructor))
1658 def _CheckUserActionUpdate(input_api, output_api):
1659 """Checks if any new user action has been added."""
1660 if any('actions.xml' == input_api.os_path.basename(f) for f in
1661 input_api.LocalPaths()):
1662 # If actions.xml is already included in the changelist, the PRESUBMIT
1663 # for actions.xml will do a more complete presubmit check.
1666 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm'))
1667 action_re = r'[^a-zA-Z]UserMetricsAction\("([^"]*)'
1668 current_actions = None
1669 for f in input_api.AffectedFiles(file_filter=file_filter):
1670 for line_num, line in f.ChangedContents():
1671 match = input_api.re.search(action_re, line)
1673 # Loads contents in tools/metrics/actions/actions.xml to memory. It's
1675 if not current_actions:
1676 with open('tools/metrics/actions/actions.xml') as actions_f:
1677 current_actions = actions_f.read()
1678 # Search for the matched user action name in |current_actions|.
1679 for action_name in match.groups():
1680 action = 'name="{0}"'.format(action_name)
1681 if action not in current_actions:
1682 return [output_api.PresubmitPromptWarning(
1683 'File %s line %d: %s is missing in '
1684 'tools/metrics/actions/actions.xml. Please run '
1685 'tools/metrics/actions/extract_actions.py to update.'
1686 % (f.LocalPath(), line_num, action_name))]
1690 def _ImportJSONCommentEater(input_api):
1692 sys.path = sys.path + [input_api.os_path.join(
1693 input_api.PresubmitLocalPath(),
1694 'tools', 'json_comment_eater')]
1695 import json_comment_eater
1696 return json_comment_eater
1699 def _GetJSONParseError(input_api, filename, eat_comments=True):
1701 contents = input_api.ReadFile(filename)
1703 json_comment_eater = _ImportJSONCommentEater(input_api)
1704 contents = json_comment_eater.Nom(contents)
1706 input_api.json.loads(contents)
1707 except ValueError as e:
1712 def _GetIDLParseError(input_api, filename):
1714 contents = input_api.ReadFile(filename)
1715 idl_schema = input_api.os_path.join(
1716 input_api.PresubmitLocalPath(),
1717 'tools', 'json_schema_compiler', 'idl_schema.py')
1718 process = input_api.subprocess.Popen(
1719 [input_api.python_executable, idl_schema],
1720 stdin=input_api.subprocess.PIPE,
1721 stdout=input_api.subprocess.PIPE,
1722 stderr=input_api.subprocess.PIPE,
1723 universal_newlines=True)
1724 (_, error) = process.communicate(input=contents)
1725 return error or None
1726 except ValueError as e:
1730 def _CheckParseErrors(input_api, output_api):
1731 """Check that IDL and JSON files do not contain syntax errors."""
1733 '.idl': _GetIDLParseError,
1734 '.json': _GetJSONParseError,
1736 # Most JSON files are preprocessed and support comments, but these do not.
1737 json_no_comments_patterns = [
1740 # Only run IDL checker on files in these directories.
1741 idl_included_patterns = [
1742 r'^chrome[\\\/]common[\\\/]extensions[\\\/]api[\\\/]',
1743 r'^extensions[\\\/]common[\\\/]api[\\\/]',
1746 def get_action(affected_file):
1747 filename = affected_file.LocalPath()
1748 return actions.get(input_api.os_path.splitext(filename)[1])
1750 def FilterFile(affected_file):
1751 action = get_action(affected_file)
1754 path = affected_file.LocalPath()
1756 if _MatchesFile(input_api, _KNOWN_INVALID_JSON_FILE_PATTERNS, path):
1759 if (action == _GetIDLParseError and
1760 not _MatchesFile(input_api, idl_included_patterns, path)):
1765 for affected_file in input_api.AffectedFiles(
1766 file_filter=FilterFile, include_deletes=False):
1767 action = get_action(affected_file)
1769 if (action == _GetJSONParseError and
1770 _MatchesFile(input_api, json_no_comments_patterns,
1771 affected_file.LocalPath())):
1772 kwargs['eat_comments'] = False
1773 parse_error = action(input_api,
1774 affected_file.AbsoluteLocalPath(),
1777 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
1778 (affected_file.LocalPath(), parse_error)))
1782 def _CheckJavaStyle(input_api, output_api):
1783 """Runs checkstyle on changed java files and returns errors if any exist."""
1785 original_sys_path = sys.path
1787 sys.path = sys.path + [input_api.os_path.join(
1788 input_api.PresubmitLocalPath(), 'tools', 'android', 'checkstyle')]
1791 # Restore sys.path to what it was before.
1792 sys.path = original_sys_path
1794 return checkstyle.RunCheckstyle(
1795 input_api, output_api, 'tools/android/checkstyle/chromium-style-5.0.xml',
1796 black_list=_EXCLUDED_PATHS + input_api.DEFAULT_BLACK_LIST)
1799 def _MatchesFile(input_api, patterns, path):
1800 for pattern in patterns:
1801 if input_api.re.search(pattern, path):
1806 def _GetOwnersFilesToCheckForIpcOwners(input_api):
1807 """Gets a list of OWNERS files to check for correct security owners.
1810 A dictionary mapping an OWNER file to the list of OWNERS rules it must
1811 contain to cover IPC-related files with noparent reviewer rules.
1813 # Whether or not a file affects IPC is (mostly) determined by a simple list
1814 # of filename patterns.
1819 '*_param_traits*.*',
1822 '*_mojom_traits*.*',
1823 '*_struct_traits*.*',
1824 '*_type_converter*.*',
1826 # Android native IPC:
1828 # Blink uses a different file naming convention:
1832 '*TypeConverter*.*',
1835 # These third_party directories do not contain IPCs, but contain files
1836 # matching the above patterns, which trigger false positives.
1838 'third_party/crashpad/*',
1839 'third_party/win_build_output/*',
1842 # Dictionary mapping an OWNERS file path to Patterns.
1843 # Patterns is a dictionary mapping glob patterns (suitable for use in per-file
1844 # rules ) to a PatternEntry.
1845 # PatternEntry is a dictionary with two keys:
1846 # - 'files': the files that are matched by this pattern
1847 # - 'rules': the per-file rules needed for this pattern
1848 # For example, if we expect OWNERS file to contain rules for *.mojom and
1849 # *_struct_traits*.*, Patterns might look like this:
1854 # 'per-file *.mojom=set noparent',
1855 # 'per-file *.mojom=file://ipc/SECURITY_OWNERS',
1858 # '*_struct_traits*.*': {
1861 # 'per-file *_struct_traits*.*=set noparent',
1862 # 'per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS',
1868 def AddPatternToCheck(input_file, pattern):
1869 owners_file = input_api.os_path.join(
1870 input_api.os_path.dirname(input_file.LocalPath()), 'OWNERS')
1871 if owners_file not in to_check:
1872 to_check[owners_file] = {}
1873 if pattern not in to_check[owners_file]:
1874 to_check[owners_file][pattern] = {
1877 'per-file %s=set noparent' % pattern,
1878 'per-file %s=file://ipc/SECURITY_OWNERS' % pattern,
1881 to_check[owners_file][pattern]['files'].append(input_file)
1883 # Iterate through the affected files to see what we actually need to check
1884 # for. We should only nag patch authors about per-file rules if a file in that
1885 # directory would match that pattern. If a directory only contains *.mojom
1886 # files and no *_messages*.h files, we should only nag about rules for
1888 for f in input_api.AffectedFiles(include_deletes=False):
1889 # Manifest files don't have a strong naming convention. Instead, scan
1890 # affected files for .json files and see if they look like a manifest.
1891 if (f.LocalPath().endswith('.json') and
1892 not _MatchesFile(input_api, _KNOWN_INVALID_JSON_FILE_PATTERNS,
1894 json_comment_eater = _ImportJSONCommentEater(input_api)
1895 mostly_json_lines = '\n'.join(f.NewContents())
1896 # Comments aren't allowed in strict JSON, so filter them out.
1897 json_lines = json_comment_eater.Nom(mostly_json_lines)
1899 json_content = input_api.json.loads(json_lines)
1901 # There's another PRESUBMIT check that already verifies that JSON files
1902 # are not invalid, so no need to emit another warning here.
1904 if 'interface_provider_specs' in json_content:
1905 AddPatternToCheck(f, input_api.os_path.basename(f.LocalPath()))
1906 for pattern in file_patterns:
1907 if input_api.fnmatch.fnmatch(
1908 input_api.os_path.basename(f.LocalPath()), pattern):
1910 for exclude in exclude_paths:
1911 if input_api.fnmatch.fnmatch(f.LocalPath(), exclude):
1916 AddPatternToCheck(f, pattern)
1922 def _CheckIpcOwners(input_api, output_api):
1923 """Checks that affected files involving IPC have an IPC OWNERS rule."""
1924 to_check = _GetOwnersFilesToCheckForIpcOwners(input_api)
1927 # If there are any OWNERS files to check, there are IPC-related changes in
1928 # this CL. Auto-CC the review list.
1929 output_api.AppendCC('ipc-security-reviews@chromium.org')
1931 # Go through the OWNERS files to check, filtering out rules that are already
1932 # present in that OWNERS file.
1933 for owners_file, patterns in to_check.iteritems():
1935 with file(owners_file) as f:
1936 lines = set(f.read().splitlines())
1937 for entry in patterns.itervalues():
1938 entry['rules'] = [rule for rule in entry['rules'] if rule not in lines
1941 # No OWNERS file, so all the rules are definitely missing.
1944 # All the remaining lines weren't found in OWNERS files, so emit an error.
1946 for owners_file, patterns in to_check.iteritems():
1949 for _, entry in patterns.iteritems():
1950 missing_lines.extend(entry['rules'])
1951 files.extend([' %s' % f.LocalPath() for f in entry['files']])
1954 '%s needs the following lines added:\n\n%s\n\nfor files:\n%s' %
1955 (owners_file, '\n'.join(missing_lines), '\n'.join(files)))
1959 if input_api.is_committing:
1960 output = output_api.PresubmitError
1962 output = output_api.PresubmitPromptWarning
1963 results.append(output(
1964 'Found OWNERS files that need to be updated for IPC security ' +
1965 'review coverage.\nPlease update the OWNERS files below:',
1966 long_text='\n\n'.join(errors)))
1971 def _CheckUselessForwardDeclarations(input_api, output_api):
1972 """Checks that added or removed lines in non third party affected
1973 header files do not lead to new useless class or struct forward
1977 class_pattern = input_api.re.compile(r'^class\s+(\w+);$',
1978 input_api.re.MULTILINE)
1979 struct_pattern = input_api.re.compile(r'^struct\s+(\w+);$',
1980 input_api.re.MULTILINE)
1981 for f in input_api.AffectedFiles(include_deletes=False):
1982 if (f.LocalPath().startswith('third_party') and
1983 not f.LocalPath().startswith('third_party/blink') and
1984 not f.LocalPath().startswith('third_party\\blink') and
1985 not f.LocalPath().startswith('third_party/WebKit') and
1986 not f.LocalPath().startswith('third_party\\WebKit')):
1989 if not f.LocalPath().endswith('.h'):
1992 contents = input_api.ReadFile(f)
1993 fwd_decls = input_api.re.findall(class_pattern, contents)
1994 fwd_decls.extend(input_api.re.findall(struct_pattern, contents))
1996 useless_fwd_decls = []
1997 for decl in fwd_decls:
1998 count = sum(1 for _ in input_api.re.finditer(
1999 r'\b%s\b' % input_api.re.escape(decl), contents))
2001 useless_fwd_decls.append(decl)
2003 if not useless_fwd_decls:
2006 for line in f.GenerateScmDiff().splitlines():
2007 if (line.startswith('-') and not line.startswith('--') or
2008 line.startswith('+') and not line.startswith('++')):
2009 for decl in useless_fwd_decls:
2010 if input_api.re.search(r'\b%s\b' % decl, line[1:]):
2011 results.append(output_api.PresubmitPromptWarning(
2012 '%s: %s forward declaration is no longer needed' %
2013 (f.LocalPath(), decl)))
2014 useless_fwd_decls.remove(decl)
2019 def _CheckAndroidToastUsage(input_api, output_api):
2020 """Checks that code uses org.chromium.ui.widget.Toast instead of
2021 android.widget.Toast (Chromium Toast doesn't force hardware
2022 acceleration on low-end devices, saving memory).
2024 toast_import_pattern = input_api.re.compile(
2025 r'^import android\.widget\.Toast;$')
2029 sources = lambda affected_file: input_api.FilterSourceFile(
2031 black_list=(_EXCLUDED_PATHS +
2032 _TEST_CODE_EXCLUDED_PATHS +
2033 input_api.DEFAULT_BLACK_LIST +
2034 (r'^chromecast[\\\/].*',
2035 r'^remoting[\\\/].*')),
2036 white_list=(r'.*\.java$',))
2038 for f in input_api.AffectedSourceFiles(sources):
2039 for line_num, line in f.ChangedContents():
2040 if toast_import_pattern.search(line):
2041 errors.append("%s:%d" % (f.LocalPath(), line_num))
2046 results.append(output_api.PresubmitError(
2047 'android.widget.Toast usage is detected. Android toasts use hardware'
2048 ' acceleration, and can be\ncostly on low-end devices. Please use'
2049 ' org.chromium.ui.widget.Toast instead.\n'
2050 'Contact dskiba@chromium.org if you have any questions.',
2056 def _CheckAndroidCrLogUsage(input_api, output_api):
2057 """Checks that new logs using org.chromium.base.Log:
2058 - Are using 'TAG' as variable name for the tags (warn)
2059 - Are using a tag that is shorter than 20 characters (error)
2062 # Do not check format of logs in the given files
2063 cr_log_check_excluded_paths = [
2064 # //chrome/android/webapk cannot depend on //base
2065 r"^chrome[\\\/]android[\\\/]webapk[\\\/].*",
2066 # WebView license viewer code cannot depend on //base; used in stub APK.
2067 r"^android_webview[\\\/]glue[\\\/]java[\\\/]src[\\\/]com[\\\/]android[\\\/]"
2068 r"webview[\\\/]chromium[\\\/]License.*",
2071 cr_log_import_pattern = input_api.re.compile(
2072 r'^import org\.chromium\.base\.Log;$', input_api.re.MULTILINE)
2073 class_in_base_pattern = input_api.re.compile(
2074 r'^package org\.chromium\.base;$', input_api.re.MULTILINE)
2075 has_some_log_import_pattern = input_api.re.compile(
2076 r'^import .*\.Log;$', input_api.re.MULTILINE)
2077 # Extract the tag from lines like `Log.d(TAG, "*");` or `Log.d("TAG", "*");`
2078 log_call_pattern = input_api.re.compile(r'^\s*Log\.\w\((?P<tag>\"?\w+\"?)\,')
2079 log_decl_pattern = input_api.re.compile(
2080 r'^\s*private static final String TAG = "(?P<name>(.*))";',
2081 input_api.re.MULTILINE)
2083 REF_MSG = ('See docs/android_logging.md '
2084 'or contact dgn@chromium.org for more info.')
2085 sources = lambda x: input_api.FilterSourceFile(x, white_list=(r'.*\.java$',),
2086 black_list=cr_log_check_excluded_paths)
2088 tag_decl_errors = []
2089 tag_length_errors = []
2091 tag_with_dot_errors = []
2092 util_log_errors = []
2094 for f in input_api.AffectedSourceFiles(sources):
2095 file_content = input_api.ReadFile(f)
2096 has_modified_logs = False
2099 if (cr_log_import_pattern.search(file_content) or
2100 (class_in_base_pattern.search(file_content) and
2101 not has_some_log_import_pattern.search(file_content))):
2102 # Checks to run for files using cr log
2103 for line_num, line in f.ChangedContents():
2105 # Check if the new line is doing some logging
2106 match = log_call_pattern.search(line)
2108 has_modified_logs = True
2110 # Make sure it uses "TAG"
2111 if not match.group('tag') == 'TAG':
2112 tag_errors.append("%s:%d" % (f.LocalPath(), line_num))
2114 # Report non cr Log function calls in changed lines
2115 for line_num, line in f.ChangedContents():
2116 if log_call_pattern.search(line):
2117 util_log_errors.append("%s:%d" % (f.LocalPath(), line_num))
2120 if has_modified_logs:
2121 # Make sure the tag is using the "cr" prefix and is not too long
2122 match = log_decl_pattern.search(file_content)
2123 tag_name = match.group('name') if match else None
2125 tag_decl_errors.append(f.LocalPath())
2126 elif len(tag_name) > 20:
2127 tag_length_errors.append(f.LocalPath())
2128 elif '.' in tag_name:
2129 tag_with_dot_errors.append(f.LocalPath())
2133 results.append(output_api.PresubmitPromptWarning(
2134 'Please define your tags using the suggested format: .\n'
2135 '"private static final String TAG = "<package tag>".\n'
2136 'They will be prepended with "cr_" automatically.\n' + REF_MSG,
2139 if tag_length_errors:
2140 results.append(output_api.PresubmitError(
2141 'The tag length is restricted by the system to be at most '
2142 '20 characters.\n' + REF_MSG,
2146 results.append(output_api.PresubmitPromptWarning(
2147 'Please use a variable named "TAG" for your log tags.\n' + REF_MSG,
2151 results.append(output_api.PresubmitPromptWarning(
2152 'Please use org.chromium.base.Log for new logs.\n' + REF_MSG,
2155 if tag_with_dot_errors:
2156 results.append(output_api.PresubmitPromptWarning(
2157 'Dot in log tags cause them to be elided in crash reports.\n' + REF_MSG,
2158 tag_with_dot_errors))
2163 def _CheckAndroidTestJUnitFrameworkImport(input_api, output_api):
2164 """Checks that junit.framework.* is no longer used."""
2165 deprecated_junit_framework_pattern = input_api.re.compile(
2166 r'^import junit\.framework\..*;',
2167 input_api.re.MULTILINE)
2168 sources = lambda x: input_api.FilterSourceFile(
2169 x, white_list=(r'.*\.java$',), black_list=None)
2171 for f in input_api.AffectedFiles(sources):
2172 for line_num, line in f.ChangedContents():
2173 if deprecated_junit_framework_pattern.search(line):
2174 errors.append("%s:%d" % (f.LocalPath(), line_num))
2178 results.append(output_api.PresubmitError(
2179 'APIs from junit.framework.* are deprecated, please use JUnit4 framework'
2180 '(org.junit.*) from //third_party/junit. Contact yolandyan@chromium.org'
2181 ' if you have any question.', errors))
2185 def _CheckAndroidTestJUnitInheritance(input_api, output_api):
2186 """Checks that if new Java test classes have inheritance.
2187 Either the new test class is JUnit3 test or it is a JUnit4 test class
2188 with a base class, either case is undesirable.
2190 class_declaration_pattern = input_api.re.compile(r'^public class \w*Test ')
2192 sources = lambda x: input_api.FilterSourceFile(
2193 x, white_list=(r'.*Test\.java$',), black_list=None)
2195 for f in input_api.AffectedFiles(sources):
2196 if not f.OldContents():
2197 class_declaration_start_flag = False
2198 for line_num, line in f.ChangedContents():
2199 if class_declaration_pattern.search(line):
2200 class_declaration_start_flag = True
2201 if class_declaration_start_flag and ' extends ' in line:
2202 errors.append('%s:%d' % (f.LocalPath(), line_num))
2204 class_declaration_start_flag = False
2208 results.append(output_api.PresubmitPromptWarning(
2209 'The newly created files include Test classes that inherits from base'
2210 ' class. Please do not use inheritance in JUnit4 tests or add new'
2211 ' JUnit3 tests. Contact yolandyan@chromium.org if you have any'
2212 ' questions.', errors))
2215 def _CheckAndroidTestAnnotationUsage(input_api, output_api):
2216 """Checks that android.test.suitebuilder.annotation.* is no longer used."""
2217 deprecated_annotation_import_pattern = input_api.re.compile(
2218 r'^import android\.test\.suitebuilder\.annotation\..*;',
2219 input_api.re.MULTILINE)
2220 sources = lambda x: input_api.FilterSourceFile(
2221 x, white_list=(r'.*\.java$',), black_list=None)
2223 for f in input_api.AffectedFiles(sources):
2224 for line_num, line in f.ChangedContents():
2225 if deprecated_annotation_import_pattern.search(line):
2226 errors.append("%s:%d" % (f.LocalPath(), line_num))
2230 results.append(output_api.PresubmitError(
2231 'Annotations in android.test.suitebuilder.annotation have been'
2232 ' deprecated since API level 24. Please use android.support.test.filters'
2233 ' from //third_party/android_support_test_runner:runner_java instead.'
2234 ' Contact yolandyan@chromium.org if you have any questions.', errors))
2238 def _CheckAndroidNewMdpiAssetLocation(input_api, output_api):
2239 """Checks if MDPI assets are placed in a correct directory."""
2240 file_filter = lambda f: (f.LocalPath().endswith('.png') and
2241 ('/res/drawable/' in f.LocalPath() or
2242 '/res/drawable-ldrtl/' in f.LocalPath()))
2244 for f in input_api.AffectedFiles(include_deletes=False,
2245 file_filter=file_filter):
2246 errors.append(' %s' % f.LocalPath())
2250 results.append(output_api.PresubmitError(
2251 'MDPI assets should be placed in /res/drawable-mdpi/ or '
2252 '/res/drawable-ldrtl-mdpi/\ninstead of /res/drawable/ and'
2253 '/res/drawable-ldrtl/.\n'
2254 'Contact newt@chromium.org if you have questions.', errors))
2258 def _CheckAndroidWebkitImports(input_api, output_api):
2259 """Checks that code uses org.chromium.base.Callback instead of
2260 android.widget.ValueCallback except in the WebView glue layer.
2262 valuecallback_import_pattern = input_api.re.compile(
2263 r'^import android\.webkit\.ValueCallback;$')
2267 sources = lambda affected_file: input_api.FilterSourceFile(
2269 black_list=(_EXCLUDED_PATHS +
2270 _TEST_CODE_EXCLUDED_PATHS +
2271 input_api.DEFAULT_BLACK_LIST +
2272 (r'^android_webview[\\\/]glue[\\\/].*',)),
2273 white_list=(r'.*\.java$',))
2275 for f in input_api.AffectedSourceFiles(sources):
2276 for line_num, line in f.ChangedContents():
2277 if valuecallback_import_pattern.search(line):
2278 errors.append("%s:%d" % (f.LocalPath(), line_num))
2283 results.append(output_api.PresubmitError(
2284 'android.webkit.ValueCallback usage is detected outside of the glue'
2285 ' layer. To stay compatible with the support library, android.webkit.*'
2286 ' classes should only be used inside the glue layer and'
2287 ' org.chromium.base.Callback should be used instead.',
2293 class PydepsChecker(object):
2294 def __init__(self, input_api, pydeps_files):
2295 self._file_cache = {}
2296 self._input_api = input_api
2297 self._pydeps_files = pydeps_files
2299 def _LoadFile(self, path):
2300 """Returns the list of paths within a .pydeps file relative to //."""
2301 if path not in self._file_cache:
2302 with open(path) as f:
2303 self._file_cache[path] = f.read()
2304 return self._file_cache[path]
2306 def _ComputeNormalizedPydepsEntries(self, pydeps_path):
2307 """Returns an interable of paths within the .pydep, relativized to //."""
2308 os_path = self._input_api.os_path
2309 pydeps_dir = os_path.dirname(pydeps_path)
2310 entries = (l.rstrip() for l in self._LoadFile(pydeps_path).splitlines()
2311 if not l.startswith('*'))
2312 return (os_path.normpath(os_path.join(pydeps_dir, e)) for e in entries)
2314 def _CreateFilesToPydepsMap(self):
2315 """Returns a map of local_path -> list_of_pydeps."""
2317 for pydep_local_path in self._pydeps_files:
2318 for path in self._ComputeNormalizedPydepsEntries(pydep_local_path):
2319 ret.setdefault(path, []).append(pydep_local_path)
2322 def ComputeAffectedPydeps(self):
2323 """Returns an iterable of .pydeps files that might need regenerating."""
2324 affected_pydeps = set()
2325 file_to_pydeps_map = None
2326 for f in self._input_api.AffectedFiles(include_deletes=True):
2327 local_path = f.LocalPath()
2328 if local_path == 'DEPS':
2329 return self._pydeps_files
2330 elif local_path.endswith('.pydeps'):
2331 if local_path in self._pydeps_files:
2332 affected_pydeps.add(local_path)
2333 elif local_path.endswith('.py'):
2334 if file_to_pydeps_map is None:
2335 file_to_pydeps_map = self._CreateFilesToPydepsMap()
2336 affected_pydeps.update(file_to_pydeps_map.get(local_path, ()))
2337 return affected_pydeps
2339 def DetermineIfStale(self, pydeps_path):
2340 """Runs print_python_deps.py to see if the files is stale."""
2344 old_pydeps_data = self._LoadFile(pydeps_path).splitlines()
2345 cmd = old_pydeps_data[1][1:].strip()
2346 env = dict(os.environ)
2347 env['PYTHONDONTWRITEBYTECODE'] = '1'
2348 new_pydeps_data = self._input_api.subprocess.check_output(
2349 cmd + ' --output ""', shell=True, env=env)
2350 old_contents = old_pydeps_data[2:]
2351 new_contents = new_pydeps_data.splitlines()[2:]
2352 if old_pydeps_data[2:] != new_pydeps_data.splitlines()[2:]:
2353 return cmd, '\n'.join(difflib.context_diff(old_contents, new_contents))
2356 def _CheckPydepsNeedsUpdating(input_api, output_api, checker_for_tests=None):
2357 """Checks if a .pydeps file needs to be regenerated."""
2358 # This check is for Python dependency lists (.pydeps files), and involves
2359 # paths not only in the PRESUBMIT.py, but also in the .pydeps files. It
2360 # doesn't work on Windows and Mac, so skip it on other platforms.
2361 if input_api.platform != 'linux2':
2363 # TODO(agrieve): Update when there's a better way to detect
2364 # this: crbug.com/570091
2365 is_android = input_api.os_path.exists('third_party/android_tools')
2366 pydeps_files = _ALL_PYDEPS_FILES if is_android else _GENERIC_PYDEPS_FILES
2368 # First, check for new / deleted .pydeps.
2369 for f in input_api.AffectedFiles(include_deletes=True):
2370 # Check whether we are running the presubmit check for a file in src.
2371 # f.LocalPath is relative to repo (src, or internal repo).
2372 # os_path.exists is relative to src repo.
2373 # Therefore if os_path.exists is true, it means f.LocalPath is relative
2374 # to src and we can conclude that the pydeps is in src.
2375 if input_api.os_path.exists(f.LocalPath()):
2376 if f.LocalPath().endswith('.pydeps'):
2377 if f.Action() == 'D' and f.LocalPath() in _ALL_PYDEPS_FILES:
2378 results.append(output_api.PresubmitError(
2379 'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
2380 'remove %s' % f.LocalPath()))
2381 elif f.Action() != 'D' and f.LocalPath() not in _ALL_PYDEPS_FILES:
2382 results.append(output_api.PresubmitError(
2383 'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
2384 'include %s' % f.LocalPath()))
2389 checker = checker_for_tests or PydepsChecker(input_api, pydeps_files)
2391 for pydep_path in checker.ComputeAffectedPydeps():
2393 result = checker.DetermineIfStale(pydep_path)
2396 results.append(output_api.PresubmitError(
2397 'File is stale: %s\nDiff (apply to fix):\n%s\n'
2398 'To regenerate, run:\n\n %s' %
2399 (pydep_path, diff, cmd)))
2400 except input_api.subprocess.CalledProcessError as error:
2401 return [output_api.PresubmitError('Error running: %s' % error.cmd,
2402 long_text=error.output)]
2407 def _CheckSingletonInHeaders(input_api, output_api):
2408 """Checks to make sure no header files have |Singleton<|."""
2409 def FileFilter(affected_file):
2410 # It's ok for base/memory/singleton.h to have |Singleton<|.
2411 black_list = (_EXCLUDED_PATHS +
2412 input_api.DEFAULT_BLACK_LIST +
2413 (r"^base[\\\/]memory[\\\/]singleton\.h$",
2414 r"^net[\\\/]quic[\\\/]platform[\\\/]impl[\\\/]"
2415 r"quic_singleton_impl\.h$"))
2416 return input_api.FilterSourceFile(affected_file, black_list=black_list)
2418 pattern = input_api.re.compile(r'(?<!class\sbase::)Singleton\s*<')
2420 for f in input_api.AffectedSourceFiles(FileFilter):
2421 if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx') or
2422 f.LocalPath().endswith('.hpp') or f.LocalPath().endswith('.inl')):
2423 contents = input_api.ReadFile(f)
2424 for line in contents.splitlines(False):
2425 if (not line.lstrip().startswith('//') and # Strip C++ comment.
2426 pattern.search(line)):
2431 return [output_api.PresubmitError(
2432 'Found base::Singleton<T> in the following header files.\n' +
2433 'Please move them to an appropriate source file so that the ' +
2434 'template gets instantiated in a single compilation unit.',
2441 ( "-webkit-box", "flex" ),
2442 ( "-webkit-inline-box", "inline-flex" ),
2443 ( "-webkit-flex", "flex" ),
2444 ( "-webkit-inline-flex", "inline-flex" ),
2445 ( "-webkit-min-content", "min-content" ),
2446 ( "-webkit-max-content", "max-content" ),
2449 ( "-webkit-background-clip", "background-clip" ),
2450 ( "-webkit-background-origin", "background-origin" ),
2451 ( "-webkit-background-size", "background-size" ),
2452 ( "-webkit-box-shadow", "box-shadow" ),
2453 ( "-webkit-user-select", "user-select" ),
2456 ( "-webkit-gradient", "gradient" ),
2457 ( "-webkit-repeating-gradient", "repeating-gradient" ),
2458 ( "-webkit-linear-gradient", "linear-gradient" ),
2459 ( "-webkit-repeating-linear-gradient", "repeating-linear-gradient" ),
2460 ( "-webkit-radial-gradient", "radial-gradient" ),
2461 ( "-webkit-repeating-radial-gradient", "repeating-radial-gradient" ),
2464 def _CheckNoDeprecatedCss(input_api, output_api):
2465 """ Make sure that we don't use deprecated CSS
2466 properties, functions or values. Our external
2467 documentation and iOS CSS for dom distiller
2468 (reader mode) are ignored by the hooks as it
2469 needs to be consumed by WebKit. """
2471 file_inclusion_pattern = (r".+\.css$",)
2472 black_list = (_EXCLUDED_PATHS +
2473 _TEST_CODE_EXCLUDED_PATHS +
2474 input_api.DEFAULT_BLACK_LIST +
2475 (r"^chrome/common/extensions/docs",
2477 r"^components/dom_distiller/core/css/distilledpage_ios.css",
2478 r"^components/neterror/resources/neterror.css",
2479 r"^native_client_sdk"))
2480 file_filter = lambda f: input_api.FilterSourceFile(
2481 f, white_list=file_inclusion_pattern, black_list=black_list)
2482 for fpath in input_api.AffectedFiles(file_filter=file_filter):
2483 for line_num, line in fpath.ChangedContents():
2484 for (deprecated_value, value) in _DEPRECATED_CSS:
2485 if deprecated_value in line:
2486 results.append(output_api.PresubmitError(
2487 "%s:%d: Use of deprecated CSS %s, use %s instead" %
2488 (fpath.LocalPath(), line_num, deprecated_value, value)))
2493 ( "__lookupGetter__", "Object.getOwnPropertyDescriptor" ),
2494 ( "__defineGetter__", "Object.defineProperty" ),
2495 ( "__defineSetter__", "Object.defineProperty" ),
2498 def _CheckNoDeprecatedJs(input_api, output_api):
2499 """Make sure that we don't use deprecated JS in Chrome code."""
2501 file_inclusion_pattern = (r".+\.js$",) # TODO(dbeam): .html?
2502 black_list = (_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
2503 input_api.DEFAULT_BLACK_LIST)
2504 file_filter = lambda f: input_api.FilterSourceFile(
2505 f, white_list=file_inclusion_pattern, black_list=black_list)
2506 for fpath in input_api.AffectedFiles(file_filter=file_filter):
2507 for lnum, line in fpath.ChangedContents():
2508 for (deprecated, replacement) in _DEPRECATED_JS:
2509 if deprecated in line:
2510 results.append(output_api.PresubmitError(
2511 "%s:%d: Use of deprecated JS %s, use %s instead" %
2512 (fpath.LocalPath(), lnum, deprecated, replacement)))
2515 def _CheckForRiskyJsArrowFunction(line_number, line):
2517 return "line %d, is using an => (arrow) function\n %s\n" % (
2521 def _CheckForRiskyJsConstLet(input_api, line_number, line):
2522 if input_api.re.match('^\s*(const|let)\s', line):
2523 return "line %d, is using const/let keyword\n %s\n" % (
2527 def _CheckForRiskyJsFeatures(input_api, output_api):
2528 maybe_ios_js = (r"^(ios|components|ui\/webui\/resources)\/.+\.js$", )
2529 # 'ui/webui/resources/cr_components are not allowed on ios'
2530 not_ios_filter = (r".*ui\/webui\/resources\/cr_components.*", )
2531 file_filter = lambda f: input_api.FilterSourceFile(f, white_list=maybe_ios_js,
2532 black_list=not_ios_filter)
2534 for f in input_api.AffectedFiles(file_filter=file_filter):
2535 arrow_error_lines = []
2536 const_let_error_lines = []
2537 for lnum, line in f.ChangedContents():
2538 arrow_error_lines += filter(None, [
2539 _CheckForRiskyJsArrowFunction(lnum, line),
2542 const_let_error_lines += filter(None, [
2543 _CheckForRiskyJsConstLet(input_api, lnum, line),
2546 if arrow_error_lines:
2547 arrow_error_lines = map(
2548 lambda e: "%s:%s" % (f.LocalPath(), e), arrow_error_lines)
2550 output_api.PresubmitPromptWarning('\n'.join(arrow_error_lines + [
2552 Use of => (arrow) operator detected in:
2554 Please ensure your code does not run on iOS9 (=> (arrow) does not work there).
2555 https://chromium.googlesource.com/chromium/src/+/master/docs/es6_chromium.md#Arrow-Functions
2559 if const_let_error_lines:
2560 const_let_error_lines = map(
2561 lambda e: "%s:%s" % (f.LocalPath(), e), const_let_error_lines)
2563 output_api.PresubmitPromptWarning('\n'.join(const_let_error_lines + [
2565 Use of const/let keywords detected in:
2567 Please ensure your code does not run on iOS9 because const/let is not fully
2569 https://chromium.googlesource.com/chromium/src/+/master/docs/es6_chromium.md#let-Block_Scoped-Variables
2570 https://chromium.googlesource.com/chromium/src/+/master/docs/es6_chromium.md#const-Block_Scoped-Constants
2576 def _CheckForRelativeIncludes(input_api, output_api):
2577 # Need to set the sys.path so PRESUBMIT_test.py runs properly
2579 original_sys_path = sys.path
2581 sys.path = sys.path + [input_api.os_path.join(
2582 input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
2583 from cpp_checker import CppChecker
2585 # Restore sys.path to what it was before.
2586 sys.path = original_sys_path
2589 for f in input_api.AffectedFiles(include_deletes=False):
2590 if (f.LocalPath().startswith('third_party') and
2591 not f.LocalPath().startswith('third_party/WebKit') and
2592 not f.LocalPath().startswith('third_party\\WebKit')):
2595 if not CppChecker.IsCppFile(f.LocalPath()):
2598 relative_includes = [line for _, line in f.ChangedContents()
2599 if "#include" in line and "../" in line]
2600 if not relative_includes:
2602 bad_files[f.LocalPath()] = relative_includes
2607 error_descriptions = []
2608 for file_path, bad_lines in bad_files.iteritems():
2609 error_description = file_path
2610 for line in bad_lines:
2611 error_description += '\n ' + line
2612 error_descriptions.append(error_description)
2615 results.append(output_api.PresubmitError(
2616 'You added one or more relative #include paths (including "../").\n'
2617 'These shouldn\'t be used because they can be used to include headers\n'
2618 'from code that\'s not correctly specified as a dependency in the\n'
2619 'relevant BUILD.gn file(s).',
2620 error_descriptions))
2625 def _CheckWatchlistDefinitionsEntrySyntax(key, value, ast):
2626 if not isinstance(key, ast.Str):
2627 return 'Key at line %d must be a string literal' % key.lineno
2628 if not isinstance(value, ast.Dict):
2629 return 'Value at line %d must be a dict' % value.lineno
2630 if len(value.keys) != 1:
2631 return 'Dict at line %d must have single entry' % value.lineno
2632 if not isinstance(value.keys[0], ast.Str) or value.keys[0].s != 'filepath':
2634 'Entry at line %d must have a string literal \'filepath\' as key' %
2639 def _CheckWatchlistsEntrySyntax(key, value, ast):
2640 if not isinstance(key, ast.Str):
2641 return 'Key at line %d must be a string literal' % key.lineno
2642 if not isinstance(value, ast.List):
2643 return 'Value at line %d must be a list' % value.lineno
2647 def _CheckWATCHLISTSEntries(wd_dict, w_dict, ast):
2648 mismatch_template = (
2649 'Mismatch between WATCHLIST_DEFINITIONS entry (%s) and WATCHLISTS '
2655 if i >= len(wd_dict.keys):
2656 if i >= len(w_dict.keys):
2658 return mismatch_template % ('missing', 'line %d' % w_dict.keys[i].lineno)
2659 elif i >= len(w_dict.keys):
2661 mismatch_template % ('line %d' % wd_dict.keys[i].lineno, 'missing'))
2663 wd_key = wd_dict.keys[i]
2664 w_key = w_dict.keys[i]
2666 result = _CheckWatchlistDefinitionsEntrySyntax(
2667 wd_key, wd_dict.values[i], ast)
2668 if result is not None:
2669 return 'Bad entry in WATCHLIST_DEFINITIONS dict: %s' % result
2671 result = _CheckWatchlistsEntrySyntax(w_key, w_dict.values[i], ast)
2672 if result is not None:
2673 return 'Bad entry in WATCHLISTS dict: %s' % result
2675 if wd_key.s != w_key.s:
2676 return mismatch_template % (
2677 '%s at line %d' % (wd_key.s, wd_key.lineno),
2678 '%s at line %d' % (w_key.s, w_key.lineno))
2680 if wd_key.s < last_key:
2682 'WATCHLISTS dict is not sorted lexicographically at line %d and %d' %
2683 (wd_key.lineno, w_key.lineno))
2689 def _CheckWATCHLISTSSyntax(expression, ast):
2690 if not isinstance(expression, ast.Expression):
2691 return 'WATCHLISTS file must contain a valid expression'
2692 dictionary = expression.body
2693 if not isinstance(dictionary, ast.Dict) or len(dictionary.keys) != 2:
2694 return 'WATCHLISTS file must have single dict with exactly two entries'
2696 first_key = dictionary.keys[0]
2697 first_value = dictionary.values[0]
2698 second_key = dictionary.keys[1]
2699 second_value = dictionary.values[1]
2701 if (not isinstance(first_key, ast.Str) or
2702 first_key.s != 'WATCHLIST_DEFINITIONS' or
2703 not isinstance(first_value, ast.Dict)):
2705 'The first entry of the dict in WATCHLISTS file must be '
2706 'WATCHLIST_DEFINITIONS dict')
2708 if (not isinstance(second_key, ast.Str) or
2709 second_key.s != 'WATCHLISTS' or
2710 not isinstance(second_value, ast.Dict)):
2712 'The second entry of the dict in WATCHLISTS file must be '
2715 return _CheckWATCHLISTSEntries(first_value, second_value, ast)
2718 def _CheckWATCHLISTS(input_api, output_api):
2719 for f in input_api.AffectedFiles(include_deletes=False):
2720 if f.LocalPath() == 'WATCHLISTS':
2721 contents = input_api.ReadFile(f, 'r')
2724 # First, make sure that it can be evaluated.
2725 input_api.ast.literal_eval(contents)
2726 # Get an AST tree for it and scan the tree for detailed style checking.
2727 expression = input_api.ast.parse(
2728 contents, filename='WATCHLISTS', mode='eval')
2729 except ValueError as e:
2730 return [output_api.PresubmitError(
2731 'Cannot parse WATCHLISTS file', long_text=repr(e))]
2732 except SyntaxError as e:
2733 return [output_api.PresubmitError(
2734 'Cannot parse WATCHLISTS file', long_text=repr(e))]
2735 except TypeError as e:
2736 return [output_api.PresubmitError(
2737 'Cannot parse WATCHLISTS file', long_text=repr(e))]
2739 result = _CheckWATCHLISTSSyntax(expression, input_api.ast)
2740 if result is not None:
2741 return [output_api.PresubmitError(result)]
2747 def _AndroidSpecificOnUploadChecks(input_api, output_api):
2748 """Groups checks that target android code."""
2750 results.extend(_CheckAndroidCrLogUsage(input_api, output_api))
2751 results.extend(_CheckAndroidNewMdpiAssetLocation(input_api, output_api))
2752 results.extend(_CheckAndroidToastUsage(input_api, output_api))
2753 results.extend(_CheckAndroidTestJUnitInheritance(input_api, output_api))
2754 results.extend(_CheckAndroidTestJUnitFrameworkImport(input_api, output_api))
2755 results.extend(_CheckAndroidTestAnnotationUsage(input_api, output_api))
2756 results.extend(_CheckAndroidWebkitImports(input_api, output_api))
2760 def _CommonChecks(input_api, output_api):
2761 """Checks common to both upload and commit."""
2763 results.extend(input_api.canned_checks.PanProjectChecks(
2764 input_api, output_api,
2765 excluded_paths=_EXCLUDED_PATHS))
2767 author = input_api.change.author_email
2768 if author and author not in _KNOWN_ROBOTS:
2770 input_api.canned_checks.CheckAuthorizedAuthor(input_api, output_api))
2773 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
2775 _CheckNoProductionCodeUsingTestOnlyFunctionsJava(input_api, output_api))
2776 results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
2777 results.extend(_CheckNoUNIT_TESTInSourceFiles(input_api, output_api))
2778 results.extend(_CheckDCHECK_IS_ONHasBraces(input_api, output_api))
2779 results.extend(_CheckNoNewWStrings(input_api, output_api))
2780 results.extend(_CheckNoDEPSGIT(input_api, output_api))
2781 results.extend(_CheckNoBannedFunctions(input_api, output_api))
2782 results.extend(_CheckNoPragmaOnce(input_api, output_api))
2783 results.extend(_CheckNoTrinaryTrueFalse(input_api, output_api))
2784 results.extend(_CheckUnwantedDependencies(input_api, output_api))
2785 results.extend(_CheckFilePermissions(input_api, output_api))
2786 results.extend(_CheckTeamTags(input_api, output_api))
2787 results.extend(_CheckNoAuraWindowPropertyHInHeaders(input_api, output_api))
2788 results.extend(_CheckForVersionControlConflicts(input_api, output_api))
2789 results.extend(_CheckPatchFiles(input_api, output_api))
2790 results.extend(_CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api))
2791 results.extend(_CheckNoAbbreviationInPngFileName(input_api, output_api))
2792 results.extend(_CheckBuildConfigMacrosWithoutInclude(input_api, output_api))
2793 results.extend(_CheckForInvalidOSMacros(input_api, output_api))
2794 results.extend(_CheckForInvalidIfDefinedMacros(input_api, output_api))
2795 results.extend(_CheckFlakyTestUsage(input_api, output_api))
2796 results.extend(_CheckAddedDepsHaveTargetApprovals(input_api, output_api))
2798 input_api.canned_checks.CheckChangeHasNoTabs(
2801 source_file_filter=lambda x: x.LocalPath().endswith('.grd')))
2802 results.extend(_CheckSpamLogging(input_api, output_api))
2803 results.extend(_CheckForAnonymousVariables(input_api, output_api))
2804 results.extend(_CheckUserActionUpdate(input_api, output_api))
2805 results.extend(_CheckNoDeprecatedCss(input_api, output_api))
2806 results.extend(_CheckNoDeprecatedJs(input_api, output_api))
2807 results.extend(_CheckParseErrors(input_api, output_api))
2808 results.extend(_CheckForIPCRules(input_api, output_api))
2809 results.extend(_CheckForIncludeGuards(input_api, output_api))
2810 results.extend(_CheckForWindowsLineEndings(input_api, output_api))
2811 results.extend(_CheckSingletonInHeaders(input_api, output_api))
2812 results.extend(_CheckPydepsNeedsUpdating(input_api, output_api))
2813 results.extend(_CheckJavaStyle(input_api, output_api))
2814 results.extend(_CheckIpcOwners(input_api, output_api))
2815 results.extend(_CheckUselessForwardDeclarations(input_api, output_api))
2816 results.extend(_CheckForRiskyJsFeatures(input_api, output_api))
2817 results.extend(_CheckForRelativeIncludes(input_api, output_api))
2818 results.extend(_CheckWATCHLISTS(input_api, output_api))
2819 results.extend(input_api.RunTests(
2820 input_api.canned_checks.CheckVPythonSpec(input_api, output_api)))
2822 for f in input_api.AffectedFiles():
2823 path, name = input_api.os_path.split(f.LocalPath())
2824 if name == 'PRESUBMIT.py':
2825 full_path = input_api.os_path.join(input_api.PresubmitLocalPath(), path)
2826 results.extend(input_api.canned_checks.RunUnitTestsInDirectory(
2827 input_api, output_api, full_path,
2828 whitelist=[r'^PRESUBMIT_test\.py$']))
2832 def _CheckPatchFiles(input_api, output_api):
2833 problems = [f.LocalPath() for f in input_api.AffectedFiles()
2834 if f.LocalPath().endswith(('.orig', '.rej'))]
2836 return [output_api.PresubmitError(
2837 "Don't commit .rej and .orig files.", problems)]
2842 def _CheckBuildConfigMacrosWithoutInclude(input_api, output_api):
2843 # Excludes OS_CHROMEOS, which is not defined in build_config.h.
2844 macro_re = input_api.re.compile(r'^\s*#(el)?if.*\bdefined\(((OS_(?!CHROMEOS)|'
2845 'COMPILER_|ARCH_CPU_|WCHAR_T_IS_)[^)]*)')
2846 include_re = input_api.re.compile(
2847 r'^#include\s+"build/build_config.h"', input_api.re.MULTILINE)
2848 extension_re = input_api.re.compile(r'\.[a-z]+$')
2850 for f in input_api.AffectedFiles():
2851 if not f.LocalPath().endswith(('.h', '.c', '.cc', '.cpp', '.m', '.mm')):
2853 found_line_number = None
2855 for line_num, line in f.ChangedContents():
2856 match = macro_re.search(line)
2858 found_line_number = line_num
2859 found_macro = match.group(2)
2861 if not found_line_number:
2864 found_include = False
2865 for line in f.NewContents():
2866 if include_re.search(line):
2867 found_include = True
2872 if not f.LocalPath().endswith('.h'):
2873 primary_header_path = extension_re.sub('.h', f.AbsoluteLocalPath())
2875 content = input_api.ReadFile(primary_header_path, 'r')
2876 if include_re.search(content):
2880 errors.append('%s:%d %s macro is used without including build/'
2882 % (f.LocalPath(), found_line_number, found_macro))
2884 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
2888 def _DidYouMeanOSMacro(bad_macro):
2890 return {'A': 'OS_ANDROID',
2900 'W': 'OS_WIN'}[bad_macro[3].upper()]
2905 def _CheckForInvalidOSMacrosInFile(input_api, f):
2906 """Check for sensible looking, totally invalid OS macros."""
2907 preprocessor_statement = input_api.re.compile(r'^\s*#')
2908 os_macro = input_api.re.compile(r'defined\((OS_[^)]+)\)')
2910 for lnum, line in f.ChangedContents():
2911 if preprocessor_statement.search(line):
2912 for match in os_macro.finditer(line):
2913 if not match.group(1) in _VALID_OS_MACROS:
2914 good = _DidYouMeanOSMacro(match.group(1))
2915 did_you_mean = ' (did you mean %s?)' % good if good else ''
2916 results.append(' %s:%d %s%s' % (f.LocalPath(),
2923 def _CheckForInvalidOSMacros(input_api, output_api):
2924 """Check all affected files for invalid OS macros."""
2926 for f in input_api.AffectedFiles():
2927 if not f.LocalPath().endswith(('.py', '.js', '.html', '.css', '.md')):
2928 bad_macros.extend(_CheckForInvalidOSMacrosInFile(input_api, f))
2933 return [output_api.PresubmitError(
2934 'Possibly invalid OS macro[s] found. Please fix your code\n'
2935 'or add your macro to src/PRESUBMIT.py.', bad_macros)]
2938 def _CheckForInvalidIfDefinedMacrosInFile(input_api, f):
2939 """Check all affected files for invalid "if defined" macros."""
2940 ALWAYS_DEFINED_MACROS = (
2949 "TARGET_IPHONE_SIMULATOR",
2950 "TARGET_OS_EMBEDDED",
2956 ifdef_macro = input_api.re.compile(r'^\s*#.*(?:ifdef\s|defined\()([^\s\)]+)')
2958 for lnum, line in f.ChangedContents():
2959 for match in ifdef_macro.finditer(line):
2960 if match.group(1) in ALWAYS_DEFINED_MACROS:
2961 always_defined = ' %s is always defined. ' % match.group(1)
2962 did_you_mean = 'Did you mean \'#if %s\'?' % match.group(1)
2963 results.append(' %s:%d %s\n\t%s' % (f.LocalPath(),
2970 def _CheckForInvalidIfDefinedMacros(input_api, output_api):
2971 """Check all affected files for invalid "if defined" macros."""
2973 for f in input_api.AffectedFiles():
2974 if f.LocalPath().startswith('third_party/sqlite/'):
2976 if f.LocalPath().endswith(('.h', '.c', '.cc', '.m', '.mm')):
2977 bad_macros.extend(_CheckForInvalidIfDefinedMacrosInFile(input_api, f))
2982 return [output_api.PresubmitError(
2983 'Found ifdef check on always-defined macro[s]. Please fix your code\n'
2984 'or check the list of ALWAYS_DEFINED_MACROS in src/PRESUBMIT.py.',
2988 def _CheckForIPCRules(input_api, output_api):
2989 """Check for same IPC rules described in
2990 http://www.chromium.org/Home/chromium-security/education/security-tips-for-ipc
2992 base_pattern = r'IPC_ENUM_TRAITS\('
2993 inclusion_pattern = input_api.re.compile(r'(%s)' % base_pattern)
2994 comment_pattern = input_api.re.compile(r'//.*(%s)' % base_pattern)
2997 for f in input_api.AffectedSourceFiles(None):
2998 local_path = f.LocalPath()
2999 if not local_path.endswith('.h'):
3001 for line_number, line in f.ChangedContents():
3002 if inclusion_pattern.search(line) and not comment_pattern.search(line):
3004 '%s:%d\n %s' % (local_path, line_number, line.strip()))
3007 return [output_api.PresubmitPromptWarning(
3008 _IPC_ENUM_TRAITS_DEPRECATED, problems)]
3013 def _CheckForIncludeGuards(input_api, output_api):
3014 """Check that header files have proper guards against multiple inclusion.
3015 If a file should not have such guards (and it probably should) then it
3016 should include the string "no-include-guard-because-multiply-included".
3018 def is_header_file(f):
3019 return f.LocalPath().endswith('.h')
3021 def replace_special_with_underscore(string):
3022 return input_api.re.sub(r'[\\/.-]', '_', string)
3026 for f in input_api.AffectedSourceFiles(is_header_file):
3028 guard_line_number = None
3029 seen_guard_end = False
3031 file_with_path = input_api.os_path.normpath(f.LocalPath())
3032 base_file_name = input_api.os_path.splitext(
3033 input_api.os_path.basename(file_with_path))[0]
3034 upper_base_file_name = base_file_name.upper()
3036 expected_guard = replace_special_with_underscore(
3037 file_with_path.upper() + '_')
3038 expected_guard_if_blink = base_file_name + '_h'
3040 # For "path/elem/file_name.h" we should really only accept
3041 # PATH_ELEM_FILE_NAME_H_ per coding style or, if Blink,
3042 # file_name_h. Unfortunately there are too many (1000+) files
3043 # with slight deviations from the coding style. Since the most
3044 # important part is that the include guard is there, and that it's
3045 # unique, not the name, this check is forgiving for existing files.
3047 # As code becomes more uniform, this could be made stricter.
3049 guard_name_pattern_list = [
3050 # Anything with the right suffix (maybe with an extra _).
3053 # To cover include guards with Blink style.
3056 # Anything including the uppercase name of the file.
3057 r'\w*' + input_api.re.escape(replace_special_with_underscore(
3058 upper_base_file_name)) + r'\w*',
3060 guard_name_pattern = '|'.join(guard_name_pattern_list)
3061 guard_pattern = input_api.re.compile(
3062 r'#ifndef\s+(' + guard_name_pattern + ')')
3064 for line_number, line in enumerate(f.NewContents()):
3065 if 'no-include-guard-because-multiply-included' in line:
3066 guard_name = 'DUMMY' # To not trigger check outside the loop.
3069 if guard_name is None:
3070 match = guard_pattern.match(line)
3072 guard_name = match.group(1)
3073 guard_line_number = line_number
3075 # We allow existing files to use slightly wrong include
3076 # guards, but new files should get it right.
3077 if not f.OldContents():
3078 is_in_blink = file_with_path.startswith(input_api.os_path.join(
3079 'third_party', 'WebKit'))
3080 if not (guard_name == expected_guard or
3081 is_in_blink and guard_name == expected_guard_if_blink):
3083 expected_text = "%s or %s" % (expected_guard,
3084 expected_guard_if_blink)
3086 expected_text = expected_guard
3087 errors.append(output_api.PresubmitPromptWarning(
3088 'Header using the wrong include guard name %s' % guard_name,
3089 ['%s:%d' % (f.LocalPath(), line_number + 1)],
3090 'Expected: %r\nFound: %r' % (expected_text, guard_name)))
3092 # The line after #ifndef should have a #define of the same name.
3093 if line_number == guard_line_number + 1:
3094 expected_line = '#define %s' % guard_name
3095 if line != expected_line:
3096 errors.append(output_api.PresubmitPromptWarning(
3097 'Missing "%s" for include guard' % expected_line,
3098 ['%s:%d' % (f.LocalPath(), line_number + 1)],
3099 'Expected: %r\nGot: %r' % (expected_line, line)))
3101 if not seen_guard_end and line == '#endif // %s' % guard_name:
3102 seen_guard_end = True
3103 elif seen_guard_end:
3104 if line.strip() != '':
3105 errors.append(output_api.PresubmitPromptWarning(
3106 'Include guard %s not covering the whole file' % (
3107 guard_name), [f.LocalPath()]))
3108 break # Nothing else to check and enough to warn once.
3110 if guard_name is None:
3111 errors.append(output_api.PresubmitPromptWarning(
3112 'Missing include guard %s' % expected_guard,
3114 'Missing include guard in %s\n'
3115 'Recommended name: %s\n'
3116 'This check can be disabled by having the string\n'
3117 'no-include-guard-because-multiply-included in the header.' %
3118 (f.LocalPath(), expected_guard)))
3123 def _CheckForWindowsLineEndings(input_api, output_api):
3124 """Check source code and known ascii text files for Windows style line
3127 known_text_files = r'.*\.(txt|html|htm|mhtml|py|gyp|gypi|gn|isolate)$'
3129 file_inclusion_pattern = (
3131 r'.+%s' % _IMPLEMENTATION_EXTENSIONS
3135 source_file_filter = lambda f: input_api.FilterSourceFile(
3136 f, white_list=file_inclusion_pattern, black_list=None)
3137 for f in input_api.AffectedSourceFiles(source_file_filter):
3138 include_file = False
3139 for _, line in f.ChangedContents():
3140 if line.endswith('\r\n'):
3143 problems.append(f.LocalPath())
3146 return [output_api.PresubmitPromptWarning('Are you sure that you want '
3147 'these files to contain Windows style line endings?\n' +
3148 '\n'.join(problems))]
3153 def _CheckSyslogUseWarning(input_api, output_api, source_file_filter=None):
3154 """Checks that all source files use SYSLOG properly."""
3156 for f in input_api.AffectedSourceFiles(source_file_filter):
3157 for line_number, line in f.ChangedContents():
3158 if 'SYSLOG' in line:
3159 syslog_files.append(f.LocalPath() + ':' + str(line_number))
3162 return [output_api.PresubmitPromptWarning(
3163 'Please make sure there are no privacy sensitive bits of data in SYSLOG'
3164 ' calls.\nFiles to check:\n', items=syslog_files)]
3168 def _CheckCrbugLinksHaveHttps(input_api, output_api):
3169 """Checks that crbug(.com) links are correctly prefixed by https://,
3170 unless they come in the accepted form TODO(crbug.com/...)
3172 white_list = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
3173 black_list = (_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS)
3174 sources = lambda f: input_api.FilterSourceFile(
3175 f, white_list=white_list, black_list=black_list)
3177 pattern = input_api.re.compile(r'//.*(?<!:\/\/)crbug[.com]*')
3178 accepted_pattern = input_api.re.compile(r'//.*TODO\(crbug[.com]*');
3180 for f in input_api.AffectedSourceFiles(sources):
3181 for line_num, line in f.ChangedContents():
3182 if pattern.search(line) and not accepted_pattern.search(line):
3183 problems.append(' %s:%d %s' % (f.LocalPath(), line_num, line))
3186 return [output_api.PresubmitPromptWarning(
3187 'Found unprefixed crbug.com URL(s), consider prepending https://\n'+
3188 '\n'.join(problems))]
3192 def CheckChangeOnUpload(input_api, output_api):
3194 results.extend(_CommonChecks(input_api, output_api))
3195 results.extend(_CheckValidHostsInDEPS(input_api, output_api))
3197 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
3198 results.extend(_CheckUmaHistogramChanges(input_api, output_api))
3199 results.extend(_AndroidSpecificOnUploadChecks(input_api, output_api))
3200 results.extend(_CheckSyslogUseWarning(input_api, output_api))
3201 results.extend(_CheckGoogleSupportAnswerUrl(input_api, output_api))
3202 results.extend(_CheckCrbugLinksHaveHttps(input_api, output_api))
3203 results.extend(_CheckUniquePtr(input_api, output_api))
3207 def GetTryServerMasterForBot(bot):
3208 """Returns the Try Server master for the given bot.
3210 It tries to guess the master from the bot name, but may still fail
3211 and return None. There is no longer a default master.
3213 # Potentially ambiguous bot names are listed explicitly.
3215 'chromium_presubmit': 'master.tryserver.chromium.linux',
3216 'tools_build_presubmit': 'master.tryserver.chromium.linux',
3218 master = master_map.get(bot)
3220 if 'android' in bot:
3221 master = 'master.tryserver.chromium.android'
3222 elif 'linux' in bot or 'presubmit' in bot:
3223 master = 'master.tryserver.chromium.linux'
3225 master = 'master.tryserver.chromium.win'
3226 elif 'mac' in bot or 'ios' in bot:
3227 master = 'master.tryserver.chromium.mac'
3231 def CheckChangeOnCommit(input_api, output_api):
3233 results.extend(_CommonChecks(input_api, output_api))
3234 # Make sure the tree is 'open'.
3235 results.extend(input_api.canned_checks.CheckTreeIsOpen(
3238 json_url='http://chromium-status.appspot.com/current?format=json'))
3241 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
3242 results.extend(input_api.canned_checks.CheckChangeHasBugField(
3243 input_api, output_api))
3244 results.extend(input_api.canned_checks.CheckChangeHasDescription(
3245 input_api, output_api))