From: Gabor Marton Date: Mon, 17 Dec 2018 13:53:12 +0000 (+0000) Subject: [ASTImporter] Add importer specific lookup X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=564948e9bfee36801861b039545836737080cd35;p=clang [ASTImporter] Add importer specific lookup Summary: There are certain cases when normal C/C++ lookup (localUncachedLookup) does not find AST nodes. E.g.: Example 1: template struct X { friend void foo(); // this is never found in the DC of the TU. }; Example 2: // The fwd decl to Foo is not found in the lookupPtr of the DC of the // translation unit decl. struct A { struct Foo *p; }; In these cases we create a new node instead of returning with the old one. To fix it we create a new lookup table which holds every node and we are not interested in any C++ specific visibility considerations. Simply, we must know if there is an existing Decl in a given DC. Reviewers: a_sidorin, a.sidorin Subscribers: mgorny, rnkovacs, dkrupp, Szelethus, cfe-commits Differential Revision: https://reviews.llvm.org/D53708 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@349351 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/ASTImporter.h b/include/clang/AST/ASTImporter.h index cbd5c3414b..dbb9cf35dd 100644 --- a/include/clang/AST/ASTImporter.h +++ b/include/clang/AST/ASTImporter.h @@ -33,6 +33,7 @@ namespace clang { class ASTContext; +class ASTImporterLookupTable; class CXXBaseSpecifier; class CXXCtorInitializer; class Decl; @@ -80,12 +81,21 @@ class Attr; /// Imports selected nodes from one AST context into another context, /// merging AST nodes where appropriate. class ASTImporter { + friend class ASTNodeImporter; public: using NonEquivalentDeclSet = llvm::DenseSet>; using ImportedCXXBaseSpecifierMap = llvm::DenseMap; private: + + /// Pointer to the import specific lookup table, which may be shared + /// amongst several ASTImporter objects. + /// This is an externally managed resource (and should exist during the + /// lifetime of the ASTImporter object) + /// If not set then the original C/C++ lookup is used. + ASTImporterLookupTable *LookupTable = nullptr; + /// The contexts we're importing to and from. ASTContext &ToContext, &FromContext; @@ -123,9 +133,13 @@ class Attr; /// (which we have already complained about). NonEquivalentDeclSet NonEquivalentDecls; + using FoundDeclsTy = SmallVector; + FoundDeclsTy findDeclsInToCtx(DeclContext *DC, DeclarationName Name); + + void AddToLookupTable(Decl *ToD); + public: - /// Create a new AST importer. - /// + /// \param ToContext The context we'll be importing into. /// /// \param ToFileManager The file manager we'll be importing into. @@ -137,9 +151,14 @@ class Attr; /// \param MinimalImport If true, the importer will attempt to import /// as little as it can, e.g., by importing declarations as forward /// declarations that can be completed at a later point. + /// + /// \param LookupTable The importer specific lookup table which may be + /// shared amongst several ASTImporter objects. + /// If not set then the original C/C++ lookup is used. ASTImporter(ASTContext &ToContext, FileManager &ToFileManager, ASTContext &FromContext, FileManager &FromFileManager, - bool MinimalImport); + bool MinimalImport, + ASTImporterLookupTable *LookupTable = nullptr); virtual ~ASTImporter(); diff --git a/include/clang/AST/ASTImporterLookupTable.h b/include/clang/AST/ASTImporterLookupTable.h new file mode 100644 index 0000000000..14cafe817d --- /dev/null +++ b/include/clang/AST/ASTImporterLookupTable.h @@ -0,0 +1,75 @@ +//===- ASTImporterLookupTable.h - ASTImporter specific lookup--*- C++ -*---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the ASTImporterLookupTable class which implements a +// lookup procedure for the import mechanism. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_ASTIMPORTERLOOKUPTABLE_H +#define LLVM_CLANG_AST_ASTIMPORTERLOOKUPTABLE_H + +#include "clang/AST/DeclBase.h" // lookup_result +#include "clang/AST/DeclarationName.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SetVector.h" + +namespace clang { + +class ASTContext; +class NamedDecl; +class DeclContext; + +// There are certain cases when normal C/C++ lookup (localUncachedLookup) +// does not find AST nodes. E.g.: +// Example 1: +// template +// struct X { +// friend void foo(); // this is never found in the DC of the TU. +// }; +// Example 2: +// // The fwd decl to Foo is not found in the lookupPtr of the DC of the +// // translation unit decl. +// // Here we could find the node by doing a traverse throught the list of +// // the Decls in the DC, but that would not scale. +// struct A { struct Foo *p; }; +// This is a severe problem because the importer decides if it has to create a +// new Decl or not based on the lookup results. +// To overcome these cases we need an importer specific lookup table which +// holds every node and we are not interested in any C/C++ specific visibility +// considerations. Simply, we must know if there is an existing Decl in a +// given DC. Once we found it then we can handle any visibility related tasks. +class ASTImporterLookupTable { + + // We store a list of declarations for each name. + // And we collect these lists for each DeclContext. + // We could have a flat map with (DeclContext, Name) tuple as key, but a two + // level map seems easier to handle. + using DeclList = llvm::SmallSetVector; + using NameMap = llvm::SmallDenseMap; + using DCMap = llvm::DenseMap; + + void add(DeclContext *DC, NamedDecl *ND); + void remove(DeclContext *DC, NamedDecl *ND); + + DCMap LookupTable; + +public: + ASTImporterLookupTable(TranslationUnitDecl &TU); + void add(NamedDecl *ND); + void remove(NamedDecl *ND); + using LookupResult = DeclList; + LookupResult lookup(DeclContext *DC, DeclarationName Name) const; + void dump(DeclContext *DC) const; + void dump() const; +}; + +} // namespace clang + +#endif // LLVM_CLANG_AST_ASTIMPORTERLOOKUPTABLE_H diff --git a/include/clang/CrossTU/CrossTranslationUnit.h b/include/clang/CrossTU/CrossTranslationUnit.h index b5371a6de7..a5cec7e05e 100644 --- a/include/clang/CrossTU/CrossTranslationUnit.h +++ b/include/clang/CrossTU/CrossTranslationUnit.h @@ -15,6 +15,7 @@ #ifndef LLVM_CLANG_CROSSTU_CROSSTRANSLATIONUNIT_H #define LLVM_CLANG_CROSSTU_CROSSTRANSLATIONUNIT_H +#include "clang/AST/ASTImporterLookupTable.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallPtrSet.h" @@ -152,6 +153,7 @@ public: void emitCrossTUDiagnostics(const IndexError &IE); private: + void lazyInitLookupTable(TranslationUnitDecl *ToTU); ASTImporter &getOrCreateASTImporter(ASTContext &From); const FunctionDecl *findFunctionInDeclContext(const DeclContext *DC, StringRef LookupFnName); @@ -163,6 +165,7 @@ private: ASTUnitImporterMap; CompilerInstance &CI; ASTContext &Context; + std::unique_ptr LookupTable; }; } // namespace cross_tu diff --git a/lib/AST/ASTImporter.cpp b/lib/AST/ASTImporter.cpp index c67990cfcf..6a893c5035 100644 --- a/lib/AST/ASTImporter.cpp +++ b/lib/AST/ASTImporter.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "clang/AST/ASTImporter.h" +#include "clang/AST/ASTImporterLookupTable.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/ASTStructuralEquivalence.h" @@ -134,28 +135,6 @@ namespace clang { To->setIsUsed(); } - Optional ASTImporter::getFieldIndex(Decl *F) { - assert(F && (isa(*F) || isa(*F)) && - "Try to get field index for non-field."); - - auto *Owner = dyn_cast(F->getDeclContext()); - if (!Owner) - return None; - - unsigned Index = 0; - for (const auto *D : Owner->decls()) { - if (D == F) - return Index; - - if (isa(*D) || isa(*D)) - ++Index; - } - - llvm_unreachable("Field was not found in its parent context."); - - return None; - } - // FIXME: Temporary until every import returns Expected. template <> LLVM_NODISCARD Error @@ -313,12 +292,14 @@ namespace clang { if (ToD) return true; // Already imported. ToD = CreateFun(std::forward(args)...); + // Keep track of imported Decls. + Importer.MapImported(FromD, ToD); + Importer.AddToLookupTable(ToD); InitializeImportedDecl(FromD, ToD); return false; // A new Decl is created. } void InitializeImportedDecl(Decl *FromD, Decl *ToD) { - Importer.MapImported(FromD, ToD); ToD->IdentifierNamespace = FromD->IdentifierNamespace; if (FromD->hasAttrs()) for (const Attr *FromAttr : FromD->getAttrs()) @@ -2198,8 +2179,7 @@ ExpectedDecl ASTNodeImporter::VisitNamespaceDecl(NamespaceDecl *D) { MergeWithNamespace = cast(DC)->getAnonymousNamespace(); } else { SmallVector ConflictingDecls; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(Decl::IDNS_Namespace)) continue; @@ -2310,8 +2290,7 @@ ASTNodeImporter::VisitTypedefNameDecl(TypedefNameDecl *D, bool IsAlias) { if (!DC->isFunctionOrMethod()) { SmallVector ConflictingDecls; unsigned IDNS = Decl::IDNS_Ordinary; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(IDNS)) continue; @@ -2399,8 +2378,7 @@ ASTNodeImporter::VisitTypeAliasTemplateDecl(TypeAliasTemplateDecl *D) { if (!DC->isFunctionOrMethod()) { SmallVector ConflictingDecls; unsigned IDNS = Decl::IDNS_Ordinary; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(IDNS)) continue; @@ -2502,8 +2480,8 @@ ExpectedDecl ASTNodeImporter::VisitEnumDecl(EnumDecl *D) { // We may already have an enum of the same name; try to find and match it. if (!DC->isFunctionOrMethod() && SearchName) { SmallVector ConflictingDecls; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(SearchName, FoundDecls); + auto FoundDecls = + Importer.findDeclsInToCtx(DC, SearchName); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(IDNS)) continue; @@ -2615,9 +2593,8 @@ ExpectedDecl ASTNodeImporter::VisitRecordDecl(RecordDecl *D) { RecordDecl *PrevDecl = nullptr; if (!DC->isFunctionOrMethod()) { SmallVector ConflictingDecls; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(SearchName, FoundDecls); - + auto FoundDecls = + Importer.findDeclsInToCtx(DC, SearchName); if (!FoundDecls.empty()) { // We're going to have to compare D against potentially conflicting Decls, so complete it. if (D->hasExternalLexicalStorage() && !D->isCompleteDefinition()) @@ -2634,15 +2611,6 @@ ExpectedDecl ASTNodeImporter::VisitRecordDecl(RecordDecl *D) { Found = Tag->getDecl(); } - if (D->getDescribedTemplate()) { - if (auto *Template = dyn_cast(Found)) { - Found = Template->getTemplatedDecl(); - } else { - ConflictingDecls.push_back(FoundDecl); - continue; - } - } - if (auto *FoundRecord = dyn_cast(Found)) { // Do not emit false positive diagnostic in case of unnamed // struct/union and in case of anonymous structs. Would be false @@ -2838,8 +2806,7 @@ ExpectedDecl ASTNodeImporter::VisitEnumConstantDecl(EnumConstantDecl *D) { if (!LexicalDC->isFunctionOrMethod()) { SmallVector ConflictingDecls; unsigned IDNS = Decl::IDNS_Ordinary; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(IDNS)) continue; @@ -3005,9 +2972,9 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { FunctionTemplateDecl *FromFT = D->getDescribedFunctionTemplate(); // If this is a function template specialization, then try to find the same - // existing specialization in the "to" context. The localUncachedLookup - // below will not find any specialization, but would find the primary - // template; thus, we have to skip normal lookup in case of specializations. + // existing specialization in the "to" context. The lookup below will not + // find any specialization, but would find the primary template; thus, we + // have to skip normal lookup in case of specializations. // FIXME handle member function templates (TK_MemberSpecialization) similarly? if (D->getTemplatedKind() == FunctionDecl::TK_FunctionTemplateSpecialization) { @@ -3025,20 +2992,11 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { else if (!LexicalDC->isFunctionOrMethod()) { SmallVector ConflictingDecls; unsigned IDNS = Decl::IDNS_Ordinary | Decl::IDNS_OrdinaryFriend; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(IDNS)) continue; - // If template was found, look at the templated function. - if (FromFT) { - if (auto *Template = dyn_cast(FoundDecl)) - FoundDecl = Template->getTemplatedDecl(); - else - continue; - } - if (auto *FoundFunction = dyn_cast(FoundDecl)) { if (FoundFunction->hasExternalFormalLinkage() && D->hasExternalFormalLinkage()) { @@ -3299,8 +3257,7 @@ ExpectedDecl ASTNodeImporter::VisitFieldDecl(FieldDecl *D) { return ToD; // Determine whether we've already imported this field. - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (FieldDecl *FoundField = dyn_cast(FoundDecl)) { // For anonymous fields, match up by index. @@ -3385,8 +3342,7 @@ ExpectedDecl ASTNodeImporter::VisitIndirectFieldDecl(IndirectFieldDecl *D) { return ToD; // Determine whether we've already imported this field. - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (unsigned I = 0, N = FoundDecls.size(); I != N; ++I) { if (auto *FoundField = dyn_cast(FoundDecls[I])) { // For anonymous indirect fields, match up by index. @@ -3454,7 +3410,7 @@ ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) { return std::move(Err); // Determine whether we've already imported this decl. - // FriendDecl is not a NamedDecl so we cannot use localUncachedLookup. + // FriendDecl is not a NamedDecl so we cannot use lookup. auto *RD = cast(DC); FriendDecl *ImportedFriend = RD->getFirstFriend(); @@ -3532,8 +3488,7 @@ ExpectedDecl ASTNodeImporter::VisitObjCIvarDecl(ObjCIvarDecl *D) { return ToD; // Determine whether we've already imported this ivar - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (ObjCIvarDecl *FoundIvar = dyn_cast(FoundDecl)) { if (Importer.IsStructurallyEquivalent(D->getType(), @@ -3603,8 +3558,7 @@ ExpectedDecl ASTNodeImporter::VisitVarDecl(VarDecl *D) { if (D->isFileVarDecl()) { SmallVector ConflictingDecls; unsigned IDNS = Decl::IDNS_Ordinary; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(IDNS)) continue; @@ -3814,8 +3768,7 @@ ExpectedDecl ASTNodeImporter::VisitObjCMethodDecl(ObjCMethodDecl *D) { if (ToD) return ToD; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (auto *FoundMethod = dyn_cast(FoundDecl)) { if (FoundMethod->isInstanceMethod() != D->isInstanceMethod()) @@ -4122,8 +4075,7 @@ ExpectedDecl ASTNodeImporter::VisitObjCProtocolDecl(ObjCProtocolDecl *D) { return ToD; ObjCProtocolDecl *MergeWithProtocol = nullptr; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(Decl::IDNS_ObjCProtocol)) continue; @@ -4548,8 +4500,7 @@ ExpectedDecl ASTNodeImporter::VisitObjCInterfaceDecl(ObjCInterfaceDecl *D) { // Look for an existing interface with the same name. ObjCInterfaceDecl *MergeWithIface = nullptr; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(Decl::IDNS_Ordinary)) continue; @@ -4724,8 +4675,7 @@ ExpectedDecl ASTNodeImporter::VisitObjCPropertyDecl(ObjCPropertyDecl *D) { return ToD; // Check whether we have already imported this property. - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (auto *FoundProp = dyn_cast(FoundDecl)) { // Check property types. @@ -4995,8 +4945,7 @@ ExpectedDecl ASTNodeImporter::VisitClassTemplateDecl(ClassTemplateDecl *D) { // We may already have a template of the same name; try to find and match it. if (!DC->isFunctionOrMethod()) { SmallVector ConflictingDecls; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(Decl::IDNS_Ordinary | Decl::IDNS_TagFriend)) @@ -5304,8 +5253,7 @@ ExpectedDecl ASTNodeImporter::VisitVarTemplateDecl(VarTemplateDecl *D) { assert(!DC->isFunctionOrMethod() && "Variable templates cannot be declared at function scope"); SmallVector ConflictingDecls; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(Decl::IDNS_Ordinary)) continue; @@ -5537,8 +5485,7 @@ ASTNodeImporter::VisitFunctionTemplateDecl(FunctionTemplateDecl *D) { // type, and in the same context as the function we're importing. if (!LexicalDC->isFunctionOrMethod()) { unsigned IDNS = Decl::IDNS_Ordinary; - SmallVector FoundDecls; - DC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + auto FoundDecls = Importer.findDeclsInToCtx(DC, Name); for (auto *FoundDecl : FoundDecls) { if (!FoundDecl->isInIdentifierNamespace(IDNS)) continue; @@ -7666,12 +7613,14 @@ void ASTNodeImporter::ImportOverrides(CXXMethodDecl *ToMethod, ASTImporter::ASTImporter(ASTContext &ToContext, FileManager &ToFileManager, ASTContext &FromContext, FileManager &FromFileManager, - bool MinimalImport) - : ToContext(ToContext), FromContext(FromContext), + bool MinimalImport, + ASTImporterLookupTable *LookupTable) + : LookupTable(LookupTable), ToContext(ToContext), FromContext(FromContext), ToFileManager(ToFileManager), FromFileManager(FromFileManager), Minimal(MinimalImport) { - ImportedDecls[FromContext.getTranslationUnitDecl()] - = ToContext.getTranslationUnitDecl(); + + ImportedDecls[FromContext.getTranslationUnitDecl()] = + ToContext.getTranslationUnitDecl(); } ASTImporter::~ASTImporter() = default; @@ -7682,6 +7631,58 @@ Expected ASTImporter::Import_New(QualType FromT) { return make_error(); return ToT; } + +Optional ASTImporter::getFieldIndex(Decl *F) { + assert(F && (isa(*F) || isa(*F)) && + "Try to get field index for non-field."); + + auto *Owner = dyn_cast(F->getDeclContext()); + if (!Owner) + return None; + + unsigned Index = 0; + for (const auto *D : Owner->decls()) { + if (D == F) + return Index; + + if (isa(*D) || isa(*D)) + ++Index; + } + + llvm_unreachable("Field was not found in its parent context."); + + return None; +} + +ASTImporter::FoundDeclsTy +ASTImporter::findDeclsInToCtx(DeclContext *DC, DeclarationName Name) { + // We search in the redecl context because of transparent contexts. + // E.g. a simple C language enum is a transparent context: + // enum E { A, B }; + // Now if we had a global variable in the TU + // int A; + // then the enum constant 'A' and the variable 'A' violates ODR. + // We can diagnose this only if we search in the redecl context. + DeclContext *ReDC = DC->getRedeclContext(); + if (LookupTable) { + ASTImporterLookupTable::LookupResult LookupResult = + LookupTable->lookup(ReDC, Name); + return FoundDeclsTy(LookupResult.begin(), LookupResult.end()); + } else { + // FIXME Can we remove this kind of lookup? + // Or lldb really needs this C/C++ lookup? + FoundDeclsTy Result; + ReDC->localUncachedLookup(Name, Result); + return Result; + } +} + +void ASTImporter::AddToLookupTable(Decl *ToD) { + if (LookupTable) + if (auto *ToND = dyn_cast(ToD)) + LookupTable->add(ToND); +} + QualType ASTImporter::Import(QualType FromT) { if (FromT.isNull()) return {}; @@ -7774,6 +7775,11 @@ Decl *ASTImporter::Import(Decl *FromD) { } ToD = *ToDOrErr; + // Once the decl is connected to the existing declarations, i.e. when the + // redecl chain is properly set then we populate the lookup again. + // This way the primary context will be able to find all decls. + AddToLookupTable(ToD); + // Notify subclasses. Imported(FromD, ToD); diff --git a/lib/AST/ASTImporterLookupTable.cpp b/lib/AST/ASTImporterLookupTable.cpp new file mode 100644 index 0000000000..fbcd4f5cb3 --- /dev/null +++ b/lib/AST/ASTImporterLookupTable.cpp @@ -0,0 +1,129 @@ +//===- ASTImporterLookupTable.cpp - ASTImporter specific lookup -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the ASTImporterLookupTable class which implements a +// lookup procedure for the import mechanism. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTImporterLookupTable.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" + +namespace clang { + +namespace { + +struct Builder : RecursiveASTVisitor { + ASTImporterLookupTable < + Builder(ASTImporterLookupTable <) : LT(LT) {} + bool VisitNamedDecl(NamedDecl *D) { + LT.add(D); + return true; + } + bool VisitFriendDecl(FriendDecl *D) { + if (D->getFriendType()) { + QualType Ty = D->getFriendType()->getType(); + // FIXME Can this be other than elaborated? + QualType NamedTy = cast(Ty)->getNamedType(); + if (!NamedTy->isDependentType()) { + if (const auto *RTy = dyn_cast(NamedTy)) + LT.add(RTy->getAsCXXRecordDecl()); + else if (const auto *SpecTy = + dyn_cast(NamedTy)) { + LT.add(SpecTy->getAsCXXRecordDecl()); + } + } + } + return true; + } + + // Override default settings of base. + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return true; } +}; + +} // anonymous namespace + +ASTImporterLookupTable::ASTImporterLookupTable(TranslationUnitDecl &TU) { + Builder B(*this); + B.TraverseDecl(&TU); +} + +void ASTImporterLookupTable::add(DeclContext *DC, NamedDecl *ND) { + DeclList &Decls = LookupTable[DC][ND->getDeclName()]; + // Inserts if and only if there is no element in the container equal to it. + Decls.insert(ND); +} + +void ASTImporterLookupTable::remove(DeclContext *DC, NamedDecl *ND) { + DeclList &Decls = LookupTable[DC][ND->getDeclName()]; + bool EraseResult = Decls.remove(ND); + (void)EraseResult; + assert(EraseResult == true && "Trying to remove not contained Decl"); +} + +void ASTImporterLookupTable::add(NamedDecl *ND) { + assert(ND); + DeclContext *DC = ND->getDeclContext()->getPrimaryContext(); + add(DC, ND); + DeclContext *ReDC = DC->getRedeclContext()->getPrimaryContext(); + if (DC != ReDC) + add(ReDC, ND); +} + +void ASTImporterLookupTable::remove(NamedDecl *ND) { + assert(ND); + DeclContext *DC = ND->getDeclContext()->getPrimaryContext(); + remove(DC, ND); + DeclContext *ReDC = DC->getRedeclContext()->getPrimaryContext(); + if (DC != ReDC) + remove(ReDC, ND); +} + +ASTImporterLookupTable::LookupResult +ASTImporterLookupTable::lookup(DeclContext *DC, DeclarationName Name) const { + auto DCI = LookupTable.find(DC->getPrimaryContext()); + if (DCI == LookupTable.end()) + return {}; + + const auto &FoundNameMap = DCI->second; + auto NamesI = FoundNameMap.find(Name); + if (NamesI == FoundNameMap.end()) + return {}; + + return NamesI->second; +} + +void ASTImporterLookupTable::dump(DeclContext *DC) const { + auto DCI = LookupTable.find(DC->getPrimaryContext()); + if (DCI == LookupTable.end()) + llvm::errs() << "empty\n"; + const auto &FoundNameMap = DCI->second; + for (const auto &Entry : FoundNameMap) { + DeclarationName Name = Entry.first; + llvm::errs() << "==== Name: "; + Name.dump(); + const DeclList& List = Entry.second; + for (NamedDecl *ND : List) { + ND->dump(); + } + } +} + +void ASTImporterLookupTable::dump() const { + for (const auto &Entry : LookupTable) { + DeclContext *DC = Entry.first; + StringRef Primary = DC->getPrimaryContext() ? " primary" : ""; + llvm::errs() << "== DC:" << cast(DC) << Primary << "\n"; + dump(DC); + } +} + +} // namespace clang diff --git a/lib/AST/CMakeLists.txt b/lib/AST/CMakeLists.txt index adeb9f7e64..570ca718ac 100644 --- a/lib/AST/CMakeLists.txt +++ b/lib/AST/CMakeLists.txt @@ -10,6 +10,7 @@ add_clang_library(clangAST ASTDiagnostic.cpp ASTDumper.cpp ASTImporter.cpp + ASTImporterLookupTable.cpp ASTStructuralEquivalence.cpp ASTTypeTraits.cpp AttrImpl.cpp diff --git a/lib/CrossTU/CrossTranslationUnit.cpp b/lib/CrossTU/CrossTranslationUnit.cpp index 5286b90f93..6ee329c2b5 100644 --- a/lib/CrossTU/CrossTranslationUnit.cpp +++ b/lib/CrossTU/CrossTranslationUnit.cpp @@ -342,14 +342,21 @@ CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD) { return ToDecl; } +void CrossTranslationUnitContext::lazyInitLookupTable( + TranslationUnitDecl *ToTU) { + if (!LookupTable) + LookupTable = llvm::make_unique(*ToTU); +} + ASTImporter & CrossTranslationUnitContext::getOrCreateASTImporter(ASTContext &From) { auto I = ASTUnitImporterMap.find(From.getTranslationUnitDecl()); if (I != ASTUnitImporterMap.end()) return *I->second; - ASTImporter *NewImporter = - new ASTImporter(Context, Context.getSourceManager().getFileManager(), - From, From.getSourceManager().getFileManager(), false); + lazyInitLookupTable(Context.getTranslationUnitDecl()); + ASTImporter *NewImporter = new ASTImporter( + Context, Context.getSourceManager().getFileManager(), From, + From.getSourceManager().getFileManager(), false, LookupTable.get()); ASTUnitImporterMap[From.getTranslationUnitDecl()].reset(NewImporter); return *NewImporter; } diff --git a/lib/Frontend/ASTMerge.cpp b/lib/Frontend/ASTMerge.cpp index 2434113ab0..4f622da118 100644 --- a/lib/Frontend/ASTMerge.cpp +++ b/lib/Frontend/ASTMerge.cpp @@ -10,6 +10,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/ASTImporter.h" +#include "clang/AST/ASTImporterLookupTable.h" #include "clang/Basic/Diagnostic.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" @@ -38,6 +39,8 @@ void ASTMergeAction::ExecuteAction() { &CI.getASTContext()); IntrusiveRefCntPtr DiagIDs(CI.getDiagnostics().getDiagnosticIDs()); + ASTImporterLookupTable LookupTable( + *CI.getASTContext().getTranslationUnitDecl()); for (unsigned I = 0, N = ASTFiles.size(); I != N; ++I) { IntrusiveRefCntPtr Diags(new DiagnosticsEngine(DiagIDs, &CI.getDiagnosticOpts(), @@ -51,11 +54,9 @@ void ASTMergeAction::ExecuteAction() { if (!Unit) continue; - ASTImporter Importer(CI.getASTContext(), - CI.getFileManager(), - Unit->getASTContext(), - Unit->getFileManager(), - /*MinimalImport=*/false); + ASTImporter Importer(CI.getASTContext(), CI.getFileManager(), + Unit->getASTContext(), Unit->getFileManager(), + /*MinimalImport=*/false, &LookupTable); TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl(); for (auto *D : TU->decls()) { diff --git a/unittests/AST/ASTImporterTest.cpp b/unittests/AST/ASTImporterTest.cpp index 3407f7da6d..aaadeb0b9d 100644 --- a/unittests/AST/ASTImporterTest.cpp +++ b/unittests/AST/ASTImporterTest.cpp @@ -15,6 +15,8 @@ #include "MatchVerifier.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclContextInternals.h" +#include "clang/AST/ASTImporter.h" +#include "clang/AST/ASTImporterLookupTable.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" @@ -308,24 +310,27 @@ class ASTImporterTestBase : public ParameterizedTestsFixture { Unit->enableSourceFileDiagnostics(); } - void lazyInitImporter(ASTUnit *ToAST) { + void lazyInitImporter(ASTImporterLookupTable &LookupTable, ASTUnit *ToAST) { assert(ToAST); if (!Importer) { - Importer.reset(new ASTImporter( - ToAST->getASTContext(), ToAST->getFileManager(), - Unit->getASTContext(), Unit->getFileManager(), false)); + Importer.reset( + new ASTImporter(ToAST->getASTContext(), ToAST->getFileManager(), + Unit->getASTContext(), Unit->getFileManager(), + false, &LookupTable)); } assert(&ToAST->getASTContext() == &Importer->getToContext()); createVirtualFileIfNeeded(ToAST, FileName, Code); } - Decl *import(ASTUnit *ToAST, Decl *FromDecl) { - lazyInitImporter(ToAST); + Decl *import(ASTImporterLookupTable &LookupTable, ASTUnit *ToAST, + Decl *FromDecl) { + lazyInitImporter(LookupTable, ToAST); return Importer->Import(FromDecl); - } + } - QualType import(ASTUnit *ToAST, QualType FromType) { - lazyInitImporter(ToAST); + QualType import(ASTImporterLookupTable &LookupTable, ASTUnit *ToAST, + QualType FromType) { + lazyInitImporter(LookupTable, ToAST); return Importer->Import(FromType); } }; @@ -339,13 +344,23 @@ class ASTImporterTestBase : public ParameterizedTestsFixture { // vector is expanding, with the list we won't have these issues. std::list FromTUs; - void lazyInitToAST(Language ToLang) { + // Initialize the lookup table if not initialized already. + void lazyInitLookupTable(TranslationUnitDecl *ToTU) { + assert(ToTU); + if (!LookupTablePtr) + LookupTablePtr = llvm::make_unique(*ToTU); + } + + void lazyInitToAST(Language ToLang, StringRef ToSrcCode, StringRef FileName) { if (ToAST) return; ArgVector ToArgs = getArgVectorForLanguage(ToLang); + // Source code must be a valid live buffer through the tests lifetime. + ToCode = ToSrcCode; // Build the AST from an empty file. - ToAST = tooling::buildASTFromCodeWithArgs(/*Code=*/"", ToArgs, "empty.cc"); + ToAST = tooling::buildASTFromCodeWithArgs(ToCode, ToArgs, FileName); ToAST->enableSourceFileDiagnostics(); + lazyInitLookupTable(ToAST->getASTContext().getTranslationUnitDecl()); } TU *findFromTU(Decl *From) { @@ -359,6 +374,10 @@ class ASTImporterTestBase : public ParameterizedTestsFixture { return &*It; } +protected: + + std::unique_ptr LookupTablePtr; + public: // We may have several From context but only one To context. std::unique_ptr ToAST; @@ -375,26 +394,23 @@ public: FromTUs.emplace_back(FromSrcCode, InputFileName, FromArgs); TU &FromTU = FromTUs.back(); - ToCode = ToSrcCode; assert(!ToAST); - ToAST = tooling::buildASTFromCodeWithArgs(ToCode, ToArgs, OutputFileName); - ToAST->enableSourceFileDiagnostics(); + lazyInitToAST(ToLang, ToSrcCode, OutputFileName); ASTContext &FromCtx = FromTU.Unit->getASTContext(); - createVirtualFileIfNeeded(ToAST.get(), InputFileName, FromTU.Code); - IdentifierInfo *ImportedII = &FromCtx.Idents.get(Identifier); assert(ImportedII && "Declaration with the given identifier " "should be specified in test!"); DeclarationName ImportDeclName(ImportedII); - SmallVector FoundDecls; + SmallVector FoundDecls; FromCtx.getTranslationUnitDecl()->localUncachedLookup(ImportDeclName, FoundDecls); assert(FoundDecls.size() == 1); - Decl *Imported = FromTU.import(ToAST.get(), FoundDecls.front()); + Decl *Imported = + FromTU.import(*LookupTablePtr, ToAST.get(), FoundDecls.front()); assert(Imported); return std::make_tuple(*FoundDecls.begin(), Imported); @@ -420,11 +436,8 @@ public: // Creates the To context with the given source code and returns the TU decl. TranslationUnitDecl *getToTuDecl(StringRef ToSrcCode, Language ToLang) { ArgVector ToArgs = getArgVectorForLanguage(ToLang); - ToCode = ToSrcCode; assert(!ToAST); - ToAST = tooling::buildASTFromCodeWithArgs(ToCode, ToArgs, OutputFileName); - ToAST->enableSourceFileDiagnostics(); - + lazyInitToAST(ToLang, ToSrcCode, OutputFileName); return ToAST->getASTContext().getTranslationUnitDecl(); } @@ -432,15 +445,17 @@ public: // 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) { - lazyInitToAST(ToLang); + lazyInitToAST(ToLang, "", OutputFileName); TU *FromTU = findFromTU(From); - return FromTU->import(ToAST.get(), From); + assert(LookupTablePtr); + return FromTU->import(*LookupTablePtr, ToAST.get(), From); } QualType ImportType(QualType FromType, Decl *TUDecl, Language ToLang) { - lazyInitToAST(ToLang); + lazyInitToAST(ToLang, "", OutputFileName); TU *FromTU = findFromTU(TUDecl); - return FromTU->import(ToAST.get(), FromType); + assert(LookupTablePtr); + return FromTU->import(*LookupTablePtr, ToAST.get(), FromType); } ~ASTImporterTestBase() { @@ -2727,6 +2742,7 @@ private: CXXMethodDecl *Method = FirstDeclMatcher().match(ToClass, MethodMatcher); ToClass->removeDecl(Method); + LookupTablePtr->remove(Method); } ASSERT_EQ(DeclCounter().match(ToClass, MethodMatcher), 0u); @@ -3486,6 +3502,82 @@ TEST_P(ImportClasses, ImportPrototypeThenDefinition) { EXPECT_EQ(ToDef->getPreviousDecl(), ToProto); } +TEST_P(ImportClasses, ImportDefinitionWhenProtoIsInToContext) { + Decl *ToTU = getToTuDecl("struct X;", Lang_C); + Decl *FromTU1 = getTuDecl("struct X {};", Lang_C, "input1.cc"); + auto Pattern = recordDecl(hasName("X"), unless(isImplicit())); + auto ToProto = FirstDeclMatcher().match(ToTU, Pattern); + auto FromDef = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *ImportedDef = Import(FromDef, Lang_C); + + EXPECT_NE(ImportedDef, ToProto); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto ToDef = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedDef == ToDef); + EXPECT_TRUE(ToDef->isThisDeclarationADefinition()); + EXPECT_FALSE(ToProto->isThisDeclarationADefinition()); + EXPECT_EQ(ToDef->getPreviousDecl(), ToProto); +} + +TEST_P(ImportClasses, ImportDefinitionWhenProtoIsInNestedToContext) { + Decl *ToTU = getToTuDecl("struct A { struct X *Xp; };", Lang_C); + Decl *FromTU1 = getTuDecl("struct X {};", Lang_C, "input1.cc"); + auto Pattern = recordDecl(hasName("X"), unless(isImplicit())); + auto ToProto = FirstDeclMatcher().match(ToTU, Pattern); + auto FromDef = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *ImportedDef = Import(FromDef, Lang_C); + + EXPECT_NE(ImportedDef, ToProto); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto ToDef = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedDef == ToDef); + EXPECT_TRUE(ToDef->isThisDeclarationADefinition()); + EXPECT_FALSE(ToProto->isThisDeclarationADefinition()); + EXPECT_EQ(ToDef->getPreviousDecl(), ToProto); +} + +TEST_P(ImportClasses, ImportDefinitionWhenProtoIsInNestedToContextCXX) { + Decl *ToTU = getToTuDecl("struct A { struct X *Xp; };", Lang_CXX); + Decl *FromTU1 = getTuDecl("struct X {};", Lang_CXX, "input1.cc"); + auto Pattern = recordDecl(hasName("X"), unless(isImplicit())); + auto ToProto = FirstDeclMatcher().match(ToTU, Pattern); + auto FromDef = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *ImportedDef = Import(FromDef, Lang_CXX); + + EXPECT_NE(ImportedDef, ToProto); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto ToDef = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedDef == ToDef); + EXPECT_TRUE(ToDef->isThisDeclarationADefinition()); + EXPECT_FALSE(ToProto->isThisDeclarationADefinition()); + EXPECT_EQ(ToDef->getPreviousDecl(), ToProto); +} + +TEST_P(ImportClasses, ImportNestedPrototypeThenDefinition) { + Decl *FromTU0 = getTuDecl("struct A { struct X *Xp; };", Lang_C, "input0.cc"); + Decl *FromTU1 = getTuDecl("struct X {};", Lang_C, "input1.cc"); + auto Pattern = recordDecl(hasName("X"), unless(isImplicit())); + auto FromProto = FirstDeclMatcher().match(FromTU0, Pattern); + auto FromDef = FirstDeclMatcher().match(FromTU1, Pattern); + + Decl *ImportedProto = Import(FromProto, Lang_C); + Decl *ImportedDef = Import(FromDef, Lang_C); + Decl *ToTU = ImportedDef->getTranslationUnitDecl(); + + EXPECT_NE(ImportedDef, ImportedProto); + EXPECT_EQ(DeclCounter().match(ToTU, Pattern), 2u); + auto ToProto = FirstDeclMatcher().match(ToTU, Pattern); + auto ToDef = LastDeclMatcher().match(ToTU, Pattern); + EXPECT_TRUE(ImportedDef == ToDef); + EXPECT_TRUE(ImportedProto == ToProto); + EXPECT_TRUE(ToDef->isThisDeclarationADefinition()); + EXPECT_FALSE(ToProto->isThisDeclarationADefinition()); + EXPECT_EQ(ToDef->getPreviousDecl(), ToProto); +} + struct ImportClassTemplates : ASTImporterTestBase {}; TEST_P(ImportClassTemplates, @@ -3890,6 +3982,24 @@ TEST_P(ImportFriendClasses, ImportOfClassDefinitionAndFwdFriendShouldBeLinked) { EXPECT_EQ(ImportedFwd, ImportedDef->getPreviousDecl()); } +TEST_P(ASTImporterTestBase, FriendFunInClassTemplate) { + auto *Code = R"( + template + struct X { + friend void foo(){} + }; + )"; + TranslationUnitDecl *ToTU = getToTuDecl(Code, Lang_CXX); + auto *ToFoo = FirstDeclMatcher().match( + ToTU, functionDecl(hasName("foo"))); + + TranslationUnitDecl *FromTU = getTuDecl(Code, Lang_CXX, "input.cc"); + auto *FromFoo = FirstDeclMatcher().match( + FromTU, functionDecl(hasName("foo"))); + auto *ImportedFoo = Import(FromFoo, Lang_CXX); + EXPECT_EQ(ImportedFoo, ToFoo); +} + struct DeclContextTest : ASTImporterTestBase {}; TEST_P(DeclContextTest, removeDeclOfClassTemplateSpecialization) { @@ -4339,6 +4449,416 @@ TEST_P(ASTImporterTestBase, ImportingTypedefShouldImportTheCompleteType) { EXPECT_FALSE(ImportedD->getUnderlyingType()->isIncompleteType()); } +struct ASTImporterLookupTableTest : ASTImporterTestBase {}; + +TEST_P(ASTImporterLookupTableTest, OneDecl) { + auto *ToTU = getToTuDecl("int a;", Lang_CXX); + auto *D = FirstDeclMatcher().match(ToTU, varDecl(hasName("a"))); + ASTImporterLookupTable LT(*ToTU); + auto Res = LT.lookup(ToTU, D->getDeclName()); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), D); +} + +static Decl *findInDeclListOfDC(DeclContext *DC, DeclarationName Name) { + for (Decl *D : DC->decls()) { + if (auto *ND = dyn_cast(D)) + if (ND->getDeclName() == Name) + return ND; + } + return nullptr; +}; + +TEST_P(ASTImporterLookupTableTest, + FriendWhichIsnotFoundByNormalLookupShouldBeFoundByImporterSpecificLookup) { + auto *Code = R"( + template + struct X { + friend void foo(){} + }; + )"; + TranslationUnitDecl *ToTU = getToTuDecl(Code, Lang_CXX); + auto *X = FirstDeclMatcher().match( + ToTU, classTemplateDecl(hasName("X"))); + auto *Foo = FirstDeclMatcher().match( + ToTU, functionDecl(hasName("foo"))); + DeclContext *FooDC = Foo->getDeclContext(); + DeclContext *FooLexicalDC = Foo->getLexicalDeclContext(); + ASSERT_EQ(cast(FooLexicalDC), X->getTemplatedDecl()); + ASSERT_EQ(cast(FooDC), ToTU); + DeclarationName FooName = Foo->getDeclName(); + + // Cannot find in the LookupTable of its DC (TUDecl) + SmallVector FoundDecls; + FooDC->getRedeclContext()->localUncachedLookup(FooName, FoundDecls); + EXPECT_EQ(FoundDecls.size(), 0u); + + // Cannot find in the LookupTable of its LexicalDC (X) + FooLexicalDC->getRedeclContext()->localUncachedLookup(FooName, FoundDecls); + EXPECT_EQ(FoundDecls.size(), 0u); + + // Can't find in the list of Decls of the DC. + EXPECT_EQ(findInDeclListOfDC(FooDC, FooName), nullptr); + + // Can't find in the list of Decls of the LexicalDC + EXPECT_EQ(findInDeclListOfDC(FooLexicalDC, FooName), nullptr); + + // ASTImporter specific lookup finds it. + ASTImporterLookupTable LT(*ToTU); + auto Res = LT.lookup(FooDC, Foo->getDeclName()); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), Foo); +} + +TEST_P(ASTImporterLookupTableTest, + FwdDeclStructShouldBeFoundByImporterSpecificLookup) { + TranslationUnitDecl *ToTU = + getToTuDecl("struct A { struct Foo *p; };", Lang_C); + auto *Foo = + FirstDeclMatcher().match(ToTU, recordDecl(hasName("Foo"))); + auto *A = + FirstDeclMatcher().match(ToTU, recordDecl(hasName("A"))); + DeclContext *FooDC = Foo->getDeclContext(); + DeclContext *FooLexicalDC = Foo->getLexicalDeclContext(); + ASSERT_EQ(cast(FooLexicalDC), A); + ASSERT_EQ(cast(FooDC), ToTU); + DeclarationName FooName = Foo->getDeclName(); + + // Cannot find in the LookupTable of its DC (TUDecl). + SmallVector FoundDecls; + FooDC->getRedeclContext()->localUncachedLookup(FooName, FoundDecls); + EXPECT_EQ(FoundDecls.size(), 0u); + + // Cannot find in the LookupTable of its LexicalDC (A). + FooLexicalDC->getRedeclContext()->localUncachedLookup(FooName, FoundDecls); + EXPECT_EQ(FoundDecls.size(), 0u); + + // Can't find in the list of Decls of the DC. + EXPECT_EQ(findInDeclListOfDC(FooDC, FooName), nullptr); + + // Can find in the list of Decls of the LexicalDC. + EXPECT_EQ(findInDeclListOfDC(FooLexicalDC, FooName), Foo); + + // ASTImporter specific lookup finds it. + ASTImporterLookupTable LT(*ToTU); + auto Res = LT.lookup(FooDC, Foo->getDeclName()); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), Foo); +} + +TEST_P(ASTImporterLookupTableTest, LookupFindsNamesInDifferentDC) { + TranslationUnitDecl *ToTU = + getToTuDecl("int V; struct A { int V; }; struct B { int V; };", Lang_C); + DeclarationName VName = FirstDeclMatcher() + .match(ToTU, varDecl(hasName("V"))) + ->getDeclName(); + auto *A = + FirstDeclMatcher().match(ToTU, recordDecl(hasName("A"))); + auto *B = + FirstDeclMatcher().match(ToTU, recordDecl(hasName("B"))); + + ASTImporterLookupTable LT(*ToTU); + + auto Res = LT.lookup(cast(A), VName); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), FirstDeclMatcher().match( + ToTU, fieldDecl(hasName("V"), + hasParent(recordDecl(hasName("A")))))); + Res = LT.lookup(cast(B), VName); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), FirstDeclMatcher().match( + ToTU, fieldDecl(hasName("V"), + hasParent(recordDecl(hasName("B")))))); + Res = LT.lookup(ToTU, VName); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), FirstDeclMatcher().match( + ToTU, varDecl(hasName("V"), + hasParent(translationUnitDecl())))); +} + +TEST_P(ASTImporterLookupTableTest, LookupFindsOverloadedNames) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + void foo(); + void foo(int); + void foo(int, int); + )", + Lang_CXX); + + ASTImporterLookupTable LT(*ToTU); + auto *F0 = FirstDeclMatcher().match(ToTU, functionDecl()); + auto *F2 = LastDeclMatcher().match(ToTU, functionDecl()); + DeclarationName Name = F0->getDeclName(); + auto Res = LT.lookup(ToTU, Name); + EXPECT_EQ(Res.size(), 3u); + EXPECT_EQ(Res.count(F0), 1u); + EXPECT_EQ(Res.count(F2), 1u); +} + +static const RecordDecl * getRecordDeclOfFriend(FriendDecl *FD) { + QualType Ty = FD->getFriendType()->getType(); + QualType NamedTy = cast(Ty)->getNamedType(); + return cast(NamedTy)->getDecl(); +} + +TEST_P(ASTImporterLookupTableTest, LookupFindsFwdFriendClassDecl) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + class Y { friend class F; }; + )", + Lang_CXX); + + // In this case, the CXXRecordDecl is hidden, the FriendDecl is not a parent. + // So we must dig up the underlying CXXRecordDecl. + ASTImporterLookupTable LT(*ToTU); + auto *FriendD = FirstDeclMatcher().match(ToTU, friendDecl()); + const RecordDecl *RD = getRecordDeclOfFriend(FriendD); + auto *Y = FirstDeclMatcher().match( + ToTU, cxxRecordDecl(hasName("Y"))); + + DeclarationName Name = RD->getDeclName(); + auto Res = LT.lookup(ToTU, Name); + EXPECT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), RD); + + Res = LT.lookup(Y, Name); + EXPECT_EQ(Res.size(), 0u); +} + +TEST_P(ASTImporterLookupTableTest, LookupFindsFwdFriendClassTemplateDecl) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + class Y { template friend class F; }; + )", + Lang_CXX); + + ASTImporterLookupTable LT(*ToTU); + auto *F = FirstDeclMatcher().match( + ToTU, classTemplateDecl(hasName("F"))); + DeclarationName Name = F->getDeclName(); + auto Res = LT.lookup(ToTU, Name); + EXPECT_EQ(Res.size(), 2u); + EXPECT_EQ(Res.count(F), 1u); + EXPECT_EQ(Res.count(F->getTemplatedDecl()), 1u); +} + +TEST_P(ASTImporterLookupTableTest, DependentFriendClass) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + template + class F; + + template + class Y { + friend class F; + }; + )", + Lang_CXX); + + ASTImporterLookupTable LT(*ToTU); + auto *F = FirstDeclMatcher().match( + ToTU, classTemplateDecl(hasName("F"))); + DeclarationName Name = F->getDeclName(); + auto Res = LT.lookup(ToTU, Name); + EXPECT_EQ(Res.size(), 2u); + EXPECT_EQ(Res.count(F), 1u); + EXPECT_EQ(Res.count(F->getTemplatedDecl()), 1u); +} + +TEST_P(ASTImporterLookupTableTest, FriendClassTemplateSpecialization) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + template + class F; + + class Y { + friend class F; + }; + )", + Lang_CXX); + + ASTImporterLookupTable LT(*ToTU); + auto *F = FirstDeclMatcher().match( + ToTU, classTemplateDecl(hasName("F"))); + DeclarationName Name = F->getDeclName(); + auto Res = LT.lookup(ToTU, Name); + ASSERT_EQ(Res.size(), 3u); + EXPECT_EQ(Res.count(F), 1u); + EXPECT_EQ(Res.count(F->getTemplatedDecl()), 1u); + EXPECT_EQ(Res.count(*F->spec_begin()), 1u); +} + +TEST_P(ASTImporterLookupTableTest, LookupFindsFwdFriendFunctionDecl) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + class Y { friend void F(); }; + )", + Lang_CXX); + + ASTImporterLookupTable LT(*ToTU); + auto *F = + FirstDeclMatcher().match(ToTU, functionDecl(hasName("F"))); + DeclarationName Name = F->getDeclName(); + auto Res = LT.lookup(ToTU, Name); + EXPECT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), F); +} + +TEST_P(ASTImporterLookupTableTest, + LookupFindsDeclsInClassTemplateSpecialization) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + template + struct X { + int F; + }; + void foo() { + X xc; + } + )", + Lang_CXX); + + ASTImporterLookupTable LT(*ToTU); + + auto *Template = FirstDeclMatcher().match( + ToTU, classTemplateDecl(hasName("X"))); + auto *FieldInTemplate = FirstDeclMatcher().match( + ToTU, + fieldDecl(hasParent(cxxRecordDecl(hasParent(classTemplateDecl()))))); + + auto *Spec = FirstDeclMatcher().match( + ToTU, classTemplateSpecializationDecl(hasName("X"))); + FieldDecl *FieldInSpec = *Spec->field_begin(); + ASSERT_TRUE(FieldInSpec); + + DeclarationName Name = FieldInSpec->getDeclName(); + auto TemplateDC = cast(Template->getTemplatedDecl()); + + SmallVector FoundDecls; + TemplateDC->getRedeclContext()->localUncachedLookup(Name, FoundDecls); + EXPECT_EQ(FoundDecls.size(), 1u); + EXPECT_EQ(FoundDecls[0], FieldInTemplate); + + auto Res = LT.lookup(TemplateDC, Name); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), FieldInTemplate); + + cast(Spec)->getRedeclContext()->localUncachedLookup(Name, + FoundDecls); + EXPECT_EQ(FoundDecls.size(), 1u); + EXPECT_EQ(FoundDecls[0], FieldInSpec); + + Res = LT.lookup(cast(Spec), Name); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), FieldInSpec); +} + +TEST_P(ASTImporterLookupTableTest, LookupFindsFwdFriendFunctionTemplateDecl) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + class Y { template friend void F(); }; + )", + Lang_CXX); + + ASTImporterLookupTable LT(*ToTU); + auto *F = FirstDeclMatcher().match( + ToTU, functionTemplateDecl(hasName("F"))); + DeclarationName Name = F->getDeclName(); + auto Res = LT.lookup(ToTU, Name); + EXPECT_EQ(Res.size(), 2u); + EXPECT_EQ(Res.count(F), 1u); + EXPECT_EQ(Res.count(F->getTemplatedDecl()), 1u); +} + +TEST_P(ASTImporterLookupTableTest, MultipleBefriendingClasses) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + struct X; + struct A { + friend struct X; + }; + struct B { + friend struct X; + }; + )", + Lang_CXX); + + ASTImporterLookupTable LT(*ToTU); + auto *X = FirstDeclMatcher().match( + ToTU, cxxRecordDecl(hasName("X"))); + auto *FriendD0 = FirstDeclMatcher().match(ToTU, friendDecl()); + auto *FriendD1 = LastDeclMatcher().match(ToTU, friendDecl()); + const RecordDecl *RD0 = getRecordDeclOfFriend(FriendD0); + const RecordDecl *RD1 = getRecordDeclOfFriend(FriendD1); + ASSERT_EQ(RD0, RD1); + ASSERT_EQ(RD1, X); + + DeclarationName Name = X->getDeclName(); + auto Res = LT.lookup(ToTU, Name); + EXPECT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), X); +} + +TEST_P(ASTImporterLookupTableTest, EnumConstantDecl) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + enum E { + A, + B + }; + )", + Lang_C); + + ASTImporterLookupTable LT(*ToTU); + auto *E = FirstDeclMatcher().match(ToTU, enumDecl(hasName("E"))); + auto *A = FirstDeclMatcher().match( + ToTU, enumConstantDecl(hasName("A"))); + + DeclarationName Name = A->getDeclName(); + // Redecl context is the TU. + ASSERT_EQ(E->getRedeclContext(), ToTU); + + SmallVector FoundDecls; + // Normal lookup finds in the DC. + E->localUncachedLookup(Name, FoundDecls); + EXPECT_EQ(FoundDecls.size(), 1u); + + // Normal lookup finds in the Redecl context. + ToTU->localUncachedLookup(Name, FoundDecls); + EXPECT_EQ(FoundDecls.size(), 1u); + + // Import specific lookup finds in the DC. + auto Res = LT.lookup(E, Name); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), A); + + // Import specific lookup finds in the Redecl context. + Res = LT.lookup(ToTU, Name); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), A); +} + +TEST_P(ASTImporterLookupTableTest, LookupSearchesInTheWholeRedeclChain) { + TranslationUnitDecl *ToTU = getToTuDecl( + R"( + namespace N { + int A; + } + namespace N { + } + )", + Lang_CXX); + auto *N1 = + LastDeclMatcher().match(ToTU, namespaceDecl(hasName("N"))); + auto *A = FirstDeclMatcher().match(ToTU, varDecl(hasName("A"))); + DeclarationName Name = A->getDeclName(); + + ASTImporterLookupTable LT(*ToTU); + auto Res = LT.lookup(N1, Name); + ASSERT_EQ(Res.size(), 1u); + EXPECT_EQ(*Res.begin(), A); +} + INSTANTIATE_TEST_CASE_P(ParameterizedTests, DeclContextTest, ::testing::Values(ArgVector()), ); @@ -4352,6 +4872,9 @@ auto DefaultTestValuesForRunOptions = ::testing::Values( ArgVector{"-fms-compatibility"}, ArgVector{"-fdelayed-template-parsing", "-fms-compatibility"}); +INSTANTIATE_TEST_CASE_P(ParameterizedTests, ASTImporterLookupTableTest, + DefaultTestValuesForRunOptions, ); + INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportExpr, DefaultTestValuesForRunOptions, ); @@ -4367,10 +4890,10 @@ INSTANTIATE_TEST_CASE_P(ParameterizedTests, ASTImporterTestBase, INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFunctions, DefaultTestValuesForRunOptions, ); -INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFriendFunctions, +INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportClasses, DefaultTestValuesForRunOptions, ); -INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportClasses, +INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportFriendFunctions, DefaultTestValuesForRunOptions, ); INSTANTIATE_TEST_CASE_P(ParameterizedTests, ImportClassTemplates,