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 r'/base::SequenceChecker\b',
347 'Consider using SEQUENCE_CHECKER macros instead of the class directly.',
353 r'/base::ThreadChecker\b',
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. It exposes environments',
406 "to Chrome's tracing, making their memory usage visible.",
410 r'^third_party/leveldatabase/.*\.(cc|h)$',
414 'RunLoop::QuitCurrent',
416 'Please migrate away from RunLoop::QuitCurrent*() methods. Use member',
417 'methods of a specific RunLoop instance instead.',
423 'base::ScopedMockTimeMessageLoopTaskRunner',
425 'ScopedMockTimeMessageLoopTaskRunner is deprecated. Prefer',
426 'ScopedTaskEnvironment::MainThreadType::MOCK_TIME. There are still a',
427 'few cases that may require a ScopedMockTimeMessageLoopTaskRunner',
428 '(i.e. mocking the main MessageLoopForUI in browser_tests), but check',
429 'with gab@ first if you think you need it)',
437 'Using std::regex adds unnecessary binary size to Chrome. Please use',
438 're2::RE2 instead (crbug.com/755321)',
444 (r'/base::ThreadRestrictions::(ScopedAllowIO|AssertIOAllowed|'
445 r'DisallowWaiting|AssertWaitAllowed|SetWaitAllowed|ScopedAllowWait)'),
447 'Use the new API in base/threading/thread_restrictions.h.',
455 'Please consider using base::Bind{Once,Repeating} instead',
456 'of base::Bind. (crbug.com/714018)',
462 r'/\bbase::Callback<',
464 'Please consider using base::{Once,Repeating}Callback instead',
465 'of base::Callback. (crbug.com/714018)',
471 r'/\bbase::Closure\b',
473 'Please consider using base::{Once,Repeating}Closure instead',
474 'of base::Closure. (crbug.com/714018)',
482 'RunMessageLoop is deprecated, use RunLoop instead.',
490 'RunThisRunLoop is deprecated, use RunLoop directly instead.',
496 r'RunAllPendingInMessageLoop()',
498 "Prefer RunLoop over RunAllPendingInMessageLoop, please contact gab@",
499 "if you're convinced you need this.",
505 r'RunAllPendingInMessageLoop(BrowserThread',
507 'RunAllPendingInMessageLoop is deprecated. Use RunLoop for',
508 'BrowserThread::UI, TestBrowserThreadBundle::RunIOThreadUntilIdle',
509 'for BrowserThread::IO, and prefer RunLoop::QuitClosure to observe',
510 'async events instead of flushing threads.',
516 r'MessageLoopRunner',
518 'MessageLoopRunner is deprecated, use RunLoop instead.',
524 r'GetDeferredQuitTaskForRunLoop',
526 "GetDeferredQuitTaskForRunLoop shouldn't be needed, please contact",
527 "gab@ if you found a use case where this is the only solution.",
533 'sqlite3_initialize',
535 'Instead of sqlite3_initialize, depend on //sql, ',
536 '#include "sql/initialize.h" and use sql::EnsureSqliteInitialized().',
540 r'^sql/initialization\.(cc|h)$',
541 r'^third_party/sqlite/.*\.(c|cc|h)$',
547 'net::URLFetcher should no longer be used in content embedders. ',
548 'Instead, use network::SimpleURLLoader instead, which supports ',
549 'an out-of-process network stack. ',
550 'net::URLFetcher may still be used in binaries that do not embed',
555 r'^ios[\\\/].*\.(cc|h)$',
556 r'.*[\\\/]ios[\\\/].*\.(cc|h)$',
558 r'^net[\\\/].*\.(cc|h)$',
559 r'.*[\\\/]tools[\\\/].*\.(cc|h)$',
565 "arraysize is deprecated, please use base::size(array) instead ",
566 "(https://crbug.com/837308). ",
572 r'std::random_shuffle',
574 'std::random_shuffle is deprecated in C++14, and removed in C++17. Use',
575 'base::RandomShuffle instead.'
583 _IPC_ENUM_TRAITS_DEPRECATED = (
584 'You are using IPC_ENUM_TRAITS() in your code. It has been deprecated.\n'
585 'See http://www.chromium.org/Home/chromium-security/education/'
586 'security-tips-for-ipc')
589 'Some files included in this CL have file names that are too long (> 200'
590 ' characters). If committed, these files will cause issues on Windows. See'
591 ' https://crbug.com/612667 for more details.'
594 _JAVA_MULTIPLE_DEFINITION_EXCLUDED_PATHS = [
595 r".*[\\\/]BuildHooksAndroidImpl\.java",
596 r".*[\\\/]LicenseContentProvider\.java",
597 r".*[\\\/]PlatformServiceBridgeImpl.java",
600 # These paths contain test data and other known invalid JSON files.
601 _KNOWN_INVALID_JSON_FILE_PATTERNS = [
602 r'test[\\\/]data[\\\/]',
603 r'^components[\\\/]policy[\\\/]resources[\\\/]policy_templates\.json$',
604 r'^third_party[\\\/]protobuf[\\\/]',
605 r'^third_party[\\\/]WebKit[\\\/]LayoutTests[\\\/]external[\\\/]wpt[\\\/]',
606 r'^third_party[\\\/]blink[\\\/]renderer[\\\/]devtools[\\\/]protocol\.json$',
611 # Please keep sorted.
616 'OS_CAT', # For testing.
635 _ANDROID_SPECIFIC_PYDEPS_FILES = [
636 'build/android/resource_sizes.pydeps',
637 'build/android/test_runner.pydeps',
638 'build/android/test_wrapper/logdog_wrapper.pydeps',
639 'build/secondary/third_party/android_platform/'
640 'development/scripts/stack.pydeps',
641 'net/tools/testserver/testserver.pydeps',
645 _GENERIC_PYDEPS_FILES = [
646 'chrome/test/chromedriver/test/run_py_tests.pydeps',
647 'tools/binary_size/supersize.pydeps',
651 _ALL_PYDEPS_FILES = _ANDROID_SPECIFIC_PYDEPS_FILES + _GENERIC_PYDEPS_FILES
654 # Bypass the AUTHORS check for these accounts.
656 '%s-chromium-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com' % s
657 for s in ('afdo', 'angle', 'catapult', 'chromite', 'depot-tools',
658 'fuchsia-sdk', 'nacl', 'pdfium', 'perfetto', 'skia',
659 'src-internal', 'webrtc')
660 ) | set('%s@appspot.gserviceaccount.com' % s for s in ('findit-for-me',)
661 ) | set('%s@chops-service-accounts.iam.gserviceaccount.com' % s
662 for s in ('v8-ci-autoroll-builder',))
665 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
666 """Attempts to prevent use of functions intended only for testing in
667 non-testing code. For now this is just a best-effort implementation
668 that ignores header files and may have some false positives. A
669 better implementation would probably need a proper C++ parser.
671 # We only scan .cc files and the like, as the declaration of
672 # for-testing functions in header files are hard to distinguish from
673 # calls to such functions without a proper C++ parser.
674 file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
676 base_function_pattern = r'[ :]test::[^\s]+|ForTest(s|ing)?|for_test(s|ing)?'
677 inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
678 comment_pattern = input_api.re.compile(r'//.*(%s)' % base_function_pattern)
679 exclusion_pattern = input_api.re.compile(
680 r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
681 base_function_pattern, base_function_pattern))
683 def FilterFile(affected_file):
684 black_list = (_EXCLUDED_PATHS +
685 _TEST_CODE_EXCLUDED_PATHS +
686 input_api.DEFAULT_BLACK_LIST)
687 return input_api.FilterSourceFile(
689 white_list=(file_inclusion_pattern, ),
690 black_list=black_list)
693 for f in input_api.AffectedSourceFiles(FilterFile):
694 local_path = f.LocalPath()
695 for line_number, line in f.ChangedContents():
696 if (inclusion_pattern.search(line) and
697 not comment_pattern.search(line) and
698 not exclusion_pattern.search(line)):
700 '%s:%d\n %s' % (local_path, line_number, line.strip()))
703 return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
708 def _CheckNoProductionCodeUsingTestOnlyFunctionsJava(input_api, output_api):
709 """This is a simplified version of
710 _CheckNoProductionCodeUsingTestOnlyFunctions for Java files.
712 javadoc_start_re = input_api.re.compile(r'^\s*/\*\*')
713 javadoc_end_re = input_api.re.compile(r'^\s*\*/')
714 name_pattern = r'ForTest(s|ing)?'
715 # Describes an occurrence of "ForTest*" inside a // comment.
716 comment_re = input_api.re.compile(r'//.*%s' % name_pattern)
718 inclusion_re = input_api.re.compile(r'(%s)\s*\(' % name_pattern)
719 # Ignore definitions. (Comments are ignored separately.)
720 exclusion_re = input_api.re.compile(r'(%s)[^;]+\{' % name_pattern)
723 sources = lambda x: input_api.FilterSourceFile(
725 black_list=(('(?i).*test', r'.*\/junit\/')
726 + input_api.DEFAULT_BLACK_LIST),
727 white_list=(r'.*\.java$',)
729 for f in input_api.AffectedFiles(include_deletes=False, file_filter=sources):
730 local_path = f.LocalPath()
731 is_inside_javadoc = False
732 for line_number, line in f.ChangedContents():
733 if is_inside_javadoc and javadoc_end_re.search(line):
734 is_inside_javadoc = False
735 if not is_inside_javadoc and javadoc_start_re.search(line):
736 is_inside_javadoc = True
737 if is_inside_javadoc:
739 if (inclusion_re.search(line) and
740 not comment_re.search(line) and
741 not exclusion_re.search(line)):
743 '%s:%d\n %s' % (local_path, line_number, line.strip()))
746 return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
751 def _CheckNoIOStreamInHeaders(input_api, output_api):
752 """Checks to make sure no .h files include <iostream>."""
754 pattern = input_api.re.compile(r'^#include\s*<iostream>',
755 input_api.re.MULTILINE)
756 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
757 if not f.LocalPath().endswith('.h'):
759 contents = input_api.ReadFile(f)
760 if pattern.search(contents):
764 return [output_api.PresubmitError(
765 'Do not #include <iostream> in header files, since it inserts static '
766 'initialization into every file including the header. Instead, '
767 '#include <ostream>. See http://crbug.com/94794',
772 def _CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
773 """Checks to make sure no source files use UNIT_TEST."""
775 for f in input_api.AffectedFiles():
776 if (not f.LocalPath().endswith(('.cc', '.mm'))):
779 for line_num, line in f.ChangedContents():
780 if 'UNIT_TEST ' in line or line.endswith('UNIT_TEST'):
781 problems.append(' %s:%d' % (f.LocalPath(), line_num))
785 return [output_api.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
786 '\n'.join(problems))]
789 def _CheckDCHECK_IS_ONHasBraces(input_api, output_api):
790 """Checks to make sure DCHECK_IS_ON() does not skip the parentheses."""
792 pattern = input_api.re.compile(r'DCHECK_IS_ON(?!\(\))',
793 input_api.re.MULTILINE)
794 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
795 if (not f.LocalPath().endswith(('.cc', '.mm', '.h'))):
797 for lnum, line in f.ChangedContents():
798 if input_api.re.search(pattern, line):
799 errors.append(output_api.PresubmitError(
800 ('%s:%d: Use of DCHECK_IS_ON() must be written as "#if ' +
801 'DCHECK_IS_ON()", not forgetting the parentheses.')
802 % (f.LocalPath(), lnum)))
806 def _FindHistogramNameInLine(histogram_name, line):
807 """Tries to find a histogram name or prefix in a line."""
808 if not "affected-histogram" in line:
809 return histogram_name in line
810 # A histogram_suffixes tag type has an affected-histogram name as a prefix of
811 # the histogram_name.
814 histogram_prefix = line.split('\"')[1]
815 return histogram_prefix in histogram_name
818 def _CheckUmaHistogramChanges(input_api, output_api):
819 """Check that UMA histogram names in touched lines can still be found in other
820 lines of the patch or in histograms.xml. Note that this check would not catch
821 the reverse: changes in histograms.xml not matched in the code itself."""
822 touched_histograms = []
823 histograms_xml_modifications = []
824 call_pattern_c = r'\bUMA_HISTOGRAM.*\('
825 call_pattern_java = r'\bRecordHistogram\.record[a-zA-Z]+Histogram\('
826 name_pattern = r'"(.*?)"'
827 single_line_c_re = input_api.re.compile(call_pattern_c + name_pattern)
828 single_line_java_re = input_api.re.compile(call_pattern_java + name_pattern)
829 split_line_c_prefix_re = input_api.re.compile(call_pattern_c)
830 split_line_java_prefix_re = input_api.re.compile(call_pattern_java)
831 split_line_suffix_re = input_api.re.compile(r'^\s*' + name_pattern)
832 last_line_matched_prefix = False
833 for f in input_api.AffectedFiles():
834 # If histograms.xml itself is modified, keep the modified lines for later.
835 if f.LocalPath().endswith(('histograms.xml')):
836 histograms_xml_modifications = f.ChangedContents()
838 if f.LocalPath().endswith(('cc', 'mm', 'cpp')):
839 single_line_re = single_line_c_re
840 split_line_prefix_re = split_line_c_prefix_re
841 elif f.LocalPath().endswith(('java')):
842 single_line_re = single_line_java_re
843 split_line_prefix_re = split_line_java_prefix_re
846 for line_num, line in f.ChangedContents():
847 if last_line_matched_prefix:
848 suffix_found = split_line_suffix_re.search(line)
850 touched_histograms.append([suffix_found.group(1), f, line_num])
851 last_line_matched_prefix = False
853 found = single_line_re.search(line)
855 touched_histograms.append([found.group(1), f, line_num])
857 last_line_matched_prefix = split_line_prefix_re.search(line)
859 # Search for the touched histogram names in the local modifications to
860 # histograms.xml, and, if not found, on the base histograms.xml file.
861 unmatched_histograms = []
862 for histogram_info in touched_histograms:
863 histogram_name_found = False
864 for line_num, line in histograms_xml_modifications:
865 histogram_name_found = _FindHistogramNameInLine(histogram_info[0], line)
866 if histogram_name_found:
868 if not histogram_name_found:
869 unmatched_histograms.append(histogram_info)
871 histograms_xml_path = 'tools/metrics/histograms/histograms.xml'
873 if unmatched_histograms:
874 with open(histograms_xml_path) as histograms_xml:
875 for histogram_name, f, line_num in unmatched_histograms:
876 histograms_xml.seek(0)
877 histogram_name_found = False
878 for line in histograms_xml:
879 histogram_name_found = _FindHistogramNameInLine(histogram_name, line)
880 if histogram_name_found:
882 if not histogram_name_found:
883 problems.append(' [%s:%d] %s' %
884 (f.LocalPath(), line_num, histogram_name))
888 return [output_api.PresubmitPromptWarning('Some UMA_HISTOGRAM lines have '
889 'been modified and the associated histogram name has no match in either '
890 '%s or the modifications of it:' % (histograms_xml_path), problems)]
893 def _CheckFlakyTestUsage(input_api, output_api):
894 """Check that FlakyTest annotation is our own instead of the android one"""
895 pattern = input_api.re.compile(r'import android.test.FlakyTest;')
897 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
898 if f.LocalPath().endswith('Test.java'):
899 if pattern.search(input_api.ReadFile(f)):
902 return [output_api.PresubmitError(
903 'Use org.chromium.base.test.util.FlakyTest instead of '
904 'android.test.FlakyTest',
909 def _CheckNoNewWStrings(input_api, output_api):
910 """Checks to make sure we don't introduce use of wstrings."""
912 for f in input_api.AffectedFiles():
913 if (not f.LocalPath().endswith(('.cc', '.h')) or
914 f.LocalPath().endswith(('test.cc', '_win.cc', '_win.h')) or
915 '/win/' in f.LocalPath() or
916 'chrome_elf' in f.LocalPath() or
917 'install_static' in f.LocalPath()):
921 for line_num, line in f.ChangedContents():
922 if 'presubmit: allow wstring' in line:
924 elif not allowWString and 'wstring' in line:
925 problems.append(' %s:%d' % (f.LocalPath(), line_num))
932 return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
933 ' If you are calling a cross-platform API that accepts a wstring, '
935 '\n'.join(problems))]
938 def _CheckNoDEPSGIT(input_api, output_api):
939 """Make sure .DEPS.git is never modified manually."""
940 if any(f.LocalPath().endswith('.DEPS.git') for f in
941 input_api.AffectedFiles()):
942 return [output_api.PresubmitError(
943 'Never commit changes to .DEPS.git. This file is maintained by an\n'
944 'automated system based on what\'s in DEPS and your changes will be\n'
946 'See https://sites.google.com/a/chromium.org/dev/developers/how-tos/'
947 'get-the-code#Rolling_DEPS\n'
948 'for more information')]
952 def _CheckValidHostsInDEPS(input_api, output_api):
953 """Checks that DEPS file deps are from allowed_hosts."""
954 # Run only if DEPS file has been modified to annoy fewer bystanders.
955 if all(f.LocalPath() != 'DEPS' for f in input_api.AffectedFiles()):
957 # Outsource work to gclient verify
959 input_api.subprocess.check_output(['gclient', 'verify'])
961 except input_api.subprocess.CalledProcessError, error:
962 return [output_api.PresubmitError(
963 'DEPS file must have only git dependencies.',
964 long_text=error.output)]
967 def _CheckNoBannedFunctions(input_api, output_api):
968 """Make sure that banned functions are not used."""
972 def IsBlacklisted(affected_file, blacklist):
973 local_path = affected_file.LocalPath()
974 for item in blacklist:
975 if input_api.re.match(item, local_path):
979 def IsIosObcjFile(affected_file):
980 local_path = affected_file.LocalPath()
981 if input_api.os_path.splitext(local_path)[-1] not in ('.mm', '.m', '.h'):
983 basename = input_api.os_path.basename(local_path)
984 if 'ios' in basename.split('_'):
986 for sep in (input_api.os_path.sep, input_api.os_path.altsep):
987 if sep and 'ios' in local_path.split(sep):
991 def CheckForMatch(affected_file, line_num, line, func_name, message, error):
993 if func_name[0:1] == '/':
994 regex = func_name[1:]
995 if input_api.re.search(regex, line):
997 elif func_name in line:
1003 problems.append(' %s:%d:' % (affected_file.LocalPath(), line_num))
1004 for message_line in message:
1005 problems.append(' %s' % message_line)
1007 file_filter = lambda f: f.LocalPath().endswith(('.java'))
1008 for f in input_api.AffectedFiles(file_filter=file_filter):
1009 for line_num, line in f.ChangedContents():
1010 for func_name, message, error in _BANNED_JAVA_FUNCTIONS:
1011 CheckForMatch(f, line_num, line, func_name, message, error)
1013 file_filter = lambda f: f.LocalPath().endswith(('.mm', '.m', '.h'))
1014 for f in input_api.AffectedFiles(file_filter=file_filter):
1015 for line_num, line in f.ChangedContents():
1016 for func_name, message, error in _BANNED_OBJC_FUNCTIONS:
1017 CheckForMatch(f, line_num, line, func_name, message, error)
1019 for f in input_api.AffectedFiles(file_filter=IsIosObcjFile):
1020 for line_num, line in f.ChangedContents():
1021 for func_name, message, error in _BANNED_IOS_OBJC_FUNCTIONS:
1022 CheckForMatch(f, line_num, line, func_name, message, error)
1024 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm', '.h'))
1025 for f in input_api.AffectedFiles(file_filter=file_filter):
1026 for line_num, line in f.ChangedContents():
1027 for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
1028 if IsBlacklisted(f, excluded_paths):
1030 CheckForMatch(f, line_num, line, func_name, message, error)
1034 result.append(output_api.PresubmitPromptWarning(
1035 'Banned functions were used.\n' + '\n'.join(warnings)))
1037 result.append(output_api.PresubmitError(
1038 'Banned functions were used.\n' + '\n'.join(errors)))
1042 def _CheckNoPragmaOnce(input_api, output_api):
1043 """Make sure that banned functions are not used."""
1045 pattern = input_api.re.compile(r'^#pragma\s+once',
1046 input_api.re.MULTILINE)
1047 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
1048 if not f.LocalPath().endswith('.h'):
1050 contents = input_api.ReadFile(f)
1051 if pattern.search(contents):
1055 return [output_api.PresubmitError(
1056 'Do not use #pragma once in header files.\n'
1057 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
1062 def _CheckNoTrinaryTrueFalse(input_api, output_api):
1063 """Checks to make sure we don't introduce use of foo ? true : false."""
1065 pattern = input_api.re.compile(r'\?\s*(true|false)\s*:\s*(true|false)')
1066 for f in input_api.AffectedFiles():
1067 if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
1070 for line_num, line in f.ChangedContents():
1071 if pattern.match(line):
1072 problems.append(' %s:%d' % (f.LocalPath(), line_num))
1076 return [output_api.PresubmitPromptWarning(
1077 'Please consider avoiding the "? true : false" pattern if possible.\n' +
1078 '\n'.join(problems))]
1081 def _CheckUnwantedDependencies(input_api, output_api):
1082 """Runs checkdeps on #include and import statements added in this
1083 change. Breaking - rules is an error, breaking ! rules is a
1087 # We need to wait until we have an input_api object and use this
1088 # roundabout construct to import checkdeps because this file is
1089 # eval-ed and thus doesn't have __file__.
1090 original_sys_path = sys.path
1092 sys.path = sys.path + [input_api.os_path.join(
1093 input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
1095 from cpp_checker import CppChecker
1096 from java_checker import JavaChecker
1097 from proto_checker import ProtoChecker
1098 from rules import Rule
1100 # Restore sys.path to what it was before.
1101 sys.path = original_sys_path
1105 added_java_imports = []
1106 for f in input_api.AffectedFiles():
1107 if CppChecker.IsCppFile(f.LocalPath()):
1108 changed_lines = [line for _, line in f.ChangedContents()]
1109 added_includes.append([f.AbsoluteLocalPath(), changed_lines])
1110 elif ProtoChecker.IsProtoFile(f.LocalPath()):
1111 changed_lines = [line for _, line in f.ChangedContents()]
1112 added_imports.append([f.AbsoluteLocalPath(), changed_lines])
1113 elif JavaChecker.IsJavaFile(f.LocalPath()):
1114 changed_lines = [line for _, line in f.ChangedContents()]
1115 added_java_imports.append([f.AbsoluteLocalPath(), changed_lines])
1117 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
1119 error_descriptions = []
1120 warning_descriptions = []
1121 error_subjects = set()
1122 warning_subjects = set()
1123 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
1125 path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
1126 description_with_path = '%s\n %s' % (path, rule_description)
1127 if rule_type == Rule.DISALLOW:
1128 error_descriptions.append(description_with_path)
1129 error_subjects.add("#includes")
1131 warning_descriptions.append(description_with_path)
1132 warning_subjects.add("#includes")
1134 for path, rule_type, rule_description in deps_checker.CheckAddedProtoImports(
1136 path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
1137 description_with_path = '%s\n %s' % (path, rule_description)
1138 if rule_type == Rule.DISALLOW:
1139 error_descriptions.append(description_with_path)
1140 error_subjects.add("imports")
1142 warning_descriptions.append(description_with_path)
1143 warning_subjects.add("imports")
1145 for path, rule_type, rule_description in deps_checker.CheckAddedJavaImports(
1146 added_java_imports, _JAVA_MULTIPLE_DEFINITION_EXCLUDED_PATHS):
1147 path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
1148 description_with_path = '%s\n %s' % (path, rule_description)
1149 if rule_type == Rule.DISALLOW:
1150 error_descriptions.append(description_with_path)
1151 error_subjects.add("imports")
1153 warning_descriptions.append(description_with_path)
1154 warning_subjects.add("imports")
1157 if error_descriptions:
1158 results.append(output_api.PresubmitError(
1159 'You added one or more %s that violate checkdeps rules.'
1160 % " and ".join(error_subjects),
1161 error_descriptions))
1162 if warning_descriptions:
1163 results.append(output_api.PresubmitPromptOrNotify(
1164 'You added one or more %s of files that are temporarily\n'
1165 'allowed but being removed. Can you avoid introducing the\n'
1166 '%s? See relevant DEPS file(s) for details and contacts.' %
1167 (" and ".join(warning_subjects), "/".join(warning_subjects)),
1168 warning_descriptions))
1172 def _CheckFilePermissions(input_api, output_api):
1173 """Check that all files have their permissions properly set."""
1174 if input_api.platform == 'win32':
1176 checkperms_tool = input_api.os_path.join(
1177 input_api.PresubmitLocalPath(),
1178 'tools', 'checkperms', 'checkperms.py')
1179 args = [input_api.python_executable, checkperms_tool,
1180 '--root', input_api.change.RepositoryRoot()]
1181 with input_api.CreateTemporaryFile() as file_list:
1182 for f in input_api.AffectedFiles():
1183 # checkperms.py file/directory arguments must be relative to the
1185 file_list.write(f.LocalPath() + '\n')
1187 args += ['--file-list', file_list.name]
1189 input_api.subprocess.check_output(args)
1191 except input_api.subprocess.CalledProcessError as error:
1192 return [output_api.PresubmitError(
1193 'checkperms.py failed:',
1194 long_text=error.output)]
1197 def _CheckTeamTags(input_api, output_api):
1198 """Checks that OWNERS files have consistent TEAM and COMPONENT tags."""
1199 checkteamtags_tool = input_api.os_path.join(
1200 input_api.PresubmitLocalPath(),
1201 'tools', 'checkteamtags', 'checkteamtags.py')
1202 args = [input_api.python_executable, checkteamtags_tool,
1203 '--root', input_api.change.RepositoryRoot()]
1204 files = [f.LocalPath() for f in input_api.AffectedFiles(include_deletes=False)
1205 if input_api.os_path.basename(f.AbsoluteLocalPath()).upper() ==
1209 input_api.subprocess.check_output(args + files)
1211 except input_api.subprocess.CalledProcessError as error:
1212 return [output_api.PresubmitError(
1213 'checkteamtags.py failed:',
1214 long_text=error.output)]
1217 def _CheckNoAuraWindowPropertyHInHeaders(input_api, output_api):
1218 """Makes sure we don't include ui/aura/window_property.h
1221 pattern = input_api.re.compile(r'^#include\s*"ui/aura/window_property.h"')
1223 for f in input_api.AffectedFiles():
1224 if not f.LocalPath().endswith('.h'):
1226 for line_num, line in f.ChangedContents():
1227 if pattern.match(line):
1228 errors.append(' %s:%d' % (f.LocalPath(), line_num))
1232 results.append(output_api.PresubmitError(
1233 'Header files should not include ui/aura/window_property.h', errors))
1237 def _CheckForVersionControlConflictsInFile(input_api, f):
1238 pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
1240 for line_num, line in f.ChangedContents():
1241 if f.LocalPath().endswith('.md'):
1242 # First-level headers in markdown look a lot like version control
1243 # conflict markers. http://daringfireball.net/projects/markdown/basics
1245 if pattern.match(line):
1246 errors.append(' %s:%d %s' % (f.LocalPath(), line_num, line))
1250 def _CheckForVersionControlConflicts(input_api, output_api):
1251 """Usually this is not intentional and will cause a compile failure."""
1253 for f in input_api.AffectedFiles():
1254 errors.extend(_CheckForVersionControlConflictsInFile(input_api, f))
1258 results.append(output_api.PresubmitError(
1259 'Version control conflict markers found, please resolve.', errors))
1262 def _CheckGoogleSupportAnswerUrl(input_api, output_api):
1263 pattern = input_api.re.compile('support\.google\.com\/chrome.*/answer')
1265 for f in input_api.AffectedFiles():
1266 for line_num, line in f.ChangedContents():
1267 if pattern.search(line):
1268 errors.append(' %s:%d %s' % (f.LocalPath(), line_num, line))
1272 results.append(output_api.PresubmitPromptWarning(
1273 'Found Google support URL addressed by answer number. Please replace '
1274 'with a p= identifier instead. See crbug.com/679462\n', errors))
1278 def _CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api):
1279 def FilterFile(affected_file):
1280 """Filter function for use with input_api.AffectedSourceFiles,
1281 below. This filters out everything except non-test files from
1282 top-level directories that generally speaking should not hard-code
1283 service URLs (e.g. src/android_webview/, src/content/ and others).
1285 return input_api.FilterSourceFile(
1287 white_list=(r'^(android_webview|base|content|net)[\\\/].*', ),
1288 black_list=(_EXCLUDED_PATHS +
1289 _TEST_CODE_EXCLUDED_PATHS +
1290 input_api.DEFAULT_BLACK_LIST))
1292 base_pattern = ('"[^"]*(google|googleapis|googlezip|googledrive|appspot)'
1293 '\.(com|net)[^"]*"')
1294 comment_pattern = input_api.re.compile('//.*%s' % base_pattern)
1295 pattern = input_api.re.compile(base_pattern)
1296 problems = [] # items are (filename, line_number, line)
1297 for f in input_api.AffectedSourceFiles(FilterFile):
1298 for line_num, line in f.ChangedContents():
1299 if not comment_pattern.search(line) and pattern.search(line):
1300 problems.append((f.LocalPath(), line_num, line))
1303 return [output_api.PresubmitPromptOrNotify(
1304 'Most layers below src/chrome/ should not hardcode service URLs.\n'
1305 'Are you sure this is correct?',
1307 problem[0], problem[1], problem[2]) for problem in problems])]
1312 def _CheckNoAbbreviationInPngFileName(input_api, output_api):
1313 """Makes sure there are no abbreviations in the name of PNG files.
1314 The native_client_sdk directory is excluded because it has auto-generated PNG
1315 files for documentation.
1318 white_list = (r'.*_[a-z]_.*\.png$|.*_[a-z]\.png$',)
1319 black_list = (r'^native_client_sdk[\\\/]',)
1320 file_filter = lambda f: input_api.FilterSourceFile(
1321 f, white_list=white_list, black_list=black_list)
1322 for f in input_api.AffectedFiles(include_deletes=False,
1323 file_filter=file_filter):
1324 errors.append(' %s' % f.LocalPath())
1328 results.append(output_api.PresubmitError(
1329 'The name of PNG files should not have abbreviations. \n'
1330 'Use _hover.png, _center.png, instead of _h.png, _c.png.\n'
1331 'Contact oshima@chromium.org if you have questions.', errors))
1335 def _ExtractAddRulesFromParsedDeps(parsed_deps):
1336 """Extract the rules that add dependencies from a parsed DEPS file.
1339 parsed_deps: the locals dictionary from evaluating the DEPS file."""
1342 rule[1:] for rule in parsed_deps.get('include_rules', [])
1343 if rule.startswith('+') or rule.startswith('!')
1345 for _, rules in parsed_deps.get('specific_include_rules',
1348 rule[1:] for rule in rules
1349 if rule.startswith('+') or rule.startswith('!')
1354 def _ParseDeps(contents):
1355 """Simple helper for parsing DEPS files."""
1356 # Stubs for handling special syntax in the root DEPS file.
1359 def __init__(self, local_scope):
1360 self._local_scope = local_scope
1362 def Lookup(self, var_name):
1363 """Implements the Var syntax."""
1365 return self._local_scope['vars'][var_name]
1367 raise Exception('Var is not defined: %s' % var_name)
1371 'Var': _VarImpl(local_scope).Lookup,
1373 exec contents in global_scope, local_scope
1377 def _CalculateAddedDeps(os_path, old_contents, new_contents):
1378 """Helper method for _CheckAddedDepsHaveTargetApprovals. Returns
1379 a set of DEPS entries that we should look up.
1381 For a directory (rather than a specific filename) we fake a path to
1382 a specific filename by adding /DEPS. This is chosen as a file that
1383 will seldom or never be subject to per-file include_rules.
1385 # We ignore deps entries on auto-generated directories.
1386 AUTO_GENERATED_DIRS = ['grit', 'jni']
1388 old_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(old_contents))
1389 new_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(new_contents))
1391 added_deps = new_deps.difference(old_deps)
1394 for added_dep in added_deps:
1395 if added_dep.split('/')[0] in AUTO_GENERATED_DIRS:
1397 # Assume that a rule that ends in .h is a rule for a specific file.
1398 if added_dep.endswith('.h'):
1399 results.add(added_dep)
1401 results.add(os_path.join(added_dep, 'DEPS'))
1405 def _CheckAddedDepsHaveTargetApprovals(input_api, output_api):
1406 """When a dependency prefixed with + is added to a DEPS file, we
1407 want to make sure that the change is reviewed by an OWNER of the
1408 target file or directory, to avoid layering violations from being
1409 introduced. This check verifies that this happens.
1411 virtual_depended_on_files = set()
1413 file_filter = lambda f: not input_api.re.match(
1414 r"^third_party[\\\/](WebKit|blink)[\\\/].*", f.LocalPath())
1415 for f in input_api.AffectedFiles(include_deletes=False,
1416 file_filter=file_filter):
1417 filename = input_api.os_path.basename(f.LocalPath())
1418 if filename == 'DEPS':
1419 virtual_depended_on_files.update(_CalculateAddedDeps(
1421 '\n'.join(f.OldContents()),
1422 '\n'.join(f.NewContents())))
1424 if not virtual_depended_on_files:
1427 if input_api.is_committing:
1429 return [output_api.PresubmitNotifyResult(
1430 '--tbr was specified, skipping OWNERS check for DEPS additions')]
1431 if input_api.dry_run:
1432 return [output_api.PresubmitNotifyResult(
1433 'This is a dry run, skipping OWNERS check for DEPS additions')]
1434 if not input_api.change.issue:
1435 return [output_api.PresubmitError(
1436 "DEPS approval by OWNERS check failed: this change has "
1437 "no change number, so we can't check it for approvals.")]
1438 output = output_api.PresubmitError
1440 output = output_api.PresubmitNotifyResult
1442 owners_db = input_api.owners_db
1443 owner_email, reviewers = (
1444 input_api.canned_checks.GetCodereviewOwnerAndReviewers(
1446 owners_db.email_regexp,
1447 approval_needed=input_api.is_committing))
1449 owner_email = owner_email or input_api.change.author_email
1451 reviewers_plus_owner = set(reviewers)
1453 reviewers_plus_owner.add(owner_email)
1454 missing_files = owners_db.files_not_covered_by(virtual_depended_on_files,
1455 reviewers_plus_owner)
1457 # We strip the /DEPS part that was added by
1458 # _FilesToCheckForIncomingDeps to fake a path to a file in a
1460 def StripDeps(path):
1461 start_deps = path.rfind('/DEPS')
1462 if start_deps != -1:
1463 return path[:start_deps]
1466 unapproved_dependencies = ["'+%s'," % StripDeps(path)
1467 for path in missing_files]
1469 if unapproved_dependencies:
1471 output('You need LGTM from owners of depends-on paths in DEPS that were '
1472 'modified in this CL:\n %s' %
1473 '\n '.join(sorted(unapproved_dependencies)))]
1474 suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
1475 output_list.append(output(
1476 'Suggested missing target path OWNERS:\n %s' %
1477 '\n '.join(suggested_owners or [])))
1483 def _CheckSpamLogging(input_api, output_api):
1484 file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
1485 black_list = (_EXCLUDED_PATHS +
1486 _TEST_CODE_EXCLUDED_PATHS +
1487 input_api.DEFAULT_BLACK_LIST +
1488 (r"^base[\\\/]logging\.h$",
1489 r"^base[\\\/]logging\.cc$",
1490 r"^chrome[\\\/]app[\\\/]chrome_main_delegate\.cc$",
1491 r"^chrome[\\\/]browser[\\\/]chrome_browser_main\.cc$",
1492 r"^chrome[\\\/]browser[\\\/]ui[\\\/]startup[\\\/]"
1493 r"startup_browser_creator\.cc$",
1494 r"^chrome[\\\/]installer[\\\/]setup[\\\/].*",
1495 r"chrome[\\\/]browser[\\\/]diagnostics[\\\/]" +
1496 r"diagnostics_writer\.cc$",
1497 r"^chrome_elf[\\\/]dll_hash[\\\/]dll_hash_main\.cc$",
1498 r"^chromecast[\\\/]",
1499 r"^cloud_print[\\\/]",
1500 r"^components[\\\/]browser_watcher[\\\/]"
1501 r"dump_stability_report_main_win.cc$",
1502 r"^components[\\\/]html_viewer[\\\/]"
1503 r"web_test_delegate_impl\.cc$",
1504 r"^components[\\\/]zucchini[\\\/].*",
1505 # TODO(peter): Remove this exception. https://crbug.com/534537
1506 r"^content[\\\/]browser[\\\/]notifications[\\\/]"
1507 r"notification_event_dispatcher_impl\.cc$",
1508 r"^content[\\\/]common[\\\/]gpu[\\\/]client[\\\/]"
1509 r"gl_helper_benchmark\.cc$",
1510 r"^courgette[\\\/]courgette_minimal_tool\.cc$",
1511 r"^courgette[\\\/]courgette_tool\.cc$",
1512 r"^extensions[\\\/]renderer[\\\/]logging_native_handler\.cc$",
1513 r"^ipc[\\\/]ipc_logging\.cc$",
1514 r"^native_client_sdk[\\\/]",
1515 r"^remoting[\\\/]base[\\\/]logging\.h$",
1516 r"^remoting[\\\/]host[\\\/].*",
1517 r"^sandbox[\\\/]linux[\\\/].*",
1519 r"^ui[\\\/]base[\\\/]resource[\\\/]data_pack.cc$",
1520 r"^ui[\\\/]aura[\\\/]bench[\\\/]bench_main\.cc$",
1521 r"^ui[\\\/]ozone[\\\/]platform[\\\/]cast[\\\/]",
1522 r"^storage[\\\/]browser[\\\/]fileapi[\\\/]" +
1523 r"dump_file_system.cc$",
1524 r"^headless[\\\/]app[\\\/]headless_shell\.cc$"))
1525 source_file_filter = lambda x: input_api.FilterSourceFile(
1526 x, white_list=(file_inclusion_pattern,), black_list=black_list)
1531 for f in input_api.AffectedSourceFiles(source_file_filter):
1532 for _, line in f.ChangedContents():
1533 if input_api.re.search(r"\bD?LOG\s*\(\s*INFO\s*\)", line):
1534 log_info.add(f.LocalPath())
1535 elif input_api.re.search(r"\bD?LOG_IF\s*\(\s*INFO\s*,", line):
1536 log_info.add(f.LocalPath())
1538 if input_api.re.search(r"\bprintf\(", line):
1539 printf.add(f.LocalPath())
1540 elif input_api.re.search(r"\bfprintf\((stdout|stderr)", line):
1541 printf.add(f.LocalPath())
1544 return [output_api.PresubmitError(
1545 'These files spam the console log with LOG(INFO):',
1548 return [output_api.PresubmitError(
1549 'These files spam the console log with printf/fprintf:',
1554 def _CheckForAnonymousVariables(input_api, output_api):
1555 """These types are all expected to hold locks while in scope and
1556 so should never be anonymous (which causes them to be immediately
1558 they_who_must_be_named = [
1562 'SkAutoAlphaRestore',
1563 'SkAutoBitmapShaderInstall',
1564 'SkAutoBlitterChoose',
1565 'SkAutoBounderCommit',
1567 'SkAutoCanvasRestore',
1568 'SkAutoCommentBlock',
1570 'SkAutoDisableDirectionCheck',
1571 'SkAutoDisableOvalCheck',
1578 'SkAutoMaskFreeImage',
1579 'SkAutoMutexAcquire',
1580 'SkAutoPathBoundsUpdate',
1582 'SkAutoRasterClipValidate',
1588 anonymous = r'(%s)\s*[({]' % '|'.join(they_who_must_be_named)
1589 # bad: base::AutoLock(lock.get());
1590 # not bad: base::AutoLock lock(lock.get());
1591 bad_pattern = input_api.re.compile(anonymous)
1592 # good: new base::AutoLock(lock.get())
1593 good_pattern = input_api.re.compile(r'\bnew\s*' + anonymous)
1596 for f in input_api.AffectedFiles():
1597 if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
1599 for linenum, line in f.ChangedContents():
1600 if bad_pattern.search(line) and not good_pattern.search(line):
1601 errors.append('%s:%d' % (f.LocalPath(), linenum))
1604 return [output_api.PresubmitError(
1605 'These lines create anonymous variables that need to be named:',
1610 def _CheckUniquePtr(input_api, output_api):
1611 file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
1612 sources = lambda affected_file: input_api.FilterSourceFile(
1614 black_list=(_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
1615 input_api.DEFAULT_BLACK_LIST),
1616 white_list=(file_inclusion_pattern,))
1618 # Pattern to capture a single "<...>" block of template arguments. It can
1619 # handle linearly nested blocks, such as "<std::vector<std::set<T>>>", but
1620 # cannot handle branching structures, such as "<pair<set<T>,set<U>>". The
1621 # latter would likely require counting that < and > match, which is not
1622 # expressible in regular languages. Should the need arise, one can introduce
1623 # limited counting (matching up to a total number of nesting depth), which
1624 # should cover all practical cases for already a low nesting limit.
1625 template_arg_pattern = (
1626 r'<[^>]*' # Opening block of <.
1627 r'>([^<]*>)?') # Closing block of >.
1628 # Prefix expressing that whatever follows is not already inside a <...>
1630 not_inside_template_arg_pattern = r'(^|[^<,\s]\s*)'
1631 null_construct_pattern = input_api.re.compile(
1632 not_inside_template_arg_pattern
1633 + r'\bstd::unique_ptr'
1634 + template_arg_pattern
1637 # Same as template_arg_pattern, but excluding type arrays, e.g., <T[]>.
1638 template_arg_no_array_pattern = (
1639 r'<[^>]*[^]]' # Opening block of <.
1640 r'>([^(<]*[^]]>)?') # Closing block of >.
1641 # Prefix saying that what follows is the start of an expression.
1642 start_of_expr_pattern = r'(=|\breturn|^)\s*'
1643 # Suffix saying that what follows are call parentheses with a non-empty list
1645 nonempty_arg_list_pattern = r'\(([^)]|$)'
1646 return_construct_pattern = input_api.re.compile(
1647 start_of_expr_pattern
1648 + r'std::unique_ptr'
1649 + template_arg_no_array_pattern
1650 + nonempty_arg_list_pattern)
1652 problems_constructor = []
1653 problems_nullptr = []
1654 for f in input_api.AffectedSourceFiles(sources):
1655 for line_number, line in f.ChangedContents():
1657 # return std::unique_ptr<T>(foo);
1658 # bar = std::unique_ptr<T>(foo);
1660 # return std::unique_ptr<T[]>(foo);
1661 # bar = std::unique_ptr<T[]>(foo);
1662 local_path = f.LocalPath()
1663 if return_construct_pattern.search(line):
1664 problems_constructor.append(
1665 '%s:%d\n %s' % (local_path, line_number, line.strip()))
1667 # std::unique_ptr<T>()
1668 if null_construct_pattern.search(line):
1669 problems_nullptr.append(
1670 '%s:%d\n %s' % (local_path, line_number, line.strip()))
1673 if problems_nullptr:
1674 errors.append(output_api.PresubmitError(
1675 'The following files use std::unique_ptr<T>(). Use nullptr instead.',
1677 if problems_constructor:
1678 errors.append(output_api.PresubmitError(
1679 'The following files use explicit std::unique_ptr constructor.'
1680 'Use std::make_unique<T>() instead.',
1681 problems_constructor))
1685 def _CheckUserActionUpdate(input_api, output_api):
1686 """Checks if any new user action has been added."""
1687 if any('actions.xml' == input_api.os_path.basename(f) for f in
1688 input_api.LocalPaths()):
1689 # If actions.xml is already included in the changelist, the PRESUBMIT
1690 # for actions.xml will do a more complete presubmit check.
1693 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm'))
1694 action_re = r'[^a-zA-Z]UserMetricsAction\("([^"]*)'
1695 current_actions = None
1696 for f in input_api.AffectedFiles(file_filter=file_filter):
1697 for line_num, line in f.ChangedContents():
1698 match = input_api.re.search(action_re, line)
1700 # Loads contents in tools/metrics/actions/actions.xml to memory. It's
1702 if not current_actions:
1703 with open('tools/metrics/actions/actions.xml') as actions_f:
1704 current_actions = actions_f.read()
1705 # Search for the matched user action name in |current_actions|.
1706 for action_name in match.groups():
1707 action = 'name="{0}"'.format(action_name)
1708 if action not in current_actions:
1709 return [output_api.PresubmitPromptWarning(
1710 'File %s line %d: %s is missing in '
1711 'tools/metrics/actions/actions.xml. Please run '
1712 'tools/metrics/actions/extract_actions.py to update.'
1713 % (f.LocalPath(), line_num, action_name))]
1717 def _ImportJSONCommentEater(input_api):
1719 sys.path = sys.path + [input_api.os_path.join(
1720 input_api.PresubmitLocalPath(),
1721 'tools', 'json_comment_eater')]
1722 import json_comment_eater
1723 return json_comment_eater
1726 def _GetJSONParseError(input_api, filename, eat_comments=True):
1728 contents = input_api.ReadFile(filename)
1730 json_comment_eater = _ImportJSONCommentEater(input_api)
1731 contents = json_comment_eater.Nom(contents)
1733 input_api.json.loads(contents)
1734 except ValueError as e:
1739 def _GetIDLParseError(input_api, filename):
1741 contents = input_api.ReadFile(filename)
1742 idl_schema = input_api.os_path.join(
1743 input_api.PresubmitLocalPath(),
1744 'tools', 'json_schema_compiler', 'idl_schema.py')
1745 process = input_api.subprocess.Popen(
1746 [input_api.python_executable, idl_schema],
1747 stdin=input_api.subprocess.PIPE,
1748 stdout=input_api.subprocess.PIPE,
1749 stderr=input_api.subprocess.PIPE,
1750 universal_newlines=True)
1751 (_, error) = process.communicate(input=contents)
1752 return error or None
1753 except ValueError as e:
1757 def _CheckParseErrors(input_api, output_api):
1758 """Check that IDL and JSON files do not contain syntax errors."""
1760 '.idl': _GetIDLParseError,
1761 '.json': _GetJSONParseError,
1763 # Most JSON files are preprocessed and support comments, but these do not.
1764 json_no_comments_patterns = [
1767 # Only run IDL checker on files in these directories.
1768 idl_included_patterns = [
1769 r'^chrome[\\\/]common[\\\/]extensions[\\\/]api[\\\/]',
1770 r'^extensions[\\\/]common[\\\/]api[\\\/]',
1773 def get_action(affected_file):
1774 filename = affected_file.LocalPath()
1775 return actions.get(input_api.os_path.splitext(filename)[1])
1777 def FilterFile(affected_file):
1778 action = get_action(affected_file)
1781 path = affected_file.LocalPath()
1783 if _MatchesFile(input_api, _KNOWN_INVALID_JSON_FILE_PATTERNS, path):
1786 if (action == _GetIDLParseError and
1787 not _MatchesFile(input_api, idl_included_patterns, path)):
1792 for affected_file in input_api.AffectedFiles(
1793 file_filter=FilterFile, include_deletes=False):
1794 action = get_action(affected_file)
1796 if (action == _GetJSONParseError and
1797 _MatchesFile(input_api, json_no_comments_patterns,
1798 affected_file.LocalPath())):
1799 kwargs['eat_comments'] = False
1800 parse_error = action(input_api,
1801 affected_file.AbsoluteLocalPath(),
1804 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
1805 (affected_file.LocalPath(), parse_error)))
1809 def _CheckJavaStyle(input_api, output_api):
1810 """Runs checkstyle on changed java files and returns errors if any exist."""
1812 original_sys_path = sys.path
1814 sys.path = sys.path + [input_api.os_path.join(
1815 input_api.PresubmitLocalPath(), 'tools', 'android', 'checkstyle')]
1818 # Restore sys.path to what it was before.
1819 sys.path = original_sys_path
1821 return checkstyle.RunCheckstyle(
1822 input_api, output_api, 'tools/android/checkstyle/chromium-style-5.0.xml',
1823 black_list=_EXCLUDED_PATHS + input_api.DEFAULT_BLACK_LIST)
1826 def _MatchesFile(input_api, patterns, path):
1827 for pattern in patterns:
1828 if input_api.re.search(pattern, path):
1833 def _GetOwnersFilesToCheckForIpcOwners(input_api):
1834 """Gets a list of OWNERS files to check for correct security owners.
1837 A dictionary mapping an OWNER file to the list of OWNERS rules it must
1838 contain to cover IPC-related files with noparent reviewer rules.
1840 # Whether or not a file affects IPC is (mostly) determined by a simple list
1841 # of filename patterns.
1846 '*_param_traits*.*',
1849 '*_mojom_traits*.*',
1850 '*_struct_traits*.*',
1851 '*_type_converter*.*',
1853 # Android native IPC:
1855 # Blink uses a different file naming convention:
1859 '*TypeConverter*.*',
1862 # These third_party directories do not contain IPCs, but contain files
1863 # matching the above patterns, which trigger false positives.
1865 'third_party/crashpad/*',
1866 'third_party/third_party/blink/renderer/platform/bindings/*',
1867 'third_party/win_build_output/*',
1870 # Dictionary mapping an OWNERS file path to Patterns.
1871 # Patterns is a dictionary mapping glob patterns (suitable for use in per-file
1872 # rules ) to a PatternEntry.
1873 # PatternEntry is a dictionary with two keys:
1874 # - 'files': the files that are matched by this pattern
1875 # - 'rules': the per-file rules needed for this pattern
1876 # For example, if we expect OWNERS file to contain rules for *.mojom and
1877 # *_struct_traits*.*, Patterns might look like this:
1882 # 'per-file *.mojom=set noparent',
1883 # 'per-file *.mojom=file://ipc/SECURITY_OWNERS',
1886 # '*_struct_traits*.*': {
1889 # 'per-file *_struct_traits*.*=set noparent',
1890 # 'per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS',
1896 def AddPatternToCheck(input_file, pattern):
1897 owners_file = input_api.os_path.join(
1898 input_api.os_path.dirname(input_file.LocalPath()), 'OWNERS')
1899 if owners_file not in to_check:
1900 to_check[owners_file] = {}
1901 if pattern not in to_check[owners_file]:
1902 to_check[owners_file][pattern] = {
1905 'per-file %s=set noparent' % pattern,
1906 'per-file %s=file://ipc/SECURITY_OWNERS' % pattern,
1909 to_check[owners_file][pattern]['files'].append(input_file)
1911 # Iterate through the affected files to see what we actually need to check
1912 # for. We should only nag patch authors about per-file rules if a file in that
1913 # directory would match that pattern. If a directory only contains *.mojom
1914 # files and no *_messages*.h files, we should only nag about rules for
1916 for f in input_api.AffectedFiles(include_deletes=False):
1917 # Manifest files don't have a strong naming convention. Instead, scan
1918 # affected files for .json files and see if they look like a manifest.
1919 if (f.LocalPath().endswith('.json') and
1920 not _MatchesFile(input_api, _KNOWN_INVALID_JSON_FILE_PATTERNS,
1922 json_comment_eater = _ImportJSONCommentEater(input_api)
1923 mostly_json_lines = '\n'.join(f.NewContents())
1924 # Comments aren't allowed in strict JSON, so filter them out.
1925 json_lines = json_comment_eater.Nom(mostly_json_lines)
1927 json_content = input_api.json.loads(json_lines)
1929 # There's another PRESUBMIT check that already verifies that JSON files
1930 # are not invalid, so no need to emit another warning here.
1932 if 'interface_provider_specs' in json_content:
1933 AddPatternToCheck(f, input_api.os_path.basename(f.LocalPath()))
1934 for pattern in file_patterns:
1935 if input_api.fnmatch.fnmatch(
1936 input_api.os_path.basename(f.LocalPath()), pattern):
1938 for exclude in exclude_paths:
1939 if input_api.fnmatch.fnmatch(f.LocalPath(), exclude):
1944 AddPatternToCheck(f, pattern)
1950 def _CheckIpcOwners(input_api, output_api):
1951 """Checks that affected files involving IPC have an IPC OWNERS rule."""
1952 to_check = _GetOwnersFilesToCheckForIpcOwners(input_api)
1955 # If there are any OWNERS files to check, there are IPC-related changes in
1956 # this CL. Auto-CC the review list.
1957 output_api.AppendCC('ipc-security-reviews@chromium.org')
1959 # Go through the OWNERS files to check, filtering out rules that are already
1960 # present in that OWNERS file.
1961 for owners_file, patterns in to_check.iteritems():
1963 with file(owners_file) as f:
1964 lines = set(f.read().splitlines())
1965 for entry in patterns.itervalues():
1966 entry['rules'] = [rule for rule in entry['rules'] if rule not in lines
1969 # No OWNERS file, so all the rules are definitely missing.
1972 # All the remaining lines weren't found in OWNERS files, so emit an error.
1974 for owners_file, patterns in to_check.iteritems():
1977 for _, entry in patterns.iteritems():
1978 missing_lines.extend(entry['rules'])
1979 files.extend([' %s' % f.LocalPath() for f in entry['files']])
1982 'Because of the presence of files:\n%s\n\n'
1983 '%s needs the following %d lines added:\n\n%s' %
1984 ('\n'.join(files), owners_file, len(missing_lines),
1985 '\n'.join(missing_lines)))
1989 if input_api.is_committing:
1990 output = output_api.PresubmitError
1992 output = output_api.PresubmitPromptWarning
1993 results.append(output(
1994 'Found OWNERS files that need to be updated for IPC security ' +
1995 'review coverage.\nPlease update the OWNERS files below:',
1996 long_text='\n\n'.join(errors)))
2001 def _CheckUselessForwardDeclarations(input_api, output_api):
2002 """Checks that added or removed lines in non third party affected
2003 header files do not lead to new useless class or struct forward
2007 class_pattern = input_api.re.compile(r'^class\s+(\w+);$',
2008 input_api.re.MULTILINE)
2009 struct_pattern = input_api.re.compile(r'^struct\s+(\w+);$',
2010 input_api.re.MULTILINE)
2011 for f in input_api.AffectedFiles(include_deletes=False):
2012 if (f.LocalPath().startswith('third_party') and
2013 not f.LocalPath().startswith('third_party/blink') and
2014 not f.LocalPath().startswith('third_party\\blink') and
2015 not f.LocalPath().startswith('third_party/WebKit') and
2016 not f.LocalPath().startswith('third_party\\WebKit')):
2019 if not f.LocalPath().endswith('.h'):
2022 contents = input_api.ReadFile(f)
2023 fwd_decls = input_api.re.findall(class_pattern, contents)
2024 fwd_decls.extend(input_api.re.findall(struct_pattern, contents))
2026 useless_fwd_decls = []
2027 for decl in fwd_decls:
2028 count = sum(1 for _ in input_api.re.finditer(
2029 r'\b%s\b' % input_api.re.escape(decl), contents))
2031 useless_fwd_decls.append(decl)
2033 if not useless_fwd_decls:
2036 for line in f.GenerateScmDiff().splitlines():
2037 if (line.startswith('-') and not line.startswith('--') or
2038 line.startswith('+') and not line.startswith('++')):
2039 for decl in useless_fwd_decls:
2040 if input_api.re.search(r'\b%s\b' % decl, line[1:]):
2041 results.append(output_api.PresubmitPromptWarning(
2042 '%s: %s forward declaration is no longer needed' %
2043 (f.LocalPath(), decl)))
2044 useless_fwd_decls.remove(decl)
2049 def _CheckAndroidToastUsage(input_api, output_api):
2050 """Checks that code uses org.chromium.ui.widget.Toast instead of
2051 android.widget.Toast (Chromium Toast doesn't force hardware
2052 acceleration on low-end devices, saving memory).
2054 toast_import_pattern = input_api.re.compile(
2055 r'^import android\.widget\.Toast;$')
2059 sources = lambda affected_file: input_api.FilterSourceFile(
2061 black_list=(_EXCLUDED_PATHS +
2062 _TEST_CODE_EXCLUDED_PATHS +
2063 input_api.DEFAULT_BLACK_LIST +
2064 (r'^chromecast[\\\/].*',
2065 r'^remoting[\\\/].*')),
2066 white_list=(r'.*\.java$',))
2068 for f in input_api.AffectedSourceFiles(sources):
2069 for line_num, line in f.ChangedContents():
2070 if toast_import_pattern.search(line):
2071 errors.append("%s:%d" % (f.LocalPath(), line_num))
2076 results.append(output_api.PresubmitError(
2077 'android.widget.Toast usage is detected. Android toasts use hardware'
2078 ' acceleration, and can be\ncostly on low-end devices. Please use'
2079 ' org.chromium.ui.widget.Toast instead.\n'
2080 'Contact dskiba@chromium.org if you have any questions.',
2086 def _CheckAndroidCrLogUsage(input_api, output_api):
2087 """Checks that new logs using org.chromium.base.Log:
2088 - Are using 'TAG' as variable name for the tags (warn)
2089 - Are using a tag that is shorter than 20 characters (error)
2092 # Do not check format of logs in the given files
2093 cr_log_check_excluded_paths = [
2094 # //chrome/android/webapk cannot depend on //base
2095 r"^chrome[\\\/]android[\\\/]webapk[\\\/].*",
2096 # WebView license viewer code cannot depend on //base; used in stub APK.
2097 r"^android_webview[\\\/]glue[\\\/]java[\\\/]src[\\\/]com[\\\/]android[\\\/]"
2098 r"webview[\\\/]chromium[\\\/]License.*",
2101 cr_log_import_pattern = input_api.re.compile(
2102 r'^import org\.chromium\.base\.Log;$', input_api.re.MULTILINE)
2103 class_in_base_pattern = input_api.re.compile(
2104 r'^package org\.chromium\.base;$', input_api.re.MULTILINE)
2105 has_some_log_import_pattern = input_api.re.compile(
2106 r'^import .*\.Log;$', input_api.re.MULTILINE)
2107 # Extract the tag from lines like `Log.d(TAG, "*");` or `Log.d("TAG", "*");`
2108 log_call_pattern = input_api.re.compile(r'^\s*Log\.\w\((?P<tag>\"?\w+\"?)\,')
2109 log_decl_pattern = input_api.re.compile(
2110 r'^\s*private static final String TAG = "(?P<name>(.*))";',
2111 input_api.re.MULTILINE)
2113 REF_MSG = ('See docs/android_logging.md '
2114 'or contact dgn@chromium.org for more info.')
2115 sources = lambda x: input_api.FilterSourceFile(x, white_list=(r'.*\.java$',),
2116 black_list=cr_log_check_excluded_paths)
2118 tag_decl_errors = []
2119 tag_length_errors = []
2121 tag_with_dot_errors = []
2122 util_log_errors = []
2124 for f in input_api.AffectedSourceFiles(sources):
2125 file_content = input_api.ReadFile(f)
2126 has_modified_logs = False
2129 if (cr_log_import_pattern.search(file_content) or
2130 (class_in_base_pattern.search(file_content) and
2131 not has_some_log_import_pattern.search(file_content))):
2132 # Checks to run for files using cr log
2133 for line_num, line in f.ChangedContents():
2135 # Check if the new line is doing some logging
2136 match = log_call_pattern.search(line)
2138 has_modified_logs = True
2140 # Make sure it uses "TAG"
2141 if not match.group('tag') == 'TAG':
2142 tag_errors.append("%s:%d" % (f.LocalPath(), line_num))
2144 # Report non cr Log function calls in changed lines
2145 for line_num, line in f.ChangedContents():
2146 if log_call_pattern.search(line):
2147 util_log_errors.append("%s:%d" % (f.LocalPath(), line_num))
2150 if has_modified_logs:
2151 # Make sure the tag is using the "cr" prefix and is not too long
2152 match = log_decl_pattern.search(file_content)
2153 tag_name = match.group('name') if match else None
2155 tag_decl_errors.append(f.LocalPath())
2156 elif len(tag_name) > 20:
2157 tag_length_errors.append(f.LocalPath())
2158 elif '.' in tag_name:
2159 tag_with_dot_errors.append(f.LocalPath())
2163 results.append(output_api.PresubmitPromptWarning(
2164 'Please define your tags using the suggested format: .\n'
2165 '"private static final String TAG = "<package tag>".\n'
2166 'They will be prepended with "cr_" automatically.\n' + REF_MSG,
2169 if tag_length_errors:
2170 results.append(output_api.PresubmitError(
2171 'The tag length is restricted by the system to be at most '
2172 '20 characters.\n' + REF_MSG,
2176 results.append(output_api.PresubmitPromptWarning(
2177 'Please use a variable named "TAG" for your log tags.\n' + REF_MSG,
2181 results.append(output_api.PresubmitPromptWarning(
2182 'Please use org.chromium.base.Log for new logs.\n' + REF_MSG,
2185 if tag_with_dot_errors:
2186 results.append(output_api.PresubmitPromptWarning(
2187 'Dot in log tags cause them to be elided in crash reports.\n' + REF_MSG,
2188 tag_with_dot_errors))
2193 def _CheckAndroidTestJUnitFrameworkImport(input_api, output_api):
2194 """Checks that junit.framework.* is no longer used."""
2195 deprecated_junit_framework_pattern = input_api.re.compile(
2196 r'^import junit\.framework\..*;',
2197 input_api.re.MULTILINE)
2198 sources = lambda x: input_api.FilterSourceFile(
2199 x, white_list=(r'.*\.java$',), black_list=None)
2201 for f in input_api.AffectedFiles(sources):
2202 for line_num, line in f.ChangedContents():
2203 if deprecated_junit_framework_pattern.search(line):
2204 errors.append("%s:%d" % (f.LocalPath(), line_num))
2208 results.append(output_api.PresubmitError(
2209 'APIs from junit.framework.* are deprecated, please use JUnit4 framework'
2210 '(org.junit.*) from //third_party/junit. Contact yolandyan@chromium.org'
2211 ' if you have any question.', errors))
2215 def _CheckAndroidTestJUnitInheritance(input_api, output_api):
2216 """Checks that if new Java test classes have inheritance.
2217 Either the new test class is JUnit3 test or it is a JUnit4 test class
2218 with a base class, either case is undesirable.
2220 class_declaration_pattern = input_api.re.compile(r'^public class \w*Test ')
2222 sources = lambda x: input_api.FilterSourceFile(
2223 x, white_list=(r'.*Test\.java$',), black_list=None)
2225 for f in input_api.AffectedFiles(sources):
2226 if not f.OldContents():
2227 class_declaration_start_flag = False
2228 for line_num, line in f.ChangedContents():
2229 if class_declaration_pattern.search(line):
2230 class_declaration_start_flag = True
2231 if class_declaration_start_flag and ' extends ' in line:
2232 errors.append('%s:%d' % (f.LocalPath(), line_num))
2234 class_declaration_start_flag = False
2238 results.append(output_api.PresubmitPromptWarning(
2239 'The newly created files include Test classes that inherits from base'
2240 ' class. Please do not use inheritance in JUnit4 tests or add new'
2241 ' JUnit3 tests. Contact yolandyan@chromium.org if you have any'
2242 ' questions.', errors))
2245 def _CheckAndroidTestAnnotationUsage(input_api, output_api):
2246 """Checks that android.test.suitebuilder.annotation.* is no longer used."""
2247 deprecated_annotation_import_pattern = input_api.re.compile(
2248 r'^import android\.test\.suitebuilder\.annotation\..*;',
2249 input_api.re.MULTILINE)
2250 sources = lambda x: input_api.FilterSourceFile(
2251 x, white_list=(r'.*\.java$',), black_list=None)
2253 for f in input_api.AffectedFiles(sources):
2254 for line_num, line in f.ChangedContents():
2255 if deprecated_annotation_import_pattern.search(line):
2256 errors.append("%s:%d" % (f.LocalPath(), line_num))
2260 results.append(output_api.PresubmitError(
2261 'Annotations in android.test.suitebuilder.annotation have been'
2262 ' deprecated since API level 24. Please use android.support.test.filters'
2263 ' from //third_party/android_support_test_runner:runner_java instead.'
2264 ' Contact yolandyan@chromium.org if you have any questions.', errors))
2268 def _CheckAndroidNewMdpiAssetLocation(input_api, output_api):
2269 """Checks if MDPI assets are placed in a correct directory."""
2270 file_filter = lambda f: (f.LocalPath().endswith('.png') and
2271 ('/res/drawable/' in f.LocalPath() or
2272 '/res/drawable-ldrtl/' in f.LocalPath()))
2274 for f in input_api.AffectedFiles(include_deletes=False,
2275 file_filter=file_filter):
2276 errors.append(' %s' % f.LocalPath())
2280 results.append(output_api.PresubmitError(
2281 'MDPI assets should be placed in /res/drawable-mdpi/ or '
2282 '/res/drawable-ldrtl-mdpi/\ninstead of /res/drawable/ and'
2283 '/res/drawable-ldrtl/.\n'
2284 'Contact newt@chromium.org if you have questions.', errors))
2288 def _CheckAndroidWebkitImports(input_api, output_api):
2289 """Checks that code uses org.chromium.base.Callback instead of
2290 android.widget.ValueCallback except in the WebView glue layer.
2292 valuecallback_import_pattern = input_api.re.compile(
2293 r'^import android\.webkit\.ValueCallback;$')
2297 sources = lambda affected_file: input_api.FilterSourceFile(
2299 black_list=(_EXCLUDED_PATHS +
2300 _TEST_CODE_EXCLUDED_PATHS +
2301 input_api.DEFAULT_BLACK_LIST +
2302 (r'^android_webview[\\\/]glue[\\\/].*',)),
2303 white_list=(r'.*\.java$',))
2305 for f in input_api.AffectedSourceFiles(sources):
2306 for line_num, line in f.ChangedContents():
2307 if valuecallback_import_pattern.search(line):
2308 errors.append("%s:%d" % (f.LocalPath(), line_num))
2313 results.append(output_api.PresubmitError(
2314 'android.webkit.ValueCallback usage is detected outside of the glue'
2315 ' layer. To stay compatible with the support library, android.webkit.*'
2316 ' classes should only be used inside the glue layer and'
2317 ' org.chromium.base.Callback should be used instead.',
2323 class PydepsChecker(object):
2324 def __init__(self, input_api, pydeps_files):
2325 self._file_cache = {}
2326 self._input_api = input_api
2327 self._pydeps_files = pydeps_files
2329 def _LoadFile(self, path):
2330 """Returns the list of paths within a .pydeps file relative to //."""
2331 if path not in self._file_cache:
2332 with open(path) as f:
2333 self._file_cache[path] = f.read()
2334 return self._file_cache[path]
2336 def _ComputeNormalizedPydepsEntries(self, pydeps_path):
2337 """Returns an interable of paths within the .pydep, relativized to //."""
2338 os_path = self._input_api.os_path
2339 pydeps_dir = os_path.dirname(pydeps_path)
2340 entries = (l.rstrip() for l in self._LoadFile(pydeps_path).splitlines()
2341 if not l.startswith('*'))
2342 return (os_path.normpath(os_path.join(pydeps_dir, e)) for e in entries)
2344 def _CreateFilesToPydepsMap(self):
2345 """Returns a map of local_path -> list_of_pydeps."""
2347 for pydep_local_path in self._pydeps_files:
2348 for path in self._ComputeNormalizedPydepsEntries(pydep_local_path):
2349 ret.setdefault(path, []).append(pydep_local_path)
2352 def ComputeAffectedPydeps(self):
2353 """Returns an iterable of .pydeps files that might need regenerating."""
2354 affected_pydeps = set()
2355 file_to_pydeps_map = None
2356 for f in self._input_api.AffectedFiles(include_deletes=True):
2357 local_path = f.LocalPath()
2358 if local_path == 'DEPS':
2359 return self._pydeps_files
2360 elif local_path.endswith('.pydeps'):
2361 if local_path in self._pydeps_files:
2362 affected_pydeps.add(local_path)
2363 elif local_path.endswith('.py'):
2364 if file_to_pydeps_map is None:
2365 file_to_pydeps_map = self._CreateFilesToPydepsMap()
2366 affected_pydeps.update(file_to_pydeps_map.get(local_path, ()))
2367 return affected_pydeps
2369 def DetermineIfStale(self, pydeps_path):
2370 """Runs print_python_deps.py to see if the files is stale."""
2374 old_pydeps_data = self._LoadFile(pydeps_path).splitlines()
2375 cmd = old_pydeps_data[1][1:].strip()
2376 env = dict(os.environ)
2377 env['PYTHONDONTWRITEBYTECODE'] = '1'
2378 new_pydeps_data = self._input_api.subprocess.check_output(
2379 cmd + ' --output ""', shell=True, env=env)
2380 old_contents = old_pydeps_data[2:]
2381 new_contents = new_pydeps_data.splitlines()[2:]
2382 if old_pydeps_data[2:] != new_pydeps_data.splitlines()[2:]:
2383 return cmd, '\n'.join(difflib.context_diff(old_contents, new_contents))
2386 def _CheckPydepsNeedsUpdating(input_api, output_api, checker_for_tests=None):
2387 """Checks if a .pydeps file needs to be regenerated."""
2388 # This check is for Python dependency lists (.pydeps files), and involves
2389 # paths not only in the PRESUBMIT.py, but also in the .pydeps files. It
2390 # doesn't work on Windows and Mac, so skip it on other platforms.
2391 if input_api.platform != 'linux2':
2393 # TODO(agrieve): Update when there's a better way to detect
2394 # this: crbug.com/570091
2395 is_android = input_api.os_path.exists('third_party/android_tools')
2396 pydeps_files = _ALL_PYDEPS_FILES if is_android else _GENERIC_PYDEPS_FILES
2398 # First, check for new / deleted .pydeps.
2399 for f in input_api.AffectedFiles(include_deletes=True):
2400 # Check whether we are running the presubmit check for a file in src.
2401 # f.LocalPath is relative to repo (src, or internal repo).
2402 # os_path.exists is relative to src repo.
2403 # Therefore if os_path.exists is true, it means f.LocalPath is relative
2404 # to src and we can conclude that the pydeps is in src.
2405 if input_api.os_path.exists(f.LocalPath()):
2406 if f.LocalPath().endswith('.pydeps'):
2407 if f.Action() == 'D' and f.LocalPath() in _ALL_PYDEPS_FILES:
2408 results.append(output_api.PresubmitError(
2409 'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
2410 'remove %s' % f.LocalPath()))
2411 elif f.Action() != 'D' and f.LocalPath() not in _ALL_PYDEPS_FILES:
2412 results.append(output_api.PresubmitError(
2413 'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
2414 'include %s' % f.LocalPath()))
2419 checker = checker_for_tests or PydepsChecker(input_api, pydeps_files)
2421 for pydep_path in checker.ComputeAffectedPydeps():
2423 result = checker.DetermineIfStale(pydep_path)
2426 results.append(output_api.PresubmitError(
2427 'File is stale: %s\nDiff (apply to fix):\n%s\n'
2428 'To regenerate, run:\n\n %s' %
2429 (pydep_path, diff, cmd)))
2430 except input_api.subprocess.CalledProcessError as error:
2431 return [output_api.PresubmitError('Error running: %s' % error.cmd,
2432 long_text=error.output)]
2437 def _CheckSingletonInHeaders(input_api, output_api):
2438 """Checks to make sure no header files have |Singleton<|."""
2439 def FileFilter(affected_file):
2440 # It's ok for base/memory/singleton.h to have |Singleton<|.
2441 black_list = (_EXCLUDED_PATHS +
2442 input_api.DEFAULT_BLACK_LIST +
2443 (r"^base[\\\/]memory[\\\/]singleton\.h$",
2444 r"^net[\\\/]quic[\\\/]platform[\\\/]impl[\\\/]"
2445 r"quic_singleton_impl\.h$"))
2446 return input_api.FilterSourceFile(affected_file, black_list=black_list)
2448 pattern = input_api.re.compile(r'(?<!class\sbase::)Singleton\s*<')
2450 for f in input_api.AffectedSourceFiles(FileFilter):
2451 if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx') or
2452 f.LocalPath().endswith('.hpp') or f.LocalPath().endswith('.inl')):
2453 contents = input_api.ReadFile(f)
2454 for line in contents.splitlines(False):
2455 if (not line.lstrip().startswith('//') and # Strip C++ comment.
2456 pattern.search(line)):
2461 return [output_api.PresubmitError(
2462 'Found base::Singleton<T> in the following header files.\n' +
2463 'Please move them to an appropriate source file so that the ' +
2464 'template gets instantiated in a single compilation unit.',
2471 ( "-webkit-box", "flex" ),
2472 ( "-webkit-inline-box", "inline-flex" ),
2473 ( "-webkit-flex", "flex" ),
2474 ( "-webkit-inline-flex", "inline-flex" ),
2475 ( "-webkit-min-content", "min-content" ),
2476 ( "-webkit-max-content", "max-content" ),
2479 ( "-webkit-background-clip", "background-clip" ),
2480 ( "-webkit-background-origin", "background-origin" ),
2481 ( "-webkit-background-size", "background-size" ),
2482 ( "-webkit-box-shadow", "box-shadow" ),
2483 ( "-webkit-user-select", "user-select" ),
2486 ( "-webkit-gradient", "gradient" ),
2487 ( "-webkit-repeating-gradient", "repeating-gradient" ),
2488 ( "-webkit-linear-gradient", "linear-gradient" ),
2489 ( "-webkit-repeating-linear-gradient", "repeating-linear-gradient" ),
2490 ( "-webkit-radial-gradient", "radial-gradient" ),
2491 ( "-webkit-repeating-radial-gradient", "repeating-radial-gradient" ),
2494 def _CheckNoDeprecatedCss(input_api, output_api):
2495 """ Make sure that we don't use deprecated CSS
2496 properties, functions or values. Our external
2497 documentation and iOS CSS for dom distiller
2498 (reader mode) are ignored by the hooks as it
2499 needs to be consumed by WebKit. """
2501 file_inclusion_pattern = (r".+\.css$",)
2502 black_list = (_EXCLUDED_PATHS +
2503 _TEST_CODE_EXCLUDED_PATHS +
2504 input_api.DEFAULT_BLACK_LIST +
2505 (r"^chrome/common/extensions/docs",
2507 r"^components/dom_distiller/core/css/distilledpage_ios.css",
2508 r"^components/neterror/resources/neterror.css",
2509 r"^native_client_sdk"))
2510 file_filter = lambda f: input_api.FilterSourceFile(
2511 f, white_list=file_inclusion_pattern, black_list=black_list)
2512 for fpath in input_api.AffectedFiles(file_filter=file_filter):
2513 for line_num, line in fpath.ChangedContents():
2514 for (deprecated_value, value) in _DEPRECATED_CSS:
2515 if deprecated_value in line:
2516 results.append(output_api.PresubmitError(
2517 "%s:%d: Use of deprecated CSS %s, use %s instead" %
2518 (fpath.LocalPath(), line_num, deprecated_value, value)))
2523 ( "__lookupGetter__", "Object.getOwnPropertyDescriptor" ),
2524 ( "__defineGetter__", "Object.defineProperty" ),
2525 ( "__defineSetter__", "Object.defineProperty" ),
2528 def _CheckNoDeprecatedJs(input_api, output_api):
2529 """Make sure that we don't use deprecated JS in Chrome code."""
2531 file_inclusion_pattern = (r".+\.js$",) # TODO(dbeam): .html?
2532 black_list = (_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
2533 input_api.DEFAULT_BLACK_LIST)
2534 file_filter = lambda f: input_api.FilterSourceFile(
2535 f, white_list=file_inclusion_pattern, black_list=black_list)
2536 for fpath in input_api.AffectedFiles(file_filter=file_filter):
2537 for lnum, line in fpath.ChangedContents():
2538 for (deprecated, replacement) in _DEPRECATED_JS:
2539 if deprecated in line:
2540 results.append(output_api.PresubmitError(
2541 "%s:%d: Use of deprecated JS %s, use %s instead" %
2542 (fpath.LocalPath(), lnum, deprecated, replacement)))
2545 def _CheckForRiskyJsArrowFunction(line_number, line):
2547 return "line %d, is using an => (arrow) function\n %s\n" % (
2551 def _CheckForRiskyJsConstLet(input_api, line_number, line):
2552 if input_api.re.match('^\s*(const|let)\s', line):
2553 return "line %d, is using const/let keyword\n %s\n" % (
2557 def _CheckForRiskyJsFeatures(input_api, output_api):
2558 maybe_ios_js = (r"^(ios|components|ui\/webui\/resources)\/.+\.js$", )
2559 # 'ui/webui/resources/cr_components are not allowed on ios'
2560 not_ios_filter = (r".*ui\/webui\/resources\/cr_components.*", )
2561 file_filter = lambda f: input_api.FilterSourceFile(f, white_list=maybe_ios_js,
2562 black_list=not_ios_filter)
2564 for f in input_api.AffectedFiles(file_filter=file_filter):
2565 arrow_error_lines = []
2566 const_let_error_lines = []
2567 for lnum, line in f.ChangedContents():
2568 arrow_error_lines += filter(None, [
2569 _CheckForRiskyJsArrowFunction(lnum, line),
2572 const_let_error_lines += filter(None, [
2573 _CheckForRiskyJsConstLet(input_api, lnum, line),
2576 if arrow_error_lines:
2577 arrow_error_lines = map(
2578 lambda e: "%s:%s" % (f.LocalPath(), e), arrow_error_lines)
2580 output_api.PresubmitPromptWarning('\n'.join(arrow_error_lines + [
2582 Use of => (arrow) operator detected in:
2584 Please ensure your code does not run on iOS9 (=> (arrow) does not work there).
2585 https://chromium.googlesource.com/chromium/src/+/master/docs/es6_chromium.md#Arrow-Functions
2589 if const_let_error_lines:
2590 const_let_error_lines = map(
2591 lambda e: "%s:%s" % (f.LocalPath(), e), const_let_error_lines)
2593 output_api.PresubmitPromptWarning('\n'.join(const_let_error_lines + [
2595 Use of const/let keywords detected in:
2597 Please ensure your code does not run on iOS9 because const/let is not fully
2599 https://chromium.googlesource.com/chromium/src/+/master/docs/es6_chromium.md#let-Block_Scoped-Variables
2600 https://chromium.googlesource.com/chromium/src/+/master/docs/es6_chromium.md#const-Block_Scoped-Constants
2606 def _CheckForRelativeIncludes(input_api, output_api):
2607 # Need to set the sys.path so PRESUBMIT_test.py runs properly
2609 original_sys_path = sys.path
2611 sys.path = sys.path + [input_api.os_path.join(
2612 input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
2613 from cpp_checker import CppChecker
2615 # Restore sys.path to what it was before.
2616 sys.path = original_sys_path
2619 for f in input_api.AffectedFiles(include_deletes=False):
2620 if (f.LocalPath().startswith('third_party') and
2621 not f.LocalPath().startswith('third_party/WebKit') and
2622 not f.LocalPath().startswith('third_party\\WebKit')):
2625 if not CppChecker.IsCppFile(f.LocalPath()):
2628 relative_includes = [line for _, line in f.ChangedContents()
2629 if "#include" in line and "../" in line]
2630 if not relative_includes:
2632 bad_files[f.LocalPath()] = relative_includes
2637 error_descriptions = []
2638 for file_path, bad_lines in bad_files.iteritems():
2639 error_description = file_path
2640 for line in bad_lines:
2641 error_description += '\n ' + line
2642 error_descriptions.append(error_description)
2645 results.append(output_api.PresubmitError(
2646 'You added one or more relative #include paths (including "../").\n'
2647 'These shouldn\'t be used because they can be used to include headers\n'
2648 'from code that\'s not correctly specified as a dependency in the\n'
2649 'relevant BUILD.gn file(s).',
2650 error_descriptions))
2655 def _CheckWatchlistDefinitionsEntrySyntax(key, value, ast):
2656 if not isinstance(key, ast.Str):
2657 return 'Key at line %d must be a string literal' % key.lineno
2658 if not isinstance(value, ast.Dict):
2659 return 'Value at line %d must be a dict' % value.lineno
2660 if len(value.keys) != 1:
2661 return 'Dict at line %d must have single entry' % value.lineno
2662 if not isinstance(value.keys[0], ast.Str) or value.keys[0].s != 'filepath':
2664 'Entry at line %d must have a string literal \'filepath\' as key' %
2669 def _CheckWatchlistsEntrySyntax(key, value, ast):
2670 if not isinstance(key, ast.Str):
2671 return 'Key at line %d must be a string literal' % key.lineno
2672 if not isinstance(value, ast.List):
2673 return 'Value at line %d must be a list' % value.lineno
2677 def _CheckWATCHLISTSEntries(wd_dict, w_dict, ast):
2678 mismatch_template = (
2679 'Mismatch between WATCHLIST_DEFINITIONS entry (%s) and WATCHLISTS '
2685 if i >= len(wd_dict.keys):
2686 if i >= len(w_dict.keys):
2688 return mismatch_template % ('missing', 'line %d' % w_dict.keys[i].lineno)
2689 elif i >= len(w_dict.keys):
2691 mismatch_template % ('line %d' % wd_dict.keys[i].lineno, 'missing'))
2693 wd_key = wd_dict.keys[i]
2694 w_key = w_dict.keys[i]
2696 result = _CheckWatchlistDefinitionsEntrySyntax(
2697 wd_key, wd_dict.values[i], ast)
2698 if result is not None:
2699 return 'Bad entry in WATCHLIST_DEFINITIONS dict: %s' % result
2701 result = _CheckWatchlistsEntrySyntax(w_key, w_dict.values[i], ast)
2702 if result is not None:
2703 return 'Bad entry in WATCHLISTS dict: %s' % result
2705 if wd_key.s != w_key.s:
2706 return mismatch_template % (
2707 '%s at line %d' % (wd_key.s, wd_key.lineno),
2708 '%s at line %d' % (w_key.s, w_key.lineno))
2710 if wd_key.s < last_key:
2712 'WATCHLISTS dict is not sorted lexicographically at line %d and %d' %
2713 (wd_key.lineno, w_key.lineno))
2719 def _CheckWATCHLISTSSyntax(expression, ast):
2720 if not isinstance(expression, ast.Expression):
2721 return 'WATCHLISTS file must contain a valid expression'
2722 dictionary = expression.body
2723 if not isinstance(dictionary, ast.Dict) or len(dictionary.keys) != 2:
2724 return 'WATCHLISTS file must have single dict with exactly two entries'
2726 first_key = dictionary.keys[0]
2727 first_value = dictionary.values[0]
2728 second_key = dictionary.keys[1]
2729 second_value = dictionary.values[1]
2731 if (not isinstance(first_key, ast.Str) or
2732 first_key.s != 'WATCHLIST_DEFINITIONS' or
2733 not isinstance(first_value, ast.Dict)):
2735 'The first entry of the dict in WATCHLISTS file must be '
2736 'WATCHLIST_DEFINITIONS dict')
2738 if (not isinstance(second_key, ast.Str) or
2739 second_key.s != 'WATCHLISTS' or
2740 not isinstance(second_value, ast.Dict)):
2742 'The second entry of the dict in WATCHLISTS file must be '
2745 return _CheckWATCHLISTSEntries(first_value, second_value, ast)
2748 def _CheckWATCHLISTS(input_api, output_api):
2749 for f in input_api.AffectedFiles(include_deletes=False):
2750 if f.LocalPath() == 'WATCHLISTS':
2751 contents = input_api.ReadFile(f, 'r')
2754 # First, make sure that it can be evaluated.
2755 input_api.ast.literal_eval(contents)
2756 # Get an AST tree for it and scan the tree for detailed style checking.
2757 expression = input_api.ast.parse(
2758 contents, filename='WATCHLISTS', mode='eval')
2759 except ValueError as e:
2760 return [output_api.PresubmitError(
2761 'Cannot parse WATCHLISTS file', long_text=repr(e))]
2762 except SyntaxError as e:
2763 return [output_api.PresubmitError(
2764 'Cannot parse WATCHLISTS file', long_text=repr(e))]
2765 except TypeError as e:
2766 return [output_api.PresubmitError(
2767 'Cannot parse WATCHLISTS file', long_text=repr(e))]
2769 result = _CheckWATCHLISTSSyntax(expression, input_api.ast)
2770 if result is not None:
2771 return [output_api.PresubmitError(result)]
2777 def _AndroidSpecificOnUploadChecks(input_api, output_api):
2778 """Groups checks that target android code."""
2780 results.extend(_CheckAndroidCrLogUsage(input_api, output_api))
2781 results.extend(_CheckAndroidNewMdpiAssetLocation(input_api, output_api))
2782 results.extend(_CheckAndroidToastUsage(input_api, output_api))
2783 results.extend(_CheckAndroidTestJUnitInheritance(input_api, output_api))
2784 results.extend(_CheckAndroidTestJUnitFrameworkImport(input_api, output_api))
2785 results.extend(_CheckAndroidTestAnnotationUsage(input_api, output_api))
2786 results.extend(_CheckAndroidWebkitImports(input_api, output_api))
2790 def _CommonChecks(input_api, output_api):
2791 """Checks common to both upload and commit."""
2793 results.extend(input_api.canned_checks.PanProjectChecks(
2794 input_api, output_api,
2795 excluded_paths=_EXCLUDED_PATHS))
2797 author = input_api.change.author_email
2798 if author and author not in _KNOWN_ROBOTS:
2800 input_api.canned_checks.CheckAuthorizedAuthor(input_api, output_api))
2803 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
2805 _CheckNoProductionCodeUsingTestOnlyFunctionsJava(input_api, output_api))
2806 results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
2807 results.extend(_CheckNoUNIT_TESTInSourceFiles(input_api, output_api))
2808 results.extend(_CheckDCHECK_IS_ONHasBraces(input_api, output_api))
2809 results.extend(_CheckNoNewWStrings(input_api, output_api))
2810 results.extend(_CheckNoDEPSGIT(input_api, output_api))
2811 results.extend(_CheckNoBannedFunctions(input_api, output_api))
2812 results.extend(_CheckNoPragmaOnce(input_api, output_api))
2813 results.extend(_CheckNoTrinaryTrueFalse(input_api, output_api))
2814 results.extend(_CheckUnwantedDependencies(input_api, output_api))
2815 results.extend(_CheckFilePermissions(input_api, output_api))
2816 results.extend(_CheckTeamTags(input_api, output_api))
2817 results.extend(_CheckNoAuraWindowPropertyHInHeaders(input_api, output_api))
2818 results.extend(_CheckForVersionControlConflicts(input_api, output_api))
2819 results.extend(_CheckPatchFiles(input_api, output_api))
2820 results.extend(_CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api))
2821 results.extend(_CheckNoAbbreviationInPngFileName(input_api, output_api))
2822 results.extend(_CheckBuildConfigMacrosWithoutInclude(input_api, output_api))
2823 results.extend(_CheckForInvalidOSMacros(input_api, output_api))
2824 results.extend(_CheckForInvalidIfDefinedMacros(input_api, output_api))
2825 results.extend(_CheckFlakyTestUsage(input_api, output_api))
2826 results.extend(_CheckAddedDepsHaveTargetApprovals(input_api, output_api))
2828 input_api.canned_checks.CheckChangeHasNoTabs(
2831 source_file_filter=lambda x: x.LocalPath().endswith('.grd')))
2832 results.extend(_CheckSpamLogging(input_api, output_api))
2833 results.extend(_CheckForAnonymousVariables(input_api, output_api))
2834 results.extend(_CheckUserActionUpdate(input_api, output_api))
2835 results.extend(_CheckNoDeprecatedCss(input_api, output_api))
2836 results.extend(_CheckNoDeprecatedJs(input_api, output_api))
2837 results.extend(_CheckParseErrors(input_api, output_api))
2838 results.extend(_CheckForIPCRules(input_api, output_api))
2839 results.extend(_CheckForLongPathnames(input_api, output_api))
2840 results.extend(_CheckForIncludeGuards(input_api, output_api))
2841 results.extend(_CheckForWindowsLineEndings(input_api, output_api))
2842 results.extend(_CheckSingletonInHeaders(input_api, output_api))
2843 results.extend(_CheckPydepsNeedsUpdating(input_api, output_api))
2844 results.extend(_CheckJavaStyle(input_api, output_api))
2845 results.extend(_CheckIpcOwners(input_api, output_api))
2846 results.extend(_CheckUselessForwardDeclarations(input_api, output_api))
2847 results.extend(_CheckForRiskyJsFeatures(input_api, output_api))
2848 results.extend(_CheckForRelativeIncludes(input_api, output_api))
2849 results.extend(_CheckWATCHLISTS(input_api, output_api))
2850 results.extend(input_api.RunTests(
2851 input_api.canned_checks.CheckVPythonSpec(input_api, output_api)))
2853 for f in input_api.AffectedFiles():
2854 path, name = input_api.os_path.split(f.LocalPath())
2855 if name == 'PRESUBMIT.py':
2856 full_path = input_api.os_path.join(input_api.PresubmitLocalPath(), path)
2857 test_file = input_api.os_path.join(path, 'PRESUBMIT_test.py')
2858 if f.Action() != 'D' and input_api.os_path.exists(test_file):
2859 # The PRESUBMIT.py file (and the directory containing it) might
2860 # have been affected by being moved or removed, so only try to
2861 # run the tests if they still exist.
2862 results.extend(input_api.canned_checks.RunUnitTestsInDirectory(
2863 input_api, output_api, full_path,
2864 whitelist=[r'^PRESUBMIT_test\.py$']))
2868 def _CheckPatchFiles(input_api, output_api):
2869 problems = [f.LocalPath() for f in input_api.AffectedFiles()
2870 if f.LocalPath().endswith(('.orig', '.rej'))]
2872 return [output_api.PresubmitError(
2873 "Don't commit .rej and .orig files.", problems)]
2878 def _CheckBuildConfigMacrosWithoutInclude(input_api, output_api):
2879 # Excludes OS_CHROMEOS, which is not defined in build_config.h.
2880 macro_re = input_api.re.compile(r'^\s*#(el)?if.*\bdefined\(((OS_(?!CHROMEOS)|'
2881 'COMPILER_|ARCH_CPU_|WCHAR_T_IS_)[^)]*)')
2882 include_re = input_api.re.compile(
2883 r'^#include\s+"build/build_config.h"', input_api.re.MULTILINE)
2884 extension_re = input_api.re.compile(r'\.[a-z]+$')
2886 for f in input_api.AffectedFiles():
2887 if not f.LocalPath().endswith(('.h', '.c', '.cc', '.cpp', '.m', '.mm')):
2889 found_line_number = None
2891 for line_num, line in f.ChangedContents():
2892 match = macro_re.search(line)
2894 found_line_number = line_num
2895 found_macro = match.group(2)
2897 if not found_line_number:
2900 found_include = False
2901 for line in f.NewContents():
2902 if include_re.search(line):
2903 found_include = True
2908 if not f.LocalPath().endswith('.h'):
2909 primary_header_path = extension_re.sub('.h', f.AbsoluteLocalPath())
2911 content = input_api.ReadFile(primary_header_path, 'r')
2912 if include_re.search(content):
2916 errors.append('%s:%d %s macro is used without including build/'
2918 % (f.LocalPath(), found_line_number, found_macro))
2920 return [output_api.PresubmitPromptWarning('\n'.join(errors))]
2924 def _DidYouMeanOSMacro(bad_macro):
2926 return {'A': 'OS_ANDROID',
2936 'W': 'OS_WIN'}[bad_macro[3].upper()]
2941 def _CheckForInvalidOSMacrosInFile(input_api, f):
2942 """Check for sensible looking, totally invalid OS macros."""
2943 preprocessor_statement = input_api.re.compile(r'^\s*#')
2944 os_macro = input_api.re.compile(r'defined\((OS_[^)]+)\)')
2946 for lnum, line in f.ChangedContents():
2947 if preprocessor_statement.search(line):
2948 for match in os_macro.finditer(line):
2949 if not match.group(1) in _VALID_OS_MACROS:
2950 good = _DidYouMeanOSMacro(match.group(1))
2951 did_you_mean = ' (did you mean %s?)' % good if good else ''
2952 results.append(' %s:%d %s%s' % (f.LocalPath(),
2959 def _CheckForInvalidOSMacros(input_api, output_api):
2960 """Check all affected files for invalid OS macros."""
2962 for f in input_api.AffectedFiles():
2963 if not f.LocalPath().endswith(('.py', '.js', '.html', '.css', '.md')):
2964 bad_macros.extend(_CheckForInvalidOSMacrosInFile(input_api, f))
2969 return [output_api.PresubmitError(
2970 'Possibly invalid OS macro[s] found. Please fix your code\n'
2971 'or add your macro to src/PRESUBMIT.py.', bad_macros)]
2974 def _CheckForInvalidIfDefinedMacrosInFile(input_api, f):
2975 """Check all affected files for invalid "if defined" macros."""
2976 ALWAYS_DEFINED_MACROS = (
2985 "TARGET_IPHONE_SIMULATOR",
2986 "TARGET_OS_EMBEDDED",
2992 ifdef_macro = input_api.re.compile(r'^\s*#.*(?:ifdef\s|defined\()([^\s\)]+)')
2994 for lnum, line in f.ChangedContents():
2995 for match in ifdef_macro.finditer(line):
2996 if match.group(1) in ALWAYS_DEFINED_MACROS:
2997 always_defined = ' %s is always defined. ' % match.group(1)
2998 did_you_mean = 'Did you mean \'#if %s\'?' % match.group(1)
2999 results.append(' %s:%d %s\n\t%s' % (f.LocalPath(),
3006 def _CheckForInvalidIfDefinedMacros(input_api, output_api):
3007 """Check all affected files for invalid "if defined" macros."""
3009 for f in input_api.AffectedFiles():
3010 if f.LocalPath().startswith('third_party/sqlite/'):
3012 if f.LocalPath().endswith(('.h', '.c', '.cc', '.m', '.mm')):
3013 bad_macros.extend(_CheckForInvalidIfDefinedMacrosInFile(input_api, f))
3018 return [output_api.PresubmitError(
3019 'Found ifdef check on always-defined macro[s]. Please fix your code\n'
3020 'or check the list of ALWAYS_DEFINED_MACROS in src/PRESUBMIT.py.',
3024 def _CheckForIPCRules(input_api, output_api):
3025 """Check for same IPC rules described in
3026 http://www.chromium.org/Home/chromium-security/education/security-tips-for-ipc
3028 base_pattern = r'IPC_ENUM_TRAITS\('
3029 inclusion_pattern = input_api.re.compile(r'(%s)' % base_pattern)
3030 comment_pattern = input_api.re.compile(r'//.*(%s)' % base_pattern)
3033 for f in input_api.AffectedSourceFiles(None):
3034 local_path = f.LocalPath()
3035 if not local_path.endswith('.h'):
3037 for line_number, line in f.ChangedContents():
3038 if inclusion_pattern.search(line) and not comment_pattern.search(line):
3040 '%s:%d\n %s' % (local_path, line_number, line.strip()))
3043 return [output_api.PresubmitPromptWarning(
3044 _IPC_ENUM_TRAITS_DEPRECATED, problems)]
3049 def _CheckForLongPathnames(input_api, output_api):
3050 """Check to make sure no files being submitted have long paths.
3051 This causes issues on Windows.
3054 for f in input_api.AffectedSourceFiles(None):
3055 local_path = f.LocalPath()
3056 # Windows has a path limit of 260 characters. Limit path length to 200 so
3057 # that we have some extra for the prefix on dev machines and the bots.
3058 if len(local_path) > 200:
3059 problems.append(local_path)
3062 return [output_api.PresubmitError(_LONG_PATH_ERROR, problems)]
3067 def _CheckForIncludeGuards(input_api, output_api):
3068 """Check that header files have proper guards against multiple inclusion.
3069 If a file should not have such guards (and it probably should) then it
3070 should include the string "no-include-guard-because-multiply-included".
3072 def is_chromium_header_file(f):
3073 # We only check header files under the control of the Chromium
3074 # project. That is, those outside third_party apart from
3075 # third_party/blink.
3076 file_with_path = input_api.os_path.normpath(f.LocalPath())
3077 return (file_with_path.endswith('.h') and
3078 (not file_with_path.startswith('third_party') or
3079 file_with_path.startswith(
3080 input_api.os_path.join('third_party', 'blink'))))
3082 def replace_special_with_underscore(string):
3083 return input_api.re.sub(r'[\\/.-]', '_', string)
3087 for f in input_api.AffectedSourceFiles(is_chromium_header_file):
3089 guard_line_number = None
3090 seen_guard_end = False
3092 file_with_path = input_api.os_path.normpath(f.LocalPath())
3093 base_file_name = input_api.os_path.splitext(
3094 input_api.os_path.basename(file_with_path))[0]
3095 upper_base_file_name = base_file_name.upper()
3097 expected_guard = replace_special_with_underscore(
3098 file_with_path.upper() + '_')
3100 # For "path/elem/file_name.h" we should really only accept
3101 # PATH_ELEM_FILE_NAME_H_ per coding style. Unfortunately there
3102 # are too many (1000+) files with slight deviations from the
3103 # coding style. The most important part is that the include guard
3104 # is there, and that it's unique, not the name so this check is
3105 # forgiving for existing files.
3107 # As code becomes more uniform, this could be made stricter.
3109 guard_name_pattern_list = [
3110 # Anything with the right suffix (maybe with an extra _).
3113 # To cover include guards with old Blink style.
3116 # Anything including the uppercase name of the file.
3117 r'\w*' + input_api.re.escape(replace_special_with_underscore(
3118 upper_base_file_name)) + r'\w*',
3120 guard_name_pattern = '|'.join(guard_name_pattern_list)
3121 guard_pattern = input_api.re.compile(
3122 r'#ifndef\s+(' + guard_name_pattern + ')')
3124 for line_number, line in enumerate(f.NewContents()):
3125 if 'no-include-guard-because-multiply-included' in line:
3126 guard_name = 'DUMMY' # To not trigger check outside the loop.
3129 if guard_name is None:
3130 match = guard_pattern.match(line)
3132 guard_name = match.group(1)
3133 guard_line_number = line_number
3135 # We allow existing files to use include guards whose names
3136 # don't match the chromium style guide, but new files should
3138 if not f.OldContents():
3139 if guard_name != expected_guard:
3140 errors.append(output_api.PresubmitPromptWarning(
3141 'Header using the wrong include guard name %s' % guard_name,
3142 ['%s:%d' % (f.LocalPath(), line_number + 1)],
3143 'Expected: %r\nFound: %r' % (expected_guard, guard_name)))
3145 # The line after #ifndef should have a #define of the same name.
3146 if line_number == guard_line_number + 1:
3147 expected_line = '#define %s' % guard_name
3148 if line != expected_line:
3149 errors.append(output_api.PresubmitPromptWarning(
3150 'Missing "%s" for include guard' % expected_line,
3151 ['%s:%d' % (f.LocalPath(), line_number + 1)],
3152 'Expected: %r\nGot: %r' % (expected_line, line)))
3154 if not seen_guard_end and line == '#endif // %s' % guard_name:
3155 seen_guard_end = True
3156 elif seen_guard_end:
3157 if line.strip() != '':
3158 errors.append(output_api.PresubmitPromptWarning(
3159 'Include guard %s not covering the whole file' % (
3160 guard_name), [f.LocalPath()]))
3161 break # Nothing else to check and enough to warn once.
3163 if guard_name is None:
3164 errors.append(output_api.PresubmitPromptWarning(
3165 'Missing include guard %s' % expected_guard,
3167 'Missing include guard in %s\n'
3168 'Recommended name: %s\n'
3169 'This check can be disabled by having the string\n'
3170 'no-include-guard-because-multiply-included in the header.' %
3171 (f.LocalPath(), expected_guard)))
3176 def _CheckForWindowsLineEndings(input_api, output_api):
3177 """Check source code and known ascii text files for Windows style line
3180 known_text_files = r'.*\.(txt|html|htm|mhtml|py|gyp|gypi|gn|isolate)$'
3182 file_inclusion_pattern = (
3184 r'.+%s' % _IMPLEMENTATION_EXTENSIONS
3188 source_file_filter = lambda f: input_api.FilterSourceFile(
3189 f, white_list=file_inclusion_pattern, black_list=None)
3190 for f in input_api.AffectedSourceFiles(source_file_filter):
3191 include_file = False
3192 for _, line in f.ChangedContents():
3193 if line.endswith('\r\n'):
3196 problems.append(f.LocalPath())
3199 return [output_api.PresubmitPromptWarning('Are you sure that you want '
3200 'these files to contain Windows style line endings?\n' +
3201 '\n'.join(problems))]
3206 def _CheckSyslogUseWarning(input_api, output_api, source_file_filter=None):
3207 """Checks that all source files use SYSLOG properly."""
3209 for f in input_api.AffectedSourceFiles(source_file_filter):
3210 for line_number, line in f.ChangedContents():
3211 if 'SYSLOG' in line:
3212 syslog_files.append(f.LocalPath() + ':' + str(line_number))
3215 return [output_api.PresubmitPromptWarning(
3216 'Please make sure there are no privacy sensitive bits of data in SYSLOG'
3217 ' calls.\nFiles to check:\n', items=syslog_files)]
3221 def _CheckCrbugLinksHaveHttps(input_api, output_api):
3222 """Checks that crbug(.com) links are correctly prefixed by https://,
3223 unless they come in the accepted form TODO(crbug.com/...)
3225 white_list = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
3226 black_list = (_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS)
3227 sources = lambda f: input_api.FilterSourceFile(
3228 f, white_list=white_list, black_list=black_list)
3230 pattern = input_api.re.compile(r'//.*(?<!:\/\/)crbug[.com]*')
3231 accepted_pattern = input_api.re.compile(r'//.*TODO\(crbug[.com]*');
3233 for f in input_api.AffectedSourceFiles(sources):
3234 for line_num, line in f.ChangedContents():
3235 if pattern.search(line) and not accepted_pattern.search(line):
3236 problems.append(' %s:%d %s' % (f.LocalPath(), line_num, line))
3239 return [output_api.PresubmitPromptWarning(
3240 'Found unprefixed crbug.com URL(s), consider prepending https://\n'+
3241 '\n'.join(problems))]
3245 def CheckChangeOnUpload(input_api, output_api):
3247 results.extend(_CommonChecks(input_api, output_api))
3248 results.extend(_CheckValidHostsInDEPS(input_api, output_api))
3250 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
3251 results.extend(_CheckUmaHistogramChanges(input_api, output_api))
3252 results.extend(_AndroidSpecificOnUploadChecks(input_api, output_api))
3253 results.extend(_CheckSyslogUseWarning(input_api, output_api))
3254 results.extend(_CheckGoogleSupportAnswerUrl(input_api, output_api))
3255 results.extend(_CheckCrbugLinksHaveHttps(input_api, output_api))
3256 results.extend(_CheckUniquePtr(input_api, output_api))
3260 def GetTryServerMasterForBot(bot):
3261 """Returns the Try Server master for the given bot.
3263 It tries to guess the master from the bot name, but may still fail
3264 and return None. There is no longer a default master.
3266 # Potentially ambiguous bot names are listed explicitly.
3268 'chromium_presubmit': 'master.tryserver.chromium.linux',
3269 'tools_build_presubmit': 'master.tryserver.chromium.linux',
3271 master = master_map.get(bot)
3273 if 'android' in bot:
3274 master = 'master.tryserver.chromium.android'
3275 elif 'linux' in bot or 'presubmit' in bot:
3276 master = 'master.tryserver.chromium.linux'
3278 master = 'master.tryserver.chromium.win'
3279 elif 'mac' in bot or 'ios' in bot:
3280 master = 'master.tryserver.chromium.mac'
3284 def CheckChangeOnCommit(input_api, output_api):
3286 results.extend(_CommonChecks(input_api, output_api))
3287 # Make sure the tree is 'open'.
3288 results.extend(input_api.canned_checks.CheckTreeIsOpen(
3291 json_url='http://chromium-status.appspot.com/current?format=json'))
3294 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
3295 results.extend(input_api.canned_checks.CheckChangeHasBugField(
3296 input_api, output_api))
3297 results.extend(input_api.canned_checks.CheckChangeHasDescription(
3298 input_api, output_api))