[clang][AST] Check context of record in structural equivalence.

The AST structural equivalence check did not differentiate between
a struct and a struct with same name in different namespace. When
type of a member is checked it is possible to encounter such a case
and wrongly decide that the types are similar. This problem is fixed
by check for the namespaces of a record declaration.

Reviewed By: martong

Differential Revision: https://reviews.llvm.org/D113118
This commit is contained in:
Balázs Kéri 2021-11-25 15:55:35 +01:00
parent c47108c041
commit 8e8658b19c
2 changed files with 164 additions and 0 deletions

View File

@ -1347,6 +1347,42 @@ IsStructurallyEquivalentLambdas(StructuralEquivalenceContext &Context,
return true;
}
/// Determine if context of a class is equivalent.
static bool IsRecordContextStructurallyEquivalent(RecordDecl *D1,
RecordDecl *D2) {
// The context should be completely equal, including anonymous and inline
// namespaces.
// We compare objects as part of full translation units, not subtrees of
// translation units.
DeclContext *DC1 = D1->getDeclContext()->getNonTransparentContext();
DeclContext *DC2 = D2->getDeclContext()->getNonTransparentContext();
while (true) {
// Special case: We allow a struct defined in a function to be equivalent
// with a similar struct defined outside of a function.
if ((DC1->isFunctionOrMethod() && DC2->isTranslationUnit()) ||
(DC2->isFunctionOrMethod() && DC1->isTranslationUnit()))
return true;
if (DC1->getDeclKind() != DC2->getDeclKind())
return false;
if (DC1->isTranslationUnit())
break;
if (DC1->isInlineNamespace() != DC2->isInlineNamespace())
return false;
if (const auto *ND1 = dyn_cast<NamedDecl>(DC1)) {
const auto *ND2 = cast<NamedDecl>(DC2);
if (!DC1->isInlineNamespace() &&
!IsStructurallyEquivalent(ND1->getIdentifier(), ND2->getIdentifier()))
return false;
}
DC1 = DC1->getParent()->getNonTransparentContext();
DC2 = DC2->getParent()->getNonTransparentContext();
}
return true;
}
/// Determine structural equivalence of two records.
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
RecordDecl *D1, RecordDecl *D2) {
@ -1386,6 +1422,12 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
}
}
// If the records occur in different context (namespace), these should be
// different. This is specially important if the definition of one or both
// records is missing.
if (!IsRecordContextStructurallyEquivalent(D1, D2))
return false;
// If both declarations are class template specializations, we know
// the ODR applies, so check the template and template arguments.
const auto *Spec1 = dyn_cast<ClassTemplateSpecializationDecl>(D1);

View File

@ -929,6 +929,128 @@ TEST_F(StructuralEquivalenceTest, ExplicitBoolSame) {
EXPECT_TRUE(testStructuralMatch(First, Second));
}
struct StructuralEquivalenceRecordContextTest : StructuralEquivalenceTest {};
TEST_F(StructuralEquivalenceRecordContextTest, NamespaceNoVsNamed) {
auto Decls =
makeNamedDecls("class X;", "namespace N { class X; }", Lang_CXX03, "X");
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, NamespaceNamedVsNamed) {
auto Decls = makeNamedDecls("namespace A { class X; }",
"namespace B { class X; }", Lang_CXX03, "X");
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, NamespaceAnonVsNamed) {
auto Decls = makeNamedDecls("namespace { class X; }",
"namespace N { class X; }", Lang_CXX03, "X");
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, NamespaceNoVsAnon) {
auto Decls =
makeNamedDecls("class X;", "namespace { class X; }", Lang_CXX03, "X");
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, NamespaceAnonVsAnon) {
auto Decls = makeNamedDecls("namespace { class X; }",
"namespace { class X; }", Lang_CXX03, "X");
EXPECT_TRUE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, NamespaceAnonVsAnonAnon) {
auto Decls =
makeNamedDecls("namespace { class X; }",
"namespace { namespace { class X; } }", Lang_CXX03, "X");
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest,
NamespaceNamedNamedVsNamedNamed) {
auto Decls = makeNamedDecls("namespace A { namespace N { class X; } }",
"namespace B { namespace N { class X; } }",
Lang_CXX03, "X");
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, NamespaceNamedVsInline) {
auto Decls = makeNamedDecls("namespace A { namespace A { class X; } }",
"namespace A { inline namespace A { class X; } }",
Lang_CXX17, "X");
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, NamespaceInlineVsInline) {
auto Decls = makeNamedDecls("namespace A { inline namespace A { class X; } }",
"namespace A { inline namespace B { class X; } }",
Lang_CXX17, "X");
EXPECT_TRUE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, NamespaceInlineTopLevel) {
auto Decls =
makeNamedDecls("inline namespace A { class X; } }",
"inline namespace B { class X; } }", Lang_CXX17, "X");
EXPECT_TRUE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, TransparentContext) {
auto Decls =
makeNamedDecls("extern \"C\" { class X; }", "class X;", Lang_CXX03, "X");
EXPECT_TRUE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, TransparentContextNE) {
auto Decls = makeNamedDecls("extern \"C\" { class X; }",
"namespace { class X; }", Lang_CXX03, "X");
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, TransparentContextInNamespace) {
auto Decls = makeNamedDecls("extern \"C\" { namespace N { class X; } }",
"namespace N { extern \"C\" { class X; } }",
Lang_CXX03, "X");
EXPECT_TRUE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceTest, NamespaceOfRecordMember) {
auto Decls = makeNamedDecls(
R"(
class X;
class Y { X* x; };
)",
R"(
namespace N { class X; }
class Y { N::X* x; };
)",
Lang_CXX03, "Y");
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceTest, StructDefinitionInPrototype) {
auto Decls =
makeNamedDecls("struct Param { int a; }; void foo(struct Param *p);",
"void foo(struct Param { int a; } *p);", Lang_C89);
EXPECT_TRUE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceTest, StructDefinitionInPrototypeDifferentName) {
auto Decls =
makeNamedDecls("struct Param1 { int a; }; void foo(struct Param1 *p);",
"void foo(struct Param2 { int a; } *p);", Lang_C89);
EXPECT_FALSE(testStructuralMatch(Decls));
}
TEST_F(StructuralEquivalenceRecordContextTest, RecordInsideFunction) {
auto Decls = makeNamedDecls("struct Param { int a; };",
"void f() { struct Param { int a; }; }", Lang_C89,
"Param");
EXPECT_TRUE(testStructuralMatch(Decls));
}
struct StructuralEquivalenceEnumTest : StructuralEquivalenceTest {};
TEST_F(StructuralEquivalenceEnumTest, FwdDeclEnumShouldBeEqualWithFwdDeclEnum) {