xref: /aosp_15_r20/external/cronet/third_party/libc++/src/utils/libcxx/test/modules.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# ===----------------------------------------------------------------------===##
2#
3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4# See https://llvm.org/LICENSE.txt for license information.
5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6#
7# ===----------------------------------------------------------------------===##
8
9from libcxx.header_information import module_headers
10from libcxx.header_information import header_restrictions
11from dataclasses import dataclass
12
13### SkipDeclarations
14
15# Ignore several declarations found in the includes.
16#
17# Part of these items are bugs other are not yet implemented features.
18SkipDeclarations = dict()
19
20# See comment in the header.
21SkipDeclarations["cuchar"] = ["std::mbstate_t", "std::size_t"]
22
23# Not in the synopsis.
24SkipDeclarations["cwchar"] = ["std::FILE"]
25
26# The operators are added for private types like __iom_t10.
27SkipDeclarations["iomanip"] = ["std::operator<<", "std::operator>>"]
28
29SkipDeclarations["iosfwd"] = ["std::ios_base", "std::vector"]
30
31# This header also provides declarations in the namespace that might be
32# an error.
33SkipDeclarations["filesystem"] = [
34    "std::filesystem::operator==",
35    "std::filesystem::operator!=",
36]
37
38# This is a specialization for a private type
39SkipDeclarations["iterator"] = ["std::pointer_traits"]
40
41# TODO MODULES
42# This definition is declared in string and defined in istream
43# This declaration should be part of string
44SkipDeclarations["istream"] = ["std::getline"]
45
46# P1614 (at many places) and LWG3519 too.
47SkipDeclarations["random"] = [
48    "std::operator!=",
49    # LWG3519 makes these hidden friends.
50    # Note the older versions had the requirement of these operations but not in
51    # the synopsis.
52    "std::operator<<",
53    "std::operator>>",
54    "std::operator==",
55]
56
57# Declared in the forward header since std::string uses std::allocator
58SkipDeclarations["string"] = ["std::allocator"]
59# TODO MODULES remove zombie names
60# https://libcxx.llvm.org/Status/Cxx20.html#note-p0619
61SkipDeclarations["memory"] = [
62    "std::return_temporary_buffer",
63    "std::get_temporary_buffer",
64]
65
66# TODO MODULES this should be part of ios instead
67SkipDeclarations["streambuf"] = ["std::basic_ios"]
68
69# include/__type_traits/is_swappable.h
70SkipDeclarations["type_traits"] = [
71    "std::swap",
72    # TODO MODULES gotten through __functional/unwrap_ref.h
73    "std::reference_wrapper",
74]
75
76### ExtraDeclarations
77
78# Add declarations in headers.
79#
80# Some headers have their defines in a different header, which may have
81# additional declarations.
82ExtraDeclarations = dict()
83# This declaration is in the ostream header.
84ExtraDeclarations["system_error"] = ["std::operator<<"]
85
86### ExtraHeader
87
88# Adds extra headers file to scan
89#
90# Some C++ headers in libc++ are stored in multiple physical files. There is a
91# pattern to find these files. However there are some exceptions these are
92# listed here.
93ExtraHeader = dict()
94# locale has a file and not a subdirectory
95ExtraHeader["locale"] = "v1/__locale$"
96ExtraHeader["ranges"] = "v1/__fwd/subrange.h$"
97
98# The extra header is needed since two headers are required to provide the
99# same definition.
100ExtraHeader["functional"] = "v1/__compare/compare_three_way.h$"
101
102
103# newline needs to be escaped for the module partition output.
104nl = "\\\\n"
105
106
107@dataclass
108class module_test_generator:
109    tmp_prefix: str
110    module_path: str
111    clang_tidy: str
112    clang_tidy_plugin: str
113    compiler: str
114    compiler_flags: str
115    module: str
116
117    def write_lit_configuration(self):
118        print(
119            f"""\
120// UNSUPPORTED: c++03, c++11, c++14, c++17
121// UNSUPPORTED: clang-modules-build
122
123// REQUIRES: has-clang-tidy
124
125// The GCC compiler flags are not always compatible with clang-tidy.
126// UNSUPPORTED: gcc
127
128// MODULE_DEPENDENCIES: {self.module}
129
130// RUN: echo -n > {self.tmp_prefix}.all_partitions
131"""
132        )
133
134    def process_module_partition(self, header, is_c_header):
135        # Some headers cannot be included when a libc++ feature is disabled.
136        # In that case include the header conditionally. The header __config
137        # ensures the libc++ feature macros are available.
138        if header in header_restrictions:
139            include = (
140                f"#include <__config>{nl}"
141                f"#if {header_restrictions[header]}{nl}"
142                f"#  include <{header}>{nl}"
143                f"#endif{nl}"
144            )
145        else:
146            include = f"#include <{header}>{nl}"
147
148        module_files = f'#include \\"{self.module_path}/std/{header}.inc\\"{nl}'
149        if is_c_header:
150            module_files += (
151                f'#include \\"{self.module_path}/std.compat/{header}.inc\\"{nl}'
152            )
153
154        # Generate a module partition for the header module includes. This
155        # makes it possible to verify that all headers export all their
156        # named declarations.
157        print(
158            '// RUN: echo -e "'
159            f"module;{nl}"
160            f"{include}{nl}"
161            f"{nl}"
162            f"// Use __libcpp_module_<HEADER> to ensure that modules{nl}"
163            f"// are not named as keywords or reserved names.{nl}"
164            f"export module std:__libcpp_module_{header};{nl}"
165            f"{module_files}"
166            f'" > {self.tmp_prefix}.{header}.cppm'
167        )
168
169        # Extract the information of the module partition using lang-tidy
170        print(
171            f"// RUN: {self.clang_tidy} {self.tmp_prefix}.{header}.cppm "
172            "  --checks='-*,libcpp-header-exportable-declarations' "
173            "  -config='{CheckOptions: [ "
174            "    {"
175            "      key: libcpp-header-exportable-declarations.Filename, "
176            f"     value: {header}.inc"
177            "    }, {"
178            "      key: libcpp-header-exportable-declarations.FileType, "
179            f"     value: {'CompatModulePartition' if is_c_header else 'ModulePartition'}"
180            "    }, "
181            "  ]}' "
182            f"--load={self.clang_tidy_plugin} "
183            f"-- {self.compiler_flags} "
184            f"| sort > {self.tmp_prefix}.{header}.module"
185        )
186        print(
187            f"// RUN: cat  {self.tmp_prefix}.{header}.module >> {self.tmp_prefix}.all_partitions"
188        )
189
190        return include
191
192    def process_header(self, header, include, is_c_header):
193        # Dump the information as found in the module by using the header file(s).
194        skip_declarations = " ".join(SkipDeclarations.get(header, []))
195        if skip_declarations:
196            skip_declarations = (
197                "{"
198                "  key: libcpp-header-exportable-declarations.SkipDeclarations, "
199                f' value: "{skip_declarations}" '
200                "}, "
201            )
202
203        extra_declarations = " ".join(ExtraDeclarations.get(header, []))
204        if extra_declarations:
205            extra_declarations = (
206                "{"
207                "  key: libcpp-header-exportable-declarations.ExtraDeclarations, "
208                f' value: "{extra_declarations}" '
209                "}, "
210            )
211
212        extra_header = ExtraHeader.get(header, "")
213        if extra_header:
214            extra_header = (
215                "{"
216                "  key: libcpp-header-exportable-declarations.ExtraHeader, "
217                f' value: "{extra_header}" '
218                "}, "
219            )
220
221        # Clang-tidy needs a file input
222        print(f'// RUN: echo -e "' f"{include}" f'" > {self.tmp_prefix}.{header}.cpp')
223        print(
224            f"// RUN: {self.clang_tidy} {self.tmp_prefix}.{header}.cpp "
225            "  --checks='-*,libcpp-header-exportable-declarations' "
226            "  -config='{CheckOptions: [ "
227            "    {"
228            "      key: libcpp-header-exportable-declarations.Filename, "
229            f"     value: {header}"
230            "    }, {"
231            "      key: libcpp-header-exportable-declarations.FileType, "
232            f"     value: {'CHeader' if is_c_header else 'Header'}"
233            "    }, "
234            f"   {skip_declarations} {extra_declarations} {extra_header}, "
235            "  ]}' "
236            f"--load={self.clang_tidy_plugin} "
237            f"-- {self.compiler_flags} "
238            f"| sort > {self.tmp_prefix}.{header}.include"
239        )
240        print(
241            f"// RUN: diff -u {self.tmp_prefix}.{header}.module {self.tmp_prefix}.{header}.include"
242        )
243
244    def process_module(self, module):
245        # Merge the data of the parts
246        print(
247            f"// RUN: sort -u -o {self.tmp_prefix}.all_partitions {self.tmp_prefix}.all_partitions"
248        )
249
250        # Dump the information as found in top-level module.
251        print(
252            f"// RUN: {self.clang_tidy} {self.module_path}/{module}.cppm "
253            "  --checks='-*,libcpp-header-exportable-declarations' "
254            "  -config='{CheckOptions: [ "
255            "    {"
256            "      key: libcpp-header-exportable-declarations.Header, "
257            f"     value: {module}.cppm"
258            "    }, {"
259            "      key: libcpp-header-exportable-declarations.FileType, "
260            "      value: Module"
261            "    }, "
262            "  ]}' "
263            f"--load={self.clang_tidy_plugin} "
264            f"-- {self.compiler_flags} "
265            f"| sort > {self.tmp_prefix}.module"
266        )
267
268        # Compare the sum of the parts with the top-level module.
269        print(
270            f"// RUN: diff -u {self.tmp_prefix}.all_partitions {self.tmp_prefix}.module"
271        )
272
273    # Basic smoke test. Import a module and try to compile when using all
274    # exported names. This validates the clang-tidy script does not
275    # accidentally add named declarations to the list that are not available.
276    def test_module(self, module):
277        print(
278            f"""\
279// RUN: echo 'import {module};' > {self.tmp_prefix}.compile.pass.cpp
280// RUN: cat {self.tmp_prefix}.all_partitions >> {self.tmp_prefix}.compile.pass.cpp
281// RUN: {self.compiler} {self.compiler_flags} -fsyntax-only {self.tmp_prefix}.compile.pass.cpp
282"""
283        )
284
285    def write_test(self, module, c_headers=[]):
286        self.write_lit_configuration()
287
288        # Validate all module parts.
289        for header in module_headers:
290            is_c_header = header in c_headers
291            include = self.process_module_partition(header, is_c_header)
292            self.process_header(header, include, is_c_header)
293
294        self.process_module(module)
295        self.test_module(module)
296