• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
9# Test that all named declarations with external linkage match the
10# exported declarations in their associated module partition.
11# Then it tests the sum of the exported declarations in the module
12# partitions matches the export of the std module.
13
14# Note the test of the std module requires all partitions to be tested
15# first. Since lit tests have no dependencies, this means the test needs
16# to be one monolitic test. Since the test doesn't take very long it's
17# not a huge issue.
18
19# RUN: %{python} %s %{libcxx}/utils
20
21import sys
22
23sys.path.append(sys.argv[1])
24from libcxx.header_information import module_headers
25from libcxx.header_information import header_restrictions
26
27BLOCKLIT = (
28    ""  # block Lit from interpreting a RUN/XFAIL/etc inside the generation script
29)
30
31# Ignore several declarations found in the includes.
32#
33# Part of these items are bugs other are not yet implemented features.
34SkipDeclarations = dict()
35
36# See comment in the header.
37SkipDeclarations["cuchar"] = ["std::mbstate_t", "std::size_t"]
38
39# Not in the synopsis.
40SkipDeclarations["cwchar"] = ["std::FILE"]
41
42# The operators are added for private types like __iom_t10.
43SkipDeclarations["iomanip"] = ["std::operator<<", "std::operator>>"]
44
45SkipDeclarations["iosfwd"] = ["std::ios_base", "std::vector"]
46
47# This header also provides declarations in the namespace that might be
48# an error.
49SkipDeclarations["filesystem"] = [
50    "std::filesystem::operator==",
51    "std::filesystem::operator!=",
52]
53
54# This is a specialization for a private type
55SkipDeclarations["iterator"] = ["std::pointer_traits"]
56
57# TODO MODULES
58# This definition is declared in string and defined in istream
59# This declaration should be part of string
60SkipDeclarations["istream"] = ["std::getline"]
61
62# P1614 (at many places) and LWG3519 too.
63SkipDeclarations["random"] = [
64    "std::operator!=",
65    # LWG3519 makes these hidden friends.
66    # Note the older versions had the requirement of these operations but not in
67    # the synopsis.
68    "std::operator<<",
69    "std::operator>>",
70    "std::operator==",
71]
72
73# Declared in the forward header since std::string uses std::allocator
74SkipDeclarations["string"] = ["std::allocator"]
75# TODO MODULES remove zombie names
76# https://libcxx.llvm.org/Status/Cxx20.html#note-p0619
77SkipDeclarations["memory"] = [
78    "std::return_temporary_buffer",
79    "std::get_temporary_buffer",
80]
81
82# TODO MODULES this should be part of ios instead
83SkipDeclarations["streambuf"] = ["std::basic_ios"]
84
85# include/__type_traits/is_swappable.h
86SkipDeclarations["type_traits"] = [
87    "std::swap",
88    # TODO MODULES gotten through __functional/unwrap_ref.h
89    "std::reference_wrapper",
90]
91
92# Add declarations in headers.
93#
94# Some headers have their defines in a different header, which may have
95# additional declarations.
96ExtraDeclarations = dict()
97# This declaration is in the ostream header.
98ExtraDeclarations["system_error"] = ["std::operator<<"]
99
100# Adds an extra header file to scan
101#
102#
103ExtraHeader = dict()
104# locale has a file and not a subdirectory
105ExtraHeader["locale"] = "v1/__locale$"
106ExtraHeader["thread"] = "v1/__threading_support$"
107ExtraHeader["ranges"] = "v1/__fwd/subrange.h$"
108
109# The extra header is needed since two headers are required to provide the
110# same definition.
111ExtraHeader["functional"] = "v1/__compare/compare_three_way.h$"
112
113# newline needs to be escaped for the module partition output.
114nl = '\\\\n'
115
116# Create empty file with all parts.
117print(
118    f"""\
119//--- module_std.sh.cpp
120// UNSUPPORTED{BLOCKLIT}: c++03, c++11, c++14, c++17
121// UNSUPPORTED{BLOCKLIT}: libcpp-has-no-std-modules
122// UNSUPPORTED{BLOCKLIT}: clang-modules-build
123
124// REQUIRES{BLOCKLIT}: has-clang-tidy
125
126// The GCC compiler flags are not always compatible with clang-tidy.
127// UNSUPPORTED{BLOCKLIT}: gcc
128
129// RUN{BLOCKLIT}: echo -n > %t.all_partitions
130"""
131)
132
133# Validate all module parts.
134for header in module_headers:
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    elif header == "chrono":
146        # When localization is disabled the header string is not included.
147        # When string is included chrono's operator""s is a named declaration
148        #   using std::chrono_literals::operator""s;
149        # else it is a named declaration
150        #   using std::operator""s;
151        # TODO MODULES investigate why
152        include = f"#include <string>{nl}#include <chrono>{nl}"
153    else:
154        include = f"#include <{header}>{nl}"
155
156    # Generate a module partition for the header module includes. This
157    # makes it possible to verify that all headers export all their
158    # named declarations.
159    print(
160        f"// RUN{BLOCKLIT}: echo -e \""
161        f"module;{nl}"
162        f"{include}"
163        f"{nl}"
164        f"// Use __libcpp_module_<HEADER> to ensure that modules {nl}"
165        f"// are not named as keywords or reserved names.{nl}"
166        f"export module std:__libcpp_module_{header};{nl}"
167        f'#include \\"%{{module}}/std/{header}.inc\\"{nl}'
168        f"\" > %t.{header}.cppm")
169
170    # Dump the information as found in the module's cppm file.
171    print(
172        f"// RUN{BLOCKLIT}: %{{clang-tidy}} %t.{header}.cppm "
173        "  --checks='-*,libcpp-header-exportable-declarations' "
174        "  -config='{CheckOptions: [ "
175        "    {"
176        "      key: libcpp-header-exportable-declarations.Filename, "
177        f"     value: {header}.inc"
178        "    }, {"
179        "      key: libcpp-header-exportable-declarations.FileType, "
180        "      value: ModulePartition"
181        "    }, "
182        "  ]}' "
183        "  --load=%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin "
184        "  -- %{flags} %{compile_flags} "
185        f"| sort > %t.{header}.module"
186    )
187    print(f"// RUN{BLOCKLIT}: cat  %t.{header}.module >> %t.all_partitions")
188
189    # Dump the information as found in the module by using the header file(s).
190    skip_declarations = " ".join(SkipDeclarations.get(header, []))
191    if skip_declarations:
192        skip_declarations = (
193            "{"
194            "  key: libcpp-header-exportable-declarations.SkipDeclarations, "
195            f' value: "{skip_declarations}" '
196            "}, "
197        )
198
199    extra_declarations = " ".join(ExtraDeclarations.get(header, []))
200    if extra_declarations:
201        extra_declarations = (
202            " {"
203            "  key: libcpp-header-exportable-declarations.ExtraDeclarations, "
204            f' value: "{extra_declarations}" '
205            "}, "
206        )
207
208    extra_header = ExtraHeader.get(header, "")
209    if extra_header:
210        extra_header = (
211            "{"
212            "  key: libcpp-header-exportable-declarations.ExtraHeader, "
213            f' value: "{extra_header}" '
214            "}, "
215        )
216
217    # Clang-tidy needs a file input
218    print(f'// RUN{BLOCKLIT}: echo -e "' f"{include}" f'" > %t.{header}.cpp')
219    print(
220        f"// RUN{BLOCKLIT}: %{{clang-tidy}} %t.{header}.cpp "
221        "  --checks='-*,libcpp-header-exportable-declarations' "
222        "  -config='{CheckOptions: [ "
223        f"   {{key: libcpp-header-exportable-declarations.Filename, value: {header}}}, "
224        "    {key: libcpp-header-exportable-declarations.FileType, value: Header}, "
225        f"   {skip_declarations} {extra_declarations} {extra_header}, "
226        "  ]}' "
227        "  --load=%{test-tools}/clang_tidy_checks/libcxx-tidy.plugin "
228        "  -- %{flags} %{compile_flags} "
229        f" | sort > %t.{header}.include"
230    )
231
232    # Compare the cppm and header file(s) return the same results.
233    print(f"// RUN{BLOCKLIT}: diff -u %t.{header}.module %t.{header}.include")
234
235
236# Merge the data of the parts
237print(f"// RUN{BLOCKLIT}: sort -u -o %t.all_partitions %t.all_partitions")
238
239# Dump the information as found in std.cppm.
240print(
241    f"// RUN{BLOCKLIT}: %{{clang-tidy}} %{{module}}/std.cppm "
242    "  --checks='-*,libcpp-header-exportable-declarations' "
243    "  -config='{CheckOptions: [ "
244    "    {key: libcpp-header-exportable-declarations.Header, value: std.cppm}, "
245    "    {key: libcpp-header-exportable-declarations.FileType, value: Module}, "
246    "  ]}' "
247    f" --load=%{{test-tools}}/clang_tidy_checks/libcxx-tidy.plugin "
248    "  -- %{flags} %{compile_flags} "
249    "  | sort > %t.module"
250)
251
252
253# Compare the sum of the parts with the main module.
254print(f"// RUN{BLOCKLIT}: diff -u %t.all_partitions %t.module")
255
256# Basic smoke test. Import a module and try to compile when using all
257# exported names. This validates the clang-tidy script does not accidentally
258# add named declarations to the list that are not available.
259print(f"// RUN{BLOCKLIT}: echo 'import std;' > %t.compile.pass.cpp")
260print(f"// RUN{BLOCKLIT}: cat %t.all_partitions >> %t.compile.pass.cpp")
261print(f"// RUN{BLOCKLIT}: %{{cxx}} %{{flags}} %{{compile_flags}} -fsyntax-only %t.compile.pass.cpp")
262