From: Peter Szecsi Date: Fri, 30 Mar 2018 22:03:29 +0000 (+0000) Subject: [ASTImporter] Add test helper Fixture X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4b384260348361ff9f6df8f5483b28771f87d27b;p=clang [ASTImporter] Add test helper Fixture Add a helper test Fixture, so we can add tests which can check internal attributes of AST nodes like getPreviousDecl(), isVirtual(), etc. This enables us to check if a redeclaration chain is correctly built during import, if the virtual flag is preserved during import, etc. We cannot check such attributes with the existing testImport. Also, this fixture makes it possible to import from several "From" contexts. We also added several test cases here, some of them are disabled. We plan to pass the disabled tests in other patches. Patch by Gabor Marton! Differential Revision: https://reviews.llvm.org/D43967 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@328906 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/unittests/AST/ASTImporterTest.cpp b/unittests/AST/ASTImporterTest.cpp index feadd94dde..ca024b17d3 100644 --- a/unittests/AST/ASTImporterTest.cpp +++ b/unittests/AST/ASTImporterTest.cpp @@ -17,6 +17,8 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" + +#include "DeclMatcher.h" #include "gtest/gtest.h" namespace clang { @@ -29,7 +31,7 @@ static bool isCXX(Language Lang) { return Lang == Lang_CXX || Lang == Lang_CXX11; } -static RunOptions getRunOptionsForLanguage(Language Lang) { +static ArgVector getBasicRunOptionsForLanguage(Language Lang) { ArgVector BasicArgs; // Test with basic arguments. switch (Lang) { @@ -49,6 +51,11 @@ static RunOptions getRunOptionsForLanguage(Language Lang) { case Lang_OBJCXX: llvm_unreachable("Not implemented yet!"); } + return BasicArgs; +} + +static RunOptions getRunOptionsForLanguage(Language Lang) { + ArgVector BasicArgs = getBasicRunOptionsForLanguage(Lang); // For C++, test with "-fdelayed-template-parsing" enabled to handle MSVC // default behaviour. @@ -61,6 +68,19 @@ static RunOptions getRunOptionsForLanguage(Language Lang) { return {BasicArgs}; } +// Creates a virtual file and assigns that to the context of given AST. If the +// file already exists then the file will not be created again as a duplicate. +static void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName, + const std::string &Code) { + assert(ToAST); + ASTContext &ToCtx = ToAST->getASTContext(); + auto *OFS = static_cast( + ToCtx.getSourceManager().getFileManager().getVirtualFileSystem().get()); + auto *MFS = + static_cast(OFS->overlays_begin()->get()); + MFS->addFile(FileName, 0, llvm::MemoryBuffer::getMemBuffer(Code.c_str())); +} + template testing::AssertionResult testImport(const std::string &FromCode, const ArgVector &FromArgs, @@ -79,11 +99,7 @@ testImport(const std::string &FromCode, const ArgVector &FromArgs, // Add input.cc to virtual file system so importer can 'find' it // while importing SourceLocations. - vfs::OverlayFileSystem *OFS = static_cast( - ToCtx.getSourceManager().getFileManager().getVirtualFileSystem().get()); - vfs::InMemoryFileSystem *MFS = static_cast( - OFS->overlays_begin()->get()); - MFS->addFile(InputFileName, 0, llvm::MemoryBuffer::getMemBuffer(FromCode)); + createVirtualFileIfNeeded(ToAST.get(), InputFileName, FromCode); ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx, FromAST->getFileManager(), false); @@ -133,6 +149,165 @@ void testImport(const std::string &FromCode, Language FromLang, Verifier, AMatcher)); } +const StringRef DeclToImportID = "declToImport"; + +// This class provides generic methods to write tests which can check internal +// attributes of AST nodes like getPreviousDecl(), isVirtual(), etc. Also, +// this fixture makes it possible to import from several "From" contexts. +class ASTImporterTestBase : public ::testing::TestWithParam { + + const char *const InputFileName = "input.cc"; + const char *const OutputFileName = "output.cc"; + + // Buffer for the To context, must live in the test scope. + std::string ToCode; + + struct TU { + // Buffer for the context, must live in the test scope. + std::string Code; + std::string FileName; + std::unique_ptr Unit; + TranslationUnitDecl *TUDecl = nullptr; + TU(StringRef Code, StringRef FileName, ArgVector Args) + : Code(Code), FileName(FileName), + Unit(tooling::buildASTFromCodeWithArgs(this->Code, Args, + this->FileName)), + TUDecl(Unit->getASTContext().getTranslationUnitDecl()) {} + }; + + // We may have several From contexts and related translation units. In each + // AST, the buffers for the source are handled via references and are set + // during the creation of the AST. These references must point to a valid + // buffer until the AST is alive. Thus, we must use a list in order to avoid + // moving of the stored objects because that would mean breaking the + // references in the AST. By using a vector a move could happen when the + // vector is expanding, with the list we won't have these issues. + std::list FromTUs; + +public: + // We may have several From context but only one To context. + std::unique_ptr ToAST; + + // Returns the argument vector used for a specific language, this set + // can be tweaked by the test parameters. + ArgVector getArgVectorForLanguage(Language Lang) { + ArgVector Args = getBasicRunOptionsForLanguage(Lang); + ArgVector ExtraArgs = GetParam(); + for (const auto& Arg : ExtraArgs) { + Args.push_back(Arg); + } + return Args; + } + + // Creates an AST both for the From and To source code and imports the Decl + // of the identifier into the To context. + // Must not be called more than once within the same test. + std::tuple + getImportedDecl(StringRef FromSrcCode, Language FromLang, StringRef ToSrcCode, + Language ToLang, StringRef Identifier = DeclToImportID) { + ArgVector FromArgs = getArgVectorForLanguage(FromLang), + ToArgs = getArgVectorForLanguage(ToLang); + + FromTUs.emplace_back(FromSrcCode, InputFileName, FromArgs); + TU &FromTU = FromTUs.back(); + + ToCode = ToSrcCode; + assert(!ToAST); + ToAST = tooling::buildASTFromCodeWithArgs(ToCode, ToArgs, OutputFileName); + + ASTContext &FromCtx = FromTU.Unit->getASTContext(), + &ToCtx = ToAST->getASTContext(); + + createVirtualFileIfNeeded(ToAST.get(), InputFileName, FromTU.Code); + + ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx, + FromTU.Unit->getFileManager(), false); + + IdentifierInfo *ImportedII = &FromCtx.Idents.get(Identifier); + assert(ImportedII && "Declaration with the given identifier " + "should be specified in test!"); + DeclarationName ImportDeclName(ImportedII); + SmallVector FoundDecls; + FromCtx.getTranslationUnitDecl()->localUncachedLookup(ImportDeclName, + FoundDecls); + + assert(FoundDecls.size() == 1); + + Decl *Imported = Importer.Import(FoundDecls.front()); + assert(Imported); + return std::make_tuple(*FoundDecls.begin(), Imported); + } + + // Creates a TU decl for the given source code. + // May be called several times in a given test. + TranslationUnitDecl *getTuDecl(StringRef SrcCode, Language Lang, + StringRef FileName = "input.cc") { + assert( + std::find_if(FromTUs.begin(), FromTUs.end(), [FileName](const TU &E) { + return E.FileName == FileName; + }) == FromTUs.end()); + + ArgVector Args = getArgVectorForLanguage(Lang); + FromTUs.emplace_back(SrcCode, FileName, Args); + TU &Tu = FromTUs.back(); + + return Tu.TUDecl; + } + + // Import the given Decl into the ToCtx. + // May be called several times in a given test. + // The different instances of the param From may have different ASTContext. + Decl *Import(Decl *From, Language ToLang) { + if (!ToAST) { + ArgVector ToArgs = getArgVectorForLanguage(ToLang); + // Build the AST from an empty file. + ToAST = + tooling::buildASTFromCodeWithArgs(/*Code=*/"", ToArgs, "empty.cc"); + } + + // Create a virtual file in the To Ctx which corresponds to the file from + // which we want to import the `From` Decl. Without this source locations + // will be invalid in the ToCtx. + auto It = std::find_if(FromTUs.begin(), FromTUs.end(), [From](const TU &E) { + return E.TUDecl == From->getTranslationUnitDecl(); + }); + assert(It != FromTUs.end()); + createVirtualFileIfNeeded(ToAST.get(), It->FileName, It->Code); + + ASTContext &FromCtx = From->getASTContext(), + &ToCtx = ToAST->getASTContext(); + ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx, + FromCtx.getSourceManager().getFileManager(), false); + return Importer.Import(From); + } + + ~ASTImporterTestBase() { + if (!::testing::Test::HasFailure()) return; + + for (auto &Tu : FromTUs) { + assert(Tu.Unit); + llvm::errs() << "FromAST:\n"; + Tu.Unit->getASTContext().getTranslationUnitDecl()->dump(); + llvm::errs() << "\n"; + } + if (ToAST) { + llvm::errs() << "ToAST:\n"; + ToAST->getASTContext().getTranslationUnitDecl()->dump(); + } + } +}; + +AST_MATCHER_P(RecordDecl, hasFieldOrder, std::vector, Order) { + size_t Index = 0; + for (FieldDecl *Field : Node.fields()) { + if (Index == Order.size()) + return false; + if (Field->getName() != Order[Index]) + return false; + ++Index; + } + return Index == Order.size(); +} TEST(ImportExpr, ImportStringLiteral) { MatchVerifier Verifier; @@ -816,5 +991,517 @@ TEST(ImportExpr, DependentSizedArrayType) { has(fieldDecl(hasType(dependentSizedArrayType()))))))); } +TEST_P(ASTImporterTestBase, DISABLED_ImportFunctionWithBackReferringParameter) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template struct X {}; + + void declToImport(int y, X &x) {} + + template <> struct X { + void g() { + X x; + declToImport(0, x); + } + }; + )", + Lang_CXX, "", Lang_CXX); + + MatchVerifier Verifier; + auto Matcher = functionDecl(hasName("declToImport"), + parameterCountIs(2), + hasParameter(0, hasName("y")), + hasParameter(1, hasName("x")), + hasParameter(1, hasType(asString("X &")))); + ASSERT_TRUE(Verifier.match(From, Matcher)); + EXPECT_TRUE(Verifier.match(To, Matcher)); +} + +TEST_P(ASTImporterTestBase, + TUshouldNotContainTemplatedDeclOfFunctionTemplates) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl("template void declToImport() { T a = 1; }" + "void instantiate() { declToImport(); }", + Lang_CXX, "", Lang_CXX); + + auto Check = [](Decl *D) -> bool { + auto TU = D->getTranslationUnitDecl(); + for (auto Child : TU->decls()) { + if (auto *FD = dyn_cast(Child)) { + if (FD->getNameAsString() == "declToImport") { + GTEST_NONFATAL_FAILURE_( + "TU should not contain any FunctionDecl with name declToImport"); + return false; + } + } + } + return true; + }; + + ASSERT_TRUE(Check(From)); + EXPECT_TRUE(Check(To)); +} + +TEST_P(ASTImporterTestBase, TUshouldNotContainTemplatedDeclOfClassTemplates) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl("template struct declToImport { T t; };" + "void instantiate() { declToImport(); }", + Lang_CXX, "", Lang_CXX); + + auto Check = [](Decl *D) -> bool { + auto TU = D->getTranslationUnitDecl(); + for (auto Child : TU->decls()) { + if (auto *RD = dyn_cast(Child)) { + if (RD->getNameAsString() == "declToImport") { + GTEST_NONFATAL_FAILURE_( + "TU should not contain any CXXRecordDecl with name declToImport"); + return false; + } + } + } + return true; + }; + + ASSERT_TRUE(Check(From)); + EXPECT_TRUE(Check(To)); +} + +TEST_P(ASTImporterTestBase, TUshouldNotContainTemplatedDeclOfTypeAlias) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl( + "template struct X {};" + "template using declToImport = X;" + "void instantiate() { declToImport a; }", + Lang_CXX11, "", Lang_CXX11); + + auto Check = [](Decl *D) -> bool { + auto TU = D->getTranslationUnitDecl(); + for (auto Child : TU->decls()) { + if (auto *AD = dyn_cast(Child)) { + if (AD->getNameAsString() == "declToImport") { + GTEST_NONFATAL_FAILURE_( + "TU should not contain any TypeAliasDecl with name declToImport"); + return false; + } + } + } + return true; + }; + + ASSERT_TRUE(Check(From)); + EXPECT_TRUE(Check(To)); +} + +TEST_P( + ASTImporterTestBase, + DISABLED_TUshouldNotContainClassTemplateSpecializationOfImplicitInstantiation) { + + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template + class Base {}; + class declToImport : public Base {}; + )", + Lang_CXX, "", Lang_CXX); + + // Check that the ClassTemplateSpecializationDecl is NOT the child of the TU. + auto Pattern = + translationUnitDecl(unless(has(classTemplateSpecializationDecl()))); + ASSERT_TRUE( + MatchVerifier{}.match(From->getTranslationUnitDecl(), Pattern)); + EXPECT_TRUE( + MatchVerifier{}.match(To->getTranslationUnitDecl(), Pattern)); + + // Check that the ClassTemplateSpecializationDecl is the child of the + // ClassTemplateDecl. + Pattern = translationUnitDecl(has(classTemplateDecl( + hasName("Base"), has(classTemplateSpecializationDecl())))); + ASSERT_TRUE( + MatchVerifier{}.match(From->getTranslationUnitDecl(), Pattern)); + EXPECT_TRUE( + MatchVerifier{}.match(To->getTranslationUnitDecl(), Pattern)); +} + +TEST_P(ASTImporterTestBase, + TUshouldContainClassTemplateSpecializationOfExplicitInstantiation) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + namespace NS { + template + class X {}; + template class X; + } + )", + Lang_CXX, "", Lang_CXX, "NS"); + + // Check that the ClassTemplateSpecializationDecl is NOT the child of the + // ClassTemplateDecl. + auto Pattern = namespaceDecl(has(classTemplateDecl( + hasName("X"), unless(has(classTemplateSpecializationDecl()))))); + ASSERT_TRUE(MatchVerifier{}.match(From, Pattern)); + EXPECT_TRUE(MatchVerifier{}.match(To, Pattern)); + + // Check that the ClassTemplateSpecializationDecl is the child of the + // NamespaceDecl. + Pattern = namespaceDecl(has(classTemplateSpecializationDecl(hasName("X")))); + ASSERT_TRUE(MatchVerifier{}.match(From, Pattern)); + EXPECT_TRUE(MatchVerifier{}.match(To, Pattern)); +} + +TEST_P(ASTImporterTestBase, CXXRecordDeclFieldsShouldBeInCorrectOrder) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl( + "struct declToImport { int a; int b; };", + Lang_CXX11, "", Lang_CXX11); + + MatchVerifier Verifier; + ASSERT_TRUE(Verifier.match(From, cxxRecordDecl(hasFieldOrder({"a", "b"})))); + EXPECT_TRUE(Verifier.match(To, cxxRecordDecl(hasFieldOrder({"a", "b"})))); +} + +TEST_P(ASTImporterTestBase, + DISABLED_CXXRecordDeclFieldOrderShouldNotDependOnImportOrder) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + // The original recursive algorithm of ASTImporter first imports 'c' then + // 'b' and lastly 'a'. Therefore we must restore the order somehow. + R"s( + struct declToImport { + int a = c + b; + int b = 1; + int c = 2; + }; + )s", + Lang_CXX11, "", Lang_CXX11); + + MatchVerifier Verifier; + ASSERT_TRUE( + Verifier.match(From, cxxRecordDecl(hasFieldOrder({"a", "b", "c"})))); + EXPECT_TRUE( + Verifier.match(To, cxxRecordDecl(hasFieldOrder({"a", "b", "c"})))); +} + +TEST_P(ASTImporterTestBase, DISABLED_ShouldImportImplicitCXXRecordDecl) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template + struct declToImport { + }; + )", + Lang_CXX, "", Lang_CXX); + + MatchVerifier Verifier; + // Match the implicit Decl. + auto Matcher = classTemplateDecl(has(cxxRecordDecl(has(cxxRecordDecl())))); + ASSERT_TRUE(Verifier.match(From, Matcher)); + EXPECT_TRUE(Verifier.match(To, Matcher)); +} + +TEST_P( + ASTImporterTestBase, + DISABLED_ShouldImportImplicitCXXRecordDeclOfClassTemplateSpecializationDecl) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template + class Base {}; + class declToImport : public Base {}; + )", + Lang_CXX, "", Lang_CXX); + + auto hasImplicitClass = has(cxxRecordDecl()); + auto Pattern = translationUnitDecl(has(classTemplateDecl( + hasName("Base"), + has(classTemplateSpecializationDecl(hasImplicitClass))))); + ASSERT_TRUE( + MatchVerifier{}.match(From->getTranslationUnitDecl(), Pattern)); + EXPECT_TRUE( + MatchVerifier{}.match(To->getTranslationUnitDecl(), Pattern)); +} + +TEST_P(ASTImporterTestBase, IDNSOrdinary) { + Decl *From, *To; + std::tie(From, To) = + getImportedDecl("void declToImport() {}", Lang_CXX, "", Lang_CXX); + + MatchVerifier Verifier; + auto Matcher = functionDecl(); + ASSERT_TRUE(Verifier.match(From, Matcher)); + EXPECT_TRUE(Verifier.match(To, Matcher)); + EXPECT_EQ(From->getIdentifierNamespace(), To->getIdentifierNamespace()); +} + +TEST_P(ASTImporterTestBase, DISABLED_IDNSOfNonmemberOperator) { + Decl *FromTU = getTuDecl( + R"( + struct X {}; + void operator<<(int, X); + )", + Lang_CXX); + Decl *From = LastDeclMatcher{}.match(FromTU, functionDecl()); + const Decl *To = Import(From, Lang_CXX); + EXPECT_EQ(From->getIdentifierNamespace(), To->getIdentifierNamespace()); +} + +TEST_P(ASTImporterTestBase, + ShouldImportMembersOfClassTemplateSpecializationDecl) { + Decl *From, *To; + std::tie(From, To) = getImportedDecl( + R"( + template + class Base { int a; }; + class declToImport : Base {}; + )", + Lang_CXX, "", Lang_CXX); + + auto Pattern = translationUnitDecl(has(classTemplateDecl( + hasName("Base"), + has(classTemplateSpecializationDecl(has(fieldDecl(hasName("a")))))))); + ASSERT_TRUE( + MatchVerifier{}.match(From->getTranslationUnitDecl(), Pattern)); + EXPECT_TRUE( + MatchVerifier{}.match(To->getTranslationUnitDecl(), Pattern)); +} + +INSTANTIATE_TEST_CASE_P( + ParameterizedTests, ASTImporterTestBase, + ::testing::Values(ArgVector(), ArgVector{"-fdelayed-template-parsing"}),); + +struct ImportFunctions : ASTImporterTestBase {}; + +TEST_P(ImportFunctions, + PrototypeShouldBeImportedAsAPrototypeWhenThereIsNoDefinition) { + Decl *FromTU = getTuDecl("void f();", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(!cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, + PrototypeShouldBeImportedAsDefintionWhenThereIsADefinition) { + Decl *FromTU = getTuDecl("void f(); void f() {}", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *FromD = // Prototype + FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, + DefinitionShouldBeImportedAsDefintionWhenThereIsAPrototype) { + Decl *FromTU = getTuDecl("void f(); void f() {}", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *FromD = // Definition + LastDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, DefinitionShouldBeImportedAsADefinition) { + Decl *FromTU = getTuDecl("void f() {}", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(FromD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, DISABLED_ImportPrototypeOfRecursiveFunction) { + Decl *FromTU = getTuDecl("void f(); void f() { f(); }", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *PrototypeFD = + FirstDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(PrototypeFD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, ImportDefinitionOfRecursiveFunction) { + Decl *FromTU = getTuDecl("void f(); void f() { f(); }", Lang_CXX); + auto Pattern = functionDecl(hasName("f")); + FunctionDecl *DefinitionFD = + LastDeclMatcher().match(FromTU, Pattern); + + Decl *ImportedD = Import(DefinitionFD, Lang_CXX); + Decl *ToTU = ImportedD->getTranslationUnitDecl(); + + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, ImportPrototypes) { + auto Pattern = functionDecl(hasName("f")); + + Decl *ImportedD; + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input0.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + ImportedD = Import(FromD, Lang_CXX); + } + Decl *ImportedD1; + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input1.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + ImportedD1 = Import(FromD, Lang_CXX); + } + + Decl *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_EQ(ImportedD, ImportedD1); + EXPECT_TRUE(!cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, ImportDefinitionThenPrototype) { + auto Pattern = functionDecl(hasName("f")); + + Decl *ImportedD; + { + Decl *FromTU = getTuDecl("void f(){}", Lang_CXX, "input0.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + ImportedD = Import(FromD, Lang_CXX); + } + Decl *ImportedD1; + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input1.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + ImportedD1 = Import(FromD, Lang_CXX); + } + + Decl *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 1u); + EXPECT_EQ(ImportedD, ImportedD1); + EXPECT_TRUE(cast(ImportedD)->doesThisDeclarationHaveABody()); +} + +TEST_P(ImportFunctions, ImportPrototypeThenDefinition) { + auto Pattern = functionDecl(hasName("f")); + + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input0.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + Import(FromD, Lang_CXX); + } + { + Decl *FromTU = getTuDecl("void f(){}", Lang_CXX, "input1.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + Import(FromD, Lang_CXX); + } + + Decl *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + ASSERT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + FunctionDecl *ProtoD = FirstDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(!ProtoD->doesThisDeclarationHaveABody()); + FunctionDecl *DefinitionD = + LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(DefinitionD->doesThisDeclarationHaveABody()); + EXPECT_EQ(DefinitionD->getPreviousDecl(), ProtoD); +} + +TEST_P(ImportFunctions, DISABLED_ImportPrototypeThenProtoAndDefinition) { + auto Pattern = functionDecl(hasName("f")); + + { + Decl *FromTU = getTuDecl("void f();", Lang_CXX, "input0.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + + Import(FromD, Lang_CXX); + } + { + Decl *FromTU = getTuDecl("void f(); void f(){}", Lang_CXX, "input1.cc"); + FunctionDecl *FromD = + FirstDeclMatcher().match(FromTU, Pattern); + Import(FromD, Lang_CXX); + } + + Decl *ToTU = ToAST->getASTContext().getTranslationUnitDecl(); + ASSERT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + FunctionDecl *ProtoD = FirstDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(!ProtoD->doesThisDeclarationHaveABody()); + FunctionDecl *DefinitionD = + LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(DefinitionD->doesThisDeclarationHaveABody()); + EXPECT_EQ(DefinitionD->getPreviousDecl(), ProtoD); +} + +TEST_P(ImportFunctions, OverriddenMethodsShouldBeImported) { + auto Code = + R"( + struct B { virtual void f(); }; + void B::f() {} + struct D : B { void f(); }; + )"; + auto Pattern = + cxxMethodDecl(hasName("f"), hasParent(cxxRecordDecl(hasName("D")))); + Decl *FromTU = getTuDecl(Code, Lang_CXX); + CXXMethodDecl *Proto = + FirstDeclMatcher().match(FromTU, Pattern); + + ASSERT_EQ(Proto->size_overridden_methods(), 1u); + CXXMethodDecl *To = cast(Import(Proto, Lang_CXX)); + EXPECT_EQ(To->size_overridden_methods(), 1u); +} + +TEST_P(ImportFunctions, VirtualFlagShouldBePreservedWhenImportingPrototype) { + auto Code = + R"( + struct B { virtual void f(); }; + void B::f() {} + )"; + auto Pattern = + cxxMethodDecl(hasName("f"), hasParent(cxxRecordDecl(hasName("B")))); + Decl *FromTU = getTuDecl(Code, Lang_CXX); + CXXMethodDecl *Proto = + FirstDeclMatcher().match(FromTU, Pattern); + CXXMethodDecl *Def = LastDeclMatcher().match(FromTU, Pattern); + + ASSERT_TRUE(Proto->isVirtual()); + ASSERT_TRUE(Def->isVirtual()); + CXXMethodDecl *To = cast(Import(Proto, Lang_CXX)); + EXPECT_TRUE(To->isVirtual()); +} + +INSTANTIATE_TEST_CASE_P( + ParameterizedTests, ImportFunctions, + ::testing::Values(ArgVector(), ArgVector{"-fdelayed-template-parsing"}),); + } // end namespace ast_matchers } // end namespace clang diff --git a/unittests/AST/DeclMatcher.h b/unittests/AST/DeclMatcher.h new file mode 100644 index 0000000000..c73a4cb49f --- /dev/null +++ b/unittests/AST/DeclMatcher.h @@ -0,0 +1,68 @@ +//===- unittest/AST/DeclMatcher.h - AST unit test support ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_UNITTESTS_AST_DECLMATCHER_H +#define LLVM_CLANG_UNITTESTS_AST_DECLMATCHER_H + +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace ast_matchers { + +enum class DeclMatcherKind { First, Last }; + +// Matcher class to retrieve the first/last matched node under a given AST. +template +class DeclMatcher : public MatchFinder::MatchCallback { + NodeType *Node = nullptr; + void run(const MatchFinder::MatchResult &Result) override { + if ((MatcherKind == DeclMatcherKind::First && Node == nullptr) || + MatcherKind == DeclMatcherKind::Last) { + Node = const_cast(Result.Nodes.getNodeAs("")); + } + } +public: + // Returns the first/last matched node under the tree rooted in `D`. + template + NodeType *match(const Decl *D, const MatcherType &AMatcher) { + MatchFinder Finder; + Finder.addMatcher(AMatcher.bind(""), this); + Finder.matchAST(D->getASTContext()); + assert(Node); + return Node; + } +}; +template +using LastDeclMatcher = DeclMatcher; +template +using FirstDeclMatcher = DeclMatcher; + +template +class DeclCounter : public MatchFinder::MatchCallback { + unsigned Count = 0; + void run(const MatchFinder::MatchResult &Result) override { + if(Result.Nodes.getNodeAs("")) { + ++Count; + } + } +public: + // Returns the number of matched nodes under the tree rooted in `D`. + template + unsigned match(const Decl *D, const MatcherType &AMatcher) { + MatchFinder Finder; + Finder.addMatcher(AMatcher.bind(""), this); + Finder.matchAST(D->getASTContext()); + return Count; + } +}; + +} // end namespace ast_matchers +} // end namespace clang + +#endif