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 for
      8 details on the presubmit API built into gcl.
      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                          'gfx::Point',
    120                          'gfx::PointF',
    121                          'gfx::Rect',
    122                          'gfx::Size',
    123                          'gfx::SizeF',
    124                          'gfx::Vector2d',
    125                          'gfx::Vector2dF',
    126                          ]
    127 
    128   for f in input_api.AffectedSourceFiles(source_file_filter):
    129     contents = input_api.ReadFile(f, 'rb')
    130     match = re.search(
    131       r'\bconst +' + '(?P<type>(%s))&' %
    132         string.join(pass_by_value_types, '|'),
    133       contents)
    134     if match:
    135       local_errors.append(output_api.PresubmitError(
    136         '%s passes %s by const ref instead of by value.' %
    137         (f.LocalPath(), match.group('type'))))
    138   return local_errors
    139 
    140 def CheckTodos(input_api, output_api):
    141   errors = []
    142 
    143   source_file_filter = lambda x: x
    144   for f in input_api.AffectedSourceFiles(source_file_filter):
    145     contents = input_api.ReadFile(f, 'rb')
    146     if ('FIX'+'ME') in contents:
    147       errors.append(f.LocalPath())
    148 
    149   if errors:
    150     return [output_api.PresubmitError(
    151       'All TODO comments should be of the form TODO(name).',
    152       items=errors)]
    153   return []
    154 
    155 def FindUnquotedQuote(contents, pos):
    156   match = re.search(r"(?<!\\)(?P<quote>\")", contents[pos:])
    157   return -1 if not match else match.start("quote") + pos
    158 
    159 def FindNamespaceInBlock(pos, namespace, contents, whitelist=[]):
    160   open_brace = -1
    161   close_brace = -1
    162   quote = -1
    163   name = -1
    164   brace_count = 1
    165   quote_count = 0
    166   while pos < len(contents) and brace_count > 0:
    167     if open_brace < pos: open_brace = contents.find("{", pos)
    168     if close_brace < pos: close_brace = contents.find("}", pos)
    169     if quote < pos: quote = FindUnquotedQuote(contents, pos)
    170     if name < pos: name = contents.find(("%s::" % namespace), pos)
    171 
    172     if name < 0:
    173       return False # The namespace is not used at all.
    174     if open_brace < 0:
    175       open_brace = len(contents)
    176     if close_brace < 0:
    177       close_brace = len(contents)
    178     if quote < 0:
    179       quote = len(contents)
    180 
    181     next = min(open_brace, min(close_brace, min(quote, name)))
    182 
    183     if next == open_brace:
    184       brace_count += 1
    185     elif next == close_brace:
    186       brace_count -= 1
    187     elif next == quote:
    188       quote_count = 0 if quote_count else 1
    189     elif next == name and not quote_count:
    190       in_whitelist = False
    191       for w in whitelist:
    192         if re.match(w, contents[next:]):
    193           in_whitelist = True
    194           break
    195       if not in_whitelist:
    196         return True
    197     pos = next + 1
    198   return False
    199 
    200 # Checks for the use of cc:: within the cc namespace, which is usually
    201 # redundant.
    202 def CheckNamespace(input_api, output_api):
    203   errors = []
    204 
    205   source_file_filter = lambda x: x
    206   for f in input_api.AffectedSourceFiles(source_file_filter):
    207     contents = input_api.ReadFile(f, 'rb')
    208     match = re.search(r'namespace\s*cc\s*{', contents)
    209     if match:
    210       whitelist = [
    211         r"cc::remove_if\b",
    212         ]
    213       if FindNamespaceInBlock(match.end(), 'cc', contents, whitelist=whitelist):
    214         errors.append(f.LocalPath())
    215 
    216   if errors:
    217     return [output_api.PresubmitError(
    218       'Do not use cc:: inside of the cc namespace.',
    219       items=errors)]
    220   return []
    221 
    222 
    223 def CheckChangeOnUpload(input_api, output_api):
    224   results = []
    225   results += CheckAsserts(input_api, output_api)
    226   results += CheckStdAbs(input_api, output_api)
    227   results += CheckPassByValue(input_api, output_api)
    228   results += CheckChangeLintsClean(input_api, output_api)
    229   results += CheckTodos(input_api, output_api)
    230   results += CheckNamespace(input_api, output_api)
    231   return results
    232 
    233 def GetPreferredTrySlaves(project, change):
    234   return [
    235     'linux_layout_rel',
    236     'win_gpu',
    237     'linux_gpu',
    238     'mac_gpu',
    239     'mac_gpu_retina',
    240   ]
    241