[M67 Dev][Product TV] Enable Cursor with ecore-wl2
[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       'base::SequenceChecker',
346       (
347         'Consider using SEQUENCE_CHECKER macros instead of the class directly.',
348       ),
349       False,
350       (),
351     ),
352     (
353       'base::ThreadChecker',
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.',
406       ),
407       True,
408       (
409         r'^third_party/leveldatabase/.*\.(cc|h)$',
410       ),
411     ),
412     (
413       'MessageLoop::QuitWhenIdleClosure',
414       (
415         'MessageLoop::QuitWhenIdleClosure is deprecated. Please use a',
416         'QuitWhenIdleClosure obtained from a specific RunLoop instance.',
417       ),
418       False,
419       (),
420     ),
421     (
422       'RunLoop::QuitCurrent',
423       (
424         'Please migrate away from RunLoop::QuitCurrent*() methods. Use member',
425         'methods of a specific RunLoop instance instead.',
426       ),
427       True,
428       (),
429     ),
430     (
431       'base::ScopedMockTimeMessageLoopTaskRunner',
432       (
433         'ScopedMockTimeMessageLoopTaskRunner is deprecated.',
434       ),
435       True,
436       (),
437     ),
438     (
439       r'std::regex',
440       (
441         'Using std::regex adds unnecessary binary size to Chrome. Please use',
442         're2::RE2 instead (crbug.com/755321)',
443       ),
444       True,
445       (),
446     ),
447     (
448       (r'/base::ThreadRestrictions::(ScopedAllowIO|AssertIOAllowed|'
449        r'DisallowWaiting|AssertWaitAllowed|SetWaitAllowed|ScopedAllowWait)'),
450       (
451         'Use the new API in base/threading/thread_restrictions.h.',
452       ),
453       True,
454       (),
455     ),
456     (
457       r'/\bbase::Bind\(',
458       (
459           'Please consider using base::Bind{Once,Repeating} instead',
460           'of base::Bind. (crbug.com/714018)',
461       ),
462       False,
463       (),
464     ),
465     (
466       r'/\bbase::Callback<',
467       (
468           'Please consider using base::{Once,Repeating}Callback instead',
469           'of base::Callback. (crbug.com/714018)',
470       ),
471       False,
472       (),
473     ),
474     (
475       r'/\bbase::Closure\b',
476       (
477           'Please consider using base::{Once,Repeating}Closure instead',
478           'of base::Closure. (crbug.com/714018)',
479       ),
480       False,
481       (),
482     ),
483     (
484       r'RunMessageLoop',
485       (
486           'RunMessageLoop is deprecated, use RunLoop instead.',
487       ),
488       False,
489       (),
490     ),
491     (
492       r'RunThisRunLoop',
493       (
494           'RunThisRunLoop is deprecated, use RunLoop directly instead.',
495       ),
496       False,
497       (),
498     ),
499     (
500       r'RunAllPendingInMessageLoop()',
501       (
502           "Prefer RunLoop over RunAllPendingInMessageLoop, please contact gab@",
503           "if you're convinced you need this.",
504       ),
505       False,
506       (),
507     ),
508     (
509       r'RunAllPendingInMessageLoop(BrowserThread',
510       (
511           'RunAllPendingInMessageLoop is deprecated. Use RunLoop for',
512           'BrowserThread::UI, TestBrowserThreadBundle::RunIOThreadUntilIdle',
513           'for BrowserThread::IO, and prefer RunLoop::QuitClosure to observe',
514           'async events instead of flushing threads.',
515       ),
516       False,
517       (),
518     ),
519     (
520       r'MessageLoopRunner',
521       (
522           'MessageLoopRunner is deprecated, use RunLoop instead.',
523       ),
524       False,
525       (),
526     ),
527     (
528       r'GetDeferredQuitTaskForRunLoop',
529       (
530           "GetDeferredQuitTaskForRunLoop shouldn't be needed, please contact",
531           "gab@ if you found a use case where this is the only solution.",
532       ),
533       False,
534       (),
535     ),
536     (
537       'sqlite3_initialize',
538       (
539         'Instead of sqlite3_initialize, depend on //sql, ',
540         '#include "sql/initialize.h" and use sql::EnsureSqliteInitialized().',
541       ),
542       True,
543       (
544         r'^sql/initialization\.(cc|h)$',
545         r'^third_party/sqlite/.*\.(c|cc|h)$',
546       ),
547     ),
548     (
549       'net::URLFetcher',
550       (
551         'net::URLFetcher should no longer be used in content embedders. ',
552         'Instead, use network::SimpleURLLoader instead, which supports ',
553         'an out-of-process network stack. ',
554         'net::URLFetcher may still be used in binaries that do not embed',
555         'content.',
556       ),
557       False,
558       (
559         r'^ios[\\\/].*\.(cc|h)$',
560         r'.*[\\\/]ios[\\\/].*\.(cc|h)$',
561         r'.*_ios\.(cc|h)$',
562         r'^net[\\\/].*\.(cc|h)$',
563         r'.*[\\\/]tools[\\\/].*\.(cc|h)$',
564       ),
565     ),
566 )
567
568
569 _IPC_ENUM_TRAITS_DEPRECATED = (
570     'You are using IPC_ENUM_TRAITS() in your code. It has been deprecated.\n'
571     'See http://www.chromium.org/Home/chromium-security/education/'
572     'security-tips-for-ipc')
573
574 _JAVA_MULTIPLE_DEFINITION_EXCLUDED_PATHS = [
575     r".*[\\\/]BuildHooksAndroidImpl\.java",
576     r".*[\\\/]LicenseContentProvider\.java",
577 ]
578
579 # These paths contain test data and other known invalid JSON files.
580 _KNOWN_INVALID_JSON_FILE_PATTERNS = [
581     r'test[\\\/]data[\\\/]',
582     r'^components[\\\/]policy[\\\/]resources[\\\/]policy_templates\.json$',
583     r'^third_party[\\\/]protobuf[\\\/]',
584     r'^third_party[\\\/]WebKit[\\\/]LayoutTests[\\\/]external[\\\/]wpt[\\\/]',
585 ]
586
587
588 _VALID_OS_MACROS = (
589     # Please keep sorted.
590     'OS_AIX',
591     'OS_ANDROID',
592     'OS_ASMJS',
593     'OS_BSD',
594     'OS_CAT',       # For testing.
595     'OS_CHROMEOS',
596     'OS_FREEBSD',
597     'OS_FUCHSIA',
598     'OS_IOS',
599     'OS_LINUX',
600     'OS_MACOSX',
601     'OS_NACL',
602     'OS_NACL_NONSFI',
603     'OS_NACL_SFI',
604     'OS_NETBSD',
605     'OS_OPENBSD',
606     'OS_POSIX',
607     'OS_QNX',
608     'OS_SOLARIS',
609     'OS_WIN',
610 )
611
612
613 _ANDROID_SPECIFIC_PYDEPS_FILES = [
614     'build/android/test_runner.pydeps',
615     'build/android/test_wrapper/logdog_wrapper.pydeps',
616     'build/secondary/third_party/android_platform/'
617         'development/scripts/stack.pydeps',
618     'net/tools/testserver/testserver.pydeps',
619 ]
620
621
622 _GENERIC_PYDEPS_FILES = [
623     'chrome/test/chromedriver/test/run_py_tests.pydeps',
624 ]
625
626
627 _ALL_PYDEPS_FILES = _ANDROID_SPECIFIC_PYDEPS_FILES + _GENERIC_PYDEPS_FILES
628
629
630 # Bypass the AUTHORS check for these accounts.
631 _KNOWN_ROBOTS = set(
632     '%s-chromium-autoroll@skia-buildbots.google.com.iam.gserviceaccount.com' % s
633     for s in ('afdo', 'angle', 'catapult', 'chromite', 'depot-tools',
634               'fuchsia-sdk', 'nacl', 'pdfium', 'skia', 'src-internal', 'webrtc')
635   ) | set('%s@appspot.gserviceaccount.com' % s for s in ('findit-for-me',))
636
637
638 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
639   """Attempts to prevent use of functions intended only for testing in
640   non-testing code. For now this is just a best-effort implementation
641   that ignores header files and may have some false positives. A
642   better implementation would probably need a proper C++ parser.
643   """
644   # We only scan .cc files and the like, as the declaration of
645   # for-testing functions in header files are hard to distinguish from
646   # calls to such functions without a proper C++ parser.
647   file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
648
649   base_function_pattern = r'[ :]test::[^\s]+|ForTest(s|ing)?|for_test(s|ing)?'
650   inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
651   comment_pattern = input_api.re.compile(r'//.*(%s)' % base_function_pattern)
652   exclusion_pattern = input_api.re.compile(
653     r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
654       base_function_pattern, base_function_pattern))
655
656   def FilterFile(affected_file):
657     black_list = (_EXCLUDED_PATHS +
658                   _TEST_CODE_EXCLUDED_PATHS +
659                   input_api.DEFAULT_BLACK_LIST)
660     return input_api.FilterSourceFile(
661       affected_file,
662       white_list=(file_inclusion_pattern, ),
663       black_list=black_list)
664
665   problems = []
666   for f in input_api.AffectedSourceFiles(FilterFile):
667     local_path = f.LocalPath()
668     for line_number, line in f.ChangedContents():
669       if (inclusion_pattern.search(line) and
670           not comment_pattern.search(line) and
671           not exclusion_pattern.search(line)):
672         problems.append(
673           '%s:%d\n    %s' % (local_path, line_number, line.strip()))
674
675   if problems:
676     return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
677   else:
678     return []
679
680
681 def _CheckNoProductionCodeUsingTestOnlyFunctionsJava(input_api, output_api):
682   """This is a simplified version of
683   _CheckNoProductionCodeUsingTestOnlyFunctions for Java files.
684   """
685   javadoc_start_re = input_api.re.compile(r'^\s*/\*\*')
686   javadoc_end_re = input_api.re.compile(r'^\s*\*/')
687   name_pattern = r'ForTest(s|ing)?'
688   # Describes an occurrence of "ForTest*" inside a // comment.
689   comment_re = input_api.re.compile(r'//.*%s' % name_pattern)
690   # Catch calls.
691   inclusion_re = input_api.re.compile(r'(%s)\s*\(' % name_pattern)
692   # Ignore definitions. (Comments are ignored separately.)
693   exclusion_re = input_api.re.compile(r'(%s)[^;]+\{' % name_pattern)
694
695   problems = []
696   sources = lambda x: input_api.FilterSourceFile(
697     x,
698     black_list=(('(?i).*test', r'.*\/junit\/')
699                 + input_api.DEFAULT_BLACK_LIST),
700     white_list=(r'.*\.java$',)
701   )
702   for f in input_api.AffectedFiles(include_deletes=False, file_filter=sources):
703     local_path = f.LocalPath()
704     is_inside_javadoc = False
705     for line_number, line in f.ChangedContents():
706       if is_inside_javadoc and javadoc_end_re.search(line):
707         is_inside_javadoc = False
708       if not is_inside_javadoc and javadoc_start_re.search(line):
709         is_inside_javadoc = True
710       if is_inside_javadoc:
711         continue
712       if (inclusion_re.search(line) and
713           not comment_re.search(line) and
714           not exclusion_re.search(line)):
715         problems.append(
716           '%s:%d\n    %s' % (local_path, line_number, line.strip()))
717
718   if problems:
719     return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
720   else:
721     return []
722
723
724 def _CheckNoIOStreamInHeaders(input_api, output_api):
725   """Checks to make sure no .h files include <iostream>."""
726   files = []
727   pattern = input_api.re.compile(r'^#include\s*<iostream>',
728                                  input_api.re.MULTILINE)
729   for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
730     if not f.LocalPath().endswith('.h'):
731       continue
732     contents = input_api.ReadFile(f)
733     if pattern.search(contents):
734       files.append(f)
735
736   if len(files):
737     return [output_api.PresubmitError(
738         'Do not #include <iostream> in header files, since it inserts static '
739         'initialization into every file including the header. Instead, '
740         '#include <ostream>. See http://crbug.com/94794',
741         files) ]
742   return []
743
744
745 def _CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
746   """Checks to make sure no source files use UNIT_TEST."""
747   problems = []
748   for f in input_api.AffectedFiles():
749     if (not f.LocalPath().endswith(('.cc', '.mm'))):
750       continue
751
752     for line_num, line in f.ChangedContents():
753       if 'UNIT_TEST ' in line or line.endswith('UNIT_TEST'):
754         problems.append('    %s:%d' % (f.LocalPath(), line_num))
755
756   if not problems:
757     return []
758   return [output_api.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
759       '\n'.join(problems))]
760
761
762 def _CheckDCHECK_IS_ONHasBraces(input_api, output_api):
763   """Checks to make sure DCHECK_IS_ON() does not skip the parentheses."""
764   errors = []
765   pattern = input_api.re.compile(r'DCHECK_IS_ON(?!\(\))',
766                                  input_api.re.MULTILINE)
767   for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
768     if (not f.LocalPath().endswith(('.cc', '.mm', '.h'))):
769       continue
770     for lnum, line in f.ChangedContents():
771       if input_api.re.search(pattern, line):
772         errors.append(output_api.PresubmitError(
773           ('%s:%d: Use of DCHECK_IS_ON() must be written as "#if ' +
774            'DCHECK_IS_ON()", not forgetting the parentheses.')
775           % (f.LocalPath(), lnum)))
776   return errors
777
778
779 def _FindHistogramNameInLine(histogram_name, line):
780   """Tries to find a histogram name or prefix in a line."""
781   if not "affected-histogram" in line:
782     return histogram_name in line
783   # A histogram_suffixes tag type has an affected-histogram name as a prefix of
784   # the histogram_name.
785   if not '"' in line:
786     return False
787   histogram_prefix = line.split('\"')[1]
788   return histogram_prefix in histogram_name
789
790
791 def _CheckUmaHistogramChanges(input_api, output_api):
792   """Check that UMA histogram names in touched lines can still be found in other
793   lines of the patch or in histograms.xml. Note that this check would not catch
794   the reverse: changes in histograms.xml not matched in the code itself."""
795   touched_histograms = []
796   histograms_xml_modifications = []
797   call_pattern_c = r'\bUMA_HISTOGRAM.*\('
798   call_pattern_java = r'\bRecordHistogram\.record[a-zA-Z]+Histogram\('
799   name_pattern = r'"(.*?)"'
800   single_line_c_re = input_api.re.compile(call_pattern_c + name_pattern)
801   single_line_java_re = input_api.re.compile(call_pattern_java + name_pattern)
802   split_line_c_prefix_re = input_api.re.compile(call_pattern_c)
803   split_line_java_prefix_re = input_api.re.compile(call_pattern_java)
804   split_line_suffix_re = input_api.re.compile(r'^\s*' + name_pattern)
805   last_line_matched_prefix = False
806   for f in input_api.AffectedFiles():
807     # If histograms.xml itself is modified, keep the modified lines for later.
808     if f.LocalPath().endswith(('histograms.xml')):
809       histograms_xml_modifications = f.ChangedContents()
810       continue
811     if f.LocalPath().endswith(('cc', 'mm', 'cpp')):
812       single_line_re = single_line_c_re
813       split_line_prefix_re = split_line_c_prefix_re
814     elif f.LocalPath().endswith(('java')):
815       single_line_re = single_line_java_re
816       split_line_prefix_re = split_line_java_prefix_re
817     else:
818       continue
819     for line_num, line in f.ChangedContents():
820       if last_line_matched_prefix:
821         suffix_found = split_line_suffix_re.search(line)
822         if suffix_found :
823           touched_histograms.append([suffix_found.group(1), f, line_num])
824           last_line_matched_prefix = False
825           continue
826       found = single_line_re.search(line)
827       if found:
828         touched_histograms.append([found.group(1), f, line_num])
829         continue
830       last_line_matched_prefix = split_line_prefix_re.search(line)
831
832   # Search for the touched histogram names in the local modifications to
833   # histograms.xml, and, if not found, on the base histograms.xml file.
834   unmatched_histograms = []
835   for histogram_info in touched_histograms:
836     histogram_name_found = False
837     for line_num, line in histograms_xml_modifications:
838       histogram_name_found = _FindHistogramNameInLine(histogram_info[0], line)
839       if histogram_name_found:
840         break
841     if not histogram_name_found:
842       unmatched_histograms.append(histogram_info)
843
844   histograms_xml_path = 'tools/metrics/histograms/histograms.xml'
845   problems = []
846   if unmatched_histograms:
847     with open(histograms_xml_path) as histograms_xml:
848       for histogram_name, f, line_num in unmatched_histograms:
849         histograms_xml.seek(0)
850         histogram_name_found = False
851         for line in histograms_xml:
852           histogram_name_found = _FindHistogramNameInLine(histogram_name, line)
853           if histogram_name_found:
854             break
855         if not histogram_name_found:
856           problems.append(' [%s:%d] %s' %
857                           (f.LocalPath(), line_num, histogram_name))
858
859   if not problems:
860     return []
861   return [output_api.PresubmitPromptWarning('Some UMA_HISTOGRAM lines have '
862     'been modified and the associated histogram name has no match in either '
863     '%s or the modifications of it:' % (histograms_xml_path),  problems)]
864
865
866 def _CheckFlakyTestUsage(input_api, output_api):
867   """Check that FlakyTest annotation is our own instead of the android one"""
868   pattern = input_api.re.compile(r'import android.test.FlakyTest;')
869   files = []
870   for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
871     if f.LocalPath().endswith('Test.java'):
872       if pattern.search(input_api.ReadFile(f)):
873         files.append(f)
874   if len(files):
875     return [output_api.PresubmitError(
876       'Use org.chromium.base.test.util.FlakyTest instead of '
877       'android.test.FlakyTest',
878       files)]
879   return []
880
881
882 def _CheckNoNewWStrings(input_api, output_api):
883   """Checks to make sure we don't introduce use of wstrings."""
884   problems = []
885   for f in input_api.AffectedFiles():
886     if (not f.LocalPath().endswith(('.cc', '.h')) or
887         f.LocalPath().endswith(('test.cc', '_win.cc', '_win.h')) or
888         '/win/' in f.LocalPath() or
889         'chrome_elf' in f.LocalPath() or
890         'install_static' in f.LocalPath()):
891       continue
892
893     allowWString = False
894     for line_num, line in f.ChangedContents():
895       if 'presubmit: allow wstring' in line:
896         allowWString = True
897       elif not allowWString and 'wstring' in line:
898         problems.append('    %s:%d' % (f.LocalPath(), line_num))
899         allowWString = False
900       else:
901         allowWString = False
902
903   if not problems:
904     return []
905   return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
906       '  If you are calling a cross-platform API that accepts a wstring, '
907       'fix the API.\n' +
908       '\n'.join(problems))]
909
910
911 def _CheckNoDEPSGIT(input_api, output_api):
912   """Make sure .DEPS.git is never modified manually."""
913   if any(f.LocalPath().endswith('.DEPS.git') for f in
914       input_api.AffectedFiles()):
915     return [output_api.PresubmitError(
916       'Never commit changes to .DEPS.git. This file is maintained by an\n'
917       'automated system based on what\'s in DEPS and your changes will be\n'
918       'overwritten.\n'
919       'See https://sites.google.com/a/chromium.org/dev/developers/how-tos/'
920       'get-the-code#Rolling_DEPS\n'
921       'for more information')]
922   return []
923
924
925 def _CheckValidHostsInDEPS(input_api, output_api):
926   """Checks that DEPS file deps are from allowed_hosts."""
927   # Run only if DEPS file has been modified to annoy fewer bystanders.
928   if all(f.LocalPath() != 'DEPS' for f in input_api.AffectedFiles()):
929     return []
930   # Outsource work to gclient verify
931   try:
932     input_api.subprocess.check_output(['gclient', 'verify'])
933     return []
934   except input_api.subprocess.CalledProcessError, error:
935     return [output_api.PresubmitError(
936         'DEPS file must have only git dependencies.',
937         long_text=error.output)]
938
939
940 def _CheckNoBannedFunctions(input_api, output_api):
941   """Make sure that banned functions are not used."""
942   warnings = []
943   errors = []
944
945   def IsBlacklisted(affected_file, blacklist):
946     local_path = affected_file.LocalPath()
947     for item in blacklist:
948       if input_api.re.match(item, local_path):
949         return True
950     return False
951
952   def IsIosObcjFile(affected_file):
953     local_path = affected_file.LocalPath()
954     if input_api.os_path.splitext(local_path)[-1] not in ('.mm', '.m', '.h'):
955       return False
956     basename = input_api.os_path.basename(local_path)
957     if 'ios' in basename.split('_'):
958       return True
959     for sep in (input_api.os_path.sep, input_api.os_path.altsep):
960       if sep and 'ios' in local_path.split(sep):
961         return True
962     return False
963
964   def CheckForMatch(affected_file, line_num, line, func_name, message, error):
965     matched = False
966     if func_name[0:1] == '/':
967       regex = func_name[1:]
968       if input_api.re.search(regex, line):
969         matched = True
970     elif func_name in line:
971       matched = True
972     if matched:
973       problems = warnings
974       if error:
975         problems = errors
976       problems.append('    %s:%d:' % (affected_file.LocalPath(), line_num))
977       for message_line in message:
978         problems.append('      %s' % message_line)
979
980   file_filter = lambda f: f.LocalPath().endswith(('.java'))
981   for f in input_api.AffectedFiles(file_filter=file_filter):
982     for line_num, line in f.ChangedContents():
983       for func_name, message, error in _BANNED_JAVA_FUNCTIONS:
984         CheckForMatch(f, line_num, line, func_name, message, error)
985
986   file_filter = lambda f: f.LocalPath().endswith(('.mm', '.m', '.h'))
987   for f in input_api.AffectedFiles(file_filter=file_filter):
988     for line_num, line in f.ChangedContents():
989       for func_name, message, error in _BANNED_OBJC_FUNCTIONS:
990         CheckForMatch(f, line_num, line, func_name, message, error)
991
992   for f in input_api.AffectedFiles(file_filter=IsIosObcjFile):
993     for line_num, line in f.ChangedContents():
994       for func_name, message, error in _BANNED_IOS_OBJC_FUNCTIONS:
995         CheckForMatch(f, line_num, line, func_name, message, error)
996
997   file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm', '.h'))
998   for f in input_api.AffectedFiles(file_filter=file_filter):
999     for line_num, line in f.ChangedContents():
1000       for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
1001         if IsBlacklisted(f, excluded_paths):
1002           continue
1003         CheckForMatch(f, line_num, line, func_name, message, error)
1004
1005   result = []
1006   if (warnings):
1007     result.append(output_api.PresubmitPromptWarning(
1008         'Banned functions were used.\n' + '\n'.join(warnings)))
1009   if (errors):
1010     result.append(output_api.PresubmitError(
1011         'Banned functions were used.\n' + '\n'.join(errors)))
1012   return result
1013
1014
1015 def _CheckNoPragmaOnce(input_api, output_api):
1016   """Make sure that banned functions are not used."""
1017   files = []
1018   pattern = input_api.re.compile(r'^#pragma\s+once',
1019                                  input_api.re.MULTILINE)
1020   for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
1021     if not f.LocalPath().endswith('.h'):
1022       continue
1023     contents = input_api.ReadFile(f)
1024     if pattern.search(contents):
1025       files.append(f)
1026
1027   if files:
1028     return [output_api.PresubmitError(
1029         'Do not use #pragma once in header files.\n'
1030         'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
1031         files)]
1032   return []
1033
1034
1035 def _CheckNoTrinaryTrueFalse(input_api, output_api):
1036   """Checks to make sure we don't introduce use of foo ? true : false."""
1037   problems = []
1038   pattern = input_api.re.compile(r'\?\s*(true|false)\s*:\s*(true|false)')
1039   for f in input_api.AffectedFiles():
1040     if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
1041       continue
1042
1043     for line_num, line in f.ChangedContents():
1044       if pattern.match(line):
1045         problems.append('    %s:%d' % (f.LocalPath(), line_num))
1046
1047   if not problems:
1048     return []
1049   return [output_api.PresubmitPromptWarning(
1050       'Please consider avoiding the "? true : false" pattern if possible.\n' +
1051       '\n'.join(problems))]
1052
1053
1054 def _CheckUnwantedDependencies(input_api, output_api):
1055   """Runs checkdeps on #include and import statements added in this
1056   change. Breaking - rules is an error, breaking ! rules is a
1057   warning.
1058   """
1059   import sys
1060   # We need to wait until we have an input_api object and use this
1061   # roundabout construct to import checkdeps because this file is
1062   # eval-ed and thus doesn't have __file__.
1063   original_sys_path = sys.path
1064   try:
1065     sys.path = sys.path + [input_api.os_path.join(
1066         input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
1067     import checkdeps
1068     from cpp_checker import CppChecker
1069     from java_checker import JavaChecker
1070     from proto_checker import ProtoChecker
1071     from rules import Rule
1072   finally:
1073     # Restore sys.path to what it was before.
1074     sys.path = original_sys_path
1075
1076   added_includes = []
1077   added_imports = []
1078   added_java_imports = []
1079   for f in input_api.AffectedFiles():
1080     if CppChecker.IsCppFile(f.LocalPath()):
1081       changed_lines = [line for _, line in f.ChangedContents()]
1082       added_includes.append([f.AbsoluteLocalPath(), changed_lines])
1083     elif ProtoChecker.IsProtoFile(f.LocalPath()):
1084       changed_lines = [line for _, line in f.ChangedContents()]
1085       added_imports.append([f.AbsoluteLocalPath(), changed_lines])
1086     elif JavaChecker.IsJavaFile(f.LocalPath()):
1087       changed_lines = [line for _, line in f.ChangedContents()]
1088       added_java_imports.append([f.AbsoluteLocalPath(), changed_lines])
1089
1090   deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
1091
1092   error_descriptions = []
1093   warning_descriptions = []
1094   error_subjects = set()
1095   warning_subjects = set()
1096   for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
1097       added_includes):
1098     path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
1099     description_with_path = '%s\n    %s' % (path, rule_description)
1100     if rule_type == Rule.DISALLOW:
1101       error_descriptions.append(description_with_path)
1102       error_subjects.add("#includes")
1103     else:
1104       warning_descriptions.append(description_with_path)
1105       warning_subjects.add("#includes")
1106
1107   for path, rule_type, rule_description in deps_checker.CheckAddedProtoImports(
1108       added_imports):
1109     path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
1110     description_with_path = '%s\n    %s' % (path, rule_description)
1111     if rule_type == Rule.DISALLOW:
1112       error_descriptions.append(description_with_path)
1113       error_subjects.add("imports")
1114     else:
1115       warning_descriptions.append(description_with_path)
1116       warning_subjects.add("imports")
1117
1118   for path, rule_type, rule_description in deps_checker.CheckAddedJavaImports(
1119       added_java_imports, _JAVA_MULTIPLE_DEFINITION_EXCLUDED_PATHS):
1120     path = input_api.os_path.relpath(path, input_api.PresubmitLocalPath())
1121     description_with_path = '%s\n    %s' % (path, rule_description)
1122     if rule_type == Rule.DISALLOW:
1123       error_descriptions.append(description_with_path)
1124       error_subjects.add("imports")
1125     else:
1126       warning_descriptions.append(description_with_path)
1127       warning_subjects.add("imports")
1128
1129   results = []
1130   if error_descriptions:
1131     results.append(output_api.PresubmitError(
1132         'You added one or more %s that violate checkdeps rules.'
1133             % " and ".join(error_subjects),
1134         error_descriptions))
1135   if warning_descriptions:
1136     results.append(output_api.PresubmitPromptOrNotify(
1137         'You added one or more %s of files that are temporarily\n'
1138         'allowed but being removed. Can you avoid introducing the\n'
1139         '%s? See relevant DEPS file(s) for details and contacts.' %
1140             (" and ".join(warning_subjects), "/".join(warning_subjects)),
1141         warning_descriptions))
1142   return results
1143
1144
1145 def _CheckFilePermissions(input_api, output_api):
1146   """Check that all files have their permissions properly set."""
1147   if input_api.platform == 'win32':
1148     return []
1149   checkperms_tool = input_api.os_path.join(
1150       input_api.PresubmitLocalPath(),
1151       'tools', 'checkperms', 'checkperms.py')
1152   args = [input_api.python_executable, checkperms_tool,
1153           '--root', input_api.change.RepositoryRoot()]
1154   with input_api.CreateTemporaryFile() as file_list:
1155     for f in input_api.AffectedFiles():
1156       # checkperms.py file/directory arguments must be relative to the
1157       # repository.
1158       file_list.write(f.LocalPath() + '\n')
1159     file_list.close()
1160     args += ['--file-list', file_list.name]
1161     try:
1162       input_api.subprocess.check_output(args)
1163       return []
1164     except input_api.subprocess.CalledProcessError as error:
1165       return [output_api.PresubmitError(
1166           'checkperms.py failed:',
1167           long_text=error.output)]
1168
1169
1170 def _CheckTeamTags(input_api, output_api):
1171   """Checks that OWNERS files have consistent TEAM and COMPONENT tags."""
1172   checkteamtags_tool = input_api.os_path.join(
1173       input_api.PresubmitLocalPath(),
1174       'tools', 'checkteamtags', 'checkteamtags.py')
1175   args = [input_api.python_executable, checkteamtags_tool,
1176           '--root', input_api.change.RepositoryRoot()]
1177   files = [f.LocalPath() for f in input_api.AffectedFiles(include_deletes=False)
1178            if input_api.os_path.basename(f.AbsoluteLocalPath()).upper() ==
1179            'OWNERS']
1180   try:
1181     if files:
1182       input_api.subprocess.check_output(args + files)
1183     return []
1184   except input_api.subprocess.CalledProcessError as error:
1185     return [output_api.PresubmitError(
1186         'checkteamtags.py failed:',
1187         long_text=error.output)]
1188
1189
1190 def _CheckNoAuraWindowPropertyHInHeaders(input_api, output_api):
1191   """Makes sure we don't include ui/aura/window_property.h
1192   in header files.
1193   """
1194   pattern = input_api.re.compile(r'^#include\s*"ui/aura/window_property.h"')
1195   errors = []
1196   for f in input_api.AffectedFiles():
1197     if not f.LocalPath().endswith('.h'):
1198       continue
1199     for line_num, line in f.ChangedContents():
1200       if pattern.match(line):
1201         errors.append('    %s:%d' % (f.LocalPath(), line_num))
1202
1203   results = []
1204   if errors:
1205     results.append(output_api.PresubmitError(
1206       'Header files should not include ui/aura/window_property.h', errors))
1207   return results
1208
1209
1210 def _CheckForVersionControlConflictsInFile(input_api, f):
1211   pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
1212   errors = []
1213   for line_num, line in f.ChangedContents():
1214     if f.LocalPath().endswith('.md'):
1215       # First-level headers in markdown look a lot like version control
1216       # conflict markers. http://daringfireball.net/projects/markdown/basics
1217       continue
1218     if pattern.match(line):
1219       errors.append('    %s:%d %s' % (f.LocalPath(), line_num, line))
1220   return errors
1221
1222
1223 def _CheckForVersionControlConflicts(input_api, output_api):
1224   """Usually this is not intentional and will cause a compile failure."""
1225   errors = []
1226   for f in input_api.AffectedFiles():
1227     errors.extend(_CheckForVersionControlConflictsInFile(input_api, f))
1228
1229   results = []
1230   if errors:
1231     results.append(output_api.PresubmitError(
1232       'Version control conflict markers found, please resolve.', errors))
1233   return results
1234
1235 def _CheckGoogleSupportAnswerUrl(input_api, output_api):
1236   pattern = input_api.re.compile('support\.google\.com\/chrome.*/answer')
1237   errors = []
1238   for f in input_api.AffectedFiles():
1239     for line_num, line in f.ChangedContents():
1240       if pattern.search(line):
1241         errors.append('    %s:%d %s' % (f.LocalPath(), line_num, line))
1242
1243   results = []
1244   if errors:
1245     results.append(output_api.PresubmitPromptWarning(
1246       'Found Google support URL addressed by answer number. Please replace '
1247       'with a p= identifier instead. See crbug.com/679462\n', errors))
1248   return results
1249
1250
1251 def _CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api):
1252   def FilterFile(affected_file):
1253     """Filter function for use with input_api.AffectedSourceFiles,
1254     below.  This filters out everything except non-test files from
1255     top-level directories that generally speaking should not hard-code
1256     service URLs (e.g. src/android_webview/, src/content/ and others).
1257     """
1258     return input_api.FilterSourceFile(
1259       affected_file,
1260       white_list=(r'^(android_webview|base|content|net)[\\\/].*', ),
1261       black_list=(_EXCLUDED_PATHS +
1262                   _TEST_CODE_EXCLUDED_PATHS +
1263                   input_api.DEFAULT_BLACK_LIST))
1264
1265   base_pattern = ('"[^"]*(google|googleapis|googlezip|googledrive|appspot)'
1266                   '\.(com|net)[^"]*"')
1267   comment_pattern = input_api.re.compile('//.*%s' % base_pattern)
1268   pattern = input_api.re.compile(base_pattern)
1269   problems = []  # items are (filename, line_number, line)
1270   for f in input_api.AffectedSourceFiles(FilterFile):
1271     for line_num, line in f.ChangedContents():
1272       if not comment_pattern.search(line) and pattern.search(line):
1273         problems.append((f.LocalPath(), line_num, line))
1274
1275   if problems:
1276     return [output_api.PresubmitPromptOrNotify(
1277         'Most layers below src/chrome/ should not hardcode service URLs.\n'
1278         'Are you sure this is correct?',
1279         ['  %s:%d:  %s' % (
1280             problem[0], problem[1], problem[2]) for problem in problems])]
1281   else:
1282     return []
1283
1284
1285 def _CheckNoAbbreviationInPngFileName(input_api, output_api):
1286   """Makes sure there are no abbreviations in the name of PNG files.
1287   The native_client_sdk directory is excluded because it has auto-generated PNG
1288   files for documentation.
1289   """
1290   errors = []
1291   white_list = (r'.*_[a-z]_.*\.png$|.*_[a-z]\.png$',)
1292   black_list = (r'^native_client_sdk[\\\/]',)
1293   file_filter = lambda f: input_api.FilterSourceFile(
1294       f, white_list=white_list, black_list=black_list)
1295   for f in input_api.AffectedFiles(include_deletes=False,
1296                                    file_filter=file_filter):
1297     errors.append('    %s' % f.LocalPath())
1298
1299   results = []
1300   if errors:
1301     results.append(output_api.PresubmitError(
1302         'The name of PNG files should not have abbreviations. \n'
1303         'Use _hover.png, _center.png, instead of _h.png, _c.png.\n'
1304         'Contact oshima@chromium.org if you have questions.', errors))
1305   return results
1306
1307
1308 def _ExtractAddRulesFromParsedDeps(parsed_deps):
1309   """Extract the rules that add dependencies from a parsed DEPS file.
1310
1311   Args:
1312     parsed_deps: the locals dictionary from evaluating the DEPS file."""
1313   add_rules = set()
1314   add_rules.update([
1315       rule[1:] for rule in parsed_deps.get('include_rules', [])
1316       if rule.startswith('+') or rule.startswith('!')
1317   ])
1318   for _, rules in parsed_deps.get('specific_include_rules',
1319                                               {}).iteritems():
1320     add_rules.update([
1321         rule[1:] for rule in rules
1322         if rule.startswith('+') or rule.startswith('!')
1323     ])
1324   return add_rules
1325
1326
1327 def _ParseDeps(contents):
1328   """Simple helper for parsing DEPS files."""
1329   # Stubs for handling special syntax in the root DEPS file.
1330   class _VarImpl:
1331
1332     def __init__(self, local_scope):
1333       self._local_scope = local_scope
1334
1335     def Lookup(self, var_name):
1336       """Implements the Var syntax."""
1337       try:
1338         return self._local_scope['vars'][var_name]
1339       except KeyError:
1340         raise Exception('Var is not defined: %s' % var_name)
1341
1342   local_scope = {}
1343   global_scope = {
1344       'Var': _VarImpl(local_scope).Lookup,
1345   }
1346   exec contents in global_scope, local_scope
1347   return local_scope
1348
1349
1350 def _CalculateAddedDeps(os_path, old_contents, new_contents):
1351   """Helper method for _CheckAddedDepsHaveTargetApprovals. Returns
1352   a set of DEPS entries that we should look up.
1353
1354   For a directory (rather than a specific filename) we fake a path to
1355   a specific filename by adding /DEPS. This is chosen as a file that
1356   will seldom or never be subject to per-file include_rules.
1357   """
1358   # We ignore deps entries on auto-generated directories.
1359   AUTO_GENERATED_DIRS = ['grit', 'jni']
1360
1361   old_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(old_contents))
1362   new_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(new_contents))
1363
1364   added_deps = new_deps.difference(old_deps)
1365
1366   results = set()
1367   for added_dep in added_deps:
1368     if added_dep.split('/')[0] in AUTO_GENERATED_DIRS:
1369       continue
1370     # Assume that a rule that ends in .h is a rule for a specific file.
1371     if added_dep.endswith('.h'):
1372       results.add(added_dep)
1373     else:
1374       results.add(os_path.join(added_dep, 'DEPS'))
1375   return results
1376
1377
1378 def _CheckAddedDepsHaveTargetApprovals(input_api, output_api):
1379   """When a dependency prefixed with + is added to a DEPS file, we
1380   want to make sure that the change is reviewed by an OWNER of the
1381   target file or directory, to avoid layering violations from being
1382   introduced. This check verifies that this happens.
1383   """
1384   virtual_depended_on_files = set()
1385
1386   file_filter = lambda f: not input_api.re.match(
1387       r"^third_party[\\\/](WebKit|blink)[\\\/].*", f.LocalPath())
1388   for f in input_api.AffectedFiles(include_deletes=False,
1389                                    file_filter=file_filter):
1390     filename = input_api.os_path.basename(f.LocalPath())
1391     if filename == 'DEPS':
1392       virtual_depended_on_files.update(_CalculateAddedDeps(
1393           input_api.os_path,
1394           '\n'.join(f.OldContents()),
1395           '\n'.join(f.NewContents())))
1396
1397   if not virtual_depended_on_files:
1398     return []
1399
1400   if input_api.is_committing:
1401     if input_api.tbr:
1402       return [output_api.PresubmitNotifyResult(
1403           '--tbr was specified, skipping OWNERS check for DEPS additions')]
1404     if input_api.dry_run:
1405       return [output_api.PresubmitNotifyResult(
1406           'This is a dry run, skipping OWNERS check for DEPS additions')]
1407     if not input_api.change.issue:
1408       return [output_api.PresubmitError(
1409           "DEPS approval by OWNERS check failed: this change has "
1410           "no change number, so we can't check it for approvals.")]
1411     output = output_api.PresubmitError
1412   else:
1413     output = output_api.PresubmitNotifyResult
1414
1415   owners_db = input_api.owners_db
1416   owner_email, reviewers = (
1417       input_api.canned_checks.GetCodereviewOwnerAndReviewers(
1418         input_api,
1419         owners_db.email_regexp,
1420         approval_needed=input_api.is_committing))
1421
1422   owner_email = owner_email or input_api.change.author_email
1423
1424   reviewers_plus_owner = set(reviewers)
1425   if owner_email:
1426     reviewers_plus_owner.add(owner_email)
1427   missing_files = owners_db.files_not_covered_by(virtual_depended_on_files,
1428                                                  reviewers_plus_owner)
1429
1430   # We strip the /DEPS part that was added by
1431   # _FilesToCheckForIncomingDeps to fake a path to a file in a
1432   # directory.
1433   def StripDeps(path):
1434     start_deps = path.rfind('/DEPS')
1435     if start_deps != -1:
1436       return path[:start_deps]
1437     else:
1438       return path
1439   unapproved_dependencies = ["'+%s'," % StripDeps(path)
1440                              for path in missing_files]
1441
1442   if unapproved_dependencies:
1443     output_list = [
1444       output('You need LGTM from owners of depends-on paths in DEPS that were '
1445              'modified in this CL:\n    %s' %
1446                  '\n    '.join(sorted(unapproved_dependencies)))]
1447     suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
1448     output_list.append(output(
1449         'Suggested missing target path OWNERS:\n    %s' %
1450             '\n    '.join(suggested_owners or [])))
1451     return output_list
1452
1453   return []
1454
1455
1456 def _CheckSpamLogging(input_api, output_api):
1457   file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
1458   black_list = (_EXCLUDED_PATHS +
1459                 _TEST_CODE_EXCLUDED_PATHS +
1460                 input_api.DEFAULT_BLACK_LIST +
1461                 (r"^base[\\\/]logging\.h$",
1462                  r"^base[\\\/]logging\.cc$",
1463                  r"^chrome[\\\/]app[\\\/]chrome_main_delegate\.cc$",
1464                  r"^chrome[\\\/]browser[\\\/]chrome_browser_main\.cc$",
1465                  r"^chrome[\\\/]browser[\\\/]ui[\\\/]startup[\\\/]"
1466                      r"startup_browser_creator\.cc$",
1467                  r"^chrome[\\\/]installer[\\\/]setup[\\\/].*",
1468                  r"chrome[\\\/]browser[\\\/]diagnostics[\\\/]" +
1469                      r"diagnostics_writer\.cc$",
1470                  r"^chrome_elf[\\\/]dll_hash[\\\/]dll_hash_main\.cc$",
1471                  r"^chromecast[\\\/]",
1472                  r"^cloud_print[\\\/]",
1473                  r"^components[\\\/]browser_watcher[\\\/]"
1474                      r"dump_stability_report_main_win.cc$",
1475                  r"^components[\\\/]html_viewer[\\\/]"
1476                      r"web_test_delegate_impl\.cc$",
1477                  r"^components[\\\/]zucchini[\\\/].*",
1478                  # TODO(peter): Remove this exception. https://crbug.com/534537
1479                  r"^content[\\\/]browser[\\\/]notifications[\\\/]"
1480                      r"notification_event_dispatcher_impl\.cc$",
1481                  r"^content[\\\/]common[\\\/]gpu[\\\/]client[\\\/]"
1482                      r"gl_helper_benchmark\.cc$",
1483                  r"^courgette[\\\/]courgette_minimal_tool\.cc$",
1484                  r"^courgette[\\\/]courgette_tool\.cc$",
1485                  r"^extensions[\\\/]renderer[\\\/]logging_native_handler\.cc$",
1486                  r"^ipc[\\\/]ipc_logging\.cc$",
1487                  r"^native_client_sdk[\\\/]",
1488                  r"^remoting[\\\/]base[\\\/]logging\.h$",
1489                  r"^remoting[\\\/]host[\\\/].*",
1490                  r"^sandbox[\\\/]linux[\\\/].*",
1491                  r"^tools[\\\/]",
1492                  r"^ui[\\\/]base[\\\/]resource[\\\/]data_pack.cc$",
1493                  r"^ui[\\\/]aura[\\\/]bench[\\\/]bench_main\.cc$",
1494                  r"^ui[\\\/]ozone[\\\/]platform[\\\/]cast[\\\/]",
1495                  r"^storage[\\\/]browser[\\\/]fileapi[\\\/]" +
1496                      r"dump_file_system.cc$",
1497                  r"^headless[\\\/]app[\\\/]headless_shell\.cc$"))
1498   source_file_filter = lambda x: input_api.FilterSourceFile(
1499       x, white_list=(file_inclusion_pattern,), black_list=black_list)
1500
1501   log_info = set([])
1502   printf = set([])
1503
1504   for f in input_api.AffectedSourceFiles(source_file_filter):
1505     for _, line in f.ChangedContents():
1506       if input_api.re.search(r"\bD?LOG\s*\(\s*INFO\s*\)", line):
1507         log_info.add(f.LocalPath())
1508       elif input_api.re.search(r"\bD?LOG_IF\s*\(\s*INFO\s*,", line):
1509         log_info.add(f.LocalPath())
1510
1511       if input_api.re.search(r"\bprintf\(", line):
1512         printf.add(f.LocalPath())
1513       elif input_api.re.search(r"\bfprintf\((stdout|stderr)", line):
1514         printf.add(f.LocalPath())
1515
1516   if log_info:
1517     return [output_api.PresubmitError(
1518       'These files spam the console log with LOG(INFO):',
1519       items=log_info)]
1520   if printf:
1521     return [output_api.PresubmitError(
1522       'These files spam the console log with printf/fprintf:',
1523       items=printf)]
1524   return []
1525
1526
1527 def _CheckForAnonymousVariables(input_api, output_api):
1528   """These types are all expected to hold locks while in scope and
1529      so should never be anonymous (which causes them to be immediately
1530      destroyed)."""
1531   they_who_must_be_named = [
1532     'base::AutoLock',
1533     'base::AutoReset',
1534     'base::AutoUnlock',
1535     'SkAutoAlphaRestore',
1536     'SkAutoBitmapShaderInstall',
1537     'SkAutoBlitterChoose',
1538     'SkAutoBounderCommit',
1539     'SkAutoCallProc',
1540     'SkAutoCanvasRestore',
1541     'SkAutoCommentBlock',
1542     'SkAutoDescriptor',
1543     'SkAutoDisableDirectionCheck',
1544     'SkAutoDisableOvalCheck',
1545     'SkAutoFree',
1546     'SkAutoGlyphCache',
1547     'SkAutoHDC',
1548     'SkAutoLockColors',
1549     'SkAutoLockPixels',
1550     'SkAutoMalloc',
1551     'SkAutoMaskFreeImage',
1552     'SkAutoMutexAcquire',
1553     'SkAutoPathBoundsUpdate',
1554     'SkAutoPDFRelease',
1555     'SkAutoRasterClipValidate',
1556     'SkAutoRef',
1557     'SkAutoTime',
1558     'SkAutoTrace',
1559     'SkAutoUnref',
1560   ]
1561   anonymous = r'(%s)\s*[({]' % '|'.join(they_who_must_be_named)
1562   # bad: base::AutoLock(lock.get());
1563   # not bad: base::AutoLock lock(lock.get());
1564   bad_pattern = input_api.re.compile(anonymous)
1565   # good: new base::AutoLock(lock.get())
1566   good_pattern = input_api.re.compile(r'\bnew\s*' + anonymous)
1567   errors = []
1568
1569   for f in input_api.AffectedFiles():
1570     if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
1571       continue
1572     for linenum, line in f.ChangedContents():
1573       if bad_pattern.search(line) and not good_pattern.search(line):
1574         errors.append('%s:%d' % (f.LocalPath(), linenum))
1575
1576   if errors:
1577     return [output_api.PresubmitError(
1578       'These lines create anonymous variables that need to be named:',
1579       items=errors)]
1580   return []
1581
1582
1583 def _CheckUniquePtr(input_api, output_api):
1584   file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
1585   sources = lambda affected_file: input_api.FilterSourceFile(
1586       affected_file,
1587       black_list=(_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
1588                   input_api.DEFAULT_BLACK_LIST),
1589       white_list=(file_inclusion_pattern,))
1590
1591   # Pattern to capture a single "<...>" block of template arguments. It can
1592   # handle linearly nested blocks, such as "<std::vector<std::set<T>>>", but
1593   # cannot handle branching structures, such as "<pair<set<T>,set<U>>". The
1594   # latter would likely require counting that < and > match, which is not
1595   # expressible in regular languages. Should the need arise, one can introduce
1596   # limited counting (matching up to a total number of nesting depth), which
1597   # should cover all practical cases for already a low nesting limit.
1598   template_arg_pattern = (
1599       r'<[^>]*'       # Opening block of <.
1600       r'>([^<]*>)?')  # Closing block of >.
1601   # Prefix expressing that whatever follows is not already inside a <...>
1602   # block.
1603   not_inside_template_arg_pattern = r'(^|[^<,\s]\s*)'
1604   null_construct_pattern = input_api.re.compile(
1605       not_inside_template_arg_pattern
1606       + r'\bstd::unique_ptr'
1607       + template_arg_pattern
1608       + r'\(\)')
1609
1610   # Same as template_arg_pattern, but excluding type arrays, e.g., <T[]>.
1611   template_arg_no_array_pattern = (
1612       r'<[^>]*[^]]'        # Opening block of <.
1613       r'>([^(<]*[^]]>)?')  # Closing block of >.
1614   # Prefix saying that what follows is the start of an expression.
1615   start_of_expr_pattern = r'(=|\breturn|^)\s*'
1616   # Suffix saying that what follows are call parentheses with a non-empty list
1617   # of arguments.
1618   nonempty_arg_list_pattern = r'\(([^)]|$)'
1619   return_construct_pattern = input_api.re.compile(
1620       start_of_expr_pattern
1621       + r'std::unique_ptr'
1622       + template_arg_no_array_pattern
1623       + nonempty_arg_list_pattern)
1624
1625   problems_constructor = []
1626   problems_nullptr = []
1627   for f in input_api.AffectedSourceFiles(sources):
1628     for line_number, line in f.ChangedContents():
1629       # Disallow:
1630       # return std::unique_ptr<T>(foo);
1631       # bar = std::unique_ptr<T>(foo);
1632       # But allow:
1633       # return std::unique_ptr<T[]>(foo);
1634       # bar = std::unique_ptr<T[]>(foo);
1635       local_path = f.LocalPath()
1636       if return_construct_pattern.search(line):
1637         problems_constructor.append(
1638           '%s:%d\n    %s' % (local_path, line_number, line.strip()))
1639       # Disallow:
1640       # std::unique_ptr<T>()
1641       if null_construct_pattern.search(line):
1642         problems_nullptr.append(
1643           '%s:%d\n    %s' % (local_path, line_number, line.strip()))
1644
1645   errors = []
1646   if problems_nullptr:
1647     errors.append(output_api.PresubmitError(
1648         'The following files use std::unique_ptr<T>(). Use nullptr instead.',
1649         problems_nullptr))
1650   if problems_constructor:
1651     errors.append(output_api.PresubmitError(
1652         'The following files use explicit std::unique_ptr constructor.'
1653         'Use std::make_unique<T>() instead.',
1654         problems_constructor))
1655   return errors
1656
1657
1658 def _CheckUserActionUpdate(input_api, output_api):
1659   """Checks if any new user action has been added."""
1660   if any('actions.xml' == input_api.os_path.basename(f) for f in
1661          input_api.LocalPaths()):
1662     # If actions.xml is already included in the changelist, the PRESUBMIT
1663     # for actions.xml will do a more complete presubmit check.
1664     return []
1665
1666   file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm'))
1667   action_re = r'[^a-zA-Z]UserMetricsAction\("([^"]*)'
1668   current_actions = None
1669   for f in input_api.AffectedFiles(file_filter=file_filter):
1670     for line_num, line in f.ChangedContents():
1671       match = input_api.re.search(action_re, line)
1672       if match:
1673         # Loads contents in tools/metrics/actions/actions.xml to memory. It's
1674         # loaded only once.
1675         if not current_actions:
1676           with open('tools/metrics/actions/actions.xml') as actions_f:
1677             current_actions = actions_f.read()
1678         # Search for the matched user action name in |current_actions|.
1679         for action_name in match.groups():
1680           action = 'name="{0}"'.format(action_name)
1681           if action not in current_actions:
1682             return [output_api.PresubmitPromptWarning(
1683               'File %s line %d: %s is missing in '
1684               'tools/metrics/actions/actions.xml. Please run '
1685               'tools/metrics/actions/extract_actions.py to update.'
1686               % (f.LocalPath(), line_num, action_name))]
1687   return []
1688
1689
1690 def _ImportJSONCommentEater(input_api):
1691   import sys
1692   sys.path = sys.path + [input_api.os_path.join(
1693       input_api.PresubmitLocalPath(),
1694       'tools', 'json_comment_eater')]
1695   import json_comment_eater
1696   return json_comment_eater
1697
1698
1699 def _GetJSONParseError(input_api, filename, eat_comments=True):
1700   try:
1701     contents = input_api.ReadFile(filename)
1702     if eat_comments:
1703       json_comment_eater = _ImportJSONCommentEater(input_api)
1704       contents = json_comment_eater.Nom(contents)
1705
1706     input_api.json.loads(contents)
1707   except ValueError as e:
1708     return e
1709   return None
1710
1711
1712 def _GetIDLParseError(input_api, filename):
1713   try:
1714     contents = input_api.ReadFile(filename)
1715     idl_schema = input_api.os_path.join(
1716         input_api.PresubmitLocalPath(),
1717         'tools', 'json_schema_compiler', 'idl_schema.py')
1718     process = input_api.subprocess.Popen(
1719         [input_api.python_executable, idl_schema],
1720         stdin=input_api.subprocess.PIPE,
1721         stdout=input_api.subprocess.PIPE,
1722         stderr=input_api.subprocess.PIPE,
1723         universal_newlines=True)
1724     (_, error) = process.communicate(input=contents)
1725     return error or None
1726   except ValueError as e:
1727     return e
1728
1729
1730 def _CheckParseErrors(input_api, output_api):
1731   """Check that IDL and JSON files do not contain syntax errors."""
1732   actions = {
1733     '.idl': _GetIDLParseError,
1734     '.json': _GetJSONParseError,
1735   }
1736   # Most JSON files are preprocessed and support comments, but these do not.
1737   json_no_comments_patterns = [
1738     r'^testing[\\\/]',
1739   ]
1740   # Only run IDL checker on files in these directories.
1741   idl_included_patterns = [
1742     r'^chrome[\\\/]common[\\\/]extensions[\\\/]api[\\\/]',
1743     r'^extensions[\\\/]common[\\\/]api[\\\/]',
1744   ]
1745
1746   def get_action(affected_file):
1747     filename = affected_file.LocalPath()
1748     return actions.get(input_api.os_path.splitext(filename)[1])
1749
1750   def FilterFile(affected_file):
1751     action = get_action(affected_file)
1752     if not action:
1753       return False
1754     path = affected_file.LocalPath()
1755
1756     if _MatchesFile(input_api, _KNOWN_INVALID_JSON_FILE_PATTERNS, path):
1757       return False
1758
1759     if (action == _GetIDLParseError and
1760         not _MatchesFile(input_api, idl_included_patterns, path)):
1761       return False
1762     return True
1763
1764   results = []
1765   for affected_file in input_api.AffectedFiles(
1766       file_filter=FilterFile, include_deletes=False):
1767     action = get_action(affected_file)
1768     kwargs = {}
1769     if (action == _GetJSONParseError and
1770         _MatchesFile(input_api, json_no_comments_patterns,
1771                      affected_file.LocalPath())):
1772       kwargs['eat_comments'] = False
1773     parse_error = action(input_api,
1774                          affected_file.AbsoluteLocalPath(),
1775                          **kwargs)
1776     if parse_error:
1777       results.append(output_api.PresubmitError('%s could not be parsed: %s' %
1778           (affected_file.LocalPath(), parse_error)))
1779   return results
1780
1781
1782 def _CheckJavaStyle(input_api, output_api):
1783   """Runs checkstyle on changed java files and returns errors if any exist."""
1784   import sys
1785   original_sys_path = sys.path
1786   try:
1787     sys.path = sys.path + [input_api.os_path.join(
1788         input_api.PresubmitLocalPath(), 'tools', 'android', 'checkstyle')]
1789     import checkstyle
1790   finally:
1791     # Restore sys.path to what it was before.
1792     sys.path = original_sys_path
1793
1794   return checkstyle.RunCheckstyle(
1795       input_api, output_api, 'tools/android/checkstyle/chromium-style-5.0.xml',
1796       black_list=_EXCLUDED_PATHS + input_api.DEFAULT_BLACK_LIST)
1797
1798
1799 def _MatchesFile(input_api, patterns, path):
1800   for pattern in patterns:
1801     if input_api.re.search(pattern, path):
1802       return True
1803   return False
1804
1805
1806 def _GetOwnersFilesToCheckForIpcOwners(input_api):
1807   """Gets a list of OWNERS files to check for correct security owners.
1808
1809   Returns:
1810     A dictionary mapping an OWNER file to the list of OWNERS rules it must
1811     contain to cover IPC-related files with noparent reviewer rules.
1812   """
1813   # Whether or not a file affects IPC is (mostly) determined by a simple list
1814   # of filename patterns.
1815   file_patterns = [
1816       # Legacy IPC:
1817       '*_messages.cc',
1818       '*_messages*.h',
1819       '*_param_traits*.*',
1820       # Mojo IPC:
1821       '*.mojom',
1822       '*_mojom_traits*.*',
1823       '*_struct_traits*.*',
1824       '*_type_converter*.*',
1825       '*.typemap',
1826       # Android native IPC:
1827       '*.aidl',
1828       # Blink uses a different file naming convention:
1829       '*EnumTraits*.*',
1830       "*MojomTraits*.*",
1831       '*StructTraits*.*',
1832       '*TypeConverter*.*',
1833   ]
1834
1835   # These third_party directories do not contain IPCs, but contain files
1836   # matching the above patterns, which trigger false positives.
1837   exclude_paths = [
1838       'third_party/crashpad/*',
1839       'third_party/win_build_output/*',
1840   ]
1841
1842   # Dictionary mapping an OWNERS file path to Patterns.
1843   # Patterns is a dictionary mapping glob patterns (suitable for use in per-file
1844   # rules ) to a PatternEntry.
1845   # PatternEntry is a dictionary with two keys:
1846   # - 'files': the files that are matched by this pattern
1847   # - 'rules': the per-file rules needed for this pattern
1848   # For example, if we expect OWNERS file to contain rules for *.mojom and
1849   # *_struct_traits*.*, Patterns might look like this:
1850   # {
1851   #   '*.mojom': {
1852   #     'files': ...,
1853   #     'rules': [
1854   #       'per-file *.mojom=set noparent',
1855   #       'per-file *.mojom=file://ipc/SECURITY_OWNERS',
1856   #     ],
1857   #   },
1858   #   '*_struct_traits*.*': {
1859   #     'files': ...,
1860   #     'rules': [
1861   #       'per-file *_struct_traits*.*=set noparent',
1862   #       'per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS',
1863   #     ],
1864   #   },
1865   # }
1866   to_check = {}
1867
1868   def AddPatternToCheck(input_file, pattern):
1869     owners_file = input_api.os_path.join(
1870         input_api.os_path.dirname(input_file.LocalPath()), 'OWNERS')
1871     if owners_file not in to_check:
1872       to_check[owners_file] = {}
1873     if pattern not in to_check[owners_file]:
1874       to_check[owners_file][pattern] = {
1875           'files': [],
1876           'rules': [
1877               'per-file %s=set noparent' % pattern,
1878               'per-file %s=file://ipc/SECURITY_OWNERS' % pattern,
1879           ]
1880       }
1881     to_check[owners_file][pattern]['files'].append(input_file)
1882
1883   # Iterate through the affected files to see what we actually need to check
1884   # for. We should only nag patch authors about per-file rules if a file in that
1885   # directory would match that pattern. If a directory only contains *.mojom
1886   # files and no *_messages*.h files, we should only nag about rules for
1887   # *.mojom files.
1888   for f in input_api.AffectedFiles(include_deletes=False):
1889     # Manifest files don't have a strong naming convention. Instead, scan
1890     # affected files for .json files and see if they look like a manifest.
1891     if (f.LocalPath().endswith('.json') and
1892         not _MatchesFile(input_api, _KNOWN_INVALID_JSON_FILE_PATTERNS,
1893                          f.LocalPath())):
1894       json_comment_eater = _ImportJSONCommentEater(input_api)
1895       mostly_json_lines = '\n'.join(f.NewContents())
1896       # Comments aren't allowed in strict JSON, so filter them out.
1897       json_lines = json_comment_eater.Nom(mostly_json_lines)
1898       try:
1899         json_content = input_api.json.loads(json_lines)
1900       except:
1901         # There's another PRESUBMIT check that already verifies that JSON files
1902         # are not invalid, so no need to emit another warning here.
1903         continue
1904       if 'interface_provider_specs' in json_content:
1905         AddPatternToCheck(f, input_api.os_path.basename(f.LocalPath()))
1906     for pattern in file_patterns:
1907       if input_api.fnmatch.fnmatch(
1908           input_api.os_path.basename(f.LocalPath()), pattern):
1909         skip = False
1910         for exclude in exclude_paths:
1911           if input_api.fnmatch.fnmatch(f.LocalPath(), exclude):
1912             skip = True
1913             break
1914         if skip:
1915           continue
1916         AddPatternToCheck(f, pattern)
1917         break
1918
1919   return to_check
1920
1921
1922 def _CheckIpcOwners(input_api, output_api):
1923   """Checks that affected files involving IPC have an IPC OWNERS rule."""
1924   to_check = _GetOwnersFilesToCheckForIpcOwners(input_api)
1925
1926   if to_check:
1927     # If there are any OWNERS files to check, there are IPC-related changes in
1928     # this CL. Auto-CC the review list.
1929     output_api.AppendCC('ipc-security-reviews@chromium.org')
1930
1931   # Go through the OWNERS files to check, filtering out rules that are already
1932   # present in that OWNERS file.
1933   for owners_file, patterns in to_check.iteritems():
1934     try:
1935       with file(owners_file) as f:
1936         lines = set(f.read().splitlines())
1937         for entry in patterns.itervalues():
1938           entry['rules'] = [rule for rule in entry['rules'] if rule not in lines
1939                            ]
1940     except IOError:
1941       # No OWNERS file, so all the rules are definitely missing.
1942       continue
1943
1944   # All the remaining lines weren't found in OWNERS files, so emit an error.
1945   errors = []
1946   for owners_file, patterns in to_check.iteritems():
1947     missing_lines = []
1948     files = []
1949     for _, entry in patterns.iteritems():
1950       missing_lines.extend(entry['rules'])
1951       files.extend(['  %s' % f.LocalPath() for f in entry['files']])
1952     if missing_lines:
1953       errors.append(
1954           '%s needs the following lines added:\n\n%s\n\nfor files:\n%s' %
1955           (owners_file, '\n'.join(missing_lines), '\n'.join(files)))
1956
1957   results = []
1958   if errors:
1959     if input_api.is_committing:
1960       output = output_api.PresubmitError
1961     else:
1962       output = output_api.PresubmitPromptWarning
1963     results.append(output(
1964         'Found OWNERS files that need to be updated for IPC security ' +
1965         'review coverage.\nPlease update the OWNERS files below:',
1966         long_text='\n\n'.join(errors)))
1967
1968   return results
1969
1970
1971 def _CheckUselessForwardDeclarations(input_api, output_api):
1972   """Checks that added or removed lines in non third party affected
1973      header files do not lead to new useless class or struct forward
1974      declaration.
1975   """
1976   results = []
1977   class_pattern = input_api.re.compile(r'^class\s+(\w+);$',
1978                                        input_api.re.MULTILINE)
1979   struct_pattern = input_api.re.compile(r'^struct\s+(\w+);$',
1980                                         input_api.re.MULTILINE)
1981   for f in input_api.AffectedFiles(include_deletes=False):
1982     if (f.LocalPath().startswith('third_party') and
1983         not f.LocalPath().startswith('third_party/blink') and
1984         not f.LocalPath().startswith('third_party\\blink') and
1985         not f.LocalPath().startswith('third_party/WebKit') and
1986         not f.LocalPath().startswith('third_party\\WebKit')):
1987       continue
1988
1989     if not f.LocalPath().endswith('.h'):
1990       continue
1991
1992     contents = input_api.ReadFile(f)
1993     fwd_decls = input_api.re.findall(class_pattern, contents)
1994     fwd_decls.extend(input_api.re.findall(struct_pattern, contents))
1995
1996     useless_fwd_decls = []
1997     for decl in fwd_decls:
1998       count = sum(1 for _ in input_api.re.finditer(
1999         r'\b%s\b' % input_api.re.escape(decl), contents))
2000       if count == 1:
2001         useless_fwd_decls.append(decl)
2002
2003     if not useless_fwd_decls:
2004       continue
2005
2006     for line in f.GenerateScmDiff().splitlines():
2007       if (line.startswith('-') and not line.startswith('--') or
2008           line.startswith('+') and not line.startswith('++')):
2009         for decl in useless_fwd_decls:
2010           if input_api.re.search(r'\b%s\b' % decl, line[1:]):
2011             results.append(output_api.PresubmitPromptWarning(
2012               '%s: %s forward declaration is no longer needed' %
2013               (f.LocalPath(), decl)))
2014             useless_fwd_decls.remove(decl)
2015
2016   return results
2017
2018
2019 def _CheckAndroidToastUsage(input_api, output_api):
2020   """Checks that code uses org.chromium.ui.widget.Toast instead of
2021      android.widget.Toast (Chromium Toast doesn't force hardware
2022      acceleration on low-end devices, saving memory).
2023   """
2024   toast_import_pattern = input_api.re.compile(
2025       r'^import android\.widget\.Toast;$')
2026
2027   errors = []
2028
2029   sources = lambda affected_file: input_api.FilterSourceFile(
2030       affected_file,
2031       black_list=(_EXCLUDED_PATHS +
2032                   _TEST_CODE_EXCLUDED_PATHS +
2033                   input_api.DEFAULT_BLACK_LIST +
2034                   (r'^chromecast[\\\/].*',
2035                    r'^remoting[\\\/].*')),
2036       white_list=(r'.*\.java$',))
2037
2038   for f in input_api.AffectedSourceFiles(sources):
2039     for line_num, line in f.ChangedContents():
2040       if toast_import_pattern.search(line):
2041         errors.append("%s:%d" % (f.LocalPath(), line_num))
2042
2043   results = []
2044
2045   if errors:
2046     results.append(output_api.PresubmitError(
2047         'android.widget.Toast usage is detected. Android toasts use hardware'
2048         ' acceleration, and can be\ncostly on low-end devices. Please use'
2049         ' org.chromium.ui.widget.Toast instead.\n'
2050         'Contact dskiba@chromium.org if you have any questions.',
2051         errors))
2052
2053   return results
2054
2055
2056 def _CheckAndroidCrLogUsage(input_api, output_api):
2057   """Checks that new logs using org.chromium.base.Log:
2058     - Are using 'TAG' as variable name for the tags (warn)
2059     - Are using a tag that is shorter than 20 characters (error)
2060   """
2061
2062   # Do not check format of logs in the given files
2063   cr_log_check_excluded_paths = [
2064     # //chrome/android/webapk cannot depend on //base
2065     r"^chrome[\\\/]android[\\\/]webapk[\\\/].*",
2066     # WebView license viewer code cannot depend on //base; used in stub APK.
2067     r"^android_webview[\\\/]glue[\\\/]java[\\\/]src[\\\/]com[\\\/]android[\\\/]"
2068     r"webview[\\\/]chromium[\\\/]License.*",
2069   ]
2070
2071   cr_log_import_pattern = input_api.re.compile(
2072       r'^import org\.chromium\.base\.Log;$', input_api.re.MULTILINE)
2073   class_in_base_pattern = input_api.re.compile(
2074       r'^package org\.chromium\.base;$', input_api.re.MULTILINE)
2075   has_some_log_import_pattern = input_api.re.compile(
2076       r'^import .*\.Log;$', input_api.re.MULTILINE)
2077   # Extract the tag from lines like `Log.d(TAG, "*");` or `Log.d("TAG", "*");`
2078   log_call_pattern = input_api.re.compile(r'^\s*Log\.\w\((?P<tag>\"?\w+\"?)\,')
2079   log_decl_pattern = input_api.re.compile(
2080       r'^\s*private static final String TAG = "(?P<name>(.*))";',
2081       input_api.re.MULTILINE)
2082
2083   REF_MSG = ('See docs/android_logging.md '
2084             'or contact dgn@chromium.org for more info.')
2085   sources = lambda x: input_api.FilterSourceFile(x, white_list=(r'.*\.java$',),
2086       black_list=cr_log_check_excluded_paths)
2087
2088   tag_decl_errors = []
2089   tag_length_errors = []
2090   tag_errors = []
2091   tag_with_dot_errors = []
2092   util_log_errors = []
2093
2094   for f in input_api.AffectedSourceFiles(sources):
2095     file_content = input_api.ReadFile(f)
2096     has_modified_logs = False
2097
2098     # Per line checks
2099     if (cr_log_import_pattern.search(file_content) or
2100         (class_in_base_pattern.search(file_content) and
2101             not has_some_log_import_pattern.search(file_content))):
2102       # Checks to run for files using cr log
2103       for line_num, line in f.ChangedContents():
2104
2105         # Check if the new line is doing some logging
2106         match = log_call_pattern.search(line)
2107         if match:
2108           has_modified_logs = True
2109
2110           # Make sure it uses "TAG"
2111           if not match.group('tag') == 'TAG':
2112             tag_errors.append("%s:%d" % (f.LocalPath(), line_num))
2113     else:
2114       # Report non cr Log function calls in changed lines
2115       for line_num, line in f.ChangedContents():
2116         if log_call_pattern.search(line):
2117           util_log_errors.append("%s:%d" % (f.LocalPath(), line_num))
2118
2119     # Per file checks
2120     if has_modified_logs:
2121       # Make sure the tag is using the "cr" prefix and is not too long
2122       match = log_decl_pattern.search(file_content)
2123       tag_name = match.group('name') if match else None
2124       if not tag_name:
2125         tag_decl_errors.append(f.LocalPath())
2126       elif len(tag_name) > 20:
2127         tag_length_errors.append(f.LocalPath())
2128       elif '.' in tag_name:
2129         tag_with_dot_errors.append(f.LocalPath())
2130
2131   results = []
2132   if tag_decl_errors:
2133     results.append(output_api.PresubmitPromptWarning(
2134         'Please define your tags using the suggested format: .\n'
2135         '"private static final String TAG = "<package tag>".\n'
2136         'They will be prepended with "cr_" automatically.\n' + REF_MSG,
2137         tag_decl_errors))
2138
2139   if tag_length_errors:
2140     results.append(output_api.PresubmitError(
2141         'The tag length is restricted by the system to be at most '
2142         '20 characters.\n' + REF_MSG,
2143         tag_length_errors))
2144
2145   if tag_errors:
2146     results.append(output_api.PresubmitPromptWarning(
2147         'Please use a variable named "TAG" for your log tags.\n' + REF_MSG,
2148         tag_errors))
2149
2150   if util_log_errors:
2151     results.append(output_api.PresubmitPromptWarning(
2152         'Please use org.chromium.base.Log for new logs.\n' + REF_MSG,
2153         util_log_errors))
2154
2155   if tag_with_dot_errors:
2156     results.append(output_api.PresubmitPromptWarning(
2157         'Dot in log tags cause them to be elided in crash reports.\n' + REF_MSG,
2158         tag_with_dot_errors))
2159
2160   return results
2161
2162
2163 def _CheckAndroidTestJUnitFrameworkImport(input_api, output_api):
2164   """Checks that junit.framework.* is no longer used."""
2165   deprecated_junit_framework_pattern = input_api.re.compile(
2166       r'^import junit\.framework\..*;',
2167       input_api.re.MULTILINE)
2168   sources = lambda x: input_api.FilterSourceFile(
2169       x, white_list=(r'.*\.java$',), black_list=None)
2170   errors = []
2171   for f in input_api.AffectedFiles(sources):
2172     for line_num, line in f.ChangedContents():
2173       if deprecated_junit_framework_pattern.search(line):
2174         errors.append("%s:%d" % (f.LocalPath(), line_num))
2175
2176   results = []
2177   if errors:
2178     results.append(output_api.PresubmitError(
2179       'APIs from junit.framework.* are deprecated, please use JUnit4 framework'
2180       '(org.junit.*) from //third_party/junit. Contact yolandyan@chromium.org'
2181       ' if you have any question.', errors))
2182   return results
2183
2184
2185 def _CheckAndroidTestJUnitInheritance(input_api, output_api):
2186   """Checks that if new Java test classes have inheritance.
2187      Either the new test class is JUnit3 test or it is a JUnit4 test class
2188      with a base class, either case is undesirable.
2189   """
2190   class_declaration_pattern = input_api.re.compile(r'^public class \w*Test ')
2191
2192   sources = lambda x: input_api.FilterSourceFile(
2193       x, white_list=(r'.*Test\.java$',), black_list=None)
2194   errors = []
2195   for f in input_api.AffectedFiles(sources):
2196     if not f.OldContents():
2197       class_declaration_start_flag = False
2198       for line_num, line in f.ChangedContents():
2199         if class_declaration_pattern.search(line):
2200           class_declaration_start_flag = True
2201         if class_declaration_start_flag and ' extends ' in line:
2202           errors.append('%s:%d' % (f.LocalPath(), line_num))
2203         if '{' in line:
2204           class_declaration_start_flag = False
2205
2206   results = []
2207   if errors:
2208     results.append(output_api.PresubmitPromptWarning(
2209       'The newly created files include Test classes that inherits from base'
2210       ' class. Please do not use inheritance in JUnit4 tests or add new'
2211       ' JUnit3 tests. Contact yolandyan@chromium.org if you have any'
2212       ' questions.', errors))
2213   return results
2214
2215 def _CheckAndroidTestAnnotationUsage(input_api, output_api):
2216   """Checks that android.test.suitebuilder.annotation.* is no longer used."""
2217   deprecated_annotation_import_pattern = input_api.re.compile(
2218       r'^import android\.test\.suitebuilder\.annotation\..*;',
2219       input_api.re.MULTILINE)
2220   sources = lambda x: input_api.FilterSourceFile(
2221       x, white_list=(r'.*\.java$',), black_list=None)
2222   errors = []
2223   for f in input_api.AffectedFiles(sources):
2224     for line_num, line in f.ChangedContents():
2225       if deprecated_annotation_import_pattern.search(line):
2226         errors.append("%s:%d" % (f.LocalPath(), line_num))
2227
2228   results = []
2229   if errors:
2230     results.append(output_api.PresubmitError(
2231       'Annotations in android.test.suitebuilder.annotation have been'
2232       ' deprecated since API level 24. Please use android.support.test.filters'
2233       ' from //third_party/android_support_test_runner:runner_java instead.'
2234       ' Contact yolandyan@chromium.org if you have any questions.', errors))
2235   return results
2236
2237
2238 def _CheckAndroidNewMdpiAssetLocation(input_api, output_api):
2239   """Checks if MDPI assets are placed in a correct directory."""
2240   file_filter = lambda f: (f.LocalPath().endswith('.png') and
2241                            ('/res/drawable/' in f.LocalPath() or
2242                             '/res/drawable-ldrtl/' in f.LocalPath()))
2243   errors = []
2244   for f in input_api.AffectedFiles(include_deletes=False,
2245                                    file_filter=file_filter):
2246     errors.append('    %s' % f.LocalPath())
2247
2248   results = []
2249   if errors:
2250     results.append(output_api.PresubmitError(
2251         'MDPI assets should be placed in /res/drawable-mdpi/ or '
2252         '/res/drawable-ldrtl-mdpi/\ninstead of /res/drawable/ and'
2253         '/res/drawable-ldrtl/.\n'
2254         'Contact newt@chromium.org if you have questions.', errors))
2255   return results
2256
2257
2258 def _CheckAndroidWebkitImports(input_api, output_api):
2259   """Checks that code uses org.chromium.base.Callback instead of
2260      android.widget.ValueCallback except in the WebView glue layer.
2261   """
2262   valuecallback_import_pattern = input_api.re.compile(
2263       r'^import android\.webkit\.ValueCallback;$')
2264
2265   errors = []
2266
2267   sources = lambda affected_file: input_api.FilterSourceFile(
2268       affected_file,
2269       black_list=(_EXCLUDED_PATHS +
2270                   _TEST_CODE_EXCLUDED_PATHS +
2271                   input_api.DEFAULT_BLACK_LIST +
2272                   (r'^android_webview[\\\/]glue[\\\/].*',)),
2273       white_list=(r'.*\.java$',))
2274
2275   for f in input_api.AffectedSourceFiles(sources):
2276     for line_num, line in f.ChangedContents():
2277       if valuecallback_import_pattern.search(line):
2278         errors.append("%s:%d" % (f.LocalPath(), line_num))
2279
2280   results = []
2281
2282   if errors:
2283     results.append(output_api.PresubmitError(
2284         'android.webkit.ValueCallback usage is detected outside of the glue'
2285         ' layer. To stay compatible with the support library, android.webkit.*'
2286         ' classes should only be used inside the glue layer and'
2287         ' org.chromium.base.Callback should be used instead.',
2288         errors))
2289
2290   return results
2291
2292
2293 class PydepsChecker(object):
2294   def __init__(self, input_api, pydeps_files):
2295     self._file_cache = {}
2296     self._input_api = input_api
2297     self._pydeps_files = pydeps_files
2298
2299   def _LoadFile(self, path):
2300     """Returns the list of paths within a .pydeps file relative to //."""
2301     if path not in self._file_cache:
2302       with open(path) as f:
2303         self._file_cache[path] = f.read()
2304     return self._file_cache[path]
2305
2306   def _ComputeNormalizedPydepsEntries(self, pydeps_path):
2307     """Returns an interable of paths within the .pydep, relativized to //."""
2308     os_path = self._input_api.os_path
2309     pydeps_dir = os_path.dirname(pydeps_path)
2310     entries = (l.rstrip() for l in self._LoadFile(pydeps_path).splitlines()
2311                if not l.startswith('*'))
2312     return (os_path.normpath(os_path.join(pydeps_dir, e)) for e in entries)
2313
2314   def _CreateFilesToPydepsMap(self):
2315     """Returns a map of local_path -> list_of_pydeps."""
2316     ret = {}
2317     for pydep_local_path in self._pydeps_files:
2318       for path in self._ComputeNormalizedPydepsEntries(pydep_local_path):
2319         ret.setdefault(path, []).append(pydep_local_path)
2320     return ret
2321
2322   def ComputeAffectedPydeps(self):
2323     """Returns an iterable of .pydeps files that might need regenerating."""
2324     affected_pydeps = set()
2325     file_to_pydeps_map = None
2326     for f in self._input_api.AffectedFiles(include_deletes=True):
2327       local_path = f.LocalPath()
2328       if local_path  == 'DEPS':
2329         return self._pydeps_files
2330       elif local_path.endswith('.pydeps'):
2331         if local_path in self._pydeps_files:
2332           affected_pydeps.add(local_path)
2333       elif local_path.endswith('.py'):
2334         if file_to_pydeps_map is None:
2335           file_to_pydeps_map = self._CreateFilesToPydepsMap()
2336         affected_pydeps.update(file_to_pydeps_map.get(local_path, ()))
2337     return affected_pydeps
2338
2339   def DetermineIfStale(self, pydeps_path):
2340     """Runs print_python_deps.py to see if the files is stale."""
2341     import difflib
2342     import os
2343
2344     old_pydeps_data = self._LoadFile(pydeps_path).splitlines()
2345     cmd = old_pydeps_data[1][1:].strip()
2346     env = dict(os.environ)
2347     env['PYTHONDONTWRITEBYTECODE'] = '1'
2348     new_pydeps_data = self._input_api.subprocess.check_output(
2349         cmd + ' --output ""', shell=True, env=env)
2350     old_contents = old_pydeps_data[2:]
2351     new_contents = new_pydeps_data.splitlines()[2:]
2352     if old_pydeps_data[2:] != new_pydeps_data.splitlines()[2:]:
2353       return cmd, '\n'.join(difflib.context_diff(old_contents, new_contents))
2354
2355
2356 def _CheckPydepsNeedsUpdating(input_api, output_api, checker_for_tests=None):
2357   """Checks if a .pydeps file needs to be regenerated."""
2358   # This check is for Python dependency lists (.pydeps files), and involves
2359   # paths not only in the PRESUBMIT.py, but also in the .pydeps files. It
2360   # doesn't work on Windows and Mac, so skip it on other platforms.
2361   if input_api.platform != 'linux2':
2362     return []
2363   # TODO(agrieve): Update when there's a better way to detect
2364   # this: crbug.com/570091
2365   is_android = input_api.os_path.exists('third_party/android_tools')
2366   pydeps_files = _ALL_PYDEPS_FILES if is_android else _GENERIC_PYDEPS_FILES
2367   results = []
2368   # First, check for new / deleted .pydeps.
2369   for f in input_api.AffectedFiles(include_deletes=True):
2370     # Check whether we are running the presubmit check for a file in src.
2371     # f.LocalPath is relative to repo (src, or internal repo).
2372     # os_path.exists is relative to src repo.
2373     # Therefore if os_path.exists is true, it means f.LocalPath is relative
2374     # to src and we can conclude that the pydeps is in src.
2375     if input_api.os_path.exists(f.LocalPath()):
2376       if f.LocalPath().endswith('.pydeps'):
2377         if f.Action() == 'D' and f.LocalPath() in _ALL_PYDEPS_FILES:
2378           results.append(output_api.PresubmitError(
2379               'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
2380               'remove %s' % f.LocalPath()))
2381         elif f.Action() != 'D' and f.LocalPath() not in _ALL_PYDEPS_FILES:
2382           results.append(output_api.PresubmitError(
2383               'Please update _ALL_PYDEPS_FILES within //PRESUBMIT.py to '
2384               'include %s' % f.LocalPath()))
2385
2386   if results:
2387     return results
2388
2389   checker = checker_for_tests or PydepsChecker(input_api, pydeps_files)
2390
2391   for pydep_path in checker.ComputeAffectedPydeps():
2392     try:
2393       result = checker.DetermineIfStale(pydep_path)
2394       if result:
2395         cmd, diff = result
2396         results.append(output_api.PresubmitError(
2397             'File is stale: %s\nDiff (apply to fix):\n%s\n'
2398             'To regenerate, run:\n\n    %s' %
2399             (pydep_path, diff, cmd)))
2400     except input_api.subprocess.CalledProcessError as error:
2401       return [output_api.PresubmitError('Error running: %s' % error.cmd,
2402           long_text=error.output)]
2403
2404   return results
2405
2406
2407 def _CheckSingletonInHeaders(input_api, output_api):
2408   """Checks to make sure no header files have |Singleton<|."""
2409   def FileFilter(affected_file):
2410     # It's ok for base/memory/singleton.h to have |Singleton<|.
2411     black_list = (_EXCLUDED_PATHS +
2412                   input_api.DEFAULT_BLACK_LIST +
2413                   (r"^base[\\\/]memory[\\\/]singleton\.h$",
2414                    r"^net[\\\/]quic[\\\/]platform[\\\/]impl[\\\/]"
2415                        r"quic_singleton_impl\.h$"))
2416     return input_api.FilterSourceFile(affected_file, black_list=black_list)
2417
2418   pattern = input_api.re.compile(r'(?<!class\sbase::)Singleton\s*<')
2419   files = []
2420   for f in input_api.AffectedSourceFiles(FileFilter):
2421     if (f.LocalPath().endswith('.h') or f.LocalPath().endswith('.hxx') or
2422         f.LocalPath().endswith('.hpp') or f.LocalPath().endswith('.inl')):
2423       contents = input_api.ReadFile(f)
2424       for line in contents.splitlines(False):
2425         if (not line.lstrip().startswith('//') and # Strip C++ comment.
2426             pattern.search(line)):
2427           files.append(f)
2428           break
2429
2430   if files:
2431     return [output_api.PresubmitError(
2432         'Found base::Singleton<T> in the following header files.\n' +
2433         'Please move them to an appropriate source file so that the ' +
2434         'template gets instantiated in a single compilation unit.',
2435         files) ]
2436   return []
2437
2438
2439 _DEPRECATED_CSS = [
2440   # Values
2441   ( "-webkit-box", "flex" ),
2442   ( "-webkit-inline-box", "inline-flex" ),
2443   ( "-webkit-flex", "flex" ),
2444   ( "-webkit-inline-flex", "inline-flex" ),
2445   ( "-webkit-min-content", "min-content" ),
2446   ( "-webkit-max-content", "max-content" ),
2447
2448   # Properties
2449   ( "-webkit-background-clip", "background-clip" ),
2450   ( "-webkit-background-origin", "background-origin" ),
2451   ( "-webkit-background-size", "background-size" ),
2452   ( "-webkit-box-shadow", "box-shadow" ),
2453   ( "-webkit-user-select", "user-select" ),
2454
2455   # Functions
2456   ( "-webkit-gradient", "gradient" ),
2457   ( "-webkit-repeating-gradient", "repeating-gradient" ),
2458   ( "-webkit-linear-gradient", "linear-gradient" ),
2459   ( "-webkit-repeating-linear-gradient", "repeating-linear-gradient" ),
2460   ( "-webkit-radial-gradient", "radial-gradient" ),
2461   ( "-webkit-repeating-radial-gradient", "repeating-radial-gradient" ),
2462 ]
2463
2464 def _CheckNoDeprecatedCss(input_api, output_api):
2465   """ Make sure that we don't use deprecated CSS
2466       properties, functions or values. Our external
2467       documentation and iOS CSS for dom distiller
2468       (reader mode) are ignored by the hooks as it
2469       needs to be consumed by WebKit. """
2470   results = []
2471   file_inclusion_pattern = (r".+\.css$",)
2472   black_list = (_EXCLUDED_PATHS +
2473                 _TEST_CODE_EXCLUDED_PATHS +
2474                 input_api.DEFAULT_BLACK_LIST +
2475                 (r"^chrome/common/extensions/docs",
2476                  r"^chrome/docs",
2477                  r"^components/dom_distiller/core/css/distilledpage_ios.css",
2478                  r"^components/neterror/resources/neterror.css",
2479                  r"^native_client_sdk"))
2480   file_filter = lambda f: input_api.FilterSourceFile(
2481       f, white_list=file_inclusion_pattern, black_list=black_list)
2482   for fpath in input_api.AffectedFiles(file_filter=file_filter):
2483     for line_num, line in fpath.ChangedContents():
2484       for (deprecated_value, value) in _DEPRECATED_CSS:
2485         if deprecated_value in line:
2486           results.append(output_api.PresubmitError(
2487               "%s:%d: Use of deprecated CSS %s, use %s instead" %
2488               (fpath.LocalPath(), line_num, deprecated_value, value)))
2489   return results
2490
2491
2492 _DEPRECATED_JS = [
2493   ( "__lookupGetter__", "Object.getOwnPropertyDescriptor" ),
2494   ( "__defineGetter__", "Object.defineProperty" ),
2495   ( "__defineSetter__", "Object.defineProperty" ),
2496 ]
2497
2498 def _CheckNoDeprecatedJs(input_api, output_api):
2499   """Make sure that we don't use deprecated JS in Chrome code."""
2500   results = []
2501   file_inclusion_pattern = (r".+\.js$",)  # TODO(dbeam): .html?
2502   black_list = (_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS +
2503                 input_api.DEFAULT_BLACK_LIST)
2504   file_filter = lambda f: input_api.FilterSourceFile(
2505       f, white_list=file_inclusion_pattern, black_list=black_list)
2506   for fpath in input_api.AffectedFiles(file_filter=file_filter):
2507     for lnum, line in fpath.ChangedContents():
2508       for (deprecated, replacement) in _DEPRECATED_JS:
2509         if deprecated in line:
2510           results.append(output_api.PresubmitError(
2511               "%s:%d: Use of deprecated JS %s, use %s instead" %
2512               (fpath.LocalPath(), lnum, deprecated, replacement)))
2513   return results
2514
2515 def _CheckForRiskyJsArrowFunction(line_number, line):
2516   if ' => ' in line:
2517     return "line %d, is using an => (arrow) function\n %s\n" % (
2518         line_number, line)
2519   return ''
2520
2521 def _CheckForRiskyJsConstLet(input_api, line_number, line):
2522   if input_api.re.match('^\s*(const|let)\s', line):
2523     return "line %d, is using const/let keyword\n %s\n" % (
2524         line_number, line)
2525   return ''
2526
2527 def _CheckForRiskyJsFeatures(input_api, output_api):
2528   maybe_ios_js = (r"^(ios|components|ui\/webui\/resources)\/.+\.js$", )
2529   # 'ui/webui/resources/cr_components are not allowed on ios'
2530   not_ios_filter = (r".*ui\/webui\/resources\/cr_components.*", )
2531   file_filter = lambda f: input_api.FilterSourceFile(f, white_list=maybe_ios_js,
2532                                                      black_list=not_ios_filter)
2533   results = []
2534   for f in input_api.AffectedFiles(file_filter=file_filter):
2535     arrow_error_lines = []
2536     const_let_error_lines = []
2537     for lnum, line in f.ChangedContents():
2538       arrow_error_lines += filter(None, [
2539         _CheckForRiskyJsArrowFunction(lnum, line),
2540       ])
2541
2542       const_let_error_lines += filter(None, [
2543         _CheckForRiskyJsConstLet(input_api, lnum, line),
2544       ])
2545
2546     if arrow_error_lines:
2547       arrow_error_lines = map(
2548           lambda e: "%s:%s" % (f.LocalPath(), e), arrow_error_lines)
2549       results.append(
2550           output_api.PresubmitPromptWarning('\n'.join(arrow_error_lines + [
2551 """
2552 Use of => (arrow) operator detected in:
2553 %s
2554 Please ensure your code does not run on iOS9 (=> (arrow) does not work there).
2555 https://chromium.googlesource.com/chromium/src/+/master/docs/es6_chromium.md#Arrow-Functions
2556 """ % f.LocalPath()
2557           ])))
2558
2559     if const_let_error_lines:
2560       const_let_error_lines = map(
2561           lambda e: "%s:%s" % (f.LocalPath(), e), const_let_error_lines)
2562       results.append(
2563           output_api.PresubmitPromptWarning('\n'.join(const_let_error_lines + [
2564 """
2565 Use of const/let keywords detected in:
2566 %s
2567 Please ensure your code does not run on iOS9 because const/let is not fully
2568 supported.
2569 https://chromium.googlesource.com/chromium/src/+/master/docs/es6_chromium.md#let-Block_Scoped-Variables
2570 https://chromium.googlesource.com/chromium/src/+/master/docs/es6_chromium.md#const-Block_Scoped-Constants
2571 """ % f.LocalPath()
2572           ])))
2573
2574   return results
2575
2576 def _CheckForRelativeIncludes(input_api, output_api):
2577   # Need to set the sys.path so PRESUBMIT_test.py runs properly
2578   import sys
2579   original_sys_path = sys.path
2580   try:
2581     sys.path = sys.path + [input_api.os_path.join(
2582         input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
2583     from cpp_checker import CppChecker
2584   finally:
2585     # Restore sys.path to what it was before.
2586     sys.path = original_sys_path
2587
2588   bad_files = {}
2589   for f in input_api.AffectedFiles(include_deletes=False):
2590     if (f.LocalPath().startswith('third_party') and
2591       not f.LocalPath().startswith('third_party/WebKit') and
2592       not f.LocalPath().startswith('third_party\\WebKit')):
2593       continue
2594
2595     if not CppChecker.IsCppFile(f.LocalPath()):
2596       continue
2597
2598     relative_includes = [line for _, line in f.ChangedContents()
2599                          if "#include" in line and "../" in line]
2600     if not relative_includes:
2601       continue
2602     bad_files[f.LocalPath()] = relative_includes
2603
2604   if not bad_files:
2605     return []
2606
2607   error_descriptions = []
2608   for file_path, bad_lines in bad_files.iteritems():
2609     error_description = file_path
2610     for line in bad_lines:
2611       error_description += '\n    ' + line
2612     error_descriptions.append(error_description)
2613
2614   results = []
2615   results.append(output_api.PresubmitError(
2616         'You added one or more relative #include paths (including "../").\n'
2617         'These shouldn\'t be used because they can be used to include headers\n'
2618         'from code that\'s not correctly specified as a dependency in the\n'
2619         'relevant BUILD.gn file(s).',
2620         error_descriptions))
2621
2622   return results
2623
2624
2625 def _CheckWatchlistDefinitionsEntrySyntax(key, value, ast):
2626   if not isinstance(key, ast.Str):
2627     return 'Key at line %d must be a string literal' % key.lineno
2628   if not isinstance(value, ast.Dict):
2629     return 'Value at line %d must be a dict' % value.lineno
2630   if len(value.keys) != 1:
2631     return 'Dict at line %d must have single entry' % value.lineno
2632   if not isinstance(value.keys[0], ast.Str) or value.keys[0].s != 'filepath':
2633     return (
2634         'Entry at line %d must have a string literal \'filepath\' as key' %
2635         value.lineno)
2636   return None
2637
2638
2639 def _CheckWatchlistsEntrySyntax(key, value, ast):
2640   if not isinstance(key, ast.Str):
2641     return 'Key at line %d must be a string literal' % key.lineno
2642   if not isinstance(value, ast.List):
2643     return 'Value at line %d must be a list' % value.lineno
2644   return None
2645
2646
2647 def _CheckWATCHLISTSEntries(wd_dict, w_dict, ast):
2648   mismatch_template = (
2649       'Mismatch between WATCHLIST_DEFINITIONS entry (%s) and WATCHLISTS '
2650       'entry (%s)')
2651
2652   i = 0
2653   last_key = ''
2654   while True:
2655     if i >= len(wd_dict.keys):
2656       if i >= len(w_dict.keys):
2657         return None
2658       return mismatch_template % ('missing', 'line %d' % w_dict.keys[i].lineno)
2659     elif i >= len(w_dict.keys):
2660       return (
2661           mismatch_template % ('line %d' % wd_dict.keys[i].lineno, 'missing'))
2662
2663     wd_key = wd_dict.keys[i]
2664     w_key = w_dict.keys[i]
2665
2666     result = _CheckWatchlistDefinitionsEntrySyntax(
2667         wd_key, wd_dict.values[i], ast)
2668     if result is not None:
2669       return 'Bad entry in WATCHLIST_DEFINITIONS dict: %s' % result
2670
2671     result = _CheckWatchlistsEntrySyntax(w_key, w_dict.values[i], ast)
2672     if result is not None:
2673       return 'Bad entry in WATCHLISTS dict: %s' % result
2674
2675     if wd_key.s != w_key.s:
2676       return mismatch_template % (
2677           '%s at line %d' % (wd_key.s, wd_key.lineno),
2678           '%s at line %d' % (w_key.s, w_key.lineno))
2679
2680     if wd_key.s < last_key:
2681       return (
2682           'WATCHLISTS dict is not sorted lexicographically at line %d and %d' %
2683           (wd_key.lineno, w_key.lineno))
2684     last_key = wd_key.s
2685
2686     i = i + 1
2687
2688
2689 def _CheckWATCHLISTSSyntax(expression, ast):
2690   if not isinstance(expression, ast.Expression):
2691     return 'WATCHLISTS file must contain a valid expression'
2692   dictionary = expression.body
2693   if not isinstance(dictionary, ast.Dict) or len(dictionary.keys) != 2:
2694     return 'WATCHLISTS file must have single dict with exactly two entries'
2695
2696   first_key = dictionary.keys[0]
2697   first_value = dictionary.values[0]
2698   second_key = dictionary.keys[1]
2699   second_value = dictionary.values[1]
2700
2701   if (not isinstance(first_key, ast.Str) or
2702       first_key.s != 'WATCHLIST_DEFINITIONS' or
2703       not isinstance(first_value, ast.Dict)):
2704     return (
2705         'The first entry of the dict in WATCHLISTS file must be '
2706         'WATCHLIST_DEFINITIONS dict')
2707
2708   if (not isinstance(second_key, ast.Str) or
2709       second_key.s != 'WATCHLISTS' or
2710       not isinstance(second_value, ast.Dict)):
2711     return (
2712         'The second entry of the dict in WATCHLISTS file must be '
2713         'WATCHLISTS dict')
2714
2715   return _CheckWATCHLISTSEntries(first_value, second_value, ast)
2716
2717
2718 def _CheckWATCHLISTS(input_api, output_api):
2719   for f in input_api.AffectedFiles(include_deletes=False):
2720     if f.LocalPath() == 'WATCHLISTS':
2721       contents = input_api.ReadFile(f, 'r')
2722
2723       try:
2724         # First, make sure that it can be evaluated.
2725         input_api.ast.literal_eval(contents)
2726         # Get an AST tree for it and scan the tree for detailed style checking.
2727         expression = input_api.ast.parse(
2728             contents, filename='WATCHLISTS', mode='eval')
2729       except ValueError as e:
2730         return [output_api.PresubmitError(
2731             'Cannot parse WATCHLISTS file', long_text=repr(e))]
2732       except SyntaxError as e:
2733         return [output_api.PresubmitError(
2734             'Cannot parse WATCHLISTS file', long_text=repr(e))]
2735       except TypeError as e:
2736         return [output_api.PresubmitError(
2737             'Cannot parse WATCHLISTS file', long_text=repr(e))]
2738
2739       result = _CheckWATCHLISTSSyntax(expression, input_api.ast)
2740       if result is not None:
2741         return [output_api.PresubmitError(result)]
2742       break
2743
2744   return []
2745
2746
2747 def _AndroidSpecificOnUploadChecks(input_api, output_api):
2748   """Groups checks that target android code."""
2749   results = []
2750   results.extend(_CheckAndroidCrLogUsage(input_api, output_api))
2751   results.extend(_CheckAndroidNewMdpiAssetLocation(input_api, output_api))
2752   results.extend(_CheckAndroidToastUsage(input_api, output_api))
2753   results.extend(_CheckAndroidTestJUnitInheritance(input_api, output_api))
2754   results.extend(_CheckAndroidTestJUnitFrameworkImport(input_api, output_api))
2755   results.extend(_CheckAndroidTestAnnotationUsage(input_api, output_api))
2756   results.extend(_CheckAndroidWebkitImports(input_api, output_api))
2757   return results
2758
2759
2760 def _CommonChecks(input_api, output_api):
2761   """Checks common to both upload and commit."""
2762   results = []
2763   results.extend(input_api.canned_checks.PanProjectChecks(
2764       input_api, output_api,
2765       excluded_paths=_EXCLUDED_PATHS))
2766
2767   author = input_api.change.author_email
2768   if author and author not in _KNOWN_ROBOTS:
2769     results.extend(
2770         input_api.canned_checks.CheckAuthorizedAuthor(input_api, output_api))
2771
2772   results.extend(
2773       _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
2774   results.extend(
2775       _CheckNoProductionCodeUsingTestOnlyFunctionsJava(input_api, output_api))
2776   results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
2777   results.extend(_CheckNoUNIT_TESTInSourceFiles(input_api, output_api))
2778   results.extend(_CheckDCHECK_IS_ONHasBraces(input_api, output_api))
2779   results.extend(_CheckNoNewWStrings(input_api, output_api))
2780   results.extend(_CheckNoDEPSGIT(input_api, output_api))
2781   results.extend(_CheckNoBannedFunctions(input_api, output_api))
2782   results.extend(_CheckNoPragmaOnce(input_api, output_api))
2783   results.extend(_CheckNoTrinaryTrueFalse(input_api, output_api))
2784   results.extend(_CheckUnwantedDependencies(input_api, output_api))
2785   results.extend(_CheckFilePermissions(input_api, output_api))
2786   results.extend(_CheckTeamTags(input_api, output_api))
2787   results.extend(_CheckNoAuraWindowPropertyHInHeaders(input_api, output_api))
2788   results.extend(_CheckForVersionControlConflicts(input_api, output_api))
2789   results.extend(_CheckPatchFiles(input_api, output_api))
2790   results.extend(_CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api))
2791   results.extend(_CheckNoAbbreviationInPngFileName(input_api, output_api))
2792   results.extend(_CheckBuildConfigMacrosWithoutInclude(input_api, output_api))
2793   results.extend(_CheckForInvalidOSMacros(input_api, output_api))
2794   results.extend(_CheckForInvalidIfDefinedMacros(input_api, output_api))
2795   results.extend(_CheckFlakyTestUsage(input_api, output_api))
2796   results.extend(_CheckAddedDepsHaveTargetApprovals(input_api, output_api))
2797   results.extend(
2798       input_api.canned_checks.CheckChangeHasNoTabs(
2799           input_api,
2800           output_api,
2801           source_file_filter=lambda x: x.LocalPath().endswith('.grd')))
2802   results.extend(_CheckSpamLogging(input_api, output_api))
2803   results.extend(_CheckForAnonymousVariables(input_api, output_api))
2804   results.extend(_CheckUserActionUpdate(input_api, output_api))
2805   results.extend(_CheckNoDeprecatedCss(input_api, output_api))
2806   results.extend(_CheckNoDeprecatedJs(input_api, output_api))
2807   results.extend(_CheckParseErrors(input_api, output_api))
2808   results.extend(_CheckForIPCRules(input_api, output_api))
2809   results.extend(_CheckForIncludeGuards(input_api, output_api))
2810   results.extend(_CheckForWindowsLineEndings(input_api, output_api))
2811   results.extend(_CheckSingletonInHeaders(input_api, output_api))
2812   results.extend(_CheckPydepsNeedsUpdating(input_api, output_api))
2813   results.extend(_CheckJavaStyle(input_api, output_api))
2814   results.extend(_CheckIpcOwners(input_api, output_api))
2815   results.extend(_CheckUselessForwardDeclarations(input_api, output_api))
2816   results.extend(_CheckForRiskyJsFeatures(input_api, output_api))
2817   results.extend(_CheckForRelativeIncludes(input_api, output_api))
2818   results.extend(_CheckWATCHLISTS(input_api, output_api))
2819   results.extend(input_api.RunTests(
2820     input_api.canned_checks.CheckVPythonSpec(input_api, output_api)))
2821
2822   for f in input_api.AffectedFiles():
2823     path, name = input_api.os_path.split(f.LocalPath())
2824     if name == 'PRESUBMIT.py':
2825       full_path = input_api.os_path.join(input_api.PresubmitLocalPath(), path)
2826       results.extend(input_api.canned_checks.RunUnitTestsInDirectory(
2827           input_api, output_api, full_path,
2828           whitelist=[r'^PRESUBMIT_test\.py$']))
2829   return results
2830
2831
2832 def _CheckPatchFiles(input_api, output_api):
2833   problems = [f.LocalPath() for f in input_api.AffectedFiles()
2834       if f.LocalPath().endswith(('.orig', '.rej'))]
2835   if problems:
2836     return [output_api.PresubmitError(
2837         "Don't commit .rej and .orig files.", problems)]
2838   else:
2839     return []
2840
2841
2842 def _CheckBuildConfigMacrosWithoutInclude(input_api, output_api):
2843   # Excludes OS_CHROMEOS, which is not defined in build_config.h.
2844   macro_re = input_api.re.compile(r'^\s*#(el)?if.*\bdefined\(((OS_(?!CHROMEOS)|'
2845                                   'COMPILER_|ARCH_CPU_|WCHAR_T_IS_)[^)]*)')
2846   include_re = input_api.re.compile(
2847       r'^#include\s+"build/build_config.h"', input_api.re.MULTILINE)
2848   extension_re = input_api.re.compile(r'\.[a-z]+$')
2849   errors = []
2850   for f in input_api.AffectedFiles():
2851     if not f.LocalPath().endswith(('.h', '.c', '.cc', '.cpp', '.m', '.mm')):
2852       continue
2853     found_line_number = None
2854     found_macro = None
2855     for line_num, line in f.ChangedContents():
2856       match = macro_re.search(line)
2857       if match:
2858         found_line_number = line_num
2859         found_macro = match.group(2)
2860         break
2861     if not found_line_number:
2862       continue
2863
2864     found_include = False
2865     for line in f.NewContents():
2866       if include_re.search(line):
2867         found_include = True
2868         break
2869     if found_include:
2870       continue
2871
2872     if not f.LocalPath().endswith('.h'):
2873       primary_header_path = extension_re.sub('.h', f.AbsoluteLocalPath())
2874       try:
2875         content = input_api.ReadFile(primary_header_path, 'r')
2876         if include_re.search(content):
2877           continue
2878       except IOError:
2879         pass
2880     errors.append('%s:%d %s macro is used without including build/'
2881                   'build_config.h.'
2882                   % (f.LocalPath(), found_line_number, found_macro))
2883   if errors:
2884     return [output_api.PresubmitPromptWarning('\n'.join(errors))]
2885   return []
2886
2887
2888 def _DidYouMeanOSMacro(bad_macro):
2889   try:
2890     return {'A': 'OS_ANDROID',
2891             'B': 'OS_BSD',
2892             'C': 'OS_CHROMEOS',
2893             'F': 'OS_FREEBSD',
2894             'L': 'OS_LINUX',
2895             'M': 'OS_MACOSX',
2896             'N': 'OS_NACL',
2897             'O': 'OS_OPENBSD',
2898             'P': 'OS_POSIX',
2899             'S': 'OS_SOLARIS',
2900             'W': 'OS_WIN'}[bad_macro[3].upper()]
2901   except KeyError:
2902     return ''
2903
2904
2905 def _CheckForInvalidOSMacrosInFile(input_api, f):
2906   """Check for sensible looking, totally invalid OS macros."""
2907   preprocessor_statement = input_api.re.compile(r'^\s*#')
2908   os_macro = input_api.re.compile(r'defined\((OS_[^)]+)\)')
2909   results = []
2910   for lnum, line in f.ChangedContents():
2911     if preprocessor_statement.search(line):
2912       for match in os_macro.finditer(line):
2913         if not match.group(1) in _VALID_OS_MACROS:
2914           good = _DidYouMeanOSMacro(match.group(1))
2915           did_you_mean = ' (did you mean %s?)' % good if good else ''
2916           results.append('    %s:%d %s%s' % (f.LocalPath(),
2917                                              lnum,
2918                                              match.group(1),
2919                                              did_you_mean))
2920   return results
2921
2922
2923 def _CheckForInvalidOSMacros(input_api, output_api):
2924   """Check all affected files for invalid OS macros."""
2925   bad_macros = []
2926   for f in input_api.AffectedFiles():
2927     if not f.LocalPath().endswith(('.py', '.js', '.html', '.css', '.md')):
2928       bad_macros.extend(_CheckForInvalidOSMacrosInFile(input_api, f))
2929
2930   if not bad_macros:
2931     return []
2932
2933   return [output_api.PresubmitError(
2934       'Possibly invalid OS macro[s] found. Please fix your code\n'
2935       'or add your macro to src/PRESUBMIT.py.', bad_macros)]
2936
2937
2938 def _CheckForInvalidIfDefinedMacrosInFile(input_api, f):
2939   """Check all affected files for invalid "if defined" macros."""
2940   ALWAYS_DEFINED_MACROS = (
2941       "TARGET_CPU_PPC",
2942       "TARGET_CPU_PPC64",
2943       "TARGET_CPU_68K",
2944       "TARGET_CPU_X86",
2945       "TARGET_CPU_ARM",
2946       "TARGET_CPU_MIPS",
2947       "TARGET_CPU_SPARC",
2948       "TARGET_CPU_ALPHA",
2949       "TARGET_IPHONE_SIMULATOR",
2950       "TARGET_OS_EMBEDDED",
2951       "TARGET_OS_IPHONE",
2952       "TARGET_OS_MAC",
2953       "TARGET_OS_UNIX",
2954       "TARGET_OS_WIN32",
2955   )
2956   ifdef_macro = input_api.re.compile(r'^\s*#.*(?:ifdef\s|defined\()([^\s\)]+)')
2957   results = []
2958   for lnum, line in f.ChangedContents():
2959     for match in ifdef_macro.finditer(line):
2960       if match.group(1) in ALWAYS_DEFINED_MACROS:
2961         always_defined = ' %s is always defined. ' % match.group(1)
2962         did_you_mean = 'Did you mean \'#if %s\'?' % match.group(1)
2963         results.append('    %s:%d %s\n\t%s' % (f.LocalPath(),
2964                                                lnum,
2965                                                always_defined,
2966                                                did_you_mean))
2967   return results
2968
2969
2970 def _CheckForInvalidIfDefinedMacros(input_api, output_api):
2971   """Check all affected files for invalid "if defined" macros."""
2972   bad_macros = []
2973   for f in input_api.AffectedFiles():
2974     if f.LocalPath().startswith('third_party/sqlite/'):
2975       continue
2976     if f.LocalPath().endswith(('.h', '.c', '.cc', '.m', '.mm')):
2977       bad_macros.extend(_CheckForInvalidIfDefinedMacrosInFile(input_api, f))
2978
2979   if not bad_macros:
2980     return []
2981
2982   return [output_api.PresubmitError(
2983       'Found ifdef check on always-defined macro[s]. Please fix your code\n'
2984       'or check the list of ALWAYS_DEFINED_MACROS in src/PRESUBMIT.py.',
2985       bad_macros)]
2986
2987
2988 def _CheckForIPCRules(input_api, output_api):
2989   """Check for same IPC rules described in
2990   http://www.chromium.org/Home/chromium-security/education/security-tips-for-ipc
2991   """
2992   base_pattern = r'IPC_ENUM_TRAITS\('
2993   inclusion_pattern = input_api.re.compile(r'(%s)' % base_pattern)
2994   comment_pattern = input_api.re.compile(r'//.*(%s)' % base_pattern)
2995
2996   problems = []
2997   for f in input_api.AffectedSourceFiles(None):
2998     local_path = f.LocalPath()
2999     if not local_path.endswith('.h'):
3000       continue
3001     for line_number, line in f.ChangedContents():
3002       if inclusion_pattern.search(line) and not comment_pattern.search(line):
3003         problems.append(
3004           '%s:%d\n    %s' % (local_path, line_number, line.strip()))
3005
3006   if problems:
3007     return [output_api.PresubmitPromptWarning(
3008         _IPC_ENUM_TRAITS_DEPRECATED, problems)]
3009   else:
3010     return []
3011
3012
3013 def _CheckForIncludeGuards(input_api, output_api):
3014   """Check that header files have proper guards against multiple inclusion.
3015   If a file should not have such guards (and it probably should) then it
3016   should include the string "no-include-guard-because-multiply-included".
3017   """
3018   def is_header_file(f):
3019     return f.LocalPath().endswith('.h')
3020
3021   def replace_special_with_underscore(string):
3022     return input_api.re.sub(r'[\\/.-]', '_', string)
3023
3024   errors = []
3025
3026   for f in input_api.AffectedSourceFiles(is_header_file):
3027     guard_name = None
3028     guard_line_number = None
3029     seen_guard_end = False
3030
3031     file_with_path = input_api.os_path.normpath(f.LocalPath())
3032     base_file_name = input_api.os_path.splitext(
3033       input_api.os_path.basename(file_with_path))[0]
3034     upper_base_file_name = base_file_name.upper()
3035
3036     expected_guard = replace_special_with_underscore(
3037       file_with_path.upper() + '_')
3038     expected_guard_if_blink = base_file_name + '_h'
3039
3040     # For "path/elem/file_name.h" we should really only accept
3041     # PATH_ELEM_FILE_NAME_H_ per coding style or, if Blink,
3042     # file_name_h.  Unfortunately there are too many (1000+) files
3043     # with slight deviations from the coding style. Since the most
3044     # important part is that the include guard is there, and that it's
3045     # unique, not the name, this check is forgiving for existing files.
3046     #
3047     # As code becomes more uniform, this could be made stricter.
3048
3049     guard_name_pattern_list = [
3050       # Anything with the right suffix (maybe with an extra _).
3051       r'\w+_H__?',
3052
3053       # To cover include guards with Blink style.
3054       r'\w+_h',
3055
3056       # Anything including the uppercase name of the file.
3057       r'\w*' + input_api.re.escape(replace_special_with_underscore(
3058         upper_base_file_name)) + r'\w*',
3059     ]
3060     guard_name_pattern = '|'.join(guard_name_pattern_list)
3061     guard_pattern = input_api.re.compile(
3062       r'#ifndef\s+(' + guard_name_pattern + ')')
3063
3064     for line_number, line in enumerate(f.NewContents()):
3065       if 'no-include-guard-because-multiply-included' in line:
3066         guard_name = 'DUMMY'  # To not trigger check outside the loop.
3067         break
3068
3069       if guard_name is None:
3070         match = guard_pattern.match(line)
3071         if match:
3072           guard_name = match.group(1)
3073           guard_line_number = line_number
3074
3075           # We allow existing files to use slightly wrong include
3076           # guards, but new files should get it right.
3077           if not f.OldContents():
3078             is_in_blink = file_with_path.startswith(input_api.os_path.join(
3079               'third_party', 'WebKit'))
3080             if not (guard_name == expected_guard or
3081                     is_in_blink and guard_name == expected_guard_if_blink):
3082               if is_in_blink:
3083                 expected_text = "%s or %s" % (expected_guard,
3084                                               expected_guard_if_blink)
3085               else:
3086                 expected_text = expected_guard
3087               errors.append(output_api.PresubmitPromptWarning(
3088                 'Header using the wrong include guard name %s' % guard_name,
3089                 ['%s:%d' % (f.LocalPath(), line_number + 1)],
3090                 'Expected: %r\nFound: %r' % (expected_text, guard_name)))
3091       else:
3092         # The line after #ifndef should have a #define of the same name.
3093         if line_number == guard_line_number + 1:
3094           expected_line = '#define %s' % guard_name
3095           if line != expected_line:
3096             errors.append(output_api.PresubmitPromptWarning(
3097               'Missing "%s" for include guard' % expected_line,
3098               ['%s:%d' % (f.LocalPath(), line_number + 1)],
3099               'Expected: %r\nGot: %r' % (expected_line, line)))
3100
3101         if not seen_guard_end and line == '#endif  // %s' % guard_name:
3102           seen_guard_end = True
3103         elif seen_guard_end:
3104           if line.strip() != '':
3105             errors.append(output_api.PresubmitPromptWarning(
3106               'Include guard %s not covering the whole file' % (
3107                 guard_name), [f.LocalPath()]))
3108             break  # Nothing else to check and enough to warn once.
3109
3110     if guard_name is None:
3111       errors.append(output_api.PresubmitPromptWarning(
3112         'Missing include guard %s' % expected_guard,
3113         [f.LocalPath()],
3114         'Missing include guard in %s\n'
3115         'Recommended name: %s\n'
3116         'This check can be disabled by having the string\n'
3117         'no-include-guard-because-multiply-included in the header.' %
3118         (f.LocalPath(), expected_guard)))
3119
3120   return errors
3121
3122
3123 def _CheckForWindowsLineEndings(input_api, output_api):
3124   """Check source code and known ascii text files for Windows style line
3125   endings.
3126   """
3127   known_text_files = r'.*\.(txt|html|htm|mhtml|py|gyp|gypi|gn|isolate)$'
3128
3129   file_inclusion_pattern = (
3130     known_text_files,
3131     r'.+%s' % _IMPLEMENTATION_EXTENSIONS
3132   )
3133
3134   problems = []
3135   source_file_filter = lambda f: input_api.FilterSourceFile(
3136       f, white_list=file_inclusion_pattern, black_list=None)
3137   for f in input_api.AffectedSourceFiles(source_file_filter):
3138     include_file = False
3139     for _, line in f.ChangedContents():
3140       if line.endswith('\r\n'):
3141         include_file = True
3142     if include_file:
3143       problems.append(f.LocalPath())
3144
3145   if problems:
3146     return [output_api.PresubmitPromptWarning('Are you sure that you want '
3147         'these files to contain Windows style line endings?\n' +
3148         '\n'.join(problems))]
3149
3150   return []
3151
3152
3153 def _CheckSyslogUseWarning(input_api, output_api, source_file_filter=None):
3154   """Checks that all source files use SYSLOG properly."""
3155   syslog_files = []
3156   for f in input_api.AffectedSourceFiles(source_file_filter):
3157     for line_number, line in f.ChangedContents():
3158       if 'SYSLOG' in line:
3159         syslog_files.append(f.LocalPath() + ':' + str(line_number))
3160
3161   if syslog_files:
3162     return [output_api.PresubmitPromptWarning(
3163         'Please make sure there are no privacy sensitive bits of data in SYSLOG'
3164         ' calls.\nFiles to check:\n', items=syslog_files)]
3165   return []
3166
3167
3168 def _CheckCrbugLinksHaveHttps(input_api, output_api):
3169   """Checks that crbug(.com) links are correctly prefixed by https://,
3170    unless they come in the accepted form TODO(crbug.com/...)
3171   """
3172   white_list = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
3173   black_list = (_EXCLUDED_PATHS + _TEST_CODE_EXCLUDED_PATHS)
3174   sources = lambda f: input_api.FilterSourceFile(
3175       f, white_list=white_list, black_list=black_list)
3176
3177   pattern = input_api.re.compile(r'//.*(?<!:\/\/)crbug[.com]*')
3178   accepted_pattern = input_api.re.compile(r'//.*TODO\(crbug[.com]*');
3179   problems = []
3180   for f in input_api.AffectedSourceFiles(sources):
3181     for line_num, line in f.ChangedContents():
3182       if pattern.search(line) and not accepted_pattern.search(line):
3183         problems.append('    %s:%d %s' % (f.LocalPath(), line_num, line))
3184
3185   if problems:
3186     return [output_api.PresubmitPromptWarning(
3187       'Found unprefixed crbug.com URL(s), consider prepending https://\n'+
3188       '\n'.join(problems))]
3189   return []
3190
3191
3192 def CheckChangeOnUpload(input_api, output_api):
3193   results = []
3194   results.extend(_CommonChecks(input_api, output_api))
3195   results.extend(_CheckValidHostsInDEPS(input_api, output_api))
3196   results.extend(
3197       input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
3198   results.extend(_CheckUmaHistogramChanges(input_api, output_api))
3199   results.extend(_AndroidSpecificOnUploadChecks(input_api, output_api))
3200   results.extend(_CheckSyslogUseWarning(input_api, output_api))
3201   results.extend(_CheckGoogleSupportAnswerUrl(input_api, output_api))
3202   results.extend(_CheckCrbugLinksHaveHttps(input_api, output_api))
3203   results.extend(_CheckUniquePtr(input_api, output_api))
3204   return results
3205
3206
3207 def GetTryServerMasterForBot(bot):
3208   """Returns the Try Server master for the given bot.
3209
3210   It tries to guess the master from the bot name, but may still fail
3211   and return None.  There is no longer a default master.
3212   """
3213   # Potentially ambiguous bot names are listed explicitly.
3214   master_map = {
3215       'chromium_presubmit': 'master.tryserver.chromium.linux',
3216       'tools_build_presubmit': 'master.tryserver.chromium.linux',
3217   }
3218   master = master_map.get(bot)
3219   if not master:
3220     if 'android' in bot:
3221       master = 'master.tryserver.chromium.android'
3222     elif 'linux' in bot or 'presubmit' in bot:
3223       master = 'master.tryserver.chromium.linux'
3224     elif 'win' in bot:
3225       master = 'master.tryserver.chromium.win'
3226     elif 'mac' in bot or 'ios' in bot:
3227       master = 'master.tryserver.chromium.mac'
3228   return master
3229
3230
3231 def CheckChangeOnCommit(input_api, output_api):
3232   results = []
3233   results.extend(_CommonChecks(input_api, output_api))
3234   # Make sure the tree is 'open'.
3235   results.extend(input_api.canned_checks.CheckTreeIsOpen(
3236       input_api,
3237       output_api,
3238       json_url='http://chromium-status.appspot.com/current?format=json'))
3239
3240   results.extend(
3241       input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
3242   results.extend(input_api.canned_checks.CheckChangeHasBugField(
3243       input_api, output_api))
3244   results.extend(input_api.canned_checks.CheckChangeHasDescription(
3245       input_api, output_api))
3246   return results