1# Copyright 2012 the V8 project authors. All rights reserved. 2# Redistribution and use in source and binary forms, with or without 3# modification, are permitted provided that the following conditions are 4# met: 5# 6# * Redistributions of source code must retain the above copyright 7# notice, this list of conditions and the following disclaimer. 8# * Redistributions in binary form must reproduce the above 9# copyright notice, this list of conditions and the following 10# disclaimer in the documentation and/or other materials provided 11# with the distribution. 12# * Neither the name of Google Inc. nor the names of its 13# contributors may be used to endorse or promote products derived 14# from this software without specific prior written permission. 15# 16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28"""Top-level presubmit script for V8. 29 30See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts 31for more details about the presubmit API built into gcl. 32""" 33 34import sys 35 36 37_EXCLUDED_PATHS = ( 38 r"^test[\\\/].*", 39 r"^testing[\\\/].*", 40 r"^third_party[\\\/].*", 41 r"^tools[\\\/].*", 42) 43 44 45# Regular expression that matches code only used for test binaries 46# (best effort). 47_TEST_CODE_EXCLUDED_PATHS = ( 48 r'.+-unittest\.cc', 49 # Has a method VisitForTest(). 50 r'src[\\\/]compiler[\\\/]ast-graph-builder\.cc', 51 # Test extension. 52 r'src[\\\/]extensions[\\\/]gc-extension\.cc', 53) 54 55 56_TEST_ONLY_WARNING = ( 57 'You might be calling functions intended only for testing from\n' 58 'production code. It is OK to ignore this warning if you know what\n' 59 'you are doing, as the heuristics used to detect the situation are\n' 60 'not perfect. The commit queue will not block on this warning.') 61 62 63def _V8PresubmitChecks(input_api, output_api): 64 """Runs the V8 presubmit checks.""" 65 import sys 66 sys.path.append(input_api.os_path.join( 67 input_api.PresubmitLocalPath(), 'tools')) 68 from presubmit import CppLintProcessor 69 from presubmit import SourceProcessor 70 from presubmit import CheckAuthorizedAuthor 71 from presubmit import CheckStatusFiles 72 73 results = [] 74 if not CppLintProcessor().Run(input_api.PresubmitLocalPath()): 75 results.append(output_api.PresubmitError("C++ lint check failed")) 76 if not SourceProcessor().Run(input_api.PresubmitLocalPath()): 77 results.append(output_api.PresubmitError( 78 "Copyright header, trailing whitespaces and two empty lines " \ 79 "between declarations check failed")) 80 if not CheckStatusFiles(input_api.PresubmitLocalPath()): 81 results.append(output_api.PresubmitError("Status file check failed")) 82 results.extend(CheckAuthorizedAuthor(input_api, output_api)) 83 return results 84 85 86def _CheckUnwantedDependencies(input_api, output_api): 87 """Runs checkdeps on #include statements added in this 88 change. Breaking - rules is an error, breaking ! rules is a 89 warning. 90 """ 91 # We need to wait until we have an input_api object and use this 92 # roundabout construct to import checkdeps because this file is 93 # eval-ed and thus doesn't have __file__. 94 original_sys_path = sys.path 95 try: 96 sys.path = sys.path + [input_api.os_path.join( 97 input_api.PresubmitLocalPath(), 'buildtools', 'checkdeps')] 98 import checkdeps 99 from cpp_checker import CppChecker 100 from rules import Rule 101 finally: 102 # Restore sys.path to what it was before. 103 sys.path = original_sys_path 104 105 added_includes = [] 106 for f in input_api.AffectedFiles(): 107 if not CppChecker.IsCppFile(f.LocalPath()): 108 continue 109 110 changed_lines = [line for line_num, line in f.ChangedContents()] 111 added_includes.append([f.LocalPath(), changed_lines]) 112 113 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath()) 114 115 error_descriptions = [] 116 warning_descriptions = [] 117 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes( 118 added_includes): 119 description_with_path = '%s\n %s' % (path, rule_description) 120 if rule_type == Rule.DISALLOW: 121 error_descriptions.append(description_with_path) 122 else: 123 warning_descriptions.append(description_with_path) 124 125 results = [] 126 if error_descriptions: 127 results.append(output_api.PresubmitError( 128 'You added one or more #includes that violate checkdeps rules.', 129 error_descriptions)) 130 if warning_descriptions: 131 results.append(output_api.PresubmitPromptOrNotify( 132 'You added one or more #includes of files that are temporarily\n' 133 'allowed but being removed. Can you avoid introducing the\n' 134 '#include? See relevant DEPS file(s) for details and contacts.', 135 warning_descriptions)) 136 return results 137 138 139def _CheckNoInlineHeaderIncludesInNormalHeaders(input_api, output_api): 140 """Attempts to prevent inclusion of inline headers into normal header 141 files. This tries to establish a layering where inline headers can be 142 included by other inline headers or compilation units only.""" 143 file_inclusion_pattern = r'(?!.+-inl\.h).+\.h' 144 include_directive_pattern = input_api.re.compile(r'#include ".+-inl.h"') 145 include_warning = ( 146 'You might be including an inline header (e.g. foo-inl.h) within a\n' 147 'normal header (e.g. bar.h) file. Can you avoid introducing the\n' 148 '#include? The commit queue will not block on this warning.') 149 150 def FilterFile(affected_file): 151 black_list = (_EXCLUDED_PATHS + 152 input_api.DEFAULT_BLACK_LIST) 153 return input_api.FilterSourceFile( 154 affected_file, 155 white_list=(file_inclusion_pattern, ), 156 black_list=black_list) 157 158 problems = [] 159 for f in input_api.AffectedSourceFiles(FilterFile): 160 local_path = f.LocalPath() 161 for line_number, line in f.ChangedContents(): 162 if (include_directive_pattern.search(line)): 163 problems.append( 164 '%s:%d\n %s' % (local_path, line_number, line.strip())) 165 166 if problems: 167 return [output_api.PresubmitPromptOrNotify(include_warning, problems)] 168 else: 169 return [] 170 171 172def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api): 173 """Attempts to prevent use of functions intended only for testing in 174 non-testing code. For now this is just a best-effort implementation 175 that ignores header files and may have some false positives. A 176 better implementation would probably need a proper C++ parser. 177 """ 178 # We only scan .cc files, as the declaration of for-testing functions in 179 # header files are hard to distinguish from calls to such functions without a 180 # proper C++ parser. 181 file_inclusion_pattern = r'.+\.cc' 182 183 base_function_pattern = r'[ :]test::[^\s]+|ForTest(ing)?|for_test(ing)?' 184 inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern) 185 comment_pattern = input_api.re.compile(r'//.*(%s)' % base_function_pattern) 186 exclusion_pattern = input_api.re.compile( 187 r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % ( 188 base_function_pattern, base_function_pattern)) 189 190 def FilterFile(affected_file): 191 black_list = (_EXCLUDED_PATHS + 192 _TEST_CODE_EXCLUDED_PATHS + 193 input_api.DEFAULT_BLACK_LIST) 194 return input_api.FilterSourceFile( 195 affected_file, 196 white_list=(file_inclusion_pattern, ), 197 black_list=black_list) 198 199 problems = [] 200 for f in input_api.AffectedSourceFiles(FilterFile): 201 local_path = f.LocalPath() 202 for line_number, line in f.ChangedContents(): 203 if (inclusion_pattern.search(line) and 204 not comment_pattern.search(line) and 205 not exclusion_pattern.search(line)): 206 problems.append( 207 '%s:%d\n %s' % (local_path, line_number, line.strip())) 208 209 if problems: 210 return [output_api.PresubmitPromptOrNotify(_TEST_ONLY_WARNING, problems)] 211 else: 212 return [] 213 214 215def _CheckMissingFiles(input_api, output_api): 216 """Runs verify_source_deps.py to ensure no files were added that are not in 217 GN. 218 """ 219 # We need to wait until we have an input_api object and use this 220 # roundabout construct to import checkdeps because this file is 221 # eval-ed and thus doesn't have __file__. 222 original_sys_path = sys.path 223 try: 224 sys.path = sys.path + [input_api.os_path.join( 225 input_api.PresubmitLocalPath(), 'tools')] 226 from verify_source_deps import missing_gn_files, missing_gyp_files 227 finally: 228 # Restore sys.path to what it was before. 229 sys.path = original_sys_path 230 231 gn_files = missing_gn_files() 232 gyp_files = missing_gyp_files() 233 results = [] 234 if gn_files: 235 results.append(output_api.PresubmitError( 236 "You added one or more source files but didn't update the\n" 237 "corresponding BUILD.gn files:\n", 238 gn_files)) 239 if gyp_files: 240 results.append(output_api.PresubmitError( 241 "You added one or more source files but didn't update the\n" 242 "corresponding gyp files:\n", 243 gyp_files)) 244 return results 245 246 247def _CommonChecks(input_api, output_api): 248 """Checks common to both upload and commit.""" 249 results = [] 250 results.extend(input_api.canned_checks.CheckOwners( 251 input_api, output_api, source_file_filter=None)) 252 results.extend(input_api.canned_checks.CheckPatchFormatted( 253 input_api, output_api)) 254 results.extend(input_api.canned_checks.CheckGenderNeutral( 255 input_api, output_api)) 256 results.extend(_V8PresubmitChecks(input_api, output_api)) 257 results.extend(_CheckUnwantedDependencies(input_api, output_api)) 258 results.extend( 259 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api)) 260 results.extend( 261 _CheckNoInlineHeaderIncludesInNormalHeaders(input_api, output_api)) 262 results.extend(_CheckMissingFiles(input_api, output_api)) 263 return results 264 265 266def _SkipTreeCheck(input_api, output_api): 267 """Check the env var whether we want to skip tree check. 268 Only skip if include/v8-version.h has been updated.""" 269 src_version = 'include/v8-version.h' 270 if not input_api.AffectedSourceFiles( 271 lambda file: file.LocalPath() == src_version): 272 return False 273 return input_api.environ.get('PRESUBMIT_TREE_CHECK') == 'skip' 274 275 276def CheckChangeOnUpload(input_api, output_api): 277 results = [] 278 results.extend(_CommonChecks(input_api, output_api)) 279 return results 280 281 282def CheckChangeOnCommit(input_api, output_api): 283 results = [] 284 results.extend(_CommonChecks(input_api, output_api)) 285 results.extend(input_api.canned_checks.CheckChangeHasDescription( 286 input_api, output_api)) 287 if not _SkipTreeCheck(input_api, output_api): 288 results.extend(input_api.canned_checks.CheckTreeIsOpen( 289 input_api, output_api, 290 json_url='http://v8-status.appspot.com/current?format=json')) 291 return results 292