diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -3646,6 +3646,54 @@ return ToIndirectField; } +/// Used as return type of getFriendCountAndPosition. +struct FriendCountAndPosition { + /// Number of similar looking friends. + unsigned int TotalCount; + /// Index of the specific FriendDecl. + unsigned int IndexOfDecl; +}; + +template +FriendCountAndPosition getFriendCountAndPosition( + const FriendDecl *FD, + std::function GetCanTypeOrDecl) { + unsigned int FriendCount = 0; + llvm::Optional FriendPosition; + const auto *RD = cast(FD->getLexicalDeclContext()); + + T TypeOrDecl = GetCanTypeOrDecl(FD); + + for (const FriendDecl *FoundFriend : RD->friends()) { + if (FoundFriend == FD) { + FriendPosition = FriendCount; + ++FriendCount; + } else if (!FoundFriend->getFriendDecl() == !FD->getFriendDecl() && + GetCanTypeOrDecl(FoundFriend) == TypeOrDecl) { + ++FriendCount; + } + } + + assert(FriendPosition && "Friend decl not found in own parent."); + + return {FriendCount, *FriendPosition}; +} + +FriendCountAndPosition getFriendCountAndPosition(const FriendDecl *FD) { + if (FD->getFriendType()) + return getFriendCountAndPosition(FD, [](const FriendDecl *F) { + if (TypeSourceInfo *TSI = F->getFriendType()) + return TSI->getType().getCanonicalType(); + llvm_unreachable("Wrong friend object type."); + }); + else + return getFriendCountAndPosition(FD, [](const FriendDecl *F) { + if (Decl *D = F->getFriendDecl()) + return D->getCanonicalDecl(); + llvm_unreachable("Wrong friend object type."); + }); +} + ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) { // Import the major distinguishing characteristics of a declaration. DeclContext *DC, *LexicalDC; @@ -3654,25 +3702,37 @@ // Determine whether we've already imported this decl. // FriendDecl is not a NamedDecl so we cannot use lookup. - auto *RD = cast(DC); + // We try to maintain order and count of redundant friend declarations. + const auto *RD = cast(DC); FriendDecl *ImportedFriend = RD->getFirstFriend(); + SmallVector ImportedEquivalentFriends; while (ImportedFriend) { + bool Match = false; if (D->getFriendDecl() && ImportedFriend->getFriendDecl()) { - if (IsStructuralMatch(D->getFriendDecl(), ImportedFriend->getFriendDecl(), - /*Complain=*/false)) - return Importer.MapImported(D, ImportedFriend); - + Match = + IsStructuralMatch(D->getFriendDecl(), ImportedFriend->getFriendDecl(), + /*Complain=*/false); } else if (D->getFriendType() && ImportedFriend->getFriendType()) { - if (Importer.IsStructurallyEquivalent( - D->getFriendType()->getType(), - ImportedFriend->getFriendType()->getType(), true)) - return Importer.MapImported(D, ImportedFriend); + Match = Importer.IsStructurallyEquivalent( + D->getFriendType()->getType(), + ImportedFriend->getFriendType()->getType(), /*Complain=*/false); } + if (Match) + ImportedEquivalentFriends.push_back(ImportedFriend); + ImportedFriend = ImportedFriend->getNextFriend(); } + FriendCountAndPosition CountAndPosition = getFriendCountAndPosition(D); + + assert(ImportedEquivalentFriends.size() <= CountAndPosition.TotalCount && + "Class with non-matching friends is imported, ODR check wrong?"); + if (ImportedEquivalentFriends.size() == CountAndPosition.TotalCount) + return Importer.MapImported( + D, ImportedEquivalentFriends[CountAndPosition.IndexOfDecl]); // Not found. Create it. + // The declarations will be put into order later by ImportDeclContext. FriendDecl::FriendUnion ToFU; if (NamedDecl *FriendD = D->getFriendDecl()) { NamedDecl *ToFriendD; diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp --- a/clang/unittests/AST/ASTImporterTest.cpp +++ b/clang/unittests/AST/ASTImporterTest.cpp @@ -3974,6 +3974,56 @@ EXPECT_EQ(ImportedFwd, ImportedDef->getPreviousDecl()); } +TEST_P(ImportFriendClasses, ImportOfRepeatedFriendType) { + const char *Code = + R"( + class Container { + friend class X; + friend class X; + }; + )"; + Decl *ToTu = getToTuDecl(Code, Lang_CXX03); + Decl *FromTu = getTuDecl(Code, Lang_CXX03, "from.cc"); + + auto *ToFriend1 = FirstDeclMatcher().match(ToTu, friendDecl()); + auto *ToFriend2 = LastDeclMatcher().match(ToTu, friendDecl()); + auto *FromFriend1 = + FirstDeclMatcher().match(FromTu, friendDecl()); + auto *FromFriend2 = LastDeclMatcher().match(FromTu, friendDecl()); + + FriendDecl *ToImportedFriend1 = Import(FromFriend1, Lang_CXX03); + FriendDecl *ToImportedFriend2 = Import(FromFriend2, Lang_CXX03); + + EXPECT_NE(ToImportedFriend1, ToImportedFriend2); + EXPECT_EQ(ToFriend1, ToImportedFriend1); + EXPECT_EQ(ToFriend2, ToImportedFriend2); +} + +TEST_P(ImportFriendClasses, ImportOfRepeatedFriendDecl) { + const char *Code = + R"( + class Container { + friend void f(); + friend void f(); + }; + )"; + Decl *ToTu = getToTuDecl(Code, Lang_CXX03); + Decl *FromTu = getTuDecl(Code, Lang_CXX03, "from.cc"); + + auto *ToFriend1 = FirstDeclMatcher().match(ToTu, friendDecl()); + auto *ToFriend2 = LastDeclMatcher().match(ToTu, friendDecl()); + auto *FromFriend1 = + FirstDeclMatcher().match(FromTu, friendDecl()); + auto *FromFriend2 = LastDeclMatcher().match(FromTu, friendDecl()); + + FriendDecl *ToImportedFriend1 = Import(FromFriend1, Lang_CXX03); + FriendDecl *ToImportedFriend2 = Import(FromFriend2, Lang_CXX03); + + EXPECT_NE(ToImportedFriend1, ToImportedFriend2); + EXPECT_EQ(ToFriend1, ToImportedFriend1); + EXPECT_EQ(ToFriend2, ToImportedFriend2); +} + TEST_P(ASTImporterOptionSpecificTestBase, FriendFunInClassTemplate) { auto *Code = R"( template diff --git a/clang/unittests/AST/StructuralEquivalenceTest.cpp b/clang/unittests/AST/StructuralEquivalenceTest.cpp --- a/clang/unittests/AST/StructuralEquivalenceTest.cpp +++ b/clang/unittests/AST/StructuralEquivalenceTest.cpp @@ -759,6 +759,27 @@ EXPECT_FALSE(testStructuralMatch(t)); } +TEST_F(StructuralEquivalenceRecordTest, SameFriendMultipleTimes) { + auto t = makeNamedDecls("struct foo { friend class X; };", + "struct foo { friend class X; friend class X; };", + Lang_CXX11); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceRecordTest, SameFriendsDifferentOrder) { + auto t = makeNamedDecls("struct foo { friend class X; friend class Y; };", + "struct foo { friend class Y; friend class X; };", + Lang_CXX11); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceRecordTest, SameFriendsSameOrder) { + auto t = makeNamedDecls("struct foo { friend class X; friend class Y; };", + "struct foo { friend class X; friend class Y; };", + Lang_CXX11); + EXPECT_TRUE(testStructuralMatch(t)); +} + struct StructuralEquivalenceLambdaTest : StructuralEquivalenceTest {}; TEST_F(StructuralEquivalenceLambdaTest, LambdaClassesWithDifferentMethods) {