Upload upstream chromium 69.0.3497
[platform/framework/web/chromium-efl.git] / PRESUBMIT.py
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.
4
5 """Top-level presubmit script for Chromium.
6
7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8 for more details about the presubmit API built into depot_tools.
9 """
10
11
12 _EXCLUDED_PATHS = (
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[\\\/].*",
17     r"^skia[\\\/].*",
18     r"^third_party[\\\/](WebKit|blink)[\\\/].*",
19     r"^third_party[\\\/]breakpad[\\\/].*",
20     r"^v8[\\\/].*",
21     r".*MakeFile$",
22     r".+_autogen\.h$",
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.*",
31 )
32
33
34 # Fragment of a regular expression that matches C++ and Objective-C++
35 # implementation files.
36 _IMPLEMENTATION_EXTENSIONS = r'\.(cc|cpp|cxx|mm)$'
37
38
39 # Regular expression that matches code only used for test binaries
40 # (best effort).
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$',
54 )
55
56
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.')
62
63
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')
68
69
70 _BANNED_JAVA_FUNCTIONS = (
71     (
72       'StrictMode.allowThreadDiskReads()',
73       (
74        'Prefer using StrictModeContext.allowDiskReads() to using StrictMode '
75        'directly.',
76       ),
77       False,
78     ),
79     (
80       'StrictMode.allowThreadDiskWrites()',
81       (
82        'Prefer using StrictModeContext.allowDiskWrites() to using StrictMode '
83        'directly.',
84       ),
85       False,
86     ),
87 )
88
89 _BANNED_OBJC_FUNCTIONS = (
90     (
91       'addTrackingRect:',
92       (
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',
96       ),
97       False,
98     ),
99     (
100       r'/NSTrackingArea\W',
101       (
102        'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
103        'instead.',
104        'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
105       ),
106       False,
107     ),
108     (
109       'convertPointFromBase:',
110       (
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',
114       ),
115       True,
116     ),
117     (
118       'convertPointToBase:',
119       (
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',
123       ),
124       True,
125     ),
126     (
127       'convertRectFromBase:',
128       (
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',
132       ),
133       True,
134     ),
135     (
136       'convertRectToBase:',
137       (
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',
141       ),
142       True,
143     ),
144     (
145       'convertSizeFromBase:',
146       (
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',
150       ),
151       True,
152     ),
153     (
154       'convertSizeToBase:',
155       (
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',
159       ),
160       True,
161     ),
162     (
163       r"/\s+UTF8String\s*]",
164       (
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.',
168       ),
169       True,
170     ),
171     (
172       r'__unsafe_unretained',
173       (
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.',
177       ),
178       False,
179     ),
180 )
181
182 _BANNED_IOS_OBJC_FUNCTIONS = (
183     (
184       r'/\bTEST[(]',
185       (
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 ',
189         'typedef).'
190       ),
191       True,
192     ),
193     (
194       r'/\btesting::Test\b',
195       (
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.'
199       ),
200       True,
201     ),
202 )
203
204
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.
209     (
210       r'\bNULL\b',
211       (
212        'New code should not use NULL. Use nullptr instead.',
213       ),
214       True,
215       (),
216     ),
217     (
218       'FRIEND_TEST(',
219       (
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.',
222       ),
223       False,
224       (),
225     ),
226     (
227       r'XSelectInput|CWEventMask|XCB_CW_EVENT_MASK',
228       (
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().',
233       ),
234       True,
235       (
236         r"^ui[\\\/]gl[\\\/].*\.cc$",
237         r"^media[\\\/]gpu[\\\/].*\.cc$",
238         r"^gpu[\\\/].*\.cc$",
239       ),
240     ),
241     (
242       r'XInternAtom|xcb_intern_atom',
243       (
244        'Use gfx::GetAtom() instead of interning atoms directly.',
245       ),
246       True,
247       (
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$",
251       ),
252     ),
253     (
254       'setMatrixClip',
255       (
256         'Overriding setMatrixClip() is prohibited; ',
257         'the base function is deprecated. ',
258       ),
259       True,
260       (),
261     ),
262     (
263       'SkRefPtr',
264       (
265         'The use of SkRefPtr is prohibited. ',
266         'Please use sk_sp<> instead.'
267       ),
268       True,
269       (),
270     ),
271     (
272       'SkAutoRef',
273       (
274         'The indirect use of SkRefPtr via SkAutoRef is prohibited. ',
275         'Please use sk_sp<> instead.'
276       ),
277       True,
278       (),
279     ),
280     (
281       'SkAutoTUnref',
282       (
283         'The use of SkAutoTUnref is dangerous because it implicitly ',
284         'converts to a raw pointer. Please use sk_sp<> instead.'
285       ),
286       True,
287       (),
288     ),
289     (
290       'SkAutoUnref',
291       (
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.'
295       ),
296       True,
297       (),
298     ),
299     (
300       r'/HANDLE_EINTR\(.*close',
301       (
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'
306       ),
307       True,
308       (),
309     ),
310     (
311       r'/IGNORE_EINTR\((?!.*close)',
312       (
313        'IGNORE_EINTR is only valid when wrapping close. To wrap other system',
314        'calls, use HANDLE_EINTR. See http://crbug.com/269623',
315       ),
316       True,
317       (
318         # Files that #define IGNORE_EINTR.
319         r'^base[\\\/]posix[\\\/]eintr_wrapper\.h$',
320         r'^ppapi[\\\/]tests[\\\/]test_broker\.cc$',
321       ),
322     ),
323     (
324       r'/v8::Extension\(',
325       (
326         'Do not introduce new v8::Extensions into the code base, use',
327         'gin::Wrappable instead. See http://crbug.com/334679',
328       ),
329       True,
330       (
331         r'extensions[\\\/]renderer[\\\/]safe_builtins\.*',
332       ),
333     ),
334     (
335       '#pragma comment(lib,',
336       (
337         'Specify libraries to link with in build files and not in the source.',
338       ),
339       True,
340       (
341           r'^third_party[\\\/]abseil-cpp[\\\/].*',
342       ),
343     ),
344     (
345       r'/base::SequenceChecker\b',
346       (
347         'Consider using SEQUENCE_CHECKER macros instead of the class directly.',
348       ),
349       False,
350       (),
351     ),
352     (
353       r'/base::ThreadChecker\b',
354       (
355         'Consider using THREAD_CHECKER macros instead of the class directly.',
356       ),
357       False,
358       (),
359     ),
360     (
361       r'/(Time(|Delta|Ticks)|ThreadTicks)::FromInternalValue|ToInternalValue',
362       (
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.',
371       ),
372       False,
373       (),
374     ),
375     (
376       'CallJavascriptFunctionUnsafe',
377       (
378         "Don't use CallJavascriptFunctionUnsafe() in new code. Instead, use",
379         'AllowJavascript(), OnJavascriptAllowed()/OnJavascriptDisallowed(),',
380         'and CallJavascriptFunction(). See https://goo.gl/qivavq.',
381       ),
382       False,
383       (
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)$',
387       ),
388     ),
389     (
390       'leveldb::DB::Open',
391       (
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.",
395       ),
396       True,
397       (
398         r'^third_party/leveldatabase/.*\.(cc|h)$',
399       ),
400     ),
401     (
402       'leveldb::NewMemEnv',
403       (
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.",
407       ),
408       True,
409       (
410         r'^third_party/leveldatabase/.*\.(cc|h)$',
411       ),
412     ),
413     (
414       'RunLoop::QuitCurrent',
415       (
416         'Please migrate away from RunLoop::QuitCurrent*() methods. Use member',
417         'methods of a specific RunLoop instance instead.',
418       ),
419       False,
420       (),
421     ),
422     (
423       'base::ScopedMockTimeMessageLoopTaskRunner',
424       (
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)',
430       ),
431       False,
432       (),
433     ),
434     (
435       r'std::regex',
436       (
437         'Using std::regex adds unnecessary binary size to Chrome. Please use',
438         're2::RE2 instead (crbug.com/755321)',
439       ),
440       True,
441       (),
442     ),
443     (
444       (r'/base::ThreadRestrictions::(ScopedAllowIO|AssertIOAllowed|'
445        r'DisallowWaiting|AssertWaitAllowed|SetWaitAllowed|ScopedAllowWait)'),
446       (
447         'Use the new API in base/threading/thread_restrictions.h.',
448       ),
449       True,
450       (),
451     ),
452     (
453       r'/\bbase::Bind\(',
454       (
455           'Please consider using base::Bind{Once,Repeating} instead',
456           'of base::Bind. (crbug.com/714018)',
457       ),
458       False,
459       (),
460     ),
461     (
462       r'/\bbase::Callback<',
463       (
464           'Please consider using base::{Once,Repeating}Callback instead',
465           'of base::Callback. (crbug.com/714018)',
466       ),
467       False,
468       (),
469     ),
470     (
471       r'/\bbase::Closure\b',
472       (
473           'Please consider using base::{Once,Repeating}Closure instead',
474           'of base::Closure. (crbug.com/714018)',
475       ),
476       False,
477       (),
478     ),
479     (
480       r'RunMessageLoop',
481       (
482           'RunMessageLoop is deprecated, use RunLoop instead.',
483       ),
484       False,
485       (),
486     ),
487     (
488       r'RunThisRunLoop',
489       (
490           'RunThisRunLoop is deprecated, use RunLoop directly instead.',
491       ),
492       False,
493       (),
494     ),
495     (
496       r'RunAllPendingInMessageLoop()',
497       (
498           "Prefer RunLoop over RunAllPendingInMessageLoop, please contact gab@",
499           "if you're convinced you need this.",
500       ),
501       False,
502       (),
503     ),
504     (
505       r'RunAllPendingInMessageLoop(BrowserThread',
506       (
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.',
511       ),
512       False,
513       (),
514     ),
515     (
516       r'MessageLoopRunner',
517       (
518           'MessageLoopRunner is deprecated, use RunLoop instead.',
519       ),
520       False,
521       (),
522     ),
523     (
524       r'GetDeferredQuitTaskForRunLoop',
525       (
526           "GetDeferredQuitTaskForRunLoop shouldn't be needed, please contact",
527           "gab@ if you found a use case where this is the only solution.",
528       ),
529       False,
530       (),
531     ),
532     (
533       'sqlite3_initialize',
534       (
535         'Instead of sqlite3_initialize, depend on //sql, ',
536         '#include "sql/initialize.h" and use sql::EnsureSqliteInitialized().',
537       ),
538       True,
539       (
540         r'^sql/initialization\.(cc|h)$',
541         r'^third_party/sqlite/.*\.(c|cc|h)$',
542       ),
543     ),
544     (
545       'net::URLFetcher',
546       (
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',
551         'content.',
552       ),
553       False,
554       (
555         r'^ios[\\\/].*\.(cc|h)$',
556         r'.*[\\\/]ios[\\\/].*\.(cc|h)$',
557         r'.*_ios\.(cc|h)$',
558         r'^net[\\\/].*\.(cc|h)$',
559         r'.*[\\\/]tools[\\\/].*\.(cc|h)$',
560       ),
561     ),
562     (
563       r'/\barraysize\b',
564       (
565           "arraysize is deprecated, please use base::size(array) instead ",
566           "(https://crbug.com/837308). ",
567       ),
568       False,
569       (),
570     ),
571     (
572       r'std::random_shuffle',
573       (
574         'std::random_shuffle is deprecated in C++14, and removed in C++17. Use',
575         'base::RandomShuffle instead.'
576       ),
577       True,
578       (),
579     ),
580 )
581
582
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')
587
588 _LONG_PATH_ERROR = (
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.'
592 )
593
594 _JAVA_MULTIPLE_DEFINITION_EXCLUDED_PATHS = [
595     r".*[\\\/]BuildHooksAndroidImpl\.java",
596     r".*[\\\/]LicenseContentProvider\.java",
597     r".*[\\\/]PlatformServiceBridgeImpl.java",
598 ]
599
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$',
607 ]
608
609
610 _VALID_OS_MACROS = (
611     # Please keep sorted.
612     'OS_AIX',
613     'OS_ANDROID',
614     'OS_ASMJS',
615     'OS_BSD',
616     'OS_CAT',       # For testing.
617     'OS_CHROMEOS',
618     'OS_FREEBSD',
619     'OS_FUCHSIA',
620     'OS_IOS',
621     'OS_LINUX',
622     'OS_MACOSX',
623     'OS_NACL',
624     'OS_NACL_NONSFI',
625     'OS_NACL_SFI',
626     'OS_NETBSD',
627     'OS_OPENBSD',
628     'OS_POSIX',
629     'OS_QNX',
630     'OS_SOLARIS',
631     'OS_WIN',
632 )
633
634
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',
642 ]
643
644
645 _GENERIC_PYDEPS_FILES = [
646     'chrome/test/chromedriver/test/run_py_tests.pydeps',
647     'tools/binary_size/supersize.pydeps',
648 ]
649
650
651 _ALL_PYDEPS_FILES = _ANDROID_SPECIFIC_PYDEPS_FILES + _GENERIC_PYDEPS_FILES
652
653
654 # Bypass the AUTHORS check for these accounts.
655 _KNOWN_ROBOTS = set(
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',))
663
664
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.
670   """
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
675
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))
682
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(
688       affected_file,
689       white_list=(file_inclusion_pattern, ),
690       black_list=black_list)
691
692   problems = []
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)):
699         problems.append(
700           '%s:%d\n    %s' % (local_path, line_number, line.strip()))
701
702   if problems:
703     return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
704   else:
705     return []
706
707
708 def _CheckNoProductionCodeUsingTestOnlyFunctionsJava(input_api, output_api):
709   """This is a simplified version of
710   _CheckNoProductionCodeUsingTestOnlyFunctions for Java files.
711   """
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)
717   # Catch calls.
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)
721
722   problems = []
723   sources = lambda x: input_api.FilterSourceFile(
724     x,
725     black_list=(('(?i).*test', r'.*\/junit\/')
726                 + input_api.DEFAULT_BLACK_LIST),
727     white_list=(r'.*\.java$',)
728   )
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:
738         continue
739       if (inclusion_re.search(line) and
740           not comment_re.search(line) and
741           not exclusion_re.search(line)):
742         problems.append(
743           '%s:%d\n    %s' % (local_path, line_number, line.strip()))
744
745   if problems:
746     return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
747   else:
748     return []
749
750
751 def _CheckNoIOStreamInHeaders(input_api, output_api):
752   """Checks to make sure no .h files include <iostream>."""
753   files = []
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'):
758       continue
759     contents = input_api.ReadFile(f)
760     if pattern.search(contents):
761       files.append(f)
762
763   if len(files):
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',
768         files) ]
769   return []
770
771
772 def _CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
773   """Checks to make sure no source files use UNIT_TEST."""
774   problems = []
775   for f in input_api.AffectedFiles():
776     if (not f.LocalPath().endswith(('.cc', '.mm'))):
777       continue
778
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))
782
783   if not problems:
784     return []
785   return [output_api.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
786       '\n'.join(problems))]
787
788
789 def _CheckDCHECK_IS_ONHasBraces(input_api, output_api):
790   """Checks to make sure DCHECK_IS_ON() does not skip the parentheses."""
791   errors = []
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'))):
796       continue
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)))
803   return errors
804
805
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.
812   if not '"' in line:
813     return False
814   histogram_prefix = line.split('\"')[1]
815   return histogram_prefix in histogram_name
816
817
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()
837       continue
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
844     else:
845       continue
846     for line_num, line in f.ChangedContents():
847       if last_line_matched_prefix:
848         suffix_found = split_line_suffix_re.search(line)
849         if suffix_found :
850           touched_histograms.append([suffix_found.group(1), f, line_num])
851           last_line_matched_prefix = False
852           continue
853       found = single_line_re.search(line)
854       if found:
855         touched_histograms.append([found.group(1), f, line_num])
856         continue
857       last_line_matched_prefix = split_line_prefix_re.search(line)
858
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:
867         break
868     if not histogram_name_found:
869       unmatched_histograms.append(histogram_info)
870
871   histograms_xml_path = 'tools/metrics/histograms/histograms.xml'
872   problems = []
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:
881             break
882         if not histogram_name_found:
883           problems.append(' [%s:%d] %s' %
884                           (f.LocalPath(), line_num, histogram_name))
885
886   if not problems:
887     return []
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)]
891
892
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;')
896   files = []
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)):
900         files.append(f)
901   if len(files):
902     return [output_api.PresubmitError(
903       'Use org.chromium.base.test.util.FlakyTest instead of '
904       'android.test.FlakyTest',
905       files)]
906   return []
907
908
909 def _CheckNoNewWStrings(input_api, output_api):
910   """Checks to make sure we don't introduce use of wstrings."""
911   problems = []
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()):
918       continue
919
920     allowWString = False
921     for line_num, line in f.ChangedContents():
922       if 'presubmit: allow wstring' in line:
923         allowWString = True
924       elif not allowWString and 'wstring' in line:
925         problems.append('    %s:%d' % (f.LocalPath(), line_num))
926         allowWString = False
927       else:
928         allowWString = False
929
930   if not problems:
931     return []
932   return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
933       '  If you are calling a cross-platform API that accepts a wstring, '
934       'fix the API.\n' +
935       '\n'.join(problems))]
936
937
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'
945       'overwritten.\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')]
949   return []
950
951
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()):
956     return []
957   # Outsource work to gclient verify
958   try:
959     input_api.subprocess.check_output(['gclient', 'verify'])
960     return []
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)]
965
966
967 def _CheckNoBannedFunctions(input_api, output_api):
968   """Make sure that banned functions are not used."""
969   warnings = []
970   errors = []
971
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):
976         return True
977     return False
978
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'):
982       return False
983     basename = input_api.os_path.basename(local_path)
984     if 'ios' in basename.split('_'):
985       return True
986     for sep in (input_api.os_path.sep, input_api.os_path.altsep):
987       if sep and 'ios' in local_path.split(sep):
988         return True
989     return False
990
991   def CheckForMatch(affected_file, line_num, line, func_name, message, error):
992     matched = False
993     if func_name[0:1] == '/':
994       regex = func_name[1:]
995       if input_api.re.search(regex, line):
996         matched = True
997     elif func_name in line:
998       matched = True
999     if matched:
1000       problems = warnings
1001       if error:
1002         problems = errors
1003       problems.append('    %s:%d:' % (affected_file.LocalPath(), line_num))
1004       for message_line in message:
1005         problems.append('      %s' % message_line)
1006
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)
1012
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)
1018
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)
1023
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):
1029           continue
1030         CheckForMatch(f, line_num, line, func_name, message, error)
1031
1032   result = []
1033   if (warnings):
1034     result.append(output_api.PresubmitPromptWarning(
1035         'Banned functions were used.\n' + '\n'.join(warnings)))
1036   if (errors):
1037     result.append(output_api.PresubmitError(
1038         'Banned functions were used.\n' + '\n'.join(errors)))
1039   return result
1040
1041
1042 def _CheckNoPragmaOnce(input_api, output_api):
1043   """Make sure that banned functions are not used."""
1044   files = []
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'):
1049       continue
1050     contents = input_api.ReadFile(f)
1051     if pattern.search(contents):
1052       files.append(f)
1053
1054   if files:
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',
1058         files)]
1059   return []
1060
1061
1062 def _CheckNoTrinaryTrueFalse(input_api, output_api):
1063   """Checks to make sure we don't introduce use of foo ? true : false."""
1064   problems = []
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')):
1068       continue
1069
1070     for line_num, line in f.ChangedContents():
1071       if pattern.match(line):
1072         problems.append('    %s:%d' % (f.LocalPath(), line_num))
1073
1074   if not problems:
1075     return []
1076   return [output_api.PresubmitPromptWarning(
1077       'Please consider avoiding the "? true : false" pattern if possible.\n' +
1078       '\n'.join(problems))]
1079
1080
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
1084   warning.
1085   """
1086   import sys
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
1091   try:
1092     sys.path = sys.path + [input_api.os_path.join(
1093         input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
1094     import checkdeps
1095     from cpp_checker import CppChecker
1096     from java_checker import JavaChecker
1097     from proto_checker import ProtoChecker
1098     from rules import Rule
1099   finally:
1100     # Restore sys.path to what it was before.
1101     sys.path = original_sys_path
1102
1103   added_includes = []
1104   added_imports = []
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])
1116
1117   deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
1118
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(
1124       added_includes):
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")
1130     else:
1131       warning_descriptions.append(description_with_path)
1132       warning_subjects.add("#includes")
1133
1134   for path, rule_type, rule_description in deps_checker.CheckAddedProtoImports(
1135       added_imports):
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")
1141     else:
1142       warning_descriptions.append(description_with_path)
1143       warning_subjects.add("imports")
1144
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")
1152     else:
1153       warning_descriptions.append(description_with_path)
1154       warning_subjects.add("imports")
1155
1156   results = []
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))
1169   return results
1170
1171
1172 def _CheckFilePermissions(input_api, output_api):
1173   """Check that all files have their permissions properly set."""
1174   if input_api.platform == 'win32':
1175     return []
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
1184       # repository.
1185       file_list.write(f.LocalPath() + '\n')
1186     file_list.close()
1187     args += ['--file-list', file_list.name]
1188     try:
1189       input_api.subprocess.check_output(args)
1190       return []
1191     except input_api.subprocess.CalledProcessError as error:
1192       return [output_api.PresubmitError(
1193           'checkperms.py failed:',
1194           long_text=error.output)]
1195
1196
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() ==
1206            'OWNERS']
1207   try:
1208     if files:
1209       input_api.subprocess.check_output(args + files)
1210     return []
1211   except input_api.subprocess.CalledProcessError as error:
1212     return [output_api.PresubmitError(
1213         'checkteamtags.py failed:',
1214         long_text=error.output)]
1215
1216
1217 def _CheckNoAuraWindowPropertyHInHeaders(input_api, output_api):
1218   """Makes sure we don't include ui/aura/window_property.h
1219   in header files.
1220   """
1221   pattern = input_api.re.compile(r'^#include\s*"ui/aura/window_property.h"')
1222   errors = []
1223   for f in input_api.AffectedFiles():
1224     if not f.LocalPath().endswith('.h'):
1225       continue
1226     for line_num, line in f.ChangedContents():
1227       if pattern.match(line):
1228         errors.append('    %s:%d' % (f.LocalPath(), line_num))
1229
1230   results = []
1231   if errors:
1232     results.append(output_api.PresubmitError(
1233       'Header files should not include ui/aura/window_property.h', errors))
1234   return results
1235
1236
1237 def _CheckForVersionControlConflictsInFile(input_api, f):
1238   pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
1239   errors = []
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
1244       continue
1245     if pattern.match(line):
1246       errors.append('    %s:%d %s' % (f.LocalPath(), line_num, line))
1247   return errors
1248
1249
1250 def _CheckForVersionControlConflicts(input_api, output_api):
1251   """Usually this is not intentional and will cause a compile failure."""
1252   errors = []
1253   for f in input_api.AffectedFiles():
1254     errors.extend(_CheckForVersionControlConflictsInFile(input_api, f))
1255
1256   results = []
1257   if errors:
1258     results.append(output_api.PresubmitError(
1259       'Version control conflict markers found, please resolve.', errors))
1260   return results
1261
1262 def _CheckGoogleSupportAnswerUrl(input_api, output_api):
1263   pattern = input_api.re.compile('support\.google\.com\/chrome.*/answer')
1264   errors = []
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))
1269
1270   results = []
1271   if errors:
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))
1275   return results
1276
1277
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).
1284     """
1285     return input_api.FilterSourceFile(
1286       affected_file,
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))
1291
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))
1301
1302   if problems:
1303     return [output_api.PresubmitPromptOrNotify(
1304         'Most layers below src/chrome/ should not hardcode service URLs.\n'
1305         'Are you sure this is correct?',
1306         ['  %s:%d:  %s' % (
1307             problem[0], problem[1], problem[2]) for problem in problems])]
1308   else:
1309     return []
1310
1311
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.
1316   """
1317   errors = []
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())
1325
1326   results = []
1327   if errors:
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))
1332   return results
1333
1334
1335 def _ExtractAddRulesFromParsedDeps(parsed_deps):
1336   """Extract the rules that add dependencies from a parsed DEPS file.
1337
1338   Args:
1339     parsed_deps: the locals dictionary from evaluating the DEPS file."""
1340   add_rules = set()
1341   add_rules.update([
1342       rule[1:] for rule in parsed_deps.get('include_rules', [])
1343       if rule.startswith('+') or rule.startswith('!')
1344   ])
1345   for _, rules in parsed_deps.get('specific_include_rules',
1346                                               {}).iteritems():
1347     add_rules.update([
1348         rule[1:] for rule in rules
1349         if rule.startswith('+') or rule.startswith('!')
1350     ])
1351   return add_rules
1352
1353
1354 def _ParseDeps(contents):
1355   """Simple helper for parsing DEPS files."""
1356   # Stubs for handling special syntax in the root DEPS file.
1357   class _VarImpl:
1358
1359     def __init__(self, local_scope):
1360       self._local_scope = local_scope
1361
1362     def Lookup(self, var_name):
1363       """Implements the Var syntax."""
1364       try:
1365         return self._local_scope['vars'][var_name]
1366       except KeyError:
1367         raise Exception('Var is not defined: %s' % var_name)
1368
1369   local_scope = {}
1370   global_scope = {
1371       'Var': _VarImpl(local_scope).Lookup,
1372   }
1373   exec contents in global_scope, local_scope
1374   return local_scope
1375
1376
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.
1380
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.
1384   """
1385   # We ignore deps entries on auto-generated directories.
1386   AUTO_GENERATED_DIRS = ['grit', 'jni']
1387
1388   old_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(old_contents))
1389   new_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(new_contents))
1390
1391   added_deps = new_deps.difference(old_deps)
1392
1393   results = set()
1394   for added_dep in added_deps:
1395     if added_dep.split('/')[0] in AUTO_GENERATED_DIRS:
1396       continue
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)
1400     else:
1401       results.add(os_path.join(added_dep, 'DEPS'))
1402   return results
1403
1404
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.
1410   """
1411   virtual_depended_on_files = set()
1412
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(
1420           input_api.os_path,
1421           '\n'.join(f.OldContents()),
1422           '\n'.join(f.NewContents())))
1423
1424   if not virtual_depended_on_files:
1425     return []
1426
1427   if input_api.is_committing:
1428     if input_api.tbr:
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
1439   else:
1440     output = output_api.PresubmitNotifyResult
1441
1442   owners_db = input_api.owners_db
1443   owner_email, reviewers = (
1444       input_api.canned_checks.GetCodereviewOwnerAndReviewers(
1445         input_api,
1446         owners_db.email_regexp,
1447         approval_needed=input_api.is_committing))
1448
1449   owner_email = owner_email or input_api.change.author_email
1450
1451   reviewers_plus_owner = set(reviewers)
1452   if owner_email:
1453     reviewers_plus_owner.add(owner_email)
1454   missing_files = owners_db.files_not_covered_by(virtual_depended_on_files,
1455                                                  reviewers_plus_owner)
1456
1457   # We strip the /DEPS part that was added by
1458   # _FilesToCheckForIncomingDeps to fake a path to a file in a
1459   # directory.
1460   def StripDeps(path):
1461     start_deps = path.rfind('/DEPS')
1462     if start_deps != -1:
1463       return path[:start_deps]
1464     else:
1465       return path
1466   unapproved_dependencies = ["'+%s'," % StripDeps(path)
1467                              for path in missing_files]
1468
1469   if unapproved_dependencies:
1470     output_list = [
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 [])))
1478     return output_list
1479
1480   return []
1481
1482
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[\\\/].*",
1518                  r"^tools[\\\/]",
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)
1527
1528   log_info = set([])
1529   printf = set([])
1530
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())
1537
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())
1542
1543   if log_info:
1544     return [output_api.PresubmitError(
1545       'These files spam the console log with LOG(INFO):',
1546       items=log_info)]
1547   if printf:
1548     return [output_api.PresubmitError(
1549       'These files spam the console log with printf/fprintf:',
1550       items=printf)]
1551   return []
1552
1553
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
1557      destroyed)."""
1558   they_who_must_be_named = [
1559     'base::AutoLock',
1560     'base::AutoReset',
1561     'base::AutoUnlock',
1562     'SkAutoAlphaRestore',
1563     'SkAutoBitmapShaderInstall',
1564     'SkAutoBlitterChoose',
1565     'SkAutoBounderCommit',
1566     'SkAutoCallProc',
1567     'SkAutoCanvasRestore',
1568     'SkAutoCommentBlock',
1569     'SkAutoDescriptor',
1570     'SkAutoDisableDirectionCheck',
1571     'SkAutoDisableOvalCheck',
1572     'SkAutoFree',
1573     'SkAutoGlyphCache',
1574     'SkAutoHDC',
1575     'SkAutoLockColors',
1576     'SkAutoLockPixels',
1577     'SkAutoMalloc',
1578     'SkAutoMaskFreeImage',
1579     'SkAutoMutexAcquire',
1580     'SkAutoPathBoundsUpdate',
1581     'SkAutoPDFRelease',
1582     'SkAutoRasterClipValidate',
1583     'SkAutoRef',
1584     'SkAutoTime',
1585     'SkAutoTrace',
1586     'SkAutoUnref',
1587   ]
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)
1594   errors = []
1595
1596   for f in input_api.AffectedFiles():
1597     if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
1598       continue
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))
1602
1603   if errors:
1604     return [output_api.PresubmitError(
1605       'These lines create anonymous variables that need to be named:',
1606       items=errors)]
1607   return []
1608
1609
1610 def _CheckUniquePtr(input_api, output_api):
1611   file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
1612   sources = lambda affected_file: input_api.FilterSourceFile(
1613       affected_file,
1614       black_list=(_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
1615                   input_api.DEFAULT_BLACK_LIST),
1616       white_list=(file_inclusion_pattern,))
1617
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 <...>
1629   # block.
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
1635       + r'\(\)')
1636
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
1644   # of arguments.
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)
1651
1652   problems_constructor = []
1653   problems_nullptr = []
1654   for f in input_api.AffectedSourceFiles(sources):
1655     for line_number, line in f.ChangedContents():
1656       # Disallow:
1657       # return std::unique_ptr<T>(foo);
1658       # bar = std::unique_ptr<T>(foo);
1659       # But allow:
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()))
1666       # Disallow:
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()))
1671
1672   errors = []
1673   if problems_nullptr:
1674     errors.append(output_api.PresubmitError(
1675         'The following files use std::unique_ptr<T>(). Use nullptr instead.',
1676         problems_nullptr))
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))
1682   return errors
1683
1684
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.
1691     return []
1692
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)
1699       if match:
1700         # Loads contents in tools/metrics/actions/actions.xml to memory. It's
1701         # loaded only once.
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))]
1714   return []
1715
1716
1717 def _ImportJSONCommentEater(input_api):
1718   import sys
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
1724
1725
1726 def _GetJSONParseError(input_api, filename, eat_comments=True):
1727   try:
1728     contents = input_api.ReadFile(filename)
1729     if eat_comments:
1730       json_comment_eater = _ImportJSONCommentEater(input_api)
1731       contents = json_comment_eater.Nom(contents)
1732
1733     input_api.json.loads(contents)
1734   except ValueError as e:
1735     return e
1736   return None
1737
1738
1739 def _GetIDLParseError(input_api, filename):
1740   try:
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:
1754     return e
1755
1756
1757 def _CheckParseErrors(input_api, output_api):
1758   """Check that IDL and JSON files do not contain syntax errors."""
1759   actions = {
1760     '.idl': _GetIDLParseError,
1761     '.json': _GetJSONParseError,
1762   }
1763   # Most JSON files are preprocessed and support comments, but these do not.
1764   json_no_comments_patterns = [
1765     r'^testing[\\\/]',
1766   ]
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[\\\/]',
1771   ]
1772
1773   def get_action(affected_file):
1774     filename = affected_file.LocalPath()
1775     return actions.get(input_api.os_path.splitext(filename)[1])
1776
1777   def FilterFile(affected_file):
1778     action = get_action(affected_file)
1779     if not action:
1780       return False
1781     path = affected_file.LocalPath()
1782
1783     if _MatchesFile(input_api, _KNOWN_INVALID_JSON_FILE_PATTERNS, path):
1784       return False
1785
1786     if (action == _GetIDLParseError and
1787         not _MatchesFile(input_api, idl_included_patterns, path)):
1788       return False
1789     return True
1790
1791   results = []
1792   for affected_file in input_api.AffectedFiles(
1793       file_filter=FilterFile, include_deletes=False):
1794     action = get_action(affected_file)
1795     kwargs = {}
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(),
1802                          **kwargs)
1803     if parse_error:
1804       results.append(output_api.PresubmitError('%s could not be parsed: %s' %
1805           (affected_file.LocalPath(), parse_error)))
1806   return results
1807
1808
1809 def _CheckJavaStyle(input_api, output_api):
1810   """Runs checkstyle on changed java files and returns errors if any exist."""
1811   import sys
1812   original_sys_path = sys.path
1813   try:
1814     sys.path = sys.path + [input_api.os_path.join(
1815         input_api.PresubmitLocalPath(), 'tools', 'android', 'checkstyle')]
1816     import checkstyle
1817   finally:
1818     # Restore sys.path to what it was before.
1819     sys.path = original_sys_path
1820
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)
1824
1825
1826 def _MatchesFile(input_api, patterns, path):
1827   for pattern in patterns:
1828     if input_api.re.search(pattern, path):
1829       return True
1830   return False
1831
1832
1833 def _GetOwnersFilesToCheckForIpcOwners(input_api):
1834   """Gets a list of OWNERS files to check for correct security owners.
1835
1836   Returns:
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.
1839   """
1840   # Whether or not a file affects IPC is (mostly) determined by a simple list
1841   # of filename patterns.
1842   file_patterns = [
1843       # Legacy IPC:
1844       '*_messages.cc',
1845       '*_messages*.h',
1846       '*_param_traits*.*',
1847       # Mojo IPC:
1848       '*.mojom',
1849       '*_mojom_traits*.*',
1850       '*_struct_traits*.*',
1851       '*_type_converter*.*',
1852       '*.typemap',
1853       # Android native IPC:
1854       '*.aidl',
1855       # Blink uses a different file naming convention:
1856       '*EnumTraits*.*',
1857       "*MojomTraits*.*",
1858       '*StructTraits*.*',
1859       '*TypeConverter*.*',
1860   ]
1861
1862   # These third_party directories do not contain IPCs, but contain files
1863   # matching the above patterns, which trigger false positives.
1864   exclude_paths = [
1865       'third_party/crashpad/*',
1866       'third_party/third_party/blink/renderer/platform/bindings/*',
1867       'third_party/win_build_output/*',
1868   ]
1869
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:
1878   # {
1879   #   '*.mojom': {
1880   #     'files': ...,
1881   #     'rules': [
1882   #       'per-file *.mojom=set noparent',
1883   #       'per-file *.mojom=file://ipc/SECURITY_OWNERS',
1884   #     ],
1885   #   },
1886   #   '*_struct_traits*.*': {
1887   #     'files': ...,
1888   #     'rules': [
1889   #       'per-file *_struct_traits*.*=set noparent',
1890   #       'per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS',
1891   #     ],
1892   #   },
1893   # }
1894   to_check = {}
1895
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] = {
1903           'files': [],
1904           'rules': [
1905               'per-file %s=set noparent' % pattern,
1906               'per-file %s=file://ipc/SECURITY_OWNERS' % pattern,
1907           ]
1908       }
1909     to_check[owners_file][pattern]['files'].append(input_file)
1910
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
1915   # *.mojom files.
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,
1921                          f.LocalPath())):
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)
1926       try:
1927         json_content = input_api.json.loads(json_lines)
1928       except:
1929         # There's another PRESUBMIT check that already verifies that JSON files
1930         # are not invalid, so no need to emit another warning here.
1931         continue
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):
1937         skip = False
1938         for exclude in exclude_paths:
1939           if input_api.fnmatch.fnmatch(f.LocalPath(), exclude):
1940             skip = True
1941             break
1942         if skip:
1943           continue
1944         AddPatternToCheck(f, pattern)
1945         break
1946
1947   return to_check
1948
1949
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)
1953
1954   if to_check:
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')
1958
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():
1962     try:
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
1967                            ]
1968     except IOError:
1969       # No OWNERS file, so all the rules are definitely missing.
1970       continue
1971
1972   # All the remaining lines weren't found in OWNERS files, so emit an error.
1973   errors = []
1974   for owners_file, patterns in to_check.iteritems():
1975     missing_lines = []
1976     files = []
1977     for _, entry in patterns.iteritems():
1978       missing_lines.extend(entry['rules'])
1979       files.extend(['  %s' % f.LocalPath() for f in entry['files']])
1980     if missing_lines:
1981       errors.append(
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)))
1986
1987   results = []
1988   if errors:
1989     if input_api.is_committing:
1990       output = output_api.PresubmitError
1991     else:
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)))
1997
1998   return results
1999
2000
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
2004      declaration.
2005   """
2006   results = []
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')):
2017       continue
2018
2019     if not f.LocalPath().endswith('.h'):
2020       continue
2021
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))
2025
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))
2030       if count == 1:
2031         useless_fwd_decls.append(decl)
2032
2033     if not useless_fwd_decls:
2034       continue
2035
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)
2045
2046   return results
2047
2048
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).
2053   """
2054   toast_import_pattern = input_api.re.compile(
2055       r'^import android\.widget\.Toast;$')
2056
2057   errors = []
2058
2059   sources = lambda affected_file: input_api.FilterSourceFile(
2060       affected_file,
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$',))
2067
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))
2072
2073   results = []
2074
2075   if errors:
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.',
2081         errors))
2082
2083   return results
2084
2085
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)
2090   """
2091
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.*",
2099   ]
2100
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)
2112
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)
2117
2118   tag_decl_errors = []
2119   tag_length_errors = []
2120   tag_errors = []
2121   tag_with_dot_errors = []
2122   util_log_errors = []
2123
2124   for f in input_api.AffectedSourceFiles(sources):
2125     file_content = input_api.ReadFile(f)
2126     has_modified_logs = False
2127
2128     # Per line checks
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():
2134
2135         # Check if the new line is doing some logging
2136         match = log_call_pattern.search(line)
2137         if match:
2138           has_modified_logs = True
2139
2140           # Make sure it uses "TAG"
2141           if not match.group('tag') == 'TAG':
2142             tag_errors.append("%s:%d" % (f.LocalPath(), line_num))
2143     else:
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))
2148
2149     # Per file checks
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
2154       if not tag_name:
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())
2160
2161   results = []
2162   if tag_decl_errors:
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,
2167         tag_decl_errors))
2168
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,
2173         tag_length_errors))
2174
2175   if tag_errors:
2176     results.append(output_api.PresubmitPromptWarning(
2177         'Please use a variable named "TAG" for your log tags.\n' + REF_MSG,
2178         tag_errors))
2179
2180   if util_log_errors:
2181     results.append(output_api.PresubmitPromptWarning(
2182         'Please use org.chromium.base.Log for new logs.\n' + REF_MSG,
2183         util_log_errors))
2184
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))
2189
2190   return results
2191
2192
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)
2200   errors = []
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))
2205
2206   results = []
2207   if errors:
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))
2212   return results
2213
2214
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.
2219   """
2220   class_declaration_pattern = input_api.re.compile(r'^public class \w*Test ')
2221
2222   sources = lambda x: input_api.FilterSourceFile(
2223       x, white_list=(r'.*Test\.java$',), black_list=None)
2224   errors = []
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))
2233         if '{' in line:
2234           class_declaration_start_flag = False
2235
2236   results = []
2237   if errors:
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))
2243   return results
2244
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)
2252   errors = []
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))
2257
2258   results = []
2259   if errors:
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))
2265   return results
2266
2267
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()))
2273   errors = []
2274   for f in input_api.AffectedFiles(include_deletes=False,
2275                                    file_filter=file_filter):
2276     errors.append('    %s' % f.LocalPath())
2277
2278   results = []
2279   if errors:
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))
2285   return results
2286
2287
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.
2291   """
2292   valuecallback_import_pattern = input_api.re.compile(
2293       r'^import android\.webkit\.ValueCallback;$')
2294
2295   errors = []
2296
2297   sources = lambda affected_file: input_api.FilterSourceFile(
2298       affected_file,
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$',))
2304
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))
2309
2310   results = []
2311
2312   if errors:
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.',
2318         errors))
2319
2320   return results
2321
2322
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
2328
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]
2335
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)
2343
2344   def _CreateFilesToPydepsMap(self):
2345     """Returns a map of local_path -> list_of_pydeps."""
2346     ret = {}
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)
2350     return ret
2351
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
2368
2369   def DetermineIfStale(self, pydeps_path):
2370     """Runs print_python_deps.py to see if the files is stale."""
2371     import difflib
2372     import os
2373
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))
2384
2385
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':
2392     return []
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
2397   results = []
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()))
2415
2416   if results:
2417     return results
2418
2419   checker = checker_for_tests or PydepsChecker(input_api, pydeps_files)
2420
2421   for pydep_path in checker.ComputeAffectedPydeps():
2422     try:
2423       result = checker.DetermineIfStale(pydep_path)
2424       if result:
2425         cmd, diff = result
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)]
2433
2434   return results
2435
2436
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)
2447
2448   pattern = input_api.re.compile(r'(?<!class\sbase::)Singleton\s*<')
2449   files = []
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)):
2457           files.append(f)
2458           break
2459
2460   if files:
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.',
2465         files) ]
2466   return []
2467
2468
2469 _DEPRECATED_CSS = [
2470   # Values
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" ),
2477
2478   # Properties
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" ),
2484
2485   # Functions
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" ),
2492 ]
2493
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. """
2500   results = []
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",
2506                  r"^chrome/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)))
2519   return results
2520
2521
2522 _DEPRECATED_JS = [
2523   ( "__lookupGetter__", "Object.getOwnPropertyDescriptor" ),
2524   ( "__defineGetter__", "Object.defineProperty" ),
2525   ( "__defineSetter__", "Object.defineProperty" ),
2526 ]
2527
2528 def _CheckNoDeprecatedJs(input_api, output_api):
2529   """Make sure that we don't use deprecated JS in Chrome code."""
2530   results = []
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)))
2543   return results
2544
2545 def _CheckForRiskyJsArrowFunction(line_number, line):
2546   if ' => ' in line:
2547     return "line %d, is using an => (arrow) function\n %s\n" % (
2548         line_number, line)
2549   return ''
2550
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" % (
2554         line_number, line)
2555   return ''
2556
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)
2563   results = []
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),
2570       ])
2571
2572       const_let_error_lines += filter(None, [
2573         _CheckForRiskyJsConstLet(input_api, lnum, line),
2574       ])
2575
2576     if arrow_error_lines:
2577       arrow_error_lines = map(
2578           lambda e: "%s:%s" % (f.LocalPath(), e), arrow_error_lines)
2579       results.append(
2580           output_api.PresubmitPromptWarning('\n'.join(arrow_error_lines + [
2581 """
2582 Use of => (arrow) operator detected in:
2583 %s
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
2586 """ % f.LocalPath()
2587           ])))
2588
2589     if const_let_error_lines:
2590       const_let_error_lines = map(
2591           lambda e: "%s:%s" % (f.LocalPath(), e), const_let_error_lines)
2592       results.append(
2593           output_api.PresubmitPromptWarning('\n'.join(const_let_error_lines + [
2594 """
2595 Use of const/let keywords detected in:
2596 %s
2597 Please ensure your code does not run on iOS9 because const/let is not fully
2598 supported.
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
2601 """ % f.LocalPath()
2602           ])))
2603
2604   return results
2605
2606 def _CheckForRelativeIncludes(input_api, output_api):
2607   # Need to set the sys.path so PRESUBMIT_test.py runs properly
2608   import sys
2609   original_sys_path = sys.path
2610   try:
2611     sys.path = sys.path + [input_api.os_path.join(
2612         input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
2613     from cpp_checker import CppChecker
2614   finally:
2615     # Restore sys.path to what it was before.
2616     sys.path = original_sys_path
2617
2618   bad_files = {}
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')):
2623       continue
2624
2625     if not CppChecker.IsCppFile(f.LocalPath()):
2626       continue
2627
2628     relative_includes = [line for _, line in f.ChangedContents()
2629                          if "#include" in line and "../" in line]
2630     if not relative_includes:
2631       continue
2632     bad_files[f.LocalPath()] = relative_includes
2633
2634   if not bad_files:
2635     return []
2636
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)
2643
2644   results = []
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))
2651
2652   return results
2653
2654
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':
2663     return (
2664         'Entry at line %d must have a string literal \'filepath\' as key' %
2665         value.lineno)
2666   return None
2667
2668
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
2674   return None
2675
2676
2677 def _CheckWATCHLISTSEntries(wd_dict, w_dict, ast):
2678   mismatch_template = (
2679       'Mismatch between WATCHLIST_DEFINITIONS entry (%s) and WATCHLISTS '
2680       'entry (%s)')
2681
2682   i = 0
2683   last_key = ''
2684   while True:
2685     if i >= len(wd_dict.keys):
2686       if i >= len(w_dict.keys):
2687         return None
2688       return mismatch_template % ('missing', 'line %d' % w_dict.keys[i].lineno)
2689     elif i >= len(w_dict.keys):
2690       return (
2691           mismatch_template % ('line %d' % wd_dict.keys[i].lineno, 'missing'))
2692
2693     wd_key = wd_dict.keys[i]
2694     w_key = w_dict.keys[i]
2695
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
2700
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
2704
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))
2709
2710     if wd_key.s < last_key:
2711       return (
2712           'WATCHLISTS dict is not sorted lexicographically at line %d and %d' %
2713           (wd_key.lineno, w_key.lineno))
2714     last_key = wd_key.s
2715
2716     i = i + 1
2717
2718
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'
2725
2726   first_key = dictionary.keys[0]
2727   first_value = dictionary.values[0]
2728   second_key = dictionary.keys[1]
2729   second_value = dictionary.values[1]
2730
2731   if (not isinstance(first_key, ast.Str) or
2732       first_key.s != 'WATCHLIST_DEFINITIONS' or
2733       not isinstance(first_value, ast.Dict)):
2734     return (
2735         'The first entry of the dict in WATCHLISTS file must be '
2736         'WATCHLIST_DEFINITIONS dict')
2737
2738   if (not isinstance(second_key, ast.Str) or
2739       second_key.s != 'WATCHLISTS' or
2740       not isinstance(second_value, ast.Dict)):
2741     return (
2742         'The second entry of the dict in WATCHLISTS file must be '
2743         'WATCHLISTS dict')
2744
2745   return _CheckWATCHLISTSEntries(first_value, second_value, ast)
2746
2747
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')
2752
2753       try:
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))]
2768
2769       result = _CheckWATCHLISTSSyntax(expression, input_api.ast)
2770       if result is not None:
2771         return [output_api.PresubmitError(result)]
2772       break
2773
2774   return []
2775
2776
2777 def _AndroidSpecificOnUploadChecks(input_api, output_api):
2778   """Groups checks that target android code."""
2779   results = []
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))
2787   return results
2788
2789
2790 def _CommonChecks(input_api, output_api):
2791   """Checks common to both upload and commit."""
2792   results = []
2793   results.extend(input_api.canned_checks.PanProjectChecks(
2794       input_api, output_api,
2795       excluded_paths=_EXCLUDED_PATHS))
2796
2797   author = input_api.change.author_email
2798   if author and author not in _KNOWN_ROBOTS:
2799     results.extend(
2800         input_api.canned_checks.CheckAuthorizedAuthor(input_api, output_api))
2801
2802   results.extend(
2803       _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
2804   results.extend(
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))
2827   results.extend(
2828       input_api.canned_checks.CheckChangeHasNoTabs(
2829           input_api,
2830           output_api,
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)))
2852
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$']))
2865   return results
2866
2867
2868 def _CheckPatchFiles(input_api, output_api):
2869   problems = [f.LocalPath() for f in input_api.AffectedFiles()
2870       if f.LocalPath().endswith(('.orig', '.rej'))]
2871   if problems:
2872     return [output_api.PresubmitError(
2873         "Don't commit .rej and .orig files.", problems)]
2874   else:
2875     return []
2876
2877
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]+$')
2885   errors = []
2886   for f in input_api.AffectedFiles():
2887     if not f.LocalPath().endswith(('.h', '.c', '.cc', '.cpp', '.m', '.mm')):
2888       continue
2889     found_line_number = None
2890     found_macro = None
2891     for line_num, line in f.ChangedContents():
2892       match = macro_re.search(line)
2893       if match:
2894         found_line_number = line_num
2895         found_macro = match.group(2)
2896         break
2897     if not found_line_number:
2898       continue
2899
2900     found_include = False
2901     for line in f.NewContents():
2902       if include_re.search(line):
2903         found_include = True
2904         break
2905     if found_include:
2906       continue
2907
2908     if not f.LocalPath().endswith('.h'):
2909       primary_header_path = extension_re.sub('.h', f.AbsoluteLocalPath())
2910       try:
2911         content = input_api.ReadFile(primary_header_path, 'r')
2912         if include_re.search(content):
2913           continue
2914       except IOError:
2915         pass
2916     errors.append('%s:%d %s macro is used without including build/'
2917                   'build_config.h.'
2918                   % (f.LocalPath(), found_line_number, found_macro))
2919   if errors:
2920     return [output_api.PresubmitPromptWarning('\n'.join(errors))]
2921   return []
2922
2923
2924 def _DidYouMeanOSMacro(bad_macro):
2925   try:
2926     return {'A': 'OS_ANDROID',
2927             'B': 'OS_BSD',
2928             'C': 'OS_CHROMEOS',
2929             'F': 'OS_FREEBSD',
2930             'L': 'OS_LINUX',
2931             'M': 'OS_MACOSX',
2932             'N': 'OS_NACL',
2933             'O': 'OS_OPENBSD',
2934             'P': 'OS_POSIX',
2935             'S': 'OS_SOLARIS',
2936             'W': 'OS_WIN'}[bad_macro[3].upper()]
2937   except KeyError:
2938     return ''
2939
2940
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_[^)]+)\)')
2945   results = []
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(),
2953                                              lnum,
2954                                              match.group(1),
2955                                              did_you_mean))
2956   return results
2957
2958
2959 def _CheckForInvalidOSMacros(input_api, output_api):
2960   """Check all affected files for invalid OS macros."""
2961   bad_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))
2965
2966   if not bad_macros:
2967     return []
2968
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)]
2972
2973
2974 def _CheckForInvalidIfDefinedMacrosInFile(input_api, f):
2975   """Check all affected files for invalid "if defined" macros."""
2976   ALWAYS_DEFINED_MACROS = (
2977       "TARGET_CPU_PPC",
2978       "TARGET_CPU_PPC64",
2979       "TARGET_CPU_68K",
2980       "TARGET_CPU_X86",
2981       "TARGET_CPU_ARM",
2982       "TARGET_CPU_MIPS",
2983       "TARGET_CPU_SPARC",
2984       "TARGET_CPU_ALPHA",
2985       "TARGET_IPHONE_SIMULATOR",
2986       "TARGET_OS_EMBEDDED",
2987       "TARGET_OS_IPHONE",
2988       "TARGET_OS_MAC",
2989       "TARGET_OS_UNIX",
2990       "TARGET_OS_WIN32",
2991   )
2992   ifdef_macro = input_api.re.compile(r'^\s*#.*(?:ifdef\s|defined\()([^\s\)]+)')
2993   results = []
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(),
3000                                                lnum,
3001                                                always_defined,
3002                                                did_you_mean))
3003   return results
3004
3005
3006 def _CheckForInvalidIfDefinedMacros(input_api, output_api):
3007   """Check all affected files for invalid "if defined" macros."""
3008   bad_macros = []
3009   for f in input_api.AffectedFiles():
3010     if f.LocalPath().startswith('third_party/sqlite/'):
3011       continue
3012     if f.LocalPath().endswith(('.h', '.c', '.cc', '.m', '.mm')):
3013       bad_macros.extend(_CheckForInvalidIfDefinedMacrosInFile(input_api, f))
3014
3015   if not bad_macros:
3016     return []
3017
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.',
3021       bad_macros)]
3022
3023
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
3027   """
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)
3031
3032   problems = []
3033   for f in input_api.AffectedSourceFiles(None):
3034     local_path = f.LocalPath()
3035     if not local_path.endswith('.h'):
3036       continue
3037     for line_number, line in f.ChangedContents():
3038       if inclusion_pattern.search(line) and not comment_pattern.search(line):
3039         problems.append(
3040           '%s:%d\n    %s' % (local_path, line_number, line.strip()))
3041
3042   if problems:
3043     return [output_api.PresubmitPromptWarning(
3044         _IPC_ENUM_TRAITS_DEPRECATED, problems)]
3045   else:
3046     return []
3047
3048
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.
3052   """
3053   problems = []
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)
3060
3061   if problems:
3062     return [output_api.PresubmitError(_LONG_PATH_ERROR, problems)]
3063   else:
3064     return []
3065
3066
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".
3071   """
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'))))
3081
3082   def replace_special_with_underscore(string):
3083     return input_api.re.sub(r'[\\/.-]', '_', string)
3084
3085   errors = []
3086
3087   for f in input_api.AffectedSourceFiles(is_chromium_header_file):
3088     guard_name = None
3089     guard_line_number = None
3090     seen_guard_end = False
3091
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()
3096
3097     expected_guard = replace_special_with_underscore(
3098       file_with_path.upper() + '_')
3099
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.
3106     #
3107     # As code becomes more uniform, this could be made stricter.
3108
3109     guard_name_pattern_list = [
3110       # Anything with the right suffix (maybe with an extra _).
3111       r'\w+_H__?',
3112
3113       # To cover include guards with old Blink style.
3114       r'\w+_h',
3115
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*',
3119     ]
3120     guard_name_pattern = '|'.join(guard_name_pattern_list)
3121     guard_pattern = input_api.re.compile(
3122       r'#ifndef\s+(' + guard_name_pattern + ')')
3123
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.
3127         break
3128
3129       if guard_name is None:
3130         match = guard_pattern.match(line)
3131         if match:
3132           guard_name = match.group(1)
3133           guard_line_number = line_number
3134
3135           # We allow existing files to use include guards whose names
3136           # don't match the chromium style guide, but new files should
3137           # get it right.
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)))
3144       else:
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)))
3153
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.
3162
3163     if guard_name is None:
3164       errors.append(output_api.PresubmitPromptWarning(
3165         'Missing include guard %s' % expected_guard,
3166         [f.LocalPath()],
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)))
3172
3173   return errors
3174
3175
3176 def _CheckForWindowsLineEndings(input_api, output_api):
3177   """Check source code and known ascii text files for Windows style line
3178   endings.
3179   """
3180   known_text_files = r'.*\.(txt|html|htm|mhtml|py|gyp|gypi|gn|isolate)$'
3181
3182   file_inclusion_pattern = (
3183     known_text_files,
3184     r'.+%s' % _IMPLEMENTATION_EXTENSIONS
3185   )
3186
3187   problems = []
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'):
3194         include_file = True
3195     if include_file:
3196       problems.append(f.LocalPath())
3197
3198   if problems:
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))]
3202
3203   return []
3204
3205
3206 def _CheckSyslogUseWarning(input_api, output_api, source_file_filter=None):
3207   """Checks that all source files use SYSLOG properly."""
3208   syslog_files = []
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))
3213
3214   if syslog_files:
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)]
3218   return []
3219
3220
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/...)
3224   """
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)
3229
3230   pattern = input_api.re.compile(r'//.*(?<!:\/\/)crbug[.com]*')
3231   accepted_pattern = input_api.re.compile(r'//.*TODO\(crbug[.com]*');
3232   problems = []
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))
3237
3238   if problems:
3239     return [output_api.PresubmitPromptWarning(
3240       'Found unprefixed crbug.com URL(s), consider prepending https://\n'+
3241       '\n'.join(problems))]
3242   return []
3243
3244
3245 def CheckChangeOnUpload(input_api, output_api):
3246   results = []
3247   results.extend(_CommonChecks(input_api, output_api))
3248   results.extend(_CheckValidHostsInDEPS(input_api, output_api))
3249   results.extend(
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))
3257   return results
3258
3259
3260 def GetTryServerMasterForBot(bot):
3261   """Returns the Try Server master for the given bot.
3262
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.
3265   """
3266   # Potentially ambiguous bot names are listed explicitly.
3267   master_map = {
3268       'chromium_presubmit': 'master.tryserver.chromium.linux',
3269       'tools_build_presubmit': 'master.tryserver.chromium.linux',
3270   }
3271   master = master_map.get(bot)
3272   if not master:
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'
3277     elif 'win' in bot:
3278       master = 'master.tryserver.chromium.win'
3279     elif 'mac' in bot or 'ios' in bot:
3280       master = 'master.tryserver.chromium.mac'
3281   return master
3282
3283
3284 def CheckChangeOnCommit(input_api, output_api):
3285   results = []
3286   results.extend(_CommonChecks(input_api, output_api))
3287   # Make sure the tree is 'open'.
3288   results.extend(input_api.canned_checks.CheckTreeIsOpen(
3289       input_api,
3290       output_api,
3291       json_url='http://chromium-status.appspot.com/current?format=json'))
3292
3293   results.extend(
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))
3299   return results