1 //== unittests/Serialization/LoadSpecLazily.cpp ----------------------========// 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 #include "clang/Frontend/CompilerInstance.h" 10 #include "clang/Frontend/FrontendAction.h" 11 #include "clang/Frontend/FrontendActions.h" 12 #include "clang/Parse/ParseAST.h" 13 #include "clang/Serialization/ASTDeserializationListener.h" 14 #include "clang/Tooling/Tooling.h" 15 #include "gtest/gtest.h" 16 17 using namespace llvm; 18 using namespace clang; 19 using namespace clang::tooling; 20 21 namespace { 22 23 class LoadSpecLazilyTest : public ::testing::Test { 24 void SetUp() override { 25 ASSERT_FALSE( 26 sys::fs::createUniqueDirectory("load-spec-lazily-test", TestDir)); 27 } 28 29 void TearDown() override { sys::fs::remove_directories(TestDir); } 30 31 public: 32 SmallString<256> TestDir; 33 34 void addFile(StringRef Path, StringRef Contents) { 35 ASSERT_FALSE(sys::path::is_absolute(Path)); 36 37 SmallString<256> AbsPath(TestDir); 38 sys::path::append(AbsPath, Path); 39 40 ASSERT_FALSE( 41 sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath))); 42 43 std::error_code EC; 44 llvm::raw_fd_ostream OS(AbsPath, EC); 45 ASSERT_FALSE(EC); 46 OS << Contents; 47 } 48 49 std::string GenerateModuleInterface(StringRef ModuleName, 50 StringRef Contents) { 51 std::string FileName = llvm::Twine(ModuleName + ".cppm").str(); 52 addFile(FileName, Contents); 53 54 IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS = 55 llvm::vfs::createPhysicalFileSystem(); 56 IntrusiveRefCntPtr<DiagnosticsEngine> Diags = 57 CompilerInstance::createDiagnostics(*VFS, new DiagnosticOptions()); 58 CreateInvocationOptions CIOpts; 59 CIOpts.Diags = Diags; 60 CIOpts.VFS = VFS; 61 62 std::string CacheBMIPath = 63 llvm::Twine(TestDir + "/" + ModuleName + ".pcm").str(); 64 std::string PrebuiltModulePath = 65 "-fprebuilt-module-path=" + TestDir.str().str(); 66 const char *Args[] = {"clang++", 67 "-std=c++20", 68 "--precompile", 69 PrebuiltModulePath.c_str(), 70 "-working-directory", 71 TestDir.c_str(), 72 "-I", 73 TestDir.c_str(), 74 FileName.c_str(), 75 "-o", 76 CacheBMIPath.c_str()}; 77 std::shared_ptr<CompilerInvocation> Invocation = 78 createInvocation(Args, CIOpts); 79 EXPECT_TRUE(Invocation); 80 81 CompilerInstance Instance; 82 Instance.setDiagnostics(Diags.get()); 83 Instance.setInvocation(Invocation); 84 Instance.getFrontendOpts().OutputFile = CacheBMIPath; 85 // Avoid memory leaks. 86 Instance.getFrontendOpts().DisableFree = false; 87 GenerateModuleInterfaceAction Action; 88 EXPECT_TRUE(Instance.ExecuteAction(Action)); 89 EXPECT_FALSE(Diags->hasErrorOccurred()); 90 91 return CacheBMIPath; 92 } 93 }; 94 95 enum class CheckingMode { Forbidden, Required }; 96 97 class DeclsReaderListener : public ASTDeserializationListener { 98 StringRef SpeficiedName; 99 CheckingMode Mode; 100 101 bool ReadedSpecifiedName = false; 102 103 public: 104 void DeclRead(GlobalDeclID ID, const Decl *D) override { 105 auto *ND = dyn_cast<NamedDecl>(D); 106 if (!ND) 107 return; 108 109 ReadedSpecifiedName |= ND->getName().contains(SpeficiedName); 110 if (Mode == CheckingMode::Forbidden) { 111 EXPECT_FALSE(ReadedSpecifiedName); 112 } 113 } 114 115 DeclsReaderListener(StringRef SpeficiedName, CheckingMode Mode) 116 : SpeficiedName(SpeficiedName), Mode(Mode) {} 117 118 ~DeclsReaderListener() { 119 if (Mode == CheckingMode::Required) { 120 EXPECT_TRUE(ReadedSpecifiedName); 121 } 122 } 123 }; 124 125 class LoadSpecLazilyConsumer : public ASTConsumer { 126 DeclsReaderListener Listener; 127 128 public: 129 LoadSpecLazilyConsumer(StringRef SpecifiedName, CheckingMode Mode) 130 : Listener(SpecifiedName, Mode) {} 131 132 ASTDeserializationListener *GetASTDeserializationListener() override { 133 return &Listener; 134 } 135 }; 136 137 class CheckLoadSpecLazilyAction : public ASTFrontendAction { 138 StringRef SpecifiedName; 139 CheckingMode Mode; 140 141 public: 142 std::unique_ptr<ASTConsumer> 143 CreateASTConsumer(CompilerInstance &CI, StringRef /*Unused*/) override { 144 return std::make_unique<LoadSpecLazilyConsumer>(SpecifiedName, Mode); 145 } 146 147 CheckLoadSpecLazilyAction(StringRef SpecifiedName, CheckingMode Mode) 148 : SpecifiedName(SpecifiedName), Mode(Mode) {} 149 }; 150 151 TEST_F(LoadSpecLazilyTest, BasicTest) { 152 GenerateModuleInterface("M", R"cpp( 153 export module M; 154 export template <class T> 155 class A {}; 156 export class ShouldNotBeLoaded {}; 157 export class Temp { 158 A<ShouldNotBeLoaded> AS; 159 }; 160 )cpp"); 161 162 const char *test_file_contents = R"cpp( 163 import M; 164 A<int> a; 165 )cpp"; 166 std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); 167 EXPECT_TRUE( 168 runToolOnCodeWithArgs(std::make_unique<CheckLoadSpecLazilyAction>( 169 "ShouldNotBeLoaded", CheckingMode::Forbidden), 170 test_file_contents, 171 { 172 "-std=c++20", 173 DepArg.c_str(), 174 "-I", 175 TestDir.c_str(), 176 }, 177 "test.cpp")); 178 } 179 180 TEST_F(LoadSpecLazilyTest, ChainedTest) { 181 GenerateModuleInterface("M", R"cpp( 182 export module M; 183 export template <class T> 184 class A {}; 185 )cpp"); 186 187 GenerateModuleInterface("N", R"cpp( 188 export module N; 189 export import M; 190 export class ShouldNotBeLoaded {}; 191 export class Temp { 192 A<ShouldNotBeLoaded> AS; 193 }; 194 )cpp"); 195 196 const char *test_file_contents = R"cpp( 197 import N; 198 A<int> a; 199 )cpp"; 200 std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); 201 EXPECT_TRUE( 202 runToolOnCodeWithArgs(std::make_unique<CheckLoadSpecLazilyAction>( 203 "ShouldNotBeLoaded", CheckingMode::Forbidden), 204 test_file_contents, 205 { 206 "-std=c++20", 207 DepArg.c_str(), 208 "-I", 209 TestDir.c_str(), 210 }, 211 "test.cpp")); 212 } 213 214 /// Test that we won't crash due to we may invalidate the lazy specialization 215 /// lookup table during the loading process. 216 TEST_F(LoadSpecLazilyTest, ChainedTest2) { 217 GenerateModuleInterface("M", R"cpp( 218 export module M; 219 export template <class T> 220 class A {}; 221 222 export class B {}; 223 224 export class C { 225 A<B> D; 226 }; 227 )cpp"); 228 229 GenerateModuleInterface("N", R"cpp( 230 export module N; 231 export import M; 232 export class MayBeLoaded {}; 233 234 export class Temp { 235 A<MayBeLoaded> AS; 236 }; 237 238 export class ExportedClass {}; 239 240 export template<> class A<ExportedClass> { 241 A<MayBeLoaded> AS; 242 A<B> AB; 243 }; 244 )cpp"); 245 246 const char *test_file_contents = R"cpp( 247 import N; 248 Temp T; 249 A<ExportedClass> a; 250 )cpp"; 251 std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); 252 EXPECT_TRUE(runToolOnCodeWithArgs(std::make_unique<CheckLoadSpecLazilyAction>( 253 "MayBeLoaded", CheckingMode::Required), 254 test_file_contents, 255 { 256 "-std=c++20", 257 DepArg.c_str(), 258 "-I", 259 TestDir.c_str(), 260 }, 261 "test.cpp")); 262 } 263 264 } // namespace 265