• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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