| //===- unittest/AST/ExternalASTSourceTest.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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file contains tests for Clang's ExternalASTSource. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/AST/ExternalASTSource.h" |
| #include "clang/AST/ASTConsumer.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/DeclTemplate.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "clang/Frontend/CompilerInvocation.h" |
| #include "clang/Frontend/FrontendActions.h" |
| #include "clang/Lex/PreprocessorOptions.h" |
| #include "clang/Tooling/Tooling.h" |
| #include "llvm/Support/VirtualFileSystem.h" |
| #include "gtest/gtest.h" |
| |
| using namespace clang; |
| using namespace llvm; |
| |
| struct TestExternalASTSource : public ExternalASTSource { |
| virtual void setupTestAST(ASTContext &Ctx) { |
| Ctx.getTranslationUnitDecl()->setHasExternalVisibleStorage(); |
| } |
| }; |
| |
| class TestFrontendAction : public ASTFrontendAction { |
| public: |
| TestFrontendAction(IntrusiveRefCntPtr<TestExternalASTSource> Source) |
| : Source(std::move(Source)) {} |
| |
| private: |
| void ExecuteAction() override { |
| ASTContext &Ctx = getCompilerInstance().getASTContext(); |
| Ctx.setExternalSource(Source); |
| Source->setupTestAST(Ctx); |
| return ASTFrontendAction::ExecuteAction(); |
| } |
| |
| std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, |
| StringRef InFile) override { |
| return std::make_unique<ASTConsumer>(); |
| } |
| |
| IntrusiveRefCntPtr<TestExternalASTSource> Source; |
| }; |
| |
| bool testExternalASTSource( |
| llvm::IntrusiveRefCntPtr<TestExternalASTSource> Source, |
| StringRef FileContents) { |
| |
| auto Invocation = std::make_shared<CompilerInvocation>(); |
| Invocation->getPreprocessorOpts().addRemappedFile( |
| "test.cc", MemoryBuffer::getMemBuffer(FileContents).release()); |
| const char *Args[] = { "test.cc" }; |
| |
| DiagnosticOptions InvocationDiagOpts; |
| auto InvocationDiags = CompilerInstance::createDiagnostics( |
| *llvm::vfs::getRealFileSystem(), InvocationDiagOpts); |
| CompilerInvocation::CreateFromArgs(*Invocation, Args, *InvocationDiags); |
| |
| CompilerInstance Compiler(std::move(Invocation)); |
| Compiler.setVirtualFileSystem(llvm::vfs::getRealFileSystem()); |
| Compiler.createDiagnostics(); |
| |
| TestFrontendAction Action(Source); |
| return Compiler.ExecuteAction(Action); |
| } |
| |
| // Ensure that a failed name lookup into an external source only occurs once. |
| TEST(ExternalASTSourceTest, FailedLookupOccursOnce) { |
| struct TestSource : TestExternalASTSource { |
| TestSource(unsigned &Calls) : Calls(Calls) {} |
| |
| bool |
| FindExternalVisibleDeclsByName(const DeclContext *, DeclarationName Name, |
| const DeclContext *OriginalDC) override { |
| if (Name.getAsString() == "j") |
| ++Calls; |
| return false; |
| } |
| |
| unsigned &Calls; |
| }; |
| |
| unsigned Calls = 0; |
| ASSERT_TRUE(testExternalASTSource( |
| llvm::makeIntrusiveRefCnt<TestSource>(Calls), "int j, k = j;")); |
| EXPECT_EQ(1u, Calls); |
| } |
| |
| namespace { |
| |
| /// An external source which announces, without definitions, |
| /// |
| /// template <typename T> struct A; // primary pattern |
| /// template <typename T> struct A<T *>; // partial specialization pattern |
| /// |
| /// and supplies the definition of whichever pattern it is asked to complete. |
| struct LazyTemplatePatterns : TestExternalASTSource { |
| CXXRecordDecl *Primary = nullptr; |
| ClassTemplatePartialSpecializationDecl *Partial = nullptr; |
| unsigned PrimaryCompletions = 0; |
| unsigned PartialCompletions = 0; |
| |
| void setupTestAST(ASTContext &Ctx) override { |
| TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl(); |
| IdentifierInfo &AName = Ctx.Idents.get("A"); |
| |
| auto MakeParams = [&] { |
| auto *Param = TemplateTypeParmDecl::Create( |
| Ctx, TU, {}, {}, /*D=*/0, /*P=*/0, &Ctx.Idents.get("T"), |
| /*Typename=*/true, /*ParameterPack=*/false); |
| return TemplateParameterList::Create(Ctx, {}, {}, {Param}, {}, nullptr); |
| }; |
| |
| // template <typename T> struct A; |
| Primary = CXXRecordDecl::Create(Ctx, TagDecl::TagKind::Struct, TU, {}, {}, |
| &AName); |
| auto *Template = ClassTemplateDecl::Create( |
| Ctx, TU, {}, DeclarationName(&AName), MakeParams(), Primary); |
| Primary->setDescribedClassTemplate(Template); |
| Primary->setHasExternalLexicalStorage(); |
| TU->addDecl(Template); |
| |
| // template <typename T> struct A<T *>; |
| TemplateParameterList *PartialParams = MakeParams(); |
| TemplateArgument Arg(Ctx.getPointerType(Ctx.getTemplateTypeParmType( |
| /*Depth=*/0, /*Index=*/0, /*ParameterPack=*/false, |
| cast<TemplateTypeParmDecl>(PartialParams->getParam(0))))); |
| Partial = ClassTemplatePartialSpecializationDecl::Create( |
| Ctx, TagDecl::TagKind::Struct, TU, {}, {}, PartialParams, Template, Arg, |
| Ctx.getCanonicalType(Ctx.getTemplateSpecializationType( |
| ElaboratedTypeKeyword::Struct, TemplateName(Template), Arg, {})), |
| nullptr); |
| Partial->setHasExternalLexicalStorage(); |
| |
| // Deduction against a partial specialization reads its arguments as |
| // written, so they must be supplied even though nothing was written. |
| TemplateArgumentListInfo ArgsInfo; |
| ArgsInfo.addArgument(TemplateArgumentLoc( |
| Arg, Ctx.getTrivialTypeSourceInfo(Arg.getAsType()))); |
| Partial->setTemplateArgsAsWritten( |
| ASTTemplateArgumentListInfo::Create(Ctx, ArgsInfo)); |
| |
| TU->addDecl(Partial); |
| Template->AddPartialSpecialization(Partial, {}); |
| } |
| |
| void CompleteType(TagDecl *Tag) override { |
| auto *Record = dyn_cast<CXXRecordDecl>(Tag); |
| if (!Record || Record->isCompleteDefinition()) |
| return; |
| if (Record == Primary) |
| ++PrimaryCompletions; |
| else if (Record == Partial) |
| ++PartialCompletions; |
| else |
| return; |
| Record->setHasExternalLexicalStorage(false); |
| Record->startDefinition(); |
| Record->completeDefinition(); |
| } |
| }; |
| |
| } // namespace |
| |
| // An instantiation must be able to complete the pattern it is actually built |
| // from, whichever of the two that is, and in either order. |
| TEST(ExternalASTSourceTest, CompletesPatternInEitherOrder) { |
| for (StringRef Code : {"A<int> a; A<int *> b;", "A<int *> b; A<int> a;"}) { |
| auto Source = llvm::makeIntrusiveRefCnt<LazyTemplatePatterns>(); |
| ASSERT_TRUE(testExternalASTSource(Source, Code)) << Code; |
| EXPECT_EQ(1u, Source->PrimaryCompletions) << Code; |
| EXPECT_EQ(1u, Source->PartialCompletions) << Code; |
| } |
| } |
| |
| namespace { |
| // incrementGeneration is protected, so reaching it needs a source of our own. |
| struct BumpableSource : ExternalASTSource { |
| uint32_t bump(ASTContext &C) { return incrementGeneration(C); } |
| }; |
| } // namespace |
| |
| // Multiple external sources synchronize their generations, and the return |
| // values of incrementGeneration() and getGeneration() are as expected. This |
| // is relied upon by ASTReader and LazyGenerationalUpdatePtr. |
| TEST(ExternalASTSourceTest, IncrementGeneration) { |
| std::unique_ptr<ASTUnit> AST = tooling::buildASTFromCode(""); |
| ASSERT_TRUE(AST != nullptr); |
| ASTContext &Ctx = AST->getASTContext(); |
| |
| auto Topmost = llvm::makeIntrusiveRefCnt<BumpableSource>(); |
| auto Secondary = llvm::makeIntrusiveRefCnt<BumpableSource>(); |
| Ctx.setExternalSource(Topmost); |
| ASSERT_EQ(Ctx.getExternalSource(), Topmost.get()); |
| |
| // The generation starts at the same value for the two sources. |
| ASSERT_EQ(Topmost->getGeneration(), 0u); |
| ASSERT_EQ(Secondary->getGeneration(), 0u); |
| |
| // Bumping the "secondary" source bumps the toplevel one, and leaves the two |
| // agreeing on the generation afterwards. |
| EXPECT_EQ(Secondary->bump(Ctx), 0u); |
| EXPECT_EQ(Secondary->getGeneration(), 1u); |
| EXPECT_EQ(Topmost->getGeneration(), 1u); |
| |
| // Bumping the topmost source increments its generation by one. |
| EXPECT_EQ(Topmost->bump(Ctx), 1u); |
| EXPECT_EQ(Topmost->getGeneration(), 2u); |
| // At this point, the "secondary" source is out-of-sync... |
| EXPECT_EQ(Secondary->getGeneration(), 1u); |
| |
| // Another bump synchronizes the two sources. |
| EXPECT_EQ(Secondary->bump(Ctx), 2u); |
| EXPECT_EQ(Secondary->getGeneration(), 3u); |
| EXPECT_EQ(Topmost->getGeneration(), 3u); |
| } |