xref: /llvm-project/clang/unittests/Serialization/LoadSpecLazilyTest.cpp (revision 7f4312015291a32d811a0f37e24b4d9736c524f7)
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