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