|  | //== unittests/Serialization/LoadSpecLazily.cpp ----------------------========// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "clang/Frontend/CompilerInstance.h" | 
|  | #include "clang/Frontend/FrontendAction.h" | 
|  | #include "clang/Frontend/FrontendActions.h" | 
|  | #include "clang/Parse/ParseAST.h" | 
|  | #include "clang/Serialization/ASTDeserializationListener.h" | 
|  | #include "clang/Tooling/Tooling.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | using namespace llvm; | 
|  | using namespace clang; | 
|  | using namespace clang::tooling; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class LoadSpecLazilyTest : public ::testing::Test { | 
|  | void SetUp() override { | 
|  | ASSERT_FALSE( | 
|  | sys::fs::createUniqueDirectory("load-spec-lazily-test", TestDir)); | 
|  | } | 
|  |  | 
|  | void TearDown() override { sys::fs::remove_directories(TestDir); } | 
|  |  | 
|  | public: | 
|  | SmallString<256> TestDir; | 
|  |  | 
|  | void addFile(StringRef Path, StringRef Contents) { | 
|  | ASSERT_FALSE(sys::path::is_absolute(Path)); | 
|  |  | 
|  | SmallString<256> AbsPath(TestDir); | 
|  | sys::path::append(AbsPath, Path); | 
|  |  | 
|  | ASSERT_FALSE( | 
|  | sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath))); | 
|  |  | 
|  | std::error_code EC; | 
|  | llvm::raw_fd_ostream OS(AbsPath, EC); | 
|  | ASSERT_FALSE(EC); | 
|  | OS << Contents; | 
|  | } | 
|  |  | 
|  | std::string GenerateModuleInterface(StringRef ModuleName, | 
|  | StringRef Contents) { | 
|  | std::string FileName = llvm::Twine(ModuleName + ".cppm").str(); | 
|  | addFile(FileName, Contents); | 
|  |  | 
|  | IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS = | 
|  | llvm::vfs::createPhysicalFileSystem(); | 
|  | DiagnosticOptions DiagOpts; | 
|  | IntrusiveRefCntPtr<DiagnosticsEngine> Diags = | 
|  | CompilerInstance::createDiagnostics(*VFS, DiagOpts); | 
|  | CreateInvocationOptions CIOpts; | 
|  | CIOpts.Diags = Diags; | 
|  | CIOpts.VFS = VFS; | 
|  |  | 
|  | std::string CacheBMIPath = | 
|  | llvm::Twine(TestDir + "/" + ModuleName + ".pcm").str(); | 
|  | std::string PrebuiltModulePath = | 
|  | "-fprebuilt-module-path=" + TestDir.str().str(); | 
|  | const char *Args[] = {"clang++", | 
|  | "-std=c++20", | 
|  | "--precompile", | 
|  | PrebuiltModulePath.c_str(), | 
|  | "-working-directory", | 
|  | TestDir.c_str(), | 
|  | "-I", | 
|  | TestDir.c_str(), | 
|  | FileName.c_str(), | 
|  | "-o", | 
|  | CacheBMIPath.c_str()}; | 
|  | std::shared_ptr<CompilerInvocation> Invocation = | 
|  | createInvocation(Args, CIOpts); | 
|  | EXPECT_TRUE(Invocation); | 
|  |  | 
|  | CompilerInstance Instance(std::move(Invocation)); | 
|  | Instance.setDiagnostics(Diags); | 
|  | Instance.getFrontendOpts().OutputFile = CacheBMIPath; | 
|  | // Avoid memory leaks. | 
|  | Instance.getFrontendOpts().DisableFree = false; | 
|  | GenerateModuleInterfaceAction Action; | 
|  | EXPECT_TRUE(Instance.ExecuteAction(Action)); | 
|  | EXPECT_FALSE(Diags->hasErrorOccurred()); | 
|  |  | 
|  | return CacheBMIPath; | 
|  | } | 
|  | }; | 
|  |  | 
|  | enum class CheckingMode { Forbidden, Required }; | 
|  |  | 
|  | class DeclsReaderListener : public ASTDeserializationListener { | 
|  | StringRef SpeficiedName; | 
|  | CheckingMode Mode; | 
|  |  | 
|  | bool ReadedSpecifiedName = false; | 
|  |  | 
|  | public: | 
|  | void DeclRead(GlobalDeclID ID, const Decl *D) override { | 
|  | auto *ND = dyn_cast<NamedDecl>(D); | 
|  | if (!ND) | 
|  | return; | 
|  |  | 
|  | ReadedSpecifiedName |= ND->getName().contains(SpeficiedName); | 
|  | if (Mode == CheckingMode::Forbidden) { | 
|  | EXPECT_FALSE(ReadedSpecifiedName); | 
|  | } | 
|  | } | 
|  |  | 
|  | DeclsReaderListener(StringRef SpeficiedName, CheckingMode Mode) | 
|  | : SpeficiedName(SpeficiedName), Mode(Mode) {} | 
|  |  | 
|  | ~DeclsReaderListener() { | 
|  | if (Mode == CheckingMode::Required) { | 
|  | EXPECT_TRUE(ReadedSpecifiedName); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | class LoadSpecLazilyConsumer : public ASTConsumer { | 
|  | DeclsReaderListener Listener; | 
|  |  | 
|  | public: | 
|  | LoadSpecLazilyConsumer(StringRef SpecifiedName, CheckingMode Mode) | 
|  | : Listener(SpecifiedName, Mode) {} | 
|  |  | 
|  | ASTDeserializationListener *GetASTDeserializationListener() override { | 
|  | return &Listener; | 
|  | } | 
|  | }; | 
|  |  | 
|  | class CheckLoadSpecLazilyAction : public ASTFrontendAction { | 
|  | StringRef SpecifiedName; | 
|  | CheckingMode Mode; | 
|  |  | 
|  | public: | 
|  | std::unique_ptr<ASTConsumer> | 
|  | CreateASTConsumer(CompilerInstance &CI, StringRef /*Unused*/) override { | 
|  | return std::make_unique<LoadSpecLazilyConsumer>(SpecifiedName, Mode); | 
|  | } | 
|  |  | 
|  | CheckLoadSpecLazilyAction(StringRef SpecifiedName, CheckingMode Mode) | 
|  | : SpecifiedName(SpecifiedName), Mode(Mode) {} | 
|  | }; | 
|  |  | 
|  | TEST_F(LoadSpecLazilyTest, BasicTest) { | 
|  | GenerateModuleInterface("M", R"cpp( | 
|  | export module M; | 
|  | export template <class T> | 
|  | class A {}; | 
|  | export class ShouldNotBeLoaded {}; | 
|  | export class Temp { | 
|  | A<ShouldNotBeLoaded> AS; | 
|  | }; | 
|  | )cpp"); | 
|  |  | 
|  | const char *test_file_contents = R"cpp( | 
|  | import M; | 
|  | A<int> a; | 
|  | )cpp"; | 
|  | std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); | 
|  | EXPECT_TRUE( | 
|  | runToolOnCodeWithArgs(std::make_unique<CheckLoadSpecLazilyAction>( | 
|  | "ShouldNotBeLoaded", CheckingMode::Forbidden), | 
|  | test_file_contents, | 
|  | { | 
|  | "-std=c++20", | 
|  | DepArg.c_str(), | 
|  | "-I", | 
|  | TestDir.c_str(), | 
|  | }, | 
|  | "test.cpp")); | 
|  | } | 
|  |  | 
|  | TEST_F(LoadSpecLazilyTest, ChainedTest) { | 
|  | GenerateModuleInterface("M", R"cpp( | 
|  | export module M; | 
|  | export template <class T> | 
|  | class A {}; | 
|  | )cpp"); | 
|  |  | 
|  | GenerateModuleInterface("N", R"cpp( | 
|  | export module N; | 
|  | export import M; | 
|  | export class ShouldNotBeLoaded {}; | 
|  | export class Temp { | 
|  | A<ShouldNotBeLoaded> AS; | 
|  | }; | 
|  | )cpp"); | 
|  |  | 
|  | const char *test_file_contents = R"cpp( | 
|  | import N; | 
|  | A<int> a; | 
|  | )cpp"; | 
|  | std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); | 
|  | EXPECT_TRUE( | 
|  | runToolOnCodeWithArgs(std::make_unique<CheckLoadSpecLazilyAction>( | 
|  | "ShouldNotBeLoaded", CheckingMode::Forbidden), | 
|  | test_file_contents, | 
|  | { | 
|  | "-std=c++20", | 
|  | DepArg.c_str(), | 
|  | "-I", | 
|  | TestDir.c_str(), | 
|  | }, | 
|  | "test.cpp")); | 
|  | } | 
|  |  | 
|  | /// Test that we won't crash due to we may invalidate the lazy specialization | 
|  | /// lookup table during the loading process. | 
|  | TEST_F(LoadSpecLazilyTest, ChainedTest2) { | 
|  | GenerateModuleInterface("M", R"cpp( | 
|  | export module M; | 
|  | export template <class T> | 
|  | class A {}; | 
|  |  | 
|  | export class B {}; | 
|  |  | 
|  | export class C { | 
|  | A<B> D; | 
|  | }; | 
|  | )cpp"); | 
|  |  | 
|  | GenerateModuleInterface("N", R"cpp( | 
|  | export module N; | 
|  | export import M; | 
|  | export class MayBeLoaded {}; | 
|  |  | 
|  | export class Temp { | 
|  | A<MayBeLoaded> AS; | 
|  | }; | 
|  |  | 
|  | export class ExportedClass {}; | 
|  |  | 
|  | export template<> class A<ExportedClass> { | 
|  | A<MayBeLoaded> AS; | 
|  | A<B>           AB; | 
|  | }; | 
|  | )cpp"); | 
|  |  | 
|  | const char *test_file_contents = R"cpp( | 
|  | import N; | 
|  | Temp T; | 
|  | A<ExportedClass> a; | 
|  | )cpp"; | 
|  | std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); | 
|  | EXPECT_TRUE(runToolOnCodeWithArgs(std::make_unique<CheckLoadSpecLazilyAction>( | 
|  | "MayBeLoaded", CheckingMode::Required), | 
|  | test_file_contents, | 
|  | { | 
|  | "-std=c++20", | 
|  | DepArg.c_str(), | 
|  | "-I", | 
|  | TestDir.c_str(), | 
|  | }, | 
|  | "test.cpp")); | 
|  | } | 
|  |  | 
|  | } // namespace |