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(path: 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(path&: AbsPath, a: 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(Path: FileName, Contents); |
53 | |
54 | IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS = |
55 | llvm::vfs::createPhysicalFileSystem(); |
56 | DiagnosticOptions DiagOpts; |
57 | IntrusiveRefCntPtr<DiagnosticsEngine> Diags = |
58 | CompilerInstance::createDiagnostics(VFS&: *VFS, Opts&: DiagOpts); |
59 | CreateInvocationOptions CIOpts; |
60 | CIOpts.Diags = Diags; |
61 | CIOpts.VFS = VFS; |
62 | |
63 | std::string CacheBMIPath = |
64 | llvm::Twine(TestDir + "/" + ModuleName + ".pcm" ).str(); |
65 | std::string PrebuiltModulePath = |
66 | "-fprebuilt-module-path=" + TestDir.str().str(); |
67 | const char *Args[] = {"clang++" , |
68 | "-std=c++20" , |
69 | "--precompile" , |
70 | PrebuiltModulePath.c_str(), |
71 | "-working-directory" , |
72 | TestDir.c_str(), |
73 | "-I" , |
74 | TestDir.c_str(), |
75 | FileName.c_str(), |
76 | "-o" , |
77 | CacheBMIPath.c_str()}; |
78 | std::shared_ptr<CompilerInvocation> Invocation = |
79 | createInvocation(Args, Opts: CIOpts); |
80 | EXPECT_TRUE(Invocation); |
81 | |
82 | CompilerInstance Instance(std::move(Invocation)); |
83 | Instance.setDiagnostics(Diags.get()); |
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>(Val: D); |
106 | if (!ND) |
107 | return; |
108 | |
109 | ReadedSpecifiedName |= ND->getName().contains(Other: 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>(args&: SpecifiedName, args&: Mode); |
145 | } |
146 | |
147 | CheckLoadSpecLazilyAction(StringRef SpecifiedName, CheckingMode Mode) |
148 | : SpecifiedName(SpecifiedName), Mode(Mode) {} |
149 | }; |
150 | |
151 | TEST_F(LoadSpecLazilyTest, BasicTest) { |
152 | GenerateModuleInterface(ModuleName: "M" , Contents: 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(ModuleName: "M" , Contents: R"cpp( |
182 | export module M; |
183 | export template <class T> |
184 | class A {}; |
185 | )cpp" ); |
186 | |
187 | GenerateModuleInterface(ModuleName: "N" , Contents: 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(ModuleName: "M" , Contents: 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(ModuleName: "N" , Contents: 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 | |