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