Index: clang/lib/AST/ASTStructuralEquivalence.cpp =================================================================== --- clang/lib/AST/ASTStructuralEquivalence.cpp +++ clang/lib/AST/ASTStructuralEquivalence.cpp @@ -68,7 +68,12 @@ #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprConcepts.h" +#include "clang/AST/ExprObjC.h" +#include "clang/AST/ExprOpenMP.h" #include "clang/AST/NestedNameSpecifier.h" +#include "clang/AST/StmtObjC.h" +#include "clang/AST/StmtOpenMP.h" #include "clang/AST/TemplateBase.h" #include "clang/AST/TemplateName.h" #include "clang/AST/Type.h" @@ -149,32 +154,220 @@ return true; } -/// Determine structural equivalence of two expressions. -static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, - const Expr *E1, const Expr *E2) { - if (!E1 || !E2) - return E1 == E2; +namespace { +/// Encapsulates Stmt comparison logic. +class StmtComparer { + StructuralEquivalenceContext &Context; + + // IsStmtEquivalent overloads. Each overload compares a specific statement + // and only has to compare the data that is specific to the specific statement + // class. Should only be called from TraverseStmt. - if (auto *DE1 = dyn_cast(E1)) { - auto *DE2 = dyn_cast(E2); - if (!DE2) + bool IsStmtEquivalent(const AddrLabelExpr *E1, const AddrLabelExpr *E2) { + return IsStructurallyEquivalent(Context, E1->getLabel(), E2->getLabel()); + } + + bool IsStmtEquivalent(const AtomicExpr *E1, const AtomicExpr *E2) { + return E1->getOp() == E2->getOp(); + } + + bool IsStmtEquivalent(const BinaryOperator *E1, const BinaryOperator *E2) { + return E1->getOpcode() == E2->getOpcode(); + } + + bool IsStmtEquivalent(const CallExpr *E1, const CallExpr *E2) { + // FIXME: IsStructurallyEquivalent requires non-const Decls. + Decl *Callee1 = const_cast(E1->getCalleeDecl()); + Decl *Callee2 = const_cast(E2->getCalleeDecl()); + // Compare whether both calls know their callee. + if (static_cast(Callee1) != static_cast(Callee2)) return false; + // Compare the callee. + if (static_cast(Callee1)) { + assert(Callee2); + return IsStructurallyEquivalent(Context, Callee1, Callee2); + } + return true; + } + + bool IsStmtEquivalent(const CharacterLiteral *E1, + const CharacterLiteral *E2) { + return E1->getValue() == E2->getValue() && E1->getKind() == E2->getKind(); + } + + bool IsStmtEquivalent(const ChooseExpr *E1, const ChooseExpr *E2) { + return true; // Semantics only depend on children. + } + + bool IsStmtEquivalent(const CompoundStmt *E1, const CompoundStmt *E2) { + // Number of children is actually checked by the generic children comparison + // code, but a CompoundStmt is one of the few statements where the number of + // children frequently differs and the number of statements is also always + // precomputed. Directly comparing the number of children here is thus + // just an optimization. + return E1->size() == E2->size(); + } + + bool IsStmtEquivalent(const DependentScopeDeclRefExpr *DE1, + const DependentScopeDeclRefExpr *DE2) { if (!IsStructurallyEquivalent(Context, DE1->getDeclName(), DE2->getDeclName())) return false; return IsStructurallyEquivalent(Context, DE1->getQualifier(), DE2->getQualifier()); - } else if (auto CastE1 = dyn_cast(E1)) { - auto *CastE2 = dyn_cast(E2); - if (!CastE2) + } + + bool IsStmtEquivalent(const Expr *E1, const Expr *E2) { + return IsStructurallyEquivalent(Context, E1->getType(), E2->getType()); + } + + bool IsStmtEquivalent(const ExpressionTraitExpr *E1, + const ExpressionTraitExpr *E2) { + return E1->getTrait() == E2->getTrait() && E1->getValue() == E2->getValue(); + } + + bool IsStmtEquivalent(const FloatingLiteral *E1, const FloatingLiteral *E2) { + return E1->isExact() == E2->isExact() && E1->getValue() == E2->getValue(); + } + + bool IsStmtEquivalent(const ImplicitCastExpr *CastE1, + const ImplicitCastExpr *CastE2) { + return IsStructurallyEquivalent(Context, CastE1->getType(), + CastE2->getType()); + } + + bool IsStmtEquivalent(const IntegerLiteral *E1, const IntegerLiteral *E2) { + return E1->getValue() == E2->getValue(); + } + + bool IsStmtEquivalent(const ObjCStringLiteral *E1, + const ObjCStringLiteral *E2) { + // Just wraps a StringLiteral child. + return true; + } + + bool IsStmtEquivalent(const Stmt *S1, const Stmt *S2) { return true; } + + bool IsStmtEquivalent(const SourceLocExpr *E1, const SourceLocExpr *E2) { + return E1->getIdentKind() == E2->getIdentKind(); + } + + bool IsStmtEquivalent(const StmtExpr *E1, const StmtExpr *E2) { + return E1->getTemplateDepth() == E2->getTemplateDepth(); + } + + bool IsStmtEquivalent(const StringLiteral *E1, const StringLiteral *E2) { + return E1->getBytes() == E2->getBytes(); + } + + bool IsStmtEquivalent(const SubstNonTypeTemplateParmExpr *E1, + const SubstNonTypeTemplateParmExpr *E2) { + return IsStructurallyEquivalent(Context, E1->getParameter(), + E2->getParameter()); + } + + bool IsStmtEquivalent(const SubstNonTypeTemplateParmPackExpr *E1, + const SubstNonTypeTemplateParmPackExpr *E2) { + return IsStructurallyEquivalent(Context, E1->getArgumentPack(), + E2->getArgumentPack()); + } + + bool IsStmtEquivalent(const TypeTraitExpr *E1, const TypeTraitExpr *E2) { + if (E1->getTrait() != E2->getTrait()) return false; - if (!IsStructurallyEquivalent(Context, CastE1->getType(), - CastE2->getType())) + + for (auto Pair : zip_longest(E1->getArgs(), E2->getArgs())) { + Optional Child1 = std::get<0>(Pair); + Optional Child2 = std::get<1>(Pair); + // Different number of args. + if (!Child1 || !Child2) + return false; + + if (!IsStructurallyEquivalent(Context, (*Child1)->getType(), + (*Child2)->getType())) + return false; + } + return true; + } + + bool IsStmtEquivalent(const UnaryExprOrTypeTraitExpr *E1, + const UnaryExprOrTypeTraitExpr *E2) { + if (E1->getKind() != E2->getKind()) + return false; + return IsStructurallyEquivalent(Context, E1->getTypeOfArgument(), + E2->getTypeOfArgument()); + } + + bool IsStmtEquivalent(const UnaryOperator *E1, const UnaryOperator *E2) { + return E1->getOpcode() == E2->getOpcode(); + } + + bool IsStmtEquivalent(const VAArgExpr *E1, const VAArgExpr *E2) { + // Semantics only depend on children. + return true; + } + + /// End point of the traversal chain. + bool TraverseStmt(const Stmt *S1, const Stmt *S2) { return true; } + + // Create traversal methods that traverse the class hierarchy and return + // the accumulated result of the comparison. +#define STMT(CLASS, PARENT) \ + bool TraverseStmt(const CLASS *S1, const CLASS *S2) { \ + if (!TraverseStmt(static_cast(S1), \ + static_cast(S2))) \ + return false; \ + return IsStmtEquivalent(S1, S2); \ + } +#include "clang/AST/StmtNodes.inc" + +public: + StmtComparer(StructuralEquivalenceContext &C) : Context(C) {} + + /// Determine whether two statements are equivalent. The statements have to + /// be of the same kind. The children of the statements and their properties + /// are not compared by this function. + bool IsEquivalent(const Stmt *S1, const Stmt *S2) { + if (S1->getStmtClass() != S2->getStmtClass()) + return false; + + // Dispatch to the specific traversal method for the actual statement kind. + switch (S1->getStmtClass()) { + case Stmt::NoStmtClass: + llvm_unreachable("Invalid Stmt class?"); +#define STMT(CLASS, PARENT) \ + case Stmt::StmtClass::CLASS##Class: \ + return TraverseStmt(static_cast(S1), \ + static_cast(S2)); +#define ABSTRACT_STMT(S) +#include "clang/AST/StmtNodes.inc" + } + } +}; +} // namespace + +/// Determine structural equivalence of two statements. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + const Stmt *S1, const Stmt *S2) { + if (!S1 || !S2) + return S1 == S2; + + // Compare the statements itself. + StmtComparer Comparer(Context); + if (!Comparer.IsEquivalent(S1, S2)) + return false; + + // Iterate over the children of both statements and also compare them. + for (auto Pair : zip_longest(S1->children(), S2->children())) { + Optional Child1 = std::get<0>(Pair); + Optional Child2 = std::get<1>(Pair); + // One of the statements has a different amount of children than the other, + // so the statements can't be equivalent. + if (!Child1 || !Child2) + return false; + if (!IsStructurallyEquivalent(Context, *Child1, *Child2)) return false; - return IsStructurallyEquivalent(Context, CastE1->getSubExpr(), - CastE2->getSubExpr()); } - // FIXME: Handle other kind of expressions! return true; } @@ -1626,6 +1819,13 @@ if (!IsStructurallyEquivalent(Context, D1->getType(), D2->getType())) return false; + // Compare the function bodies. + if (D1->doesThisDeclarationHaveABody() && + D2->doesThisDeclarationHaveABody()) { + if (!IsStructurallyEquivalent(Context, D1->getBody(), D2->getBody())) + return false; + } + return true; } Index: clang/unittests/AST/StructuralEquivalenceTest.cpp =================================================================== --- clang/unittests/AST/StructuralEquivalenceTest.cpp +++ clang/unittests/AST/StructuralEquivalenceTest.cpp @@ -80,6 +80,22 @@ return makeDecls(SrcCode0, SrcCode1, Lang, Matcher); } + /// Parses the code as expressions and returns them wrapped in FunctionDecls. + /// The prefix code can contain top level declarations that are prepended + /// before the function declaration + std::tuple + makeStmts(const std::string &SrcCode0, const std::string &SrcCode1, + TestLanguage Lang, const std::string &Prefix0 = "", + const std::string &Prefix1 = "") { + auto Wrap = [](const std::string &Src) { + return "void wrapped() {" + Src + ";}"; + }; + std::string WrappedSource0 = Prefix0 + Wrap(SrcCode0); + std::string WrappedSource1 = Prefix1 + Wrap(SrcCode1); + auto Matcher = namedDecl(hasName("wrapped")); + return makeDecls(WrappedSource0, WrappedSource1, Lang, Matcher); + } + bool testStructuralMatch(Decl *D0, Decl *D1) { llvm::DenseSet> NonEquivalentDecls01; llvm::DenseSet> NonEquivalentDecls10; @@ -1375,5 +1391,202 @@ findDeclPair(TU, functionDecl(hasName("x"))))); } +struct StructuralEquivalenceStmtTest : StructuralEquivalenceTest {}; + +TEST_F(StructuralEquivalenceStmtTest, AddrLabelExpr) { + auto t = makeStmts("lbl: &&lbl;", "lbl: &&lbl;", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, AddrLabelExprDifferentLabel) { + auto t = makeStmts("lbl1: lbl2: &&lbl1;", "lbl1: lbl2: &&lbl2;", Lang_CXX03); + // FIXME: Should be false. LabelDecl are incorrectly matched. + EXPECT_TRUE(testStructuralMatch(t)); +} + +static const std::string MemoryOrderSrc = R"( +enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst +}; +)"; + +TEST_F(StructuralEquivalenceStmtTest, AtomicExpr) { + std::string Prefix = "char a, b; " + MemoryOrderSrc; + auto t = + makeStmts("__atomic_load(&a, &b, memory_order_seq_cst);", + "__atomic_load(&a, &b, memory_order_seq_cst);", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, AtomicExprDifferentOp) { + std::string Prefix = "char a, b; " + MemoryOrderSrc; + auto t = + makeStmts("__atomic_load(&a, &b, memory_order_seq_cst);", + "__atomic_store(&a, &b, memory_order_seq_cst);", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, BinaryOperator) { + auto t = makeStmts("1 + 1", "1 + 1", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, BinaryOperatorDifferentOps) { + auto t = makeStmts("1 + 1", "1 - 1", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, CallExpr) { + std::string FunctionSrc = "int func();\n"; + auto t = makeStmts("func()", "func()", Lang_CXX03, FunctionSrc, FunctionSrc); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, CallExprDifferentCallee) { + std::string FunctionSrc = "int func1(); int func2();\n"; + auto t = + makeStmts("func1()", "func2()", Lang_CXX03, FunctionSrc, FunctionSrc); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, CharacterLiteral) { + auto t = makeStmts("'a'", "'a'", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, CharacterLiteralDifferentValues) { + auto t = makeStmts("'a'", "'b'", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, ExpressionTraitExpr) { + auto t = makeStmts("__is_lvalue_expr(1)", "__is_lvalue_expr(1)", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, ExpressionTraitExprDifferentKind) { + auto t = makeStmts("__is_lvalue_expr(1)", "__is_rvalue_expr(1)", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, FloatingLiteral) { + auto t = makeStmts("1.0", "1.0", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, FloatingLiteralDifferentSpelling) { + auto t = makeStmts("0x10.1p0", "16.0625", Lang_CXX17); + // Same value but with different spelling is equivalent. + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, FloatingLiteralDifferentType) { + auto t = makeStmts("1.0", "1.0f", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, FloatingLiteralDifferentValue) { + auto t = makeStmts("1.01", "1.0", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, IntegerLiteral) { + auto t = makeStmts("1", "1", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, IntegerLiteralDifferentSpelling) { + auto t = makeStmts("1", "0x1", Lang_CXX03); + // Same value but with different spelling is equivalent. + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, IntegerLiteralDifferentValue) { + auto t = makeStmts("1", "2", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, IntegerLiteralDifferentTypes) { + auto t = makeStmts("1", "1L", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, ObjCStringLiteral) { + auto t = makeStmts("@\"a\"", "@\"a\"", Lang_OBJCXX); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, ObjCStringLiteralDifferentContent) { + auto t = makeStmts("@\"a\"", "@\"b\"", Lang_OBJCXX); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, StringLiteral) { + auto t = makeStmts("\"a\"", "\"a\"", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, StringLiteralDifferentContent) { + auto t = makeStmts("\"a\"", "\"b\"", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, StringLiteralDifferentLength) { + auto t = makeStmts("\"a\"", "\"aa\"", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, TypeTraitExpr) { + auto t = makeStmts("__is_pod(int)", "__is_pod(int)", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, TypeTraitExprDifferentType) { + auto t = makeStmts("__is_pod(int)", "__is_pod(long)", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, TypeTraitExprDifferentTrait) { + auto t = makeStmts("__is_pod(int)", "__is_trivially_constructible(int)", + Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, TypeTraitExprDifferentTraits) { + auto t = makeStmts("__is_constructible(int)", "__is_constructible(int, int)", + Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, UnaryExprOrTypeTraitExpr) { + auto t = makeStmts("sizeof(int)", "sizeof(int)", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, UnaryExprOrTypeTraitExprDifferentKind) { + auto t = makeStmts("sizeof(int)", "alignof(long)", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, UnaryExprOrTypeTraitExprDifferentType) { + auto t = makeStmts("sizeof(int)", "sizeof(long)", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, UnaryOperator) { + auto t = makeStmts("+1", "+1", Lang_CXX03); + EXPECT_TRUE(testStructuralMatch(t)); +} + +TEST_F(StructuralEquivalenceStmtTest, UnaryOperatorDifferentOps) { + auto t = makeStmts("+1", "-1", Lang_CXX03); + EXPECT_FALSE(testStructuralMatch(t)); +} + } // end namespace ast_matchers } // end namespace clang