//== 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 VFS = llvm::vfs::createPhysicalFileSystem(); IntrusiveRefCntPtr Diags = CompilerInstance::createDiagnostics(*VFS, new DiagnosticOptions()); 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 Invocation = createInvocation(Args, CIOpts); EXPECT_TRUE(Invocation); CompilerInstance Instance; Instance.setDiagnostics(Diags.get()); Instance.setInvocation(Invocation); 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(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 CreateASTConsumer(CompilerInstance &CI, StringRef /*Unused*/) override { return std::make_unique(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 A {}; export class ShouldNotBeLoaded {}; export class Temp { A AS; }; )cpp"); const char *test_file_contents = R"cpp( import M; A a; )cpp"; std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); EXPECT_TRUE( runToolOnCodeWithArgs(std::make_unique( "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 A {}; )cpp"); GenerateModuleInterface("N", R"cpp( export module N; export import M; export class ShouldNotBeLoaded {}; export class Temp { A AS; }; )cpp"); const char *test_file_contents = R"cpp( import N; A a; )cpp"; std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); EXPECT_TRUE( runToolOnCodeWithArgs(std::make_unique( "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 A {}; export class B {}; export class C { A D; }; )cpp"); GenerateModuleInterface("N", R"cpp( export module N; export import M; export class MayBeLoaded {}; export class Temp { A AS; }; export class ExportedClass {}; export template<> class A { A AS; A AB; }; )cpp"); const char *test_file_contents = R"cpp( import N; Temp T; A a; )cpp"; std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); EXPECT_TRUE(runToolOnCodeWithArgs(std::make_unique( "MayBeLoaded", CheckingMode::Required), test_file_contents, { "-std=c++20", DepArg.c_str(), "-I", TestDir.c_str(), }, "test.cpp")); } } // namespace