# Copyright 2024 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Chromium presubmit script for base/allocator/partition_allocator. See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for more details on the presubmit API built into depot_tools. """ PRESUBMIT_VERSION = '2.0.0' # This is the base path of the partition_alloc directory when stored inside the # chromium repository. PRESUBMIT.py is executed from chromium. _PARTITION_ALLOC_BASE_PATH = 'base/allocator/partition_allocator/src/' # Pattern matching C/C++ source files, for use in allowlist args. _SOURCE_FILE_PATTERN = r'.*\.(h|hpp|c|cc|cpp)$' # Similar pattern, matching GN files. _BUILD_FILE_PATTERN = r'.*\.(gn|gni)$' # This is adapted from Chromium's PRESUBMIT.py. The differences are: # - Base path: It is relative to the partition_alloc's source directory instead # of chromium. # - Stricter: A single format is allowed: `PATH_ELEM_FILE_NAME_H_`. def CheckForIncludeGuards(input_api, output_api): """Check that header files have proper include guards""" def guard_for_file(file): local_path = file.LocalPath() if input_api.is_windows: local_path = local_path.replace('\\', '/') assert local_path.startswith(_PARTITION_ALLOC_BASE_PATH) guard = input_api.os_path.normpath( local_path[len(_PARTITION_ALLOC_BASE_PATH):]) guard = guard + '_' guard = guard.upper() guard = input_api.re.sub(r'[+\\/.-]', '_', guard) return guard def is_partition_alloc_header_file(f): # We only check header files. return f.LocalPath().endswith('.h') errors = [] for f in input_api.AffectedSourceFiles(is_partition_alloc_header_file): expected_guard = guard_for_file(f) # Unlike the Chromium's top-level PRESUBMIT.py, we enforce a stricter # rule which accepts only `PATH_ELEM_FILE_NAME_H_` per coding style. guard_name_pattern = input_api.re.escape(expected_guard) guard_pattern = input_api.re.compile(r'#ifndef\s+(' + guard_name_pattern + ')') guard_name = None guard_line_number = None seen_guard_end = False for line_number, line in enumerate(f.NewContents()): if guard_name is None: match = guard_pattern.match(line) if match: guard_name = match.group(1) guard_line_number = line_number continue # The line after #ifndef should have a #define of the same name. if line_number == guard_line_number + 1: expected_line = '#define %s' % guard_name if line != expected_line: errors.append( output_api.PresubmitPromptWarning( 'Missing "%s" for include guard' % expected_line, ['%s:%d' % (f.LocalPath(), line_number + 1)], 'Expected: %r\nGot: %r' % (expected_line, line))) if not seen_guard_end and line == '#endif // %s' % guard_name: seen_guard_end = True continue if seen_guard_end: if line.strip() != '': errors.append( output_api.PresubmitPromptWarning( 'Include guard %s not covering the whole file' % (guard_name), [f.LocalPath()])) break # Nothing else to check and enough to warn once. if guard_name is None: errors.append( output_api.PresubmitPromptWarning( 'Missing include guard in %s\n' 'Recommended name: %s\n' % (f.LocalPath(), expected_guard))) return errors # In .gn and .gni files, check there are no unexpected dependencies on files # located outside of the partition_alloc repository. # # This is important, because partition_alloc has no CQ bots on its own, but only # through the chromium's CQ. # # Only //build_overrides/ is allowed, as it provides embedders, a way to # overrides the default build settings and forward the dependencies to # partition_alloc. def CheckNoExternalImportInGn(input_api, output_api): # Match and capture from import(""). import_re = input_api.re.compile(r'^ *import\("([^"]+)"\)') sources = lambda affected_file: input_api.FilterSourceFile( affected_file, files_to_skip=[], files_to_check=[_BUILD_FILE_PATTERN]) errors = [] for f in input_api.AffectedSourceFiles(sources): for line_number, line in f.ChangedContents(): match = import_re.search(line) if not match: continue import_path = match.group(1) if import_path.startswith('//build_overrides/'): continue if not import_path.startswith('//'): continue; errors.append(output_api.PresubmitError( '%s:%d\nPartitionAlloc disallow external import: %s' % (f.LocalPath(), line_number + 1, import_path))) return errors; # partition_alloc still supports C++17, because Skia still uses C++17. def CheckCpp17CompatibleHeaders(input_api, output_api): CPP_20_HEADERS = [ "barrier", "bit", #"compare", Three-way comparison may be used under appropriate guards. "format", "numbers", "ranges", "semaphore", "source_location", "span", "stop_token", "syncstream", "version", ] CPP_23_HEADERS = [ "expected", "flat_map", "flat_set", "generator", "mdspan", "print", "spanstream", "stacktrace", "stdatomic.h", "stdfloat", ] sources = lambda affected_file: input_api.FilterSourceFile( affected_file, # compiler_specific.h may use these headers in guarded ways. files_to_skip=[ r'.*partition_alloc_base/augmentations/compiler_specific\.h' ], files_to_check=[_SOURCE_FILE_PATTERN]) errors = [] for f in input_api.AffectedSourceFiles(sources): # for line_number, line in f.ChangedContents(): for line_number, line in enumerate(f.NewContents()): for header in CPP_20_HEADERS: if not "#include <%s>" % header in line: continue errors.append( output_api.PresubmitError( '%s:%d\nPartitionAlloc disallows C++20 headers: <%s>' % (f.LocalPath(), line_number + 1, header))) for header in CPP_23_HEADERS: if not "#include <%s>" % header in line: continue errors.append( output_api.PresubmitError( '%s:%d\nPartitionAlloc disallows C++23 headers: <%s>' % (f.LocalPath(), line_number + 1, header))) return errors def CheckCpp17CompatibleKeywords(input_api, output_api): CPP_20_KEYWORDS = [ "concept", "consteval", "constinit", "co_await", "co_return", "co_yield", "requires", "std::hardware_", "std::is_constant_evaluated", "std::bit_cast", "std::midpoint", "std::to_array", ] # Note: C++23 doesn't introduce new keywords. sources = lambda affected_file: input_api.FilterSourceFile( affected_file, # compiler_specific.h may use these keywords in guarded macros. files_to_skip=[r'.*partition_alloc_base/compiler_specific\.h'], files_to_check=[_SOURCE_FILE_PATTERN]) errors = [] for f in input_api.AffectedSourceFiles(sources): for line_number, line in f.ChangedContents(): for keyword in CPP_20_KEYWORDS: if not keyword in line: continue # Skip if part of a comment if '//' in line and line.index('//') < line.index(keyword): continue # Make sure there are word separators around the keyword: regex = r'\b%s\b' % keyword if not input_api.re.search(regex, line): continue errors.append( output_api.PresubmitError( '%s:%d\nPartitionAlloc disallows C++20 keywords: %s' % (f.LocalPath(), line_number + 1, keyword))) return errors