| //== 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(); |
| IntrusiveRefCntPtr<DiagnosticsEngine> 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<CompilerInvocation> 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<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 |