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