Skip to content

Commit

Permalink
[AST] Structural equivalence of methods
Browse files Browse the repository at this point in the history
Summary:
Added structural equivalence check for C++ methods.
Improved structural equivalence tests.
Added related ASTImporter tests.

Reviewers: a.sidorin, szepet, xazax.hun, martong, a_sidorin

Reviewed By: martong, a_sidorin

Subscribers: a_sidorin, rnkovacs, cfe-commits

Differential Revision: https://reviews.llvm.org/D48628

llvm-svn: 336776
  • Loading branch information
balazske committed Jul 11, 2018
1 parent e21cfa7 commit c7797c4
Show file tree
Hide file tree
Showing 4 changed files with 619 additions and 73 deletions.
24 changes: 17 additions & 7 deletions clang/lib/AST/ASTImporter.cpp
Expand Up @@ -230,6 +230,7 @@ namespace clang {
bool IsStructuralMatch(EnumConstantDecl *FromEC, EnumConstantDecl *ToEC);
bool IsStructuralMatch(FunctionTemplateDecl *From,
FunctionTemplateDecl *To);
bool IsStructuralMatch(FunctionDecl *From, FunctionDecl *To);
bool IsStructuralMatch(ClassTemplateDecl *From, ClassTemplateDecl *To);
bool IsStructuralMatch(VarTemplateDecl *From, VarTemplateDecl *To);
Decl *VisitDecl(Decl *D);
Expand Down Expand Up @@ -1525,6 +1526,13 @@ bool ASTNodeImporter::IsStructuralMatch(FunctionTemplateDecl *From,
return Ctx.IsStructurallyEquivalent(From, To);
}

bool ASTNodeImporter::IsStructuralMatch(FunctionDecl *From, FunctionDecl *To) {
StructuralEquivalenceContext Ctx(
Importer.getFromContext(), Importer.getToContext(),
Importer.getNonEquivalentDecls(), false, false);
return Ctx.IsStructurallyEquivalent(From, To);
}

bool ASTNodeImporter::IsStructuralMatch(EnumConstantDecl *FromEC,
EnumConstantDecl *ToEC) {
const llvm::APSInt &FromVal = FromEC->getInitVal();
Expand Down Expand Up @@ -2433,13 +2441,15 @@ Decl *ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
if (auto *FoundFunction = dyn_cast<FunctionDecl>(FoundDecl)) {
if (FoundFunction->hasExternalFormalLinkage() &&
D->hasExternalFormalLinkage()) {
if (Importer.IsStructurallyEquivalent(D->getType(),
FoundFunction->getType())) {
if (D->doesThisDeclarationHaveABody() &&
FoundFunction->hasBody())
return Importer.Imported(D, FoundFunction);
FoundByLookup = FoundFunction;
break;
if (IsStructuralMatch(D, FoundFunction)) {
const FunctionDecl *Definition = nullptr;
if (D->doesThisDeclarationHaveABody() &&
FoundFunction->hasBody(Definition)) {
return Importer.Imported(
D, const_cast<FunctionDecl *>(Definition));
}
FoundByLookup = FoundFunction;
break;
}

// FIXME: Check for overloading more carefully, e.g., by boosting
Expand Down
94 changes: 83 additions & 11 deletions clang/lib/AST/ASTStructuralEquivalence.cpp
Expand Up @@ -250,6 +250,9 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if (T1.isNull() || T2.isNull())
return T1.isNull() && T2.isNull();

QualType OrigT1 = T1;
QualType OrigT2 = T2;

if (!Context.StrictTypeSpelling) {
// We aren't being strict about token-to-token equivalence of types,
// so map down to the canonical type.
Expand Down Expand Up @@ -422,6 +425,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
case Type::FunctionProto: {
const auto *Proto1 = cast<FunctionProtoType>(T1);
const auto *Proto2 = cast<FunctionProtoType>(T2);

if (Proto1->getNumParams() != Proto2->getNumParams())
return false;
for (unsigned I = 0, N = Proto1->getNumParams(); I != N; ++I) {
Expand All @@ -431,23 +435,33 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
}
if (Proto1->isVariadic() != Proto2->isVariadic())
return false;
if (Proto1->getExceptionSpecType() != Proto2->getExceptionSpecType())

if (Proto1->getTypeQuals() != Proto2->getTypeQuals())
return false;
if (Proto1->getExceptionSpecType() == EST_Dynamic) {
if (Proto1->getNumExceptions() != Proto2->getNumExceptions())

// Check exceptions, this information is lost in canonical type.
const auto *OrigProto1 =
cast<FunctionProtoType>(OrigT1.getDesugaredType(Context.FromCtx));
const auto *OrigProto2 =
cast<FunctionProtoType>(OrigT2.getDesugaredType(Context.ToCtx));
auto Spec1 = OrigProto1->getExceptionSpecType();
auto Spec2 = OrigProto2->getExceptionSpecType();

if (Spec1 != Spec2)
return false;
if (Spec1 == EST_Dynamic) {
if (OrigProto1->getNumExceptions() != OrigProto2->getNumExceptions())
return false;
for (unsigned I = 0, N = Proto1->getNumExceptions(); I != N; ++I) {
if (!IsStructurallyEquivalent(Context, Proto1->getExceptionType(I),
Proto2->getExceptionType(I)))
for (unsigned I = 0, N = OrigProto1->getNumExceptions(); I != N; ++I) {
if (!IsStructurallyEquivalent(Context, OrigProto1->getExceptionType(I),
OrigProto2->getExceptionType(I)))
return false;
}
} else if (isComputedNoexcept(Proto1->getExceptionSpecType())) {
if (!IsStructurallyEquivalent(Context, Proto1->getNoexceptExpr(),
Proto2->getNoexceptExpr()))
} else if (isComputedNoexcept(Spec1)) {
if (!IsStructurallyEquivalent(Context, OrigProto1->getNoexceptExpr(),
OrigProto2->getNoexceptExpr()))
return false;
}
if (Proto1->getTypeQuals() != Proto2->getTypeQuals())
return false;

// Fall through to check the bits common with FunctionNoProtoType.
LLVM_FALLTHROUGH;
Expand Down Expand Up @@ -830,6 +844,56 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return true;
}

/// Determine structural equivalence of two methodss.
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
CXXMethodDecl *Method1,
CXXMethodDecl *Method2) {
bool PropertiesEqual =
Method1->getDeclKind() == Method2->getDeclKind() &&
Method1->getRefQualifier() == Method2->getRefQualifier() &&
Method1->getAccess() == Method2->getAccess() &&
Method1->getOverloadedOperator() == Method2->getOverloadedOperator() &&
Method1->isStatic() == Method2->isStatic() &&
Method1->isConst() == Method2->isConst() &&
Method1->isVolatile() == Method2->isVolatile() &&
Method1->isVirtual() == Method2->isVirtual() &&
Method1->isPure() == Method2->isPure() &&
Method1->isDefaulted() == Method2->isDefaulted() &&
Method1->isDeleted() == Method2->isDeleted();
if (!PropertiesEqual)
return false;
// FIXME: Check for 'final'.

if (auto *Constructor1 = dyn_cast<CXXConstructorDecl>(Method1)) {
auto *Constructor2 = cast<CXXConstructorDecl>(Method2);
if (Constructor1->isExplicit() != Constructor2->isExplicit())
return false;
}

if (auto *Conversion1 = dyn_cast<CXXConversionDecl>(Method1)) {
auto *Conversion2 = cast<CXXConversionDecl>(Method2);
if (Conversion1->isExplicit() != Conversion2->isExplicit())
return false;
if (!IsStructurallyEquivalent(Context, Conversion1->getConversionType(),
Conversion2->getConversionType()))
return false;
}

const IdentifierInfo *Name1 = Method1->getIdentifier();
const IdentifierInfo *Name2 = Method2->getIdentifier();
if (!::IsStructurallyEquivalent(Name1, Name2)) {
return false;
// TODO: Names do not match, add warning like at check for FieldDecl.
}

// Check the prototypes.
if (!::IsStructurallyEquivalent(Context,
Method1->getType(), Method2->getType()))
return false;

return true;
}

/// Determine structural equivalence of two records.
static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
RecordDecl *D1, RecordDecl *D2) {
Expand Down Expand Up @@ -1445,6 +1509,14 @@ bool StructuralEquivalenceContext::Finish() {
// Kind mismatch.
Equivalent = false;
}
} else if (auto *MD1 = dyn_cast<CXXMethodDecl>(D1)) {
if (auto *MD2 = dyn_cast<CXXMethodDecl>(D2)) {
if (!::IsStructurallyEquivalent(*this, MD1, MD2))
Equivalent = false;
} else {
// Kind mismatch.
Equivalent = false;
}
} else if (FunctionDecl *FD1 = dyn_cast<FunctionDecl>(D1)) {
if (FunctionDecl *FD2 = dyn_cast<FunctionDecl>(D2)) {
if (!::IsStructurallyEquivalent(FD1->getIdentifier(),
Expand Down
126 changes: 126 additions & 0 deletions clang/unittests/AST/ASTImporterTest.cpp
Expand Up @@ -2243,6 +2243,132 @@ TEST_P(ImportExpr, UnresolvedMemberExpr) {
compoundStmt(has(callExpr(has(unresolvedMemberExpr())))))))));
}

TEST_P(ASTImporterTestBase, ImportOfEquivalentRecord) {
Decl *ToR1;
{
Decl *FromTU = getTuDecl(
"struct A { };", Lang_CXX, "input0.cc");
auto *FromR = FirstDeclMatcher<CXXRecordDecl>().match(
FromTU, cxxRecordDecl(hasName("A")));

ToR1 = Import(FromR, Lang_CXX);
}

Decl *ToR2;
{
Decl *FromTU = getTuDecl(
"struct A { };", Lang_CXX, "input1.cc");
auto *FromR = FirstDeclMatcher<CXXRecordDecl>().match(
FromTU, cxxRecordDecl(hasName("A")));

ToR2 = Import(FromR, Lang_CXX);
}

EXPECT_EQ(ToR1, ToR2);
}

TEST_P(ASTImporterTestBase, ImportOfNonEquivalentRecord) {
Decl *ToR1;
{
Decl *FromTU = getTuDecl(
"struct A { int x; };", Lang_CXX, "input0.cc");
auto *FromR = FirstDeclMatcher<CXXRecordDecl>().match(
FromTU, cxxRecordDecl(hasName("A")));
ToR1 = Import(FromR, Lang_CXX);
}
Decl *ToR2;
{
Decl *FromTU = getTuDecl(
"struct A { unsigned x; };", Lang_CXX, "input1.cc");
auto *FromR = FirstDeclMatcher<CXXRecordDecl>().match(
FromTU, cxxRecordDecl(hasName("A")));
ToR2 = Import(FromR, Lang_CXX);
}
EXPECT_NE(ToR1, ToR2);
}

TEST_P(ASTImporterTestBase, ImportOfEquivalentField) {
Decl *ToF1;
{
Decl *FromTU = getTuDecl(
"struct A { int x; };", Lang_CXX, "input0.cc");
auto *FromF = FirstDeclMatcher<FieldDecl>().match(
FromTU, fieldDecl(hasName("x")));
ToF1 = Import(FromF, Lang_CXX);
}
Decl *ToF2;
{
Decl *FromTU = getTuDecl(
"struct A { int x; };", Lang_CXX, "input1.cc");
auto *FromF = FirstDeclMatcher<FieldDecl>().match(
FromTU, fieldDecl(hasName("x")));
ToF2 = Import(FromF, Lang_CXX);
}
EXPECT_EQ(ToF1, ToF2);
}

TEST_P(ASTImporterTestBase, ImportOfNonEquivalentField) {
Decl *ToF1;
{
Decl *FromTU = getTuDecl(
"struct A { int x; };", Lang_CXX, "input0.cc");
auto *FromF = FirstDeclMatcher<FieldDecl>().match(
FromTU, fieldDecl(hasName("x")));
ToF1 = Import(FromF, Lang_CXX);
}
Decl *ToF2;
{
Decl *FromTU = getTuDecl(
"struct A { unsigned x; };", Lang_CXX, "input1.cc");
auto *FromF = FirstDeclMatcher<FieldDecl>().match(
FromTU, fieldDecl(hasName("x")));
ToF2 = Import(FromF, Lang_CXX);
}
EXPECT_NE(ToF1, ToF2);
}

TEST_P(ASTImporterTestBase, ImportOfEquivalentMethod) {
Decl *ToM1;
{
Decl *FromTU = getTuDecl(
"struct A { void x(); }; void A::x() { }", Lang_CXX, "input0.cc");
auto *FromM = FirstDeclMatcher<FunctionDecl>().match(
FromTU, functionDecl(hasName("x"), isDefinition()));
ToM1 = Import(FromM, Lang_CXX);
}
Decl *ToM2;
{
Decl *FromTU = getTuDecl(
"struct A { void x(); }; void A::x() { }", Lang_CXX, "input1.cc");
auto *FromM = FirstDeclMatcher<FunctionDecl>().match(
FromTU, functionDecl(hasName("x"), isDefinition()));
ToM2 = Import(FromM, Lang_CXX);
}
EXPECT_EQ(ToM1, ToM2);
}

TEST_P(ASTImporterTestBase, ImportOfNonEquivalentMethod) {
Decl *ToM1;
{
Decl *FromTU = getTuDecl(
"struct A { void x(); }; void A::x() { }",
Lang_CXX, "input0.cc");
auto *FromM = FirstDeclMatcher<FunctionDecl>().match(
FromTU, functionDecl(hasName("x"), isDefinition()));
ToM1 = Import(FromM, Lang_CXX);
}
Decl *ToM2;
{
Decl *FromTU = getTuDecl(
"struct A { void x() const; }; void A::x() const { }",
Lang_CXX, "input1.cc");
auto *FromM = FirstDeclMatcher<FunctionDecl>().match(
FromTU, functionDecl(hasName("x"), isDefinition()));
ToM2 = Import(FromM, Lang_CXX);
}
EXPECT_NE(ToM1, ToM2);
}

struct DeclContextTest : ASTImporterTestBase {};

TEST_P(DeclContextTest, removeDeclOfClassTemplateSpecialization) {
Expand Down

0 comments on commit c7797c4

Please sign in to comment.