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
7See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8for more details about the presubmit API built into gcl.
9"""
10
11
12import re
13import sys
14
15
16_EXCLUDED_PATHS = (
17    r"^breakpad[\\\/].*",
18    r"^native_client_sdk[\\\/]src[\\\/]build_tools[\\\/]make_rules.py",
19    r"^native_client_sdk[\\\/]src[\\\/]build_tools[\\\/]make_simple.py",
20    r"^native_client_sdk[\\\/]src[\\\/]tools[\\\/].*.mk",
21    r"^net[\\\/]tools[\\\/]spdyshark[\\\/].*",
22    r"^skia[\\\/].*",
23    r"^v8[\\\/].*",
24    r".*MakeFile$",
25    r".+_autogen\.h$",
26    r".+[\\\/]pnacl_shim\.c$",
27    r"^gpu[\\\/]config[\\\/].*_list_json\.cc$",
28    r"^chrome[\\\/]browser[\\\/]resources[\\\/]pdf[\\\/]index.js"
29)
30
31# The NetscapePlugIn library is excluded from pan-project as it will soon
32# be deleted together with the rest of the NPAPI and it's not worthwhile to
33# update the coding style until then.
34_TESTRUNNER_PATHS = (
35    r"^content[\\\/]shell[\\\/]tools[\\\/]plugin[\\\/].*",
36)
37
38# Fragment of a regular expression that matches C++ and Objective-C++
39# implementation files.
40_IMPLEMENTATION_EXTENSIONS = r'\.(cc|cpp|cxx|mm)$'
41
42# Regular expression that matches code only used for test binaries
43# (best effort).
44_TEST_CODE_EXCLUDED_PATHS = (
45    r'.*[\\\/](fake_|test_|mock_).+%s' % _IMPLEMENTATION_EXTENSIONS,
46    r'.+_test_(base|support|util)%s' % _IMPLEMENTATION_EXTENSIONS,
47    r'.+_(api|browser|kif|perf|pixel|unit|ui)?test(_[a-z]+)?%s' %
48        _IMPLEMENTATION_EXTENSIONS,
49    r'.+profile_sync_service_harness%s' % _IMPLEMENTATION_EXTENSIONS,
50    r'.*[\\\/](test|tool(s)?)[\\\/].*',
51    # content_shell is used for running layout tests.
52    r'content[\\\/]shell[\\\/].*',
53    # At request of folks maintaining this folder.
54    r'chrome[\\\/]browser[\\\/]automation[\\\/].*',
55    # Non-production example code.
56    r'mojo[\\\/]examples[\\\/].*',
57    # Launcher for running iOS tests on the simulator.
58    r'testing[\\\/]iossim[\\\/]iossim\.mm$',
59)
60
61_TEST_ONLY_WARNING = (
62    'You might be calling functions intended only for testing from\n'
63    'production code.  It is OK to ignore this warning if you know what\n'
64    'you are doing, as the heuristics used to detect the situation are\n'
65    'not perfect.  The commit queue will not block on this warning.')
66
67
68_INCLUDE_ORDER_WARNING = (
69    'Your #include order seems to be broken. Send mail to\n'
70    'marja@chromium.org if this is not the case.')
71
72
73_BANNED_OBJC_FUNCTIONS = (
74    (
75      'addTrackingRect:',
76      (
77       'The use of -[NSView addTrackingRect:owner:userData:assumeInside:] is'
78       'prohibited. Please use CrTrackingArea instead.',
79       'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
80      ),
81      False,
82    ),
83    (
84      r'/NSTrackingArea\W',
85      (
86       'The use of NSTrackingAreas is prohibited. Please use CrTrackingArea',
87       'instead.',
88       'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
89      ),
90      False,
91    ),
92    (
93      'convertPointFromBase:',
94      (
95       'The use of -[NSView convertPointFromBase:] is almost certainly wrong.',
96       'Please use |convertPoint:(point) fromView:nil| instead.',
97       'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
98      ),
99      True,
100    ),
101    (
102      'convertPointToBase:',
103      (
104       'The use of -[NSView convertPointToBase:] is almost certainly wrong.',
105       'Please use |convertPoint:(point) toView:nil| instead.',
106       'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
107      ),
108      True,
109    ),
110    (
111      'convertRectFromBase:',
112      (
113       'The use of -[NSView convertRectFromBase:] is almost certainly wrong.',
114       'Please use |convertRect:(point) fromView:nil| instead.',
115       'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
116      ),
117      True,
118    ),
119    (
120      'convertRectToBase:',
121      (
122       'The use of -[NSView convertRectToBase:] is almost certainly wrong.',
123       'Please use |convertRect:(point) toView:nil| instead.',
124       'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
125      ),
126      True,
127    ),
128    (
129      'convertSizeFromBase:',
130      (
131       'The use of -[NSView convertSizeFromBase:] is almost certainly wrong.',
132       'Please use |convertSize:(point) fromView:nil| instead.',
133       'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
134      ),
135      True,
136    ),
137    (
138      'convertSizeToBase:',
139      (
140       'The use of -[NSView convertSizeToBase:] is almost certainly wrong.',
141       'Please use |convertSize:(point) toView:nil| instead.',
142       'http://dev.chromium.org/developers/coding-style/cocoa-dos-and-donts',
143      ),
144      True,
145    ),
146)
147
148
149_BANNED_CPP_FUNCTIONS = (
150    # Make sure that gtest's FRIEND_TEST() macro is not used; the
151    # FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be
152    # used instead since that allows for FLAKY_ and DISABLED_ prefixes.
153    (
154      'FRIEND_TEST(',
155      (
156       'Chromium code should not use gtest\'s FRIEND_TEST() macro. Include',
157       'base/gtest_prod_util.h and use FRIEND_TEST_ALL_PREFIXES() instead.',
158      ),
159      False,
160      (),
161    ),
162    (
163      'ScopedAllowIO',
164      (
165       'New code should not use ScopedAllowIO. Post a task to the blocking',
166       'pool or the FILE thread instead.',
167      ),
168      True,
169      (
170        r"^base[\\\/]process[\\\/]process_metrics_linux\.cc$",
171        r"^chrome[\\\/]browser[\\\/]chromeos[\\\/]boot_times_loader\.cc$",
172        r"^components[\\\/]crash[\\\/]app[\\\/]breakpad_mac\.mm$",
173        r"^content[\\\/]shell[\\\/]browser[\\\/]shell_browser_main\.cc$",
174        r"^content[\\\/]shell[\\\/]browser[\\\/]shell_message_filter\.cc$",
175        r"^mojo[\\\/]system[\\\/]raw_shared_buffer_posix\.cc$",
176        r"^net[\\\/]disk_cache[\\\/]cache_util\.cc$",
177        r"^net[\\\/]url_request[\\\/]test_url_fetcher_factory\.cc$",
178      ),
179    ),
180    (
181      'SkRefPtr',
182      (
183        'The use of SkRefPtr is prohibited. ',
184        'Please use skia::RefPtr instead.'
185      ),
186      True,
187      (),
188    ),
189    (
190      'SkAutoRef',
191      (
192        'The indirect use of SkRefPtr via SkAutoRef is prohibited. ',
193        'Please use skia::RefPtr instead.'
194      ),
195      True,
196      (),
197    ),
198    (
199      'SkAutoTUnref',
200      (
201        'The use of SkAutoTUnref is dangerous because it implicitly ',
202        'converts to a raw pointer. Please use skia::RefPtr instead.'
203      ),
204      True,
205      (),
206    ),
207    (
208      'SkAutoUnref',
209      (
210        'The indirect use of SkAutoTUnref through SkAutoUnref is dangerous ',
211        'because it implicitly converts to a raw pointer. ',
212        'Please use skia::RefPtr instead.'
213      ),
214      True,
215      (),
216    ),
217    (
218      r'/HANDLE_EINTR\(.*close',
219      (
220       'HANDLE_EINTR(close) is invalid. If close fails with EINTR, the file',
221       'descriptor will be closed, and it is incorrect to retry the close.',
222       'Either call close directly and ignore its return value, or wrap close',
223       'in IGNORE_EINTR to use its return value. See http://crbug.com/269623'
224      ),
225      True,
226      (),
227    ),
228    (
229      r'/IGNORE_EINTR\((?!.*close)',
230      (
231       'IGNORE_EINTR is only valid when wrapping close. To wrap other system',
232       'calls, use HANDLE_EINTR. See http://crbug.com/269623',
233      ),
234      True,
235      (
236        # Files that #define IGNORE_EINTR.
237        r'^base[\\\/]posix[\\\/]eintr_wrapper\.h$',
238        r'^ppapi[\\\/]tests[\\\/]test_broker\.cc$',
239      ),
240    ),
241    (
242      r'/v8::Extension\(',
243      (
244        'Do not introduce new v8::Extensions into the code base, use',
245        'gin::Wrappable instead. See http://crbug.com/334679',
246      ),
247      True,
248      (
249        r'extensions[\\\/]renderer[\\\/]safe_builtins\.*',
250      ),
251    ),
252)
253
254_IPC_ENUM_TRAITS_DEPRECATED = (
255    'You are using IPC_ENUM_TRAITS() in your code. It has been deprecated.\n'
256    'See http://www.chromium.org/Home/chromium-security/education/security-tips-for-ipc')
257
258
259_VALID_OS_MACROS = (
260    # Please keep sorted.
261    'OS_ANDROID',
262    'OS_ANDROID_HOST',
263    'OS_BSD',
264    'OS_CAT',       # For testing.
265    'OS_CHROMEOS',
266    'OS_FREEBSD',
267    'OS_IOS',
268    'OS_LINUX',
269    'OS_MACOSX',
270    'OS_NACL',
271    'OS_OPENBSD',
272    'OS_POSIX',
273    'OS_QNX',
274    'OS_SOLARIS',
275    'OS_WIN',
276)
277
278
279def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
280  """Attempts to prevent use of functions intended only for testing in
281  non-testing code. For now this is just a best-effort implementation
282  that ignores header files and may have some false positives. A
283  better implementation would probably need a proper C++ parser.
284  """
285  # We only scan .cc files and the like, as the declaration of
286  # for-testing functions in header files are hard to distinguish from
287  # calls to such functions without a proper C++ parser.
288  file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
289
290  base_function_pattern = r'[ :]test::[^\s]+|ForTest(ing)?|for_test(ing)?'
291  inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
292  comment_pattern = input_api.re.compile(r'//.*(%s)' % base_function_pattern)
293  exclusion_pattern = input_api.re.compile(
294    r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
295      base_function_pattern, base_function_pattern))
296
297  def FilterFile(affected_file):
298    black_list = (_EXCLUDED_PATHS +
299                  _TEST_CODE_EXCLUDED_PATHS +
300                  input_api.DEFAULT_BLACK_LIST)
301    return input_api.FilterSourceFile(
302      affected_file,
303      white_list=(file_inclusion_pattern, ),
304      black_list=black_list)
305
306  problems = []
307  for f in input_api.AffectedSourceFiles(FilterFile):
308    local_path = f.LocalPath()
309    for line_number, line in f.ChangedContents():
310      if (inclusion_pattern.search(line) and
311          not comment_pattern.search(line) and
312          not exclusion_pattern.search(line)):
313        problems.append(
314          '%s:%d\n    %s' % (local_path, line_number, line.strip()))
315
316  if problems:
317    return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)]
318  else:
319    return []
320
321
322def _CheckNoIOStreamInHeaders(input_api, output_api):
323  """Checks to make sure no .h files include <iostream>."""
324  files = []
325  pattern = input_api.re.compile(r'^#include\s*<iostream>',
326                                 input_api.re.MULTILINE)
327  for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
328    if not f.LocalPath().endswith('.h'):
329      continue
330    contents = input_api.ReadFile(f)
331    if pattern.search(contents):
332      files.append(f)
333
334  if len(files):
335    return [ output_api.PresubmitError(
336        'Do not #include <iostream> in header files, since it inserts static '
337        'initialization into every file including the header. Instead, '
338        '#include <ostream>. See http://crbug.com/94794',
339        files) ]
340  return []
341
342
343def _CheckNoUNIT_TESTInSourceFiles(input_api, output_api):
344  """Checks to make sure no source files use UNIT_TEST"""
345  problems = []
346  for f in input_api.AffectedFiles():
347    if (not f.LocalPath().endswith(('.cc', '.mm'))):
348      continue
349
350    for line_num, line in f.ChangedContents():
351      if 'UNIT_TEST ' in line or line.endswith('UNIT_TEST'):
352        problems.append('    %s:%d' % (f.LocalPath(), line_num))
353
354  if not problems:
355    return []
356  return [output_api.PresubmitPromptWarning('UNIT_TEST is only for headers.\n' +
357      '\n'.join(problems))]
358
359
360def _CheckNoNewWStrings(input_api, output_api):
361  """Checks to make sure we don't introduce use of wstrings."""
362  problems = []
363  for f in input_api.AffectedFiles():
364    if (not f.LocalPath().endswith(('.cc', '.h')) or
365        f.LocalPath().endswith(('test.cc', '_win.cc', '_win.h'))):
366      continue
367
368    allowWString = False
369    for line_num, line in f.ChangedContents():
370      if 'presubmit: allow wstring' in line:
371        allowWString = True
372      elif not allowWString and 'wstring' in line:
373        problems.append('    %s:%d' % (f.LocalPath(), line_num))
374        allowWString = False
375      else:
376        allowWString = False
377
378  if not problems:
379    return []
380  return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
381      '  If you are calling a cross-platform API that accepts a wstring, '
382      'fix the API.\n' +
383      '\n'.join(problems))]
384
385
386def _CheckNoDEPSGIT(input_api, output_api):
387  """Make sure .DEPS.git is never modified manually."""
388  if any(f.LocalPath().endswith('.DEPS.git') for f in
389      input_api.AffectedFiles()):
390    return [output_api.PresubmitError(
391      'Never commit changes to .DEPS.git. This file is maintained by an\n'
392      'automated system based on what\'s in DEPS and your changes will be\n'
393      'overwritten.\n'
394      'See https://sites.google.com/a/chromium.org/dev/developers/how-tos/get-the-code#Rolling_DEPS\n'
395      'for more information')]
396  return []
397
398
399def _CheckValidHostsInDEPS(input_api, output_api):
400  """Checks that DEPS file deps are from allowed_hosts."""
401  # Run only if DEPS file has been modified to annoy fewer bystanders.
402  if all(f.LocalPath() != 'DEPS' for f in input_api.AffectedFiles()):
403    return []
404  # Outsource work to gclient verify
405  try:
406    input_api.subprocess.check_output(['gclient', 'verify'])
407    return []
408  except input_api.subprocess.CalledProcessError, error:
409    return [output_api.PresubmitError(
410        'DEPS file must have only git dependencies.',
411        long_text=error.output)]
412
413
414def _CheckNoBannedFunctions(input_api, output_api):
415  """Make sure that banned functions are not used."""
416  warnings = []
417  errors = []
418
419  file_filter = lambda f: f.LocalPath().endswith(('.mm', '.m', '.h'))
420  for f in input_api.AffectedFiles(file_filter=file_filter):
421    for line_num, line in f.ChangedContents():
422      for func_name, message, error in _BANNED_OBJC_FUNCTIONS:
423        matched = False
424        if func_name[0:1] == '/':
425          regex = func_name[1:]
426          if input_api.re.search(regex, line):
427            matched = True
428        elif func_name in line:
429            matched = True
430        if matched:
431          problems = warnings;
432          if error:
433            problems = errors;
434          problems.append('    %s:%d:' % (f.LocalPath(), line_num))
435          for message_line in message:
436            problems.append('      %s' % message_line)
437
438  file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm', '.h'))
439  for f in input_api.AffectedFiles(file_filter=file_filter):
440    for line_num, line in f.ChangedContents():
441      for func_name, message, error, excluded_paths in _BANNED_CPP_FUNCTIONS:
442        def IsBlacklisted(affected_file, blacklist):
443          local_path = affected_file.LocalPath()
444          for item in blacklist:
445            if input_api.re.match(item, local_path):
446              return True
447          return False
448        if IsBlacklisted(f, excluded_paths):
449          continue
450        matched = False
451        if func_name[0:1] == '/':
452          regex = func_name[1:]
453          if input_api.re.search(regex, line):
454            matched = True
455        elif func_name in line:
456            matched = True
457        if matched:
458          problems = warnings;
459          if error:
460            problems = errors;
461          problems.append('    %s:%d:' % (f.LocalPath(), line_num))
462          for message_line in message:
463            problems.append('      %s' % message_line)
464
465  result = []
466  if (warnings):
467    result.append(output_api.PresubmitPromptWarning(
468        'Banned functions were used.\n' + '\n'.join(warnings)))
469  if (errors):
470    result.append(output_api.PresubmitError(
471        'Banned functions were used.\n' + '\n'.join(errors)))
472  return result
473
474
475def _CheckNoPragmaOnce(input_api, output_api):
476  """Make sure that banned functions are not used."""
477  files = []
478  pattern = input_api.re.compile(r'^#pragma\s+once',
479                                 input_api.re.MULTILINE)
480  for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
481    if not f.LocalPath().endswith('.h'):
482      continue
483    contents = input_api.ReadFile(f)
484    if pattern.search(contents):
485      files.append(f)
486
487  if files:
488    return [output_api.PresubmitError(
489        'Do not use #pragma once in header files.\n'
490        'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
491        files)]
492  return []
493
494
495def _CheckNoTrinaryTrueFalse(input_api, output_api):
496  """Checks to make sure we don't introduce use of foo ? true : false."""
497  problems = []
498  pattern = input_api.re.compile(r'\?\s*(true|false)\s*:\s*(true|false)')
499  for f in input_api.AffectedFiles():
500    if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
501      continue
502
503    for line_num, line in f.ChangedContents():
504      if pattern.match(line):
505        problems.append('    %s:%d' % (f.LocalPath(), line_num))
506
507  if not problems:
508    return []
509  return [output_api.PresubmitPromptWarning(
510      'Please consider avoiding the "? true : false" pattern if possible.\n' +
511      '\n'.join(problems))]
512
513
514def _CheckUnwantedDependencies(input_api, output_api):
515  """Runs checkdeps on #include statements added in this
516  change. Breaking - rules is an error, breaking ! rules is a
517  warning.
518  """
519  # We need to wait until we have an input_api object and use this
520  # roundabout construct to import checkdeps because this file is
521  # eval-ed and thus doesn't have __file__.
522  original_sys_path = sys.path
523  try:
524    sys.path = sys.path + [input_api.os_path.join(
525        input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')]
526    import checkdeps
527    from cpp_checker import CppChecker
528    from rules import Rule
529  finally:
530    # Restore sys.path to what it was before.
531    sys.path = original_sys_path
532
533  added_includes = []
534  for f in input_api.AffectedFiles():
535    if not CppChecker.IsCppFile(f.LocalPath()):
536      continue
537
538    changed_lines = [line for line_num, line in f.ChangedContents()]
539    added_includes.append([f.LocalPath(), changed_lines])
540
541  deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
542
543  error_descriptions = []
544  warning_descriptions = []
545  for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
546      added_includes):
547    description_with_path = '%s\n    %s' % (path, rule_description)
548    if rule_type == Rule.DISALLOW:
549      error_descriptions.append(description_with_path)
550    else:
551      warning_descriptions.append(description_with_path)
552
553  results = []
554  if error_descriptions:
555    results.append(output_api.PresubmitError(
556        'You added one or more #includes that violate checkdeps rules.',
557        error_descriptions))
558  if warning_descriptions:
559    results.append(output_api.PresubmitPromptOrNotify(
560        'You added one or more #includes of files that are temporarily\n'
561        'allowed but being removed. Can you avoid introducing the\n'
562        '#include? See relevant DEPS file(s) for details and contacts.',
563        warning_descriptions))
564  return results
565
566
567def _CheckFilePermissions(input_api, output_api):
568  """Check that all files have their permissions properly set."""
569  if input_api.platform == 'win32':
570    return []
571  args = [sys.executable, 'tools/checkperms/checkperms.py', '--root',
572          input_api.change.RepositoryRoot()]
573  for f in input_api.AffectedFiles():
574    args += ['--file', f.LocalPath()]
575  checkperms = input_api.subprocess.Popen(args,
576                                          stdout=input_api.subprocess.PIPE)
577  errors = checkperms.communicate()[0].strip()
578  if errors:
579    return [output_api.PresubmitError('checkperms.py failed.',
580                                      errors.splitlines())]
581  return []
582
583
584def _CheckNoAuraWindowPropertyHInHeaders(input_api, output_api):
585  """Makes sure we don't include ui/aura/window_property.h
586  in header files.
587  """
588  pattern = input_api.re.compile(r'^#include\s*"ui/aura/window_property.h"')
589  errors = []
590  for f in input_api.AffectedFiles():
591    if not f.LocalPath().endswith('.h'):
592      continue
593    for line_num, line in f.ChangedContents():
594      if pattern.match(line):
595        errors.append('    %s:%d' % (f.LocalPath(), line_num))
596
597  results = []
598  if errors:
599    results.append(output_api.PresubmitError(
600      'Header files should not include ui/aura/window_property.h', errors))
601  return results
602
603
604def _CheckIncludeOrderForScope(scope, input_api, file_path, changed_linenums):
605  """Checks that the lines in scope occur in the right order.
606
607  1. C system files in alphabetical order
608  2. C++ system files in alphabetical order
609  3. Project's .h files
610  """
611
612  c_system_include_pattern = input_api.re.compile(r'\s*#include <.*\.h>')
613  cpp_system_include_pattern = input_api.re.compile(r'\s*#include <.*>')
614  custom_include_pattern = input_api.re.compile(r'\s*#include ".*')
615
616  C_SYSTEM_INCLUDES, CPP_SYSTEM_INCLUDES, CUSTOM_INCLUDES = range(3)
617
618  state = C_SYSTEM_INCLUDES
619
620  previous_line = ''
621  previous_line_num = 0
622  problem_linenums = []
623  for line_num, line in scope:
624    if c_system_include_pattern.match(line):
625      if state != C_SYSTEM_INCLUDES:
626        problem_linenums.append((line_num, previous_line_num))
627      elif previous_line and previous_line > line:
628        problem_linenums.append((line_num, previous_line_num))
629    elif cpp_system_include_pattern.match(line):
630      if state == C_SYSTEM_INCLUDES:
631        state = CPP_SYSTEM_INCLUDES
632      elif state == CUSTOM_INCLUDES:
633        problem_linenums.append((line_num, previous_line_num))
634      elif previous_line and previous_line > line:
635        problem_linenums.append((line_num, previous_line_num))
636    elif custom_include_pattern.match(line):
637      if state != CUSTOM_INCLUDES:
638        state = CUSTOM_INCLUDES
639      elif previous_line and previous_line > line:
640        problem_linenums.append((line_num, previous_line_num))
641    else:
642      problem_linenums.append(line_num)
643    previous_line = line
644    previous_line_num = line_num
645
646  warnings = []
647  for (line_num, previous_line_num) in problem_linenums:
648    if line_num in changed_linenums or previous_line_num in changed_linenums:
649      warnings.append('    %s:%d' % (file_path, line_num))
650  return warnings
651
652
653def _CheckIncludeOrderInFile(input_api, f, changed_linenums):
654  """Checks the #include order for the given file f."""
655
656  system_include_pattern = input_api.re.compile(r'\s*#include \<.*')
657  # Exclude the following includes from the check:
658  # 1) #include <.../...>, e.g., <sys/...> includes often need to appear in a
659  # specific order.
660  # 2) <atlbase.h>, "build/build_config.h"
661  excluded_include_pattern = input_api.re.compile(
662      r'\s*#include (\<.*/.*|\<atlbase\.h\>|"build/build_config.h")')
663  custom_include_pattern = input_api.re.compile(r'\s*#include "(?P<FILE>.*)"')
664  # Match the final or penultimate token if it is xxxtest so we can ignore it
665  # when considering the special first include.
666  test_file_tag_pattern = input_api.re.compile(
667    r'_[a-z]+test(?=(_[a-zA-Z0-9]+)?\.)')
668  if_pattern = input_api.re.compile(
669      r'\s*#\s*(if|elif|else|endif|define|undef).*')
670  # Some files need specialized order of includes; exclude such files from this
671  # check.
672  uncheckable_includes_pattern = input_api.re.compile(
673      r'\s*#include '
674      '("ipc/.*macros\.h"|<windows\.h>|".*gl.*autogen.h")\s*')
675
676  contents = f.NewContents()
677  warnings = []
678  line_num = 0
679
680  # Handle the special first include. If the first include file is
681  # some/path/file.h, the corresponding including file can be some/path/file.cc,
682  # some/other/path/file.cc, some/path/file_platform.cc, some/path/file-suffix.h
683  # etc. It's also possible that no special first include exists.
684  # If the included file is some/path/file_platform.h the including file could
685  # also be some/path/file_xxxtest_platform.h.
686  including_file_base_name = test_file_tag_pattern.sub(
687    '', input_api.os_path.basename(f.LocalPath()))
688
689  for line in contents:
690    line_num += 1
691    if system_include_pattern.match(line):
692      # No special first include -> process the line again along with normal
693      # includes.
694      line_num -= 1
695      break
696    match = custom_include_pattern.match(line)
697    if match:
698      match_dict = match.groupdict()
699      header_basename = test_file_tag_pattern.sub(
700        '', input_api.os_path.basename(match_dict['FILE'])).replace('.h', '')
701
702      if header_basename not in including_file_base_name:
703        # No special first include -> process the line again along with normal
704        # includes.
705        line_num -= 1
706      break
707
708  # Split into scopes: Each region between #if and #endif is its own scope.
709  scopes = []
710  current_scope = []
711  for line in contents[line_num:]:
712    line_num += 1
713    if uncheckable_includes_pattern.match(line):
714      continue
715    if if_pattern.match(line):
716      scopes.append(current_scope)
717      current_scope = []
718    elif ((system_include_pattern.match(line) or
719           custom_include_pattern.match(line)) and
720          not excluded_include_pattern.match(line)):
721      current_scope.append((line_num, line))
722  scopes.append(current_scope)
723
724  for scope in scopes:
725    warnings.extend(_CheckIncludeOrderForScope(scope, input_api, f.LocalPath(),
726                                               changed_linenums))
727  return warnings
728
729
730def _CheckIncludeOrder(input_api, output_api):
731  """Checks that the #include order is correct.
732
733  1. The corresponding header for source files.
734  2. C system files in alphabetical order
735  3. C++ system files in alphabetical order
736  4. Project's .h files in alphabetical order
737
738  Each region separated by #if, #elif, #else, #endif, #define and #undef follows
739  these rules separately.
740  """
741  def FileFilterIncludeOrder(affected_file):
742    black_list = (_EXCLUDED_PATHS + input_api.DEFAULT_BLACK_LIST)
743    return input_api.FilterSourceFile(affected_file, black_list=black_list)
744
745  warnings = []
746  for f in input_api.AffectedFiles(file_filter=FileFilterIncludeOrder):
747    if f.LocalPath().endswith(('.cc', '.h')):
748      changed_linenums = set(line_num for line_num, _ in f.ChangedContents())
749      warnings.extend(_CheckIncludeOrderInFile(input_api, f, changed_linenums))
750
751  results = []
752  if warnings:
753    results.append(output_api.PresubmitPromptOrNotify(_INCLUDE_ORDER_WARNING,
754                                                      warnings))
755  return results
756
757
758def _CheckForVersionControlConflictsInFile(input_api, f):
759  pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
760  errors = []
761  for line_num, line in f.ChangedContents():
762    if pattern.match(line):
763      errors.append('    %s:%d %s' % (f.LocalPath(), line_num, line))
764  return errors
765
766
767def _CheckForVersionControlConflicts(input_api, output_api):
768  """Usually this is not intentional and will cause a compile failure."""
769  errors = []
770  for f in input_api.AffectedFiles():
771    errors.extend(_CheckForVersionControlConflictsInFile(input_api, f))
772
773  results = []
774  if errors:
775    results.append(output_api.PresubmitError(
776      'Version control conflict markers found, please resolve.', errors))
777  return results
778
779
780def _CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api):
781  def FilterFile(affected_file):
782    """Filter function for use with input_api.AffectedSourceFiles,
783    below.  This filters out everything except non-test files from
784    top-level directories that generally speaking should not hard-code
785    service URLs (e.g. src/android_webview/, src/content/ and others).
786    """
787    return input_api.FilterSourceFile(
788      affected_file,
789      white_list=(r'^(android_webview|base|content|net)[\\\/].*', ),
790      black_list=(_EXCLUDED_PATHS +
791                  _TEST_CODE_EXCLUDED_PATHS +
792                  input_api.DEFAULT_BLACK_LIST))
793
794  base_pattern = '"[^"]*google\.com[^"]*"'
795  comment_pattern = input_api.re.compile('//.*%s' % base_pattern)
796  pattern = input_api.re.compile(base_pattern)
797  problems = []  # items are (filename, line_number, line)
798  for f in input_api.AffectedSourceFiles(FilterFile):
799    for line_num, line in f.ChangedContents():
800      if not comment_pattern.search(line) and pattern.search(line):
801        problems.append((f.LocalPath(), line_num, line))
802
803  if problems:
804    return [output_api.PresubmitPromptOrNotify(
805        'Most layers below src/chrome/ should not hardcode service URLs.\n'
806        'Are you sure this is correct?',
807        ['  %s:%d:  %s' % (
808            problem[0], problem[1], problem[2]) for problem in problems])]
809  else:
810    return []
811
812
813def _CheckNoAbbreviationInPngFileName(input_api, output_api):
814  """Makes sure there are no abbreviations in the name of PNG files.
815  """
816  pattern = input_api.re.compile(r'.*_[a-z]_.*\.png$|.*_[a-z]\.png$')
817  errors = []
818  for f in input_api.AffectedFiles(include_deletes=False):
819    if pattern.match(f.LocalPath()):
820      errors.append('    %s' % f.LocalPath())
821
822  results = []
823  if errors:
824    results.append(output_api.PresubmitError(
825        'The name of PNG files should not have abbreviations. \n'
826        'Use _hover.png, _center.png, instead of _h.png, _c.png.\n'
827        'Contact oshima@chromium.org if you have questions.', errors))
828  return results
829
830
831def _FilesToCheckForIncomingDeps(re, changed_lines):
832  """Helper method for _CheckAddedDepsHaveTargetApprovals. Returns
833  a set of DEPS entries that we should look up.
834
835  For a directory (rather than a specific filename) we fake a path to
836  a specific filename by adding /DEPS. This is chosen as a file that
837  will seldom or never be subject to per-file include_rules.
838  """
839  # We ignore deps entries on auto-generated directories.
840  AUTO_GENERATED_DIRS = ['grit', 'jni']
841
842  # This pattern grabs the path without basename in the first
843  # parentheses, and the basename (if present) in the second. It
844  # relies on the simple heuristic that if there is a basename it will
845  # be a header file ending in ".h".
846  pattern = re.compile(
847      r"""['"]\+([^'"]+?)(/[a-zA-Z0-9_]+\.h)?['"].*""")
848  results = set()
849  for changed_line in changed_lines:
850    m = pattern.match(changed_line)
851    if m:
852      path = m.group(1)
853      if path.split('/')[0] not in AUTO_GENERATED_DIRS:
854        if m.group(2):
855          results.add('%s%s' % (path, m.group(2)))
856        else:
857          results.add('%s/DEPS' % path)
858  return results
859
860
861def _CheckAddedDepsHaveTargetApprovals(input_api, output_api):
862  """When a dependency prefixed with + is added to a DEPS file, we
863  want to make sure that the change is reviewed by an OWNER of the
864  target file or directory, to avoid layering violations from being
865  introduced. This check verifies that this happens.
866  """
867  changed_lines = set()
868  for f in input_api.AffectedFiles():
869    filename = input_api.os_path.basename(f.LocalPath())
870    if filename == 'DEPS':
871      changed_lines |= set(line.strip()
872                           for line_num, line
873                           in f.ChangedContents())
874  if not changed_lines:
875    return []
876
877  virtual_depended_on_files = _FilesToCheckForIncomingDeps(input_api.re,
878                                                           changed_lines)
879  if not virtual_depended_on_files:
880    return []
881
882  if input_api.is_committing:
883    if input_api.tbr:
884      return [output_api.PresubmitNotifyResult(
885          '--tbr was specified, skipping OWNERS check for DEPS additions')]
886    if not input_api.change.issue:
887      return [output_api.PresubmitError(
888          "DEPS approval by OWNERS check failed: this change has "
889          "no Rietveld issue number, so we can't check it for approvals.")]
890    output = output_api.PresubmitError
891  else:
892    output = output_api.PresubmitNotifyResult
893
894  owners_db = input_api.owners_db
895  owner_email, reviewers = input_api.canned_checks._RietveldOwnerAndReviewers(
896      input_api,
897      owners_db.email_regexp,
898      approval_needed=input_api.is_committing)
899
900  owner_email = owner_email or input_api.change.author_email
901
902  reviewers_plus_owner = set(reviewers)
903  if owner_email:
904    reviewers_plus_owner.add(owner_email)
905  missing_files = owners_db.files_not_covered_by(virtual_depended_on_files,
906                                                 reviewers_plus_owner)
907
908  # We strip the /DEPS part that was added by
909  # _FilesToCheckForIncomingDeps to fake a path to a file in a
910  # directory.
911  def StripDeps(path):
912    start_deps = path.rfind('/DEPS')
913    if start_deps != -1:
914      return path[:start_deps]
915    else:
916      return path
917  unapproved_dependencies = ["'+%s'," % StripDeps(path)
918                             for path in missing_files]
919
920  if unapproved_dependencies:
921    output_list = [
922      output('Missing LGTM from OWNERS of dependencies added to DEPS:\n    %s' %
923             '\n    '.join(sorted(unapproved_dependencies)))]
924    if not input_api.is_committing:
925      suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
926      output_list.append(output(
927          'Suggested missing target path OWNERS:\n    %s' %
928          '\n    '.join(suggested_owners or [])))
929    return output_list
930
931  return []
932
933
934def _CheckSpamLogging(input_api, output_api):
935  file_inclusion_pattern = r'.+%s' % _IMPLEMENTATION_EXTENSIONS
936  black_list = (_EXCLUDED_PATHS +
937                _TEST_CODE_EXCLUDED_PATHS +
938                input_api.DEFAULT_BLACK_LIST +
939                (r"^base[\\\/]logging\.h$",
940                 r"^base[\\\/]logging\.cc$",
941                 r"^chrome[\\\/]app[\\\/]chrome_main_delegate\.cc$",
942                 r"^chrome[\\\/]browser[\\\/]chrome_browser_main\.cc$",
943                 r"^chrome[\\\/]browser[\\\/]ui[\\\/]startup[\\\/]"
944                     r"startup_browser_creator\.cc$",
945                 r"^chrome[\\\/]installer[\\\/]setup[\\\/].*",
946                 r"chrome[\\\/]browser[\\\/]diagnostics[\\\/]" +
947                     r"diagnostics_writer\.cc$",
948                 r"^chrome_elf[\\\/]dll_hash[\\\/]dll_hash_main\.cc$",
949                 r"^chromecast[\\\/]",
950                 r"^cloud_print[\\\/]",
951                 r"^content[\\\/]common[\\\/]gpu[\\\/]client[\\\/]"
952                     r"gl_helper_benchmark\.cc$",
953                 r"^courgette[\\\/]courgette_tool\.cc$",
954                 r"^extensions[\\\/]renderer[\\\/]logging_native_handler\.cc$",
955                 r"^native_client_sdk[\\\/]",
956                 r"^remoting[\\\/]base[\\\/]logging\.h$",
957                 r"^remoting[\\\/]host[\\\/].*",
958                 r"^sandbox[\\\/]linux[\\\/].*",
959                 r"^tools[\\\/]",
960                 r"^ui[\\\/]aura[\\\/]bench[\\\/]bench_main\.cc$",
961                 r"^webkit[\\\/]browser[\\\/]fileapi[\\\/]" +
962                     r"dump_file_system.cc$",))
963  source_file_filter = lambda x: input_api.FilterSourceFile(
964      x, white_list=(file_inclusion_pattern,), black_list=black_list)
965
966  log_info = []
967  printf = []
968
969  for f in input_api.AffectedSourceFiles(source_file_filter):
970    contents = input_api.ReadFile(f, 'rb')
971    if re.search(r"\bD?LOG\s*\(\s*INFO\s*\)", contents):
972      log_info.append(f.LocalPath())
973    elif re.search(r"\bD?LOG_IF\s*\(\s*INFO\s*,", contents):
974      log_info.append(f.LocalPath())
975
976    if re.search(r"\bprintf\(", contents):
977      printf.append(f.LocalPath())
978    elif re.search(r"\bfprintf\((stdout|stderr)", contents):
979      printf.append(f.LocalPath())
980
981  if log_info:
982    return [output_api.PresubmitError(
983      'These files spam the console log with LOG(INFO):',
984      items=log_info)]
985  if printf:
986    return [output_api.PresubmitError(
987      'These files spam the console log with printf/fprintf:',
988      items=printf)]
989  return []
990
991
992def _CheckForAnonymousVariables(input_api, output_api):
993  """These types are all expected to hold locks while in scope and
994     so should never be anonymous (which causes them to be immediately
995     destroyed)."""
996  they_who_must_be_named = [
997    'base::AutoLock',
998    'base::AutoReset',
999    'base::AutoUnlock',
1000    'SkAutoAlphaRestore',
1001    'SkAutoBitmapShaderInstall',
1002    'SkAutoBlitterChoose',
1003    'SkAutoBounderCommit',
1004    'SkAutoCallProc',
1005    'SkAutoCanvasRestore',
1006    'SkAutoCommentBlock',
1007    'SkAutoDescriptor',
1008    'SkAutoDisableDirectionCheck',
1009    'SkAutoDisableOvalCheck',
1010    'SkAutoFree',
1011    'SkAutoGlyphCache',
1012    'SkAutoHDC',
1013    'SkAutoLockColors',
1014    'SkAutoLockPixels',
1015    'SkAutoMalloc',
1016    'SkAutoMaskFreeImage',
1017    'SkAutoMutexAcquire',
1018    'SkAutoPathBoundsUpdate',
1019    'SkAutoPDFRelease',
1020    'SkAutoRasterClipValidate',
1021    'SkAutoRef',
1022    'SkAutoTime',
1023    'SkAutoTrace',
1024    'SkAutoUnref',
1025  ]
1026  anonymous = r'(%s)\s*[({]' % '|'.join(they_who_must_be_named)
1027  # bad: base::AutoLock(lock.get());
1028  # not bad: base::AutoLock lock(lock.get());
1029  bad_pattern = input_api.re.compile(anonymous)
1030  # good: new base::AutoLock(lock.get())
1031  good_pattern = input_api.re.compile(r'\bnew\s*' + anonymous)
1032  errors = []
1033
1034  for f in input_api.AffectedFiles():
1035    if not f.LocalPath().endswith(('.cc', '.h', '.inl', '.m', '.mm')):
1036      continue
1037    for linenum, line in f.ChangedContents():
1038      if bad_pattern.search(line) and not good_pattern.search(line):
1039        errors.append('%s:%d' % (f.LocalPath(), linenum))
1040
1041  if errors:
1042    return [output_api.PresubmitError(
1043      'These lines create anonymous variables that need to be named:',
1044      items=errors)]
1045  return []
1046
1047
1048def _CheckCygwinShell(input_api, output_api):
1049  source_file_filter = lambda x: input_api.FilterSourceFile(
1050      x, white_list=(r'.+\.(gyp|gypi)$',))
1051  cygwin_shell = []
1052
1053  for f in input_api.AffectedSourceFiles(source_file_filter):
1054    for linenum, line in f.ChangedContents():
1055      if 'msvs_cygwin_shell' in line:
1056        cygwin_shell.append(f.LocalPath())
1057        break
1058
1059  if cygwin_shell:
1060    return [output_api.PresubmitError(
1061      'These files should not use msvs_cygwin_shell (the default is 0):',
1062      items=cygwin_shell)]
1063  return []
1064
1065
1066def _CheckUserActionUpdate(input_api, output_api):
1067  """Checks if any new user action has been added."""
1068  if any('actions.xml' == input_api.os_path.basename(f) for f in
1069         input_api.LocalPaths()):
1070    # If actions.xml is already included in the changelist, the PRESUBMIT
1071    # for actions.xml will do a more complete presubmit check.
1072    return []
1073
1074  file_filter = lambda f: f.LocalPath().endswith(('.cc', '.mm'))
1075  action_re = r'[^a-zA-Z]UserMetricsAction\("([^"]*)'
1076  current_actions = None
1077  for f in input_api.AffectedFiles(file_filter=file_filter):
1078    for line_num, line in f.ChangedContents():
1079      match = input_api.re.search(action_re, line)
1080      if match:
1081        # Loads contents in tools/metrics/actions/actions.xml to memory. It's
1082        # loaded only once.
1083        if not current_actions:
1084          with open('tools/metrics/actions/actions.xml') as actions_f:
1085            current_actions = actions_f.read()
1086        # Search for the matched user action name in |current_actions|.
1087        for action_name in match.groups():
1088          action = 'name="{0}"'.format(action_name)
1089          if action not in current_actions:
1090            return [output_api.PresubmitPromptWarning(
1091              'File %s line %d: %s is missing in '
1092              'tools/metrics/actions/actions.xml. Please run '
1093              'tools/metrics/actions/extract_actions.py to update.'
1094              % (f.LocalPath(), line_num, action_name))]
1095  return []
1096
1097
1098def _GetJSONParseError(input_api, filename, eat_comments=True):
1099  try:
1100    contents = input_api.ReadFile(filename)
1101    if eat_comments:
1102      json_comment_eater = input_api.os_path.join(
1103          input_api.PresubmitLocalPath(),
1104          'tools', 'json_comment_eater', 'json_comment_eater.py')
1105      process = input_api.subprocess.Popen(
1106          [input_api.python_executable, json_comment_eater],
1107          stdin=input_api.subprocess.PIPE,
1108          stdout=input_api.subprocess.PIPE,
1109          universal_newlines=True)
1110      (contents, _) = process.communicate(input=contents)
1111
1112    input_api.json.loads(contents)
1113  except ValueError as e:
1114    return e
1115  return None
1116
1117
1118def _GetIDLParseError(input_api, filename):
1119  try:
1120    contents = input_api.ReadFile(filename)
1121    idl_schema = input_api.os_path.join(
1122        input_api.PresubmitLocalPath(),
1123        'tools', 'json_schema_compiler', 'idl_schema.py')
1124    process = input_api.subprocess.Popen(
1125        [input_api.python_executable, idl_schema],
1126        stdin=input_api.subprocess.PIPE,
1127        stdout=input_api.subprocess.PIPE,
1128        stderr=input_api.subprocess.PIPE,
1129        universal_newlines=True)
1130    (_, error) = process.communicate(input=contents)
1131    return error or None
1132  except ValueError as e:
1133    return e
1134
1135
1136def _CheckParseErrors(input_api, output_api):
1137  """Check that IDL and JSON files do not contain syntax errors."""
1138  actions = {
1139    '.idl': _GetIDLParseError,
1140    '.json': _GetJSONParseError,
1141  }
1142  # These paths contain test data and other known invalid JSON files.
1143  excluded_patterns = [
1144    r'test[\\\/]data[\\\/]',
1145    r'^components[\\\/]policy[\\\/]resources[\\\/]policy_templates\.json$',
1146  ]
1147  # Most JSON files are preprocessed and support comments, but these do not.
1148  json_no_comments_patterns = [
1149    r'^testing[\\\/]',
1150  ]
1151  # Only run IDL checker on files in these directories.
1152  idl_included_patterns = [
1153    r'^chrome[\\\/]common[\\\/]extensions[\\\/]api[\\\/]',
1154    r'^extensions[\\\/]common[\\\/]api[\\\/]',
1155  ]
1156
1157  def get_action(affected_file):
1158    filename = affected_file.LocalPath()
1159    return actions.get(input_api.os_path.splitext(filename)[1])
1160
1161  def MatchesFile(patterns, path):
1162    for pattern in patterns:
1163      if input_api.re.search(pattern, path):
1164        return True
1165    return False
1166
1167  def FilterFile(affected_file):
1168    action = get_action(affected_file)
1169    if not action:
1170      return False
1171    path = affected_file.LocalPath()
1172
1173    if MatchesFile(excluded_patterns, path):
1174      return False
1175
1176    if (action == _GetIDLParseError and
1177        not MatchesFile(idl_included_patterns, path)):
1178      return False
1179    return True
1180
1181  results = []
1182  for affected_file in input_api.AffectedFiles(
1183      file_filter=FilterFile, include_deletes=False):
1184    action = get_action(affected_file)
1185    kwargs = {}
1186    if (action == _GetJSONParseError and
1187        MatchesFile(json_no_comments_patterns, affected_file.LocalPath())):
1188      kwargs['eat_comments'] = False
1189    parse_error = action(input_api,
1190                         affected_file.AbsoluteLocalPath(),
1191                         **kwargs)
1192    if parse_error:
1193      results.append(output_api.PresubmitError('%s could not be parsed: %s' %
1194          (affected_file.LocalPath(), parse_error)))
1195  return results
1196
1197
1198def _CheckJavaStyle(input_api, output_api):
1199  """Runs checkstyle on changed java files and returns errors if any exist."""
1200  original_sys_path = sys.path
1201  try:
1202    sys.path = sys.path + [input_api.os_path.join(
1203        input_api.PresubmitLocalPath(), 'tools', 'android', 'checkstyle')]
1204    import checkstyle
1205  finally:
1206    # Restore sys.path to what it was before.
1207    sys.path = original_sys_path
1208
1209  return checkstyle.RunCheckstyle(
1210      input_api, output_api, 'tools/android/checkstyle/chromium-style-5.0.xml')
1211
1212
1213_DEPRECATED_CSS = [
1214  # Values
1215  ( "-webkit-box", "flex" ),
1216  ( "-webkit-inline-box", "inline-flex" ),
1217  ( "-webkit-flex", "flex" ),
1218  ( "-webkit-inline-flex", "inline-flex" ),
1219  ( "-webkit-min-content", "min-content" ),
1220  ( "-webkit-max-content", "max-content" ),
1221
1222  # Properties
1223  ( "-webkit-background-clip", "background-clip" ),
1224  ( "-webkit-background-origin", "background-origin" ),
1225  ( "-webkit-background-size", "background-size" ),
1226  ( "-webkit-box-shadow", "box-shadow" ),
1227
1228  # Functions
1229  ( "-webkit-gradient", "gradient" ),
1230  ( "-webkit-repeating-gradient", "repeating-gradient" ),
1231  ( "-webkit-linear-gradient", "linear-gradient" ),
1232  ( "-webkit-repeating-linear-gradient", "repeating-linear-gradient" ),
1233  ( "-webkit-radial-gradient", "radial-gradient" ),
1234  ( "-webkit-repeating-radial-gradient", "repeating-radial-gradient" ),
1235]
1236
1237def _CheckNoDeprecatedCSS(input_api, output_api):
1238  """ Make sure that we don't use deprecated CSS
1239      properties, functions or values. Our external
1240      documentation is ignored by the hooks as it
1241      needs to be consumed by WebKit. """
1242  results = []
1243  file_inclusion_pattern = (r".+\.css$")
1244  black_list = (_EXCLUDED_PATHS +
1245                _TEST_CODE_EXCLUDED_PATHS +
1246                input_api.DEFAULT_BLACK_LIST +
1247                (r"^chrome/common/extensions/docs",
1248                 r"^chrome/docs",
1249                 r"^native_client_sdk"))
1250  file_filter = lambda f: input_api.FilterSourceFile(
1251      f, white_list=file_inclusion_pattern, black_list=black_list)
1252  for fpath in input_api.AffectedFiles(file_filter=file_filter):
1253    for line_num, line in fpath.ChangedContents():
1254      for (deprecated_value, value) in _DEPRECATED_CSS:
1255        if input_api.re.search(deprecated_value, line):
1256          results.append(output_api.PresubmitError(
1257              "%s:%d: Use of deprecated CSS %s, use %s instead" %
1258              (fpath.LocalPath(), line_num, deprecated_value, value)))
1259  return results
1260
1261def _CommonChecks(input_api, output_api):
1262  """Checks common to both upload and commit."""
1263  results = []
1264  results.extend(input_api.canned_checks.PanProjectChecks(
1265      input_api, output_api,
1266      excluded_paths=_EXCLUDED_PATHS + _TESTRUNNER_PATHS))
1267  results.extend(_CheckAuthorizedAuthor(input_api, output_api))
1268  results.extend(
1269      _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
1270  results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
1271  results.extend(_CheckNoUNIT_TESTInSourceFiles(input_api, output_api))
1272  results.extend(_CheckNoNewWStrings(input_api, output_api))
1273  results.extend(_CheckNoDEPSGIT(input_api, output_api))
1274  results.extend(_CheckNoBannedFunctions(input_api, output_api))
1275  results.extend(_CheckNoPragmaOnce(input_api, output_api))
1276  results.extend(_CheckNoTrinaryTrueFalse(input_api, output_api))
1277  results.extend(_CheckUnwantedDependencies(input_api, output_api))
1278  results.extend(_CheckFilePermissions(input_api, output_api))
1279  results.extend(_CheckNoAuraWindowPropertyHInHeaders(input_api, output_api))
1280  results.extend(_CheckIncludeOrder(input_api, output_api))
1281  results.extend(_CheckForVersionControlConflicts(input_api, output_api))
1282  results.extend(_CheckPatchFiles(input_api, output_api))
1283  results.extend(_CheckHardcodedGoogleHostsInLowerLayers(input_api, output_api))
1284  results.extend(_CheckNoAbbreviationInPngFileName(input_api, output_api))
1285  results.extend(_CheckForInvalidOSMacros(input_api, output_api))
1286  results.extend(_CheckAddedDepsHaveTargetApprovals(input_api, output_api))
1287  results.extend(
1288      input_api.canned_checks.CheckChangeHasNoTabs(
1289          input_api,
1290          output_api,
1291          source_file_filter=lambda x: x.LocalPath().endswith('.grd')))
1292  results.extend(_CheckSpamLogging(input_api, output_api))
1293  results.extend(_CheckForAnonymousVariables(input_api, output_api))
1294  results.extend(_CheckCygwinShell(input_api, output_api))
1295  results.extend(_CheckUserActionUpdate(input_api, output_api))
1296  results.extend(_CheckNoDeprecatedCSS(input_api, output_api))
1297  results.extend(_CheckParseErrors(input_api, output_api))
1298  results.extend(_CheckForIPCRules(input_api, output_api))
1299
1300  if any('PRESUBMIT.py' == f.LocalPath() for f in input_api.AffectedFiles()):
1301    results.extend(input_api.canned_checks.RunUnitTestsInDirectory(
1302        input_api, output_api,
1303        input_api.PresubmitLocalPath(),
1304        whitelist=[r'^PRESUBMIT_test\.py$']))
1305  return results
1306
1307
1308def _CheckAuthorizedAuthor(input_api, output_api):
1309  """For non-googler/chromites committers, verify the author's email address is
1310  in AUTHORS.
1311  """
1312  # TODO(maruel): Add it to input_api?
1313  import fnmatch
1314
1315  author = input_api.change.author_email
1316  if not author:
1317    input_api.logging.info('No author, skipping AUTHOR check')
1318    return []
1319  authors_path = input_api.os_path.join(
1320      input_api.PresubmitLocalPath(), 'AUTHORS')
1321  valid_authors = (
1322      input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
1323      for line in open(authors_path))
1324  valid_authors = [item.group(1).lower() for item in valid_authors if item]
1325  if not any(fnmatch.fnmatch(author.lower(), valid) for valid in valid_authors):
1326    input_api.logging.info('Valid authors are %s', ', '.join(valid_authors))
1327    return [output_api.PresubmitPromptWarning(
1328        ('%s is not in AUTHORS file. If you are a new contributor, please visit'
1329        '\n'
1330        'http://www.chromium.org/developers/contributing-code and read the '
1331        '"Legal" section\n'
1332        'If you are a chromite, verify the contributor signed the CLA.') %
1333        author)]
1334  return []
1335
1336
1337def _CheckPatchFiles(input_api, output_api):
1338  problems = [f.LocalPath() for f in input_api.AffectedFiles()
1339      if f.LocalPath().endswith(('.orig', '.rej'))]
1340  if problems:
1341    return [output_api.PresubmitError(
1342        "Don't commit .rej and .orig files.", problems)]
1343  else:
1344    return []
1345
1346
1347def _DidYouMeanOSMacro(bad_macro):
1348  try:
1349    return {'A': 'OS_ANDROID',
1350            'B': 'OS_BSD',
1351            'C': 'OS_CHROMEOS',
1352            'F': 'OS_FREEBSD',
1353            'L': 'OS_LINUX',
1354            'M': 'OS_MACOSX',
1355            'N': 'OS_NACL',
1356            'O': 'OS_OPENBSD',
1357            'P': 'OS_POSIX',
1358            'S': 'OS_SOLARIS',
1359            'W': 'OS_WIN'}[bad_macro[3].upper()]
1360  except KeyError:
1361    return ''
1362
1363
1364def _CheckForInvalidOSMacrosInFile(input_api, f):
1365  """Check for sensible looking, totally invalid OS macros."""
1366  preprocessor_statement = input_api.re.compile(r'^\s*#')
1367  os_macro = input_api.re.compile(r'defined\((OS_[^)]+)\)')
1368  results = []
1369  for lnum, line in f.ChangedContents():
1370    if preprocessor_statement.search(line):
1371      for match in os_macro.finditer(line):
1372        if not match.group(1) in _VALID_OS_MACROS:
1373          good = _DidYouMeanOSMacro(match.group(1))
1374          did_you_mean = ' (did you mean %s?)' % good if good else ''
1375          results.append('    %s:%d %s%s' % (f.LocalPath(),
1376                                             lnum,
1377                                             match.group(1),
1378                                             did_you_mean))
1379  return results
1380
1381
1382def _CheckForInvalidOSMacros(input_api, output_api):
1383  """Check all affected files for invalid OS macros."""
1384  bad_macros = []
1385  for f in input_api.AffectedFiles():
1386    if not f.LocalPath().endswith(('.py', '.js', '.html', '.css')):
1387      bad_macros.extend(_CheckForInvalidOSMacrosInFile(input_api, f))
1388
1389  if not bad_macros:
1390    return []
1391
1392  return [output_api.PresubmitError(
1393      'Possibly invalid OS macro[s] found. Please fix your code\n'
1394      'or add your macro to src/PRESUBMIT.py.', bad_macros)]
1395
1396def _CheckForIPCRules(input_api, output_api):
1397  """Check for same IPC rules described in
1398  http://www.chromium.org/Home/chromium-security/education/security-tips-for-ipc
1399  """
1400  base_pattern = r'IPC_ENUM_TRAITS\('
1401  inclusion_pattern = input_api.re.compile(r'(%s)' % base_pattern)
1402  comment_pattern = input_api.re.compile(r'//.*(%s)' % base_pattern)
1403
1404  problems = []
1405  for f in input_api.AffectedSourceFiles(None):
1406    local_path = f.LocalPath()
1407    if not local_path.endswith('.h'):
1408      continue
1409    for line_number, line in f.ChangedContents():
1410      if inclusion_pattern.search(line) and not comment_pattern.search(line):
1411        problems.append(
1412          '%s:%d\n    %s' % (local_path, line_number, line.strip()))
1413
1414  if problems:
1415    return [output_api.PresubmitPromptWarning(
1416        _IPC_ENUM_TRAITS_DEPRECATED, problems)]
1417  else:
1418    return []
1419
1420
1421def CheckChangeOnUpload(input_api, output_api):
1422  results = []
1423  results.extend(_CommonChecks(input_api, output_api))
1424  results.extend(_CheckValidHostsInDEPS(input_api, output_api))
1425  results.extend(_CheckJavaStyle(input_api, output_api))
1426  return results
1427
1428
1429def GetTryServerMasterForBot(bot):
1430  """Returns the Try Server master for the given bot.
1431
1432  It tries to guess the master from the bot name, but may still fail
1433  and return None.  There is no longer a default master.
1434  """
1435  # Potentially ambiguous bot names are listed explicitly.
1436  master_map = {
1437      'linux_gpu': 'tryserver.chromium.gpu',
1438      'mac_gpu': 'tryserver.chromium.gpu',
1439      'win_gpu': 'tryserver.chromium.gpu',
1440      'chromium_presubmit': 'tryserver.chromium.linux',
1441      'blink_presubmit': 'tryserver.chromium.linux',
1442      'tools_build_presubmit': 'tryserver.chromium.linux',
1443  }
1444  master = master_map.get(bot)
1445  if not master:
1446    if 'gpu' in bot:
1447      master = 'tryserver.chromium.gpu'
1448    elif 'linux' in bot or 'android' in bot or 'presubmit' in bot:
1449      master = 'tryserver.chromium.linux'
1450    elif 'win' in bot:
1451      master = 'tryserver.chromium.win'
1452    elif 'mac' in bot or 'ios' in bot:
1453      master = 'tryserver.chromium.mac'
1454  return master
1455
1456
1457def GetDefaultTryConfigs(bots=None):
1458  """Returns a list of ('bot', set(['tests']), optionally filtered by [bots].
1459
1460  To add tests to this list, they MUST be in the the corresponding master's
1461  gatekeeper config. For example, anything on master.chromium would be closed by
1462  tools/build/masters/master.chromium/master_gatekeeper_cfg.py.
1463
1464  If 'bots' is specified, will only return configurations for bots in that list.
1465  """
1466
1467  standard_tests = [
1468      'base_unittests',
1469      'browser_tests',
1470      'cacheinvalidation_unittests',
1471      'check_deps',
1472      'check_deps2git',
1473      'content_browsertests',
1474      'content_unittests',
1475      'crypto_unittests',
1476      'gpu_unittests',
1477      'interactive_ui_tests',
1478      'ipc_tests',
1479      'jingle_unittests',
1480      'media_unittests',
1481      'net_unittests',
1482      'ppapi_unittests',
1483      'printing_unittests',
1484      'sql_unittests',
1485      'sync_unit_tests',
1486      'unit_tests',
1487      # Broken in release.
1488      #'url_unittests',
1489      #'webkit_unit_tests',
1490  ]
1491
1492  builders_and_tests = {
1493      # TODO(maruel): Figure out a way to run 'sizes' where people can
1494      # effectively update the perf expectation correctly.  This requires a
1495      # clobber=True build running 'sizes'. 'sizes' is not accurate with
1496      # incremental build. Reference:
1497      # http://chromium.org/developers/tree-sheriffs/perf-sheriffs.
1498      # TODO(maruel): An option would be to run 'sizes' but not count a failure
1499      # of this step as a try job failure.
1500      'android_aosp': ['compile'],
1501      'android_arm64_dbg_recipe': ['slave_steps'],
1502      'android_chromium_gn_compile_dbg': ['compile'],
1503      'android_chromium_gn_compile_rel': ['compile'],
1504      'android_clang_dbg': ['slave_steps'],
1505      'android_clang_dbg_recipe': ['slave_steps'],
1506      'android_dbg_tests_recipe': ['slave_steps'],
1507      'cros_x86': ['defaulttests'],
1508      'ios_dbg_simulator': [
1509          'compile',
1510          'base_unittests',
1511          'content_unittests',
1512          'crypto_unittests',
1513          'url_unittests',
1514          'net_unittests',
1515          'sql_unittests',
1516          'ui_unittests',
1517      ],
1518      'ios_rel_device': ['compile'],
1519      'ios_rel_device_ninja': ['compile'],
1520      'linux_asan': ['compile'],
1521      'mac_asan': ['compile'],
1522      #TODO(stip): Change the name of this builder to reflect that it's release.
1523      'linux_gtk': standard_tests,
1524      'linux_chromeos_asan': ['compile'],
1525      'linux_chromium_chromeos_clang_dbg': ['defaulttests'],
1526      'linux_chromium_chromeos_rel_swarming': ['defaulttests'],
1527      'linux_chromium_compile_dbg': ['defaulttests'],
1528      'linux_chromium_gn_dbg': ['compile'],
1529      'linux_chromium_gn_rel': ['defaulttests'],
1530      'linux_chromium_rel_swarming': ['defaulttests'],
1531      'linux_chromium_clang_dbg': ['defaulttests'],
1532      'linux_gpu': ['defaulttests'],
1533      'linux_nacl_sdk_build': ['compile'],
1534      'mac_chromium_compile_dbg': ['defaulttests'],
1535      'mac_chromium_rel_swarming': ['defaulttests'],
1536      'mac_gpu': ['defaulttests'],
1537      'mac_nacl_sdk_build': ['compile'],
1538      'win_chromium_compile_dbg': ['defaulttests'],
1539      'win_chromium_dbg': ['defaulttests'],
1540      'win_chromium_rel_swarming': ['defaulttests'],
1541      'win_chromium_x64_rel_swarming': ['defaulttests'],
1542      'win_gpu': ['defaulttests'],
1543      'win_nacl_sdk_build': ['compile'],
1544      'win8_chromium_rel': ['defaulttests'],
1545  }
1546
1547  if bots:
1548    filtered_builders_and_tests = dict((bot, set(builders_and_tests[bot]))
1549                                       for bot in bots)
1550  else:
1551    filtered_builders_and_tests = dict(
1552        (bot, set(tests))
1553        for bot, tests in builders_and_tests.iteritems())
1554
1555  # Build up the mapping from tryserver master to bot/test.
1556  out = dict()
1557  for bot, tests in filtered_builders_and_tests.iteritems():
1558    out.setdefault(GetTryServerMasterForBot(bot), {})[bot] = tests
1559  return out
1560
1561
1562def CheckChangeOnCommit(input_api, output_api):
1563  results = []
1564  results.extend(_CommonChecks(input_api, output_api))
1565  # TODO(thestig) temporarily disabled, doesn't work in third_party/
1566  #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
1567  #    input_api, output_api, sources))
1568  # Make sure the tree is 'open'.
1569  results.extend(input_api.canned_checks.CheckTreeIsOpen(
1570      input_api,
1571      output_api,
1572      json_url='http://chromium-status.appspot.com/current?format=json'))
1573
1574  results.extend(input_api.canned_checks.CheckChangeHasBugField(
1575      input_api, output_api))
1576  results.extend(input_api.canned_checks.CheckChangeHasDescription(
1577      input_api, output_api))
1578  return results
1579
1580
1581def GetPreferredTryMasters(project, change):
1582  files = change.LocalPaths()
1583
1584  if not files or all(re.search(r'[\\\/]OWNERS$', f) for f in files):
1585    return {}
1586
1587  if all(re.search(r'\.(m|mm)$|(^|[\\\/_])mac[\\\/_.]', f) for f in files):
1588    return GetDefaultTryConfigs([
1589        'mac_chromium_compile_dbg',
1590        'mac_chromium_rel_swarming',
1591    ])
1592  if all(re.search('(^|[/_])win[/_.]', f) for f in files):
1593    return GetDefaultTryConfigs([
1594        'win_chromium_dbg',
1595        'win_chromium_rel_swarming',
1596        'win8_chromium_rel',
1597    ])
1598  if all(re.search(r'(^|[\\\/_])android[\\\/_.]', f) for f in files):
1599    return GetDefaultTryConfigs([
1600        'android_aosp',
1601        'android_clang_dbg',
1602        'android_dbg_tests_recipe',
1603    ])
1604  if all(re.search(r'[\\\/_]ios[\\\/_.]', f) for f in files):
1605    return GetDefaultTryConfigs(['ios_rel_device', 'ios_dbg_simulator'])
1606
1607  builders = [
1608      'android_arm64_dbg_recipe',
1609      'android_chromium_gn_compile_rel',
1610      'android_chromium_gn_compile_dbg',
1611      'android_clang_dbg',
1612      'android_clang_dbg_recipe',
1613      'android_dbg_tests_recipe',
1614      'ios_dbg_simulator',
1615      'ios_rel_device',
1616      'ios_rel_device_ninja',
1617      'linux_chromium_chromeos_rel_swarming',
1618      'linux_chromium_clang_dbg',
1619      'linux_chromium_gn_dbg',
1620      'linux_chromium_gn_rel',
1621      'linux_chromium_rel_swarming',
1622      'linux_gpu',
1623      'mac_chromium_compile_dbg',
1624      'mac_chromium_rel_swarming',
1625      'mac_gpu',
1626      'win_chromium_compile_dbg',
1627      'win_chromium_rel_swarming',
1628      'win_chromium_x64_rel_swarming',
1629      'win_gpu',
1630      'win8_chromium_rel',
1631  ]
1632
1633  # Match things like path/aura/file.cc and path/file_aura.cc.
1634  # Same for chromeos.
1635  if any(re.search(r'[\\\/_](aura|chromeos)', f) for f in files):
1636    builders.extend([
1637        'linux_chromeos_asan',
1638        'linux_chromium_chromeos_clang_dbg'
1639    ])
1640
1641  # If there are gyp changes to base, build, or chromeos, run a full cros build
1642  # in addition to the shorter linux_chromeos build. Changes to high level gyp
1643  # files have a much higher chance of breaking the cros build, which is
1644  # differnt from the linux_chromeos build that most chrome developers test
1645  # with.
1646  if any(re.search('^(base|build|chromeos).*\.gypi?$', f) for f in files):
1647    builders.extend(['cros_x86'])
1648
1649  # The AOSP bot doesn't build the chrome/ layer, so ignore any changes to it
1650  # unless they're .gyp(i) files as changes to those files can break the gyp
1651  # step on that bot.
1652  if (not all(re.search('^chrome', f) for f in files) or
1653      any(re.search('\.gypi?$', f) for f in files)):
1654    builders.extend(['android_aosp'])
1655
1656  return GetDefaultTryConfigs(builders)
1657