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
17using namespace llvm;
18using namespace clang;
19using namespace clang::tooling;
20
21namespace {
22
23class 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
31public:
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
95enum class CheckingMode { Forbidden, Required };
96
97class DeclsReaderListener : public ASTDeserializationListener {
98 StringRef SpeficiedName;
99 CheckingMode Mode;
100
101 bool ReadedSpecifiedName = false;
102
103public:
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
125class LoadSpecLazilyConsumer : public ASTConsumer {
126 DeclsReaderListener Listener;
127
128public:
129 LoadSpecLazilyConsumer(StringRef SpecifiedName, CheckingMode Mode)
130 : Listener(SpecifiedName, Mode) {}
131
132 ASTDeserializationListener *GetASTDeserializationListener() override {
133 return &Listener;
134 }
135};
136
137class CheckLoadSpecLazilyAction : public ASTFrontendAction {
138 StringRef SpecifiedName;
139 CheckingMode Mode;
140
141public:
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
151TEST_F(LoadSpecLazilyTest, BasicTest) {
152 GenerateModuleInterface(ModuleName: "M", Contents: R"cpp(
153export module M;
154export template <class T>
155class A {};
156export class ShouldNotBeLoaded {};
157export class Temp {
158 A<ShouldNotBeLoaded> AS;
159};
160 )cpp");
161
162 const char *test_file_contents = R"cpp(
163import M;
164A<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
180TEST_F(LoadSpecLazilyTest, ChainedTest) {
181 GenerateModuleInterface(ModuleName: "M", Contents: R"cpp(
182export module M;
183export template <class T>
184class A {};
185 )cpp");
186
187 GenerateModuleInterface(ModuleName: "N", Contents: R"cpp(
188export module N;
189export import M;
190export class ShouldNotBeLoaded {};
191export class Temp {
192 A<ShouldNotBeLoaded> AS;
193};
194 )cpp");
195
196 const char *test_file_contents = R"cpp(
197import N;
198A<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.
216TEST_F(LoadSpecLazilyTest, ChainedTest2) {
217 GenerateModuleInterface(ModuleName: "M", Contents: R"cpp(
218export module M;
219export template <class T>
220class A {};
221
222export class B {};
223
224export class C {
225 A<B> D;
226};
227 )cpp");
228
229 GenerateModuleInterface(ModuleName: "N", Contents: R"cpp(
230export module N;
231export import M;
232export class MayBeLoaded {};
233
234export class Temp {
235 A<MayBeLoaded> AS;
236};
237
238export class ExportedClass {};
239
240export template<> class A<ExportedClass> {
241 A<MayBeLoaded> AS;
242 A<B> AB;
243};
244 )cpp");
245
246 const char *test_file_contents = R"cpp(
247import N;
248Temp T;
249A<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

source code of clang/unittests/Serialization/LoadSpecLazilyTest.cpp