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