//== unittests/Serialization/NamespaceLookupOptimizationTest.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/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Parse/ParseAST.h" #include "clang/Serialization/ASTReader.h" #include "clang/Tooling/Tooling.h" #include "gtest/gtest.h" using namespace llvm; using namespace clang; using namespace clang::tooling; namespace { class NamespaceLookupTest : public ::testing::Test { void SetUp() override { ASSERT_FALSE( sys::fs::createUniqueDirectory("namespace-lookup-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(); DiagnosticOptions DiagOpts; IntrusiveRefCntPtr 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 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; } }; struct NamespaceLookupResult { int NumLocalNamespaces = 0; int NumExternalNamespaces = 0; }; class NamespaceLookupConsumer : public ASTConsumer { NamespaceLookupResult &Result; public: explicit NamespaceLookupConsumer(NamespaceLookupResult &Result) : Result(Result) {} void HandleTranslationUnit(ASTContext &Context) override { TranslationUnitDecl *TU = Context.getTranslationUnitDecl(); ASSERT_TRUE(TU); ASTReader *Chain = dyn_cast_or_null(Context.getExternalSource()); ASSERT_TRUE(Chain); for (const Decl *D : TU->lookup(DeclarationName(&Context.Idents.get("N")))) { if (!isa(D)) continue; if (!D->isFromASTFile()) { ++Result.NumLocalNamespaces; } else { ++Result.NumExternalNamespaces; EXPECT_EQ(D, Chain->getKeyDeclaration(D)); } } } }; class NamespaceLookupAction : public ASTFrontendAction { NamespaceLookupResult &Result; public: explicit NamespaceLookupAction(NamespaceLookupResult &Result) : Result(Result) {} std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef /*Unused*/) override { return std::make_unique(Result); } }; TEST_F(NamespaceLookupTest, ExternalNamespacesOnly) { GenerateModuleInterface("M1", R"cpp( export module M1; namespace N {} )cpp"); GenerateModuleInterface("M2", R"cpp( export module M2; namespace N {} )cpp"); GenerateModuleInterface("M3", R"cpp( export module M3; namespace N {} )cpp"); const char *test_file_contents = R"cpp( import M1; import M2; import M3; )cpp"; std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); NamespaceLookupResult Result; EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(Result), test_file_contents, { "-std=c++20", DepArg.c_str(), "-I", TestDir.c_str(), }, "main.cpp")); EXPECT_EQ(0, Result.NumLocalNamespaces); EXPECT_EQ(1, Result.NumExternalNamespaces); } TEST_F(NamespaceLookupTest, ExternalReplacedByLocal) { GenerateModuleInterface("M1", R"cpp( export module M1; namespace N {} )cpp"); GenerateModuleInterface("M2", R"cpp( export module M2; namespace N {} )cpp"); GenerateModuleInterface("M3", R"cpp( export module M3; namespace N {} )cpp"); const char *test_file_contents = R"cpp( import M1; import M2; import M3; namespace N {} )cpp"; std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); NamespaceLookupResult Result; EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(Result), test_file_contents, { "-std=c++20", DepArg.c_str(), "-I", TestDir.c_str(), }, "main.cpp")); EXPECT_EQ(1, Result.NumLocalNamespaces); EXPECT_EQ(0, Result.NumExternalNamespaces); } TEST_F(NamespaceLookupTest, LocalAndExternalInterleaved) { GenerateModuleInterface("M1", R"cpp( export module M1; namespace N {} )cpp"); GenerateModuleInterface("M2", R"cpp( export module M2; namespace N {} )cpp"); GenerateModuleInterface("M3", R"cpp( export module M3; namespace N {} )cpp"); const char *test_file_contents = R"cpp( import M1; namespace N {} import M2; import M3; )cpp"; std::string DepArg = "-fprebuilt-module-path=" + TestDir.str().str(); NamespaceLookupResult Result; EXPECT_TRUE(runToolOnCodeWithArgs( std::make_unique(Result), test_file_contents, { "-std=c++20", DepArg.c_str(), "-I", TestDir.c_str(), }, "main.cpp")); EXPECT_EQ(1, Result.NumLocalNamespaces); EXPECT_EQ(1, Result.NumExternalNamespaces); } } // namespace