1# Copyright 2024 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Chromium presubmit script for base/allocator/partition_allocator. 5 6See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts 7for more details on the presubmit API built into depot_tools. 8""" 9 10PRESUBMIT_VERSION = '2.0.0' 11 12# This is the base path of the partition_alloc directory when stored inside the 13# chromium repository. PRESUBMIT.py is executed from chromium. 14_PARTITION_ALLOC_BASE_PATH = 'base/allocator/partition_allocator/src/' 15 16# Pattern matching C/C++ source files, for use in allowlist args. 17_SOURCE_FILE_PATTERN = r'.*\.(h|hpp|c|cc|cpp)$' 18 19# Similar pattern, matching GN files. 20_BUILD_FILE_PATTERN = r'.*\.(gn|gni)$' 21 22# This is adapted from Chromium's PRESUBMIT.py. The differences are: 23# - Base path: It is relative to the partition_alloc's source directory instead 24# of chromium. 25# - Stricter: A single format is allowed: `PATH_ELEM_FILE_NAME_H_`. 26def CheckForIncludeGuards(input_api, output_api): 27 """Check that header files have proper include guards""" 28 29 def guard_for_file(file): 30 local_path = file.LocalPath() 31 if input_api.is_windows: 32 local_path = local_path.replace('\\', '/') 33 assert local_path.startswith(_PARTITION_ALLOC_BASE_PATH) 34 guard = input_api.os_path.normpath( 35 local_path[len(_PARTITION_ALLOC_BASE_PATH):]) 36 guard = guard + '_' 37 guard = guard.upper() 38 guard = input_api.re.sub(r'[+\\/.-]', '_', guard) 39 return guard 40 41 def is_partition_alloc_header_file(f): 42 # We only check header files. 43 return f.LocalPath().endswith('.h') 44 45 errors = [] 46 47 for f in input_api.AffectedSourceFiles(is_partition_alloc_header_file): 48 expected_guard = guard_for_file(f) 49 50 # Unlike the Chromium's top-level PRESUBMIT.py, we enforce a stricter 51 # rule which accepts only `PATH_ELEM_FILE_NAME_H_` per coding style. 52 guard_name_pattern = input_api.re.escape(expected_guard) 53 guard_pattern = input_api.re.compile(r'#ifndef\s+(' + 54 guard_name_pattern + ')') 55 56 guard_name = None 57 guard_line_number = None 58 seen_guard_end = False 59 for line_number, line in enumerate(f.NewContents()): 60 if guard_name is None: 61 match = guard_pattern.match(line) 62 if match: 63 guard_name = match.group(1) 64 guard_line_number = line_number 65 continue 66 67 # The line after #ifndef should have a #define of the same name. 68 if line_number == guard_line_number + 1: 69 expected_line = '#define %s' % guard_name 70 if line != expected_line: 71 errors.append( 72 output_api.PresubmitPromptWarning( 73 'Missing "%s" for include guard' % expected_line, 74 ['%s:%d' % (f.LocalPath(), line_number + 1)], 75 'Expected: %r\nGot: %r' % (expected_line, line))) 76 77 if not seen_guard_end and line == '#endif // %s' % guard_name: 78 seen_guard_end = True 79 continue 80 81 if seen_guard_end: 82 if line.strip() != '': 83 errors.append( 84 output_api.PresubmitPromptWarning( 85 'Include guard %s not covering the whole file' % 86 (guard_name), [f.LocalPath()])) 87 break # Nothing else to check and enough to warn once. 88 89 if guard_name is None: 90 errors.append( 91 output_api.PresubmitPromptWarning( 92 'Missing include guard in %s\n' 93 'Recommended name: %s\n' % 94 (f.LocalPath(), expected_guard))) 95 96 return errors 97 98# In .gn and .gni files, check there are no unexpected dependencies on files 99# located outside of the partition_alloc repository. 100# 101# This is important, because partition_alloc has no CQ bots on its own, but only 102# through the chromium's CQ. 103# 104# Only //build_overrides/ is allowed, as it provides embedders, a way to 105# overrides the default build settings and forward the dependencies to 106# partition_alloc. 107def CheckNoExternalImportInGn(input_api, output_api): 108 # Match and capture <path> from import("<path>"). 109 import_re = input_api.re.compile(r'^ *import\("([^"]+)"\)') 110 111 sources = lambda affected_file: input_api.FilterSourceFile( 112 affected_file, 113 files_to_skip=[], 114 files_to_check=[_BUILD_FILE_PATTERN]) 115 116 errors = [] 117 for f in input_api.AffectedSourceFiles(sources): 118 for line_number, line in f.ChangedContents(): 119 match = import_re.search(line) 120 if not match: 121 continue 122 import_path = match.group(1) 123 if import_path.startswith('//build_overrides/'): 124 continue 125 if not import_path.startswith('//'): 126 continue; 127 errors.append(output_api.PresubmitError( 128 '%s:%d\nPartitionAlloc disallow external import: %s' % 129 (f.LocalPath(), line_number + 1, import_path))) 130 return errors; 131 132# partition_alloc still supports C++17, because Skia still uses C++17. 133def CheckCpp17CompatibleHeaders(input_api, output_api): 134 CPP_20_HEADERS = [ 135 "barrier", 136 "bit", 137 #"compare", Three-way comparison may be used under appropriate guards. 138 "format", 139 "numbers", 140 "ranges", 141 "semaphore", 142 "source_location", 143 "span", 144 "stop_token", 145 "syncstream", 146 "version", 147 ] 148 149 CPP_23_HEADERS = [ 150 "expected", 151 "flat_map", 152 "flat_set", 153 "generator", 154 "mdspan", 155 "print", 156 "spanstream", 157 "stacktrace", 158 "stdatomic.h", 159 "stdfloat", 160 ] 161 162 sources = lambda affected_file: input_api.FilterSourceFile( 163 affected_file, 164 # compiler_specific.h may use these headers in guarded ways. 165 files_to_skip=[ 166 r'.*partition_alloc_base/augmentations/compiler_specific\.h' 167 ], 168 files_to_check=[_SOURCE_FILE_PATTERN]) 169 170 errors = [] 171 for f in input_api.AffectedSourceFiles(sources): 172 # for line_number, line in f.ChangedContents(): 173 for line_number, line in enumerate(f.NewContents()): 174 for header in CPP_20_HEADERS: 175 if not "#include <%s>" % header in line: 176 continue 177 errors.append( 178 output_api.PresubmitError( 179 '%s:%d\nPartitionAlloc disallows C++20 headers: <%s>' 180 % (f.LocalPath(), line_number + 1, header))) 181 for header in CPP_23_HEADERS: 182 if not "#include <%s>" % header in line: 183 continue 184 errors.append( 185 output_api.PresubmitError( 186 '%s:%d\nPartitionAlloc disallows C++23 headers: <%s>' 187 % (f.LocalPath(), line_number + 1, header))) 188 return errors 189 190def CheckCpp17CompatibleKeywords(input_api, output_api): 191 CPP_20_KEYWORDS = [ 192 "concept", 193 "consteval", 194 "constinit", 195 "co_await", 196 "co_return", 197 "co_yield", 198 "requires", 199 "std::hardware_", 200 "std::is_constant_evaluated", 201 "std::bit_cast", 202 "std::midpoint", 203 "std::to_array", 204 ] 205 # Note: C++23 doesn't introduce new keywords. 206 207 sources = lambda affected_file: input_api.FilterSourceFile( 208 affected_file, 209 # compiler_specific.h may use these keywords in guarded macros. 210 files_to_skip=[r'.*partition_alloc_base/compiler_specific\.h'], 211 files_to_check=[_SOURCE_FILE_PATTERN]) 212 213 errors = [] 214 for f in input_api.AffectedSourceFiles(sources): 215 for line_number, line in f.ChangedContents(): 216 for keyword in CPP_20_KEYWORDS: 217 if not keyword in line: 218 continue 219 # Skip if part of a comment 220 if '//' in line and line.index('//') < line.index(keyword): 221 continue 222 223 # Make sure there are word separators around the keyword: 224 regex = r'\b%s\b' % keyword 225 if not input_api.re.search(regex, line): 226 continue 227 228 errors.append( 229 output_api.PresubmitError( 230 '%s:%d\nPartitionAlloc disallows C++20 keywords: %s' 231 % (f.LocalPath(), line_number + 1, keyword))) 232 return errors 233