Home | History | Annotate | Download | only in cc
      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 cc.
      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 import re
     12 import string
     13 
     14 CC_SOURCE_FILES=(r'^cc/.*\.(cc|h)$',)
     15 
     16 def CheckChangeLintsClean(input_api, output_api):
     17   input_api.cpplint._cpplint_state.ResetErrorCounts()  # reset global state
     18   source_filter = lambda x: input_api.FilterSourceFile(
     19     x, white_list=CC_SOURCE_FILES, black_list=None)
     20   files = [f.AbsoluteLocalPath() for f in
     21            input_api.AffectedSourceFiles(source_filter)]
     22   level = 1  # strict, but just warn
     23 
     24   for file_name in files:
     25     input_api.cpplint.ProcessFile(file_name, level)
     26 
     27   if not input_api.cpplint._cpplint_state.error_count:
     28     return []
     29 
     30   return [output_api.PresubmitPromptWarning(
     31     'Changelist failed cpplint.py check.')]
     32 
     33 def CheckAsserts(input_api, output_api, white_list=CC_SOURCE_FILES, black_list=None):
     34   black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
     35   source_file_filter = lambda x: input_api.FilterSourceFile(x, white_list, black_list)
     36 
     37   assert_files = []
     38   notreached_files = []
     39 
     40   for f in input_api.AffectedSourceFiles(source_file_filter):
     41     contents = input_api.ReadFile(f, 'rb')
     42     # WebKit ASSERT() is not allowed.
     43     if re.search(r"\bASSERT\(", contents):
     44       assert_files.append(f.LocalPath())
     45     # WebKit ASSERT_NOT_REACHED() is not allowed.
     46     if re.search(r"ASSERT_NOT_REACHED\(", contents):
     47       notreached_files.append(f.LocalPath())
     48 
     49   if assert_files:
     50     return [output_api.PresubmitError(
     51       'These files use ASSERT instead of using DCHECK:',
     52       items=assert_files)]
     53   if notreached_files:
     54     return [output_api.PresubmitError(
     55       'These files use ASSERT_NOT_REACHED instead of using NOTREACHED:',
     56       items=notreached_files)]
     57   return []
     58 
     59 def CheckStdAbs(input_api, output_api,
     60                 white_list=CC_SOURCE_FILES, black_list=None):
     61   black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
     62   source_file_filter = lambda x: input_api.FilterSourceFile(x,
     63                                                             white_list,
     64                                                             black_list)
     65 
     66   using_std_abs_files = []
     67   found_fabs_files = []
     68   missing_std_prefix_files = []
     69 
     70   for f in input_api.AffectedSourceFiles(source_file_filter):
     71     contents = input_api.ReadFile(f, 'rb')
     72     if re.search(r"using std::f?abs;", contents):
     73       using_std_abs_files.append(f.LocalPath())
     74     if re.search(r"\bfabsf?\(", contents):
     75       found_fabs_files.append(f.LocalPath());
     76 
     77     no_std_prefix = r"(?<!std::)"
     78     # Matches occurrences of abs/absf/fabs/fabsf without a "std::" prefix.
     79     abs_without_prefix = r"%s(\babsf?\()" % no_std_prefix
     80     fabs_without_prefix = r"%s(\bfabsf?\()" % no_std_prefix
     81     # Skips matching any lines that have "// NOLINT".
     82     no_nolint = r"(?![^\n]*//\s+NOLINT)"
     83 
     84     expression = re.compile("(%s|%s)%s" %
     85         (abs_without_prefix, fabs_without_prefix, no_nolint))
     86     if expression.search(contents):
     87       missing_std_prefix_files.append(f.LocalPath())
     88 
     89   result = []
     90   if using_std_abs_files:
     91     result.append(output_api.PresubmitError(
     92         'These files have "using std::abs" which is not permitted.',
     93         items=using_std_abs_files))
     94   if found_fabs_files:
     95     result.append(output_api.PresubmitError(
     96         'std::abs() should be used instead of std::fabs() for consistency.',
     97         items=found_fabs_files))
     98   if missing_std_prefix_files:
     99     result.append(output_api.PresubmitError(
    100         'These files use abs(), absf(), fabs(), or fabsf() without qualifying '
    101         'the std namespace. Please use std::abs() in all places.',
    102         items=missing_std_prefix_files))
    103   return result
    104 
    105 def CheckPassByValue(input_api,
    106                      output_api,
    107                      white_list=CC_SOURCE_FILES,
    108                      black_list=None):
    109   black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
    110   source_file_filter = lambda x: input_api.FilterSourceFile(x,
    111                                                             white_list,
    112                                                             black_list)
    113 
    114   local_errors = []
    115 
    116   # Well-defined simple classes containing only <= 4 ints, or <= 2 floats.
    117   pass_by_value_types = ['base::Time',
    118                          'base::TimeTicks',
    119                          ]
    120 
    121   for f in input_api.AffectedSourceFiles(source_file_filter):
    122     contents = input_api.ReadFile(f, 'rb')
    123     match = re.search(
    124       r'\bconst +' + '(?P<type>(%s))&' %
    125         string.join(pass_by_value_types, '|'),
    126       contents)
    127     if match:
    128       local_errors.append(output_api.PresubmitError(
    129         '%s passes %s by const ref instead of by value.' %
    130         (f.LocalPath(), match.group('type'))))
    131   return local_errors
    132 
    133 def CheckTodos(input_api, output_api):
    134   errors = []
    135 
    136   source_file_filter = lambda x: x
    137   for f in input_api.AffectedSourceFiles(source_file_filter):
    138     contents = input_api.ReadFile(f, 'rb')
    139     if ('FIX'+'ME') in contents:
    140       errors.append(f.LocalPath())
    141 
    142   if errors:
    143     return [output_api.PresubmitError(
    144       'All TODO comments should be of the form TODO(name). ' +
    145       'Use TODO instead of FIX' + 'ME',
    146       items=errors)]
    147   return []
    148 
    149 def FindUnquotedQuote(contents, pos):
    150   match = re.search(r"(?<!\\)(?P<quote>\")", contents[pos:])
    151   return -1 if not match else match.start("quote") + pos
    152 
    153 def FindUselessIfdefs(input_api, output_api):
    154   errors = []
    155   source_file_filter = lambda x: x
    156   for f in input_api.AffectedSourceFiles(source_file_filter):
    157     contents = input_api.ReadFile(f, 'rb')
    158     if re.search(r'#if\s*0\s', contents):
    159       errors.append(f.LocalPath())
    160   if errors:
    161     return [output_api.PresubmitError(
    162       'Don\'t use #if '+'0; just delete the code.',
    163       items=errors)]
    164   return []
    165 
    166 def FindNamespaceInBlock(pos, namespace, contents, whitelist=[]):
    167   open_brace = -1
    168   close_brace = -1
    169   quote = -1
    170   name = -1
    171   brace_count = 1
    172   quote_count = 0
    173   while pos < len(contents) and brace_count > 0:
    174     if open_brace < pos: open_brace = contents.find("{", pos)
    175     if close_brace < pos: close_brace = contents.find("}", pos)
    176     if quote < pos: quote = FindUnquotedQuote(contents, pos)
    177     if name < pos: name = contents.find(("%s::" % namespace), pos)
    178 
    179     if name < 0:
    180       return False # The namespace is not used at all.
    181     if open_brace < 0:
    182       open_brace = len(contents)
    183     if close_brace < 0:
    184       close_brace = len(contents)
    185     if quote < 0:
    186       quote = len(contents)
    187 
    188     next = min(open_brace, min(close_brace, min(quote, name)))
    189 
    190     if next == open_brace:
    191       brace_count += 1
    192     elif next == close_brace:
    193       brace_count -= 1
    194     elif next == quote:
    195       quote_count = 0 if quote_count else 1
    196     elif next == name and not quote_count:
    197       in_whitelist = False
    198       for w in whitelist:
    199         if re.match(w, contents[next:]):
    200           in_whitelist = True
    201           break
    202       if not in_whitelist:
    203         return True
    204     pos = next + 1
    205   return False
    206 
    207 # Checks for the use of cc:: within the cc namespace, which is usually
    208 # redundant.
    209 def CheckNamespace(input_api, output_api):
    210   errors = []
    211 
    212   source_file_filter = lambda x: x
    213   for f in input_api.AffectedSourceFiles(source_file_filter):
    214     contents = input_api.ReadFile(f, 'rb')
    215     match = re.search(r'namespace\s*cc\s*{', contents)
    216     if match:
    217       whitelist = [
    218         r"cc::remove_if\b",
    219         ]
    220       if FindNamespaceInBlock(match.end(), 'cc', contents, whitelist=whitelist):
    221         errors.append(f.LocalPath())
    222 
    223   if errors:
    224     return [output_api.PresubmitError(
    225       'Do not use cc:: inside of the cc namespace.',
    226       items=errors)]
    227   return []
    228 
    229 def CheckForUseOfWrongClock(input_api,
    230                             output_api,
    231                             white_list=CC_SOURCE_FILES,
    232                             black_list=None):
    233   """Make sure new lines of code don't use a clock susceptible to skew."""
    234   black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
    235   source_file_filter = lambda x: input_api.FilterSourceFile(x,
    236                                                             white_list,
    237                                                             black_list)
    238   # Regular expression that should detect any explicit references to the
    239   # base::Time type (or base::Clock/DefaultClock), whether in using decls,
    240   # typedefs, or to call static methods.
    241   base_time_type_pattern = r'(^|\W)base::(Time|Clock|DefaultClock)(\W|$)'
    242 
    243   # Regular expression that should detect references to the base::Time class
    244   # members, such as a call to base::Time::Now.
    245   base_time_member_pattern = r'(^|\W)(Time|Clock|DefaultClock)::'
    246 
    247   # Regular expression to detect "using base::Time" declarations.  We want to
    248   # prevent these from triggerring a warning.  For example, it's perfectly
    249   # reasonable for code to be written like this:
    250   #
    251   #   using base::Time;
    252   #   ...
    253   #   int64 foo_us = foo_s * Time::kMicrosecondsPerSecond;
    254   using_base_time_decl_pattern = r'^\s*using\s+(::)?base::Time\s*;'
    255 
    256   # Regular expression to detect references to the kXXX constants in the
    257   # base::Time class.  We want to prevent these from triggerring a warning.
    258   base_time_konstant_pattern = r'(^|\W)Time::k\w+'
    259 
    260   problem_re = input_api.re.compile(
    261       r'(' + base_time_type_pattern + r')|(' + base_time_member_pattern + r')')
    262   exception_re = input_api.re.compile(
    263       r'(' + using_base_time_decl_pattern + r')|(' +
    264       base_time_konstant_pattern + r')')
    265   problems = []
    266   for f in input_api.AffectedSourceFiles(source_file_filter):
    267     for line_number, line in f.ChangedContents():
    268       if problem_re.search(line):
    269         if not exception_re.search(line):
    270           problems.append(
    271               '  %s:%d\n    %s' % (f.LocalPath(), line_number, line.strip()))
    272 
    273   if problems:
    274     return [output_api.PresubmitPromptOrNotify(
    275         'You added one or more references to the base::Time class and/or one\n'
    276         'of its member functions (or base::Clock/DefaultClock). In cc code,\n'
    277         'it is most certainly incorrect! Instead use base::TimeTicks.\n\n'
    278         '\n'.join(problems))]
    279   else:
    280     return []
    281 
    282 def CheckChangeOnUpload(input_api, output_api):
    283   results = []
    284   results += CheckAsserts(input_api, output_api)
    285   results += CheckStdAbs(input_api, output_api)
    286   results += CheckPassByValue(input_api, output_api)
    287   results += CheckChangeLintsClean(input_api, output_api)
    288   results += CheckTodos(input_api, output_api)
    289   results += CheckNamespace(input_api, output_api)
    290   results += CheckForUseOfWrongClock(input_api, output_api)
    291   results += FindUselessIfdefs(input_api, output_api)
    292   results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api)
    293   return results
    294 
    295 def GetPreferredTryMasters(project, change):
    296   return {
    297     'tryserver.blink': {
    298       'linux_blink_rel': set(['defaulttests']),
    299     },
    300     'tryserver.chromium.gpu': {
    301       'linux_gpu': set(['defaulttests']),
    302       'mac_gpu': set(['defaulttests']),
    303       'win_gpu': set(['defaulttests']),
    304     },
    305   }
    306