//===- unittest/Tooling/SourceCodeBuildersTest.cpp ------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Tooling/Transformer/SourceCodeBuilders.h" #include "clang/AST/Type.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/Tooling.h" #include "llvm/Testing/Support/SupportHelpers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using namespace clang; using namespace tooling; using namespace ast_matchers; namespace { using MatchResult = MatchFinder::MatchResult; using llvm::ValueIs; // Create a valid translation unit from a statement. static std::string wrapSnippet(StringRef StatementCode) { return ("namespace std {\n" "template struct unique_ptr {\n" " T* operator->() const;\n" " T& operator*() const;\n" "};\n" "template struct shared_ptr {\n" " T* operator->() const;\n" " T& operator*() const;\n" "};\n" "}\n" "struct A { void super(); };\n" "struct S : public A { S(); S(int); int Field; };\n" "S operator+(const S &a, const S &b);\n" "struct Smart {\n" " S* operator->() const;\n" " S& operator*() const;\n" "};\n" "auto test_snippet = []{" + StatementCode + "};") .str(); } static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) { return varDecl(hasName("test_snippet"), hasDescendant(compoundStmt(hasAnySubstatement(Matcher)))); } struct TestMatch { // The AST unit from which `result` is built. We bundle it because it backs // the result. Users are not expected to access it. std::unique_ptr AstUnit; // The result to use in the test. References `ast_unit`. MatchResult Result; }; // Matches `Matcher` against the statement `StatementCode` and returns the // result. Handles putting the statement inside a function and modifying the // matcher correspondingly. `Matcher` should match one of the statements in // `StatementCode` exactly -- that is, produce exactly one match. However, // `StatementCode` may contain other statements not described by `Matcher`. static llvm::Optional matchStmt(StringRef StatementCode, StatementMatcher Matcher) { auto AstUnit = buildASTFromCodeWithArgs(wrapSnippet(StatementCode), {"-Wno-unused-value"}); if (AstUnit == nullptr) { ADD_FAILURE() << "AST construction failed"; return llvm::None; } ASTContext &Context = AstUnit->getASTContext(); auto Matches = ast_matchers::match(wrapMatcher(Matcher), Context); // We expect a single, exact match for the statement. if (Matches.size() != 1) { ADD_FAILURE() << "Wrong number of matches: " << Matches.size(); return llvm::None; } return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)}; } static void testPredicate(bool (*Pred)(const Expr &), StringRef Snippet, bool Expected) { auto StmtMatch = matchStmt(Snippet, expr().bind("expr")); ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; EXPECT_EQ(Expected, Pred(*StmtMatch->Result.Nodes.getNodeAs("expr"))) << "Snippet: " << Snippet; } // Tests the predicate on the call argument, assuming `Snippet` is a function // call. static void testPredicateOnArg(bool (*Pred)(const Expr &), StringRef Snippet, bool Expected) { auto StmtMatch = matchStmt( Snippet, expr(ignoringImplicit(callExpr(hasArgument( 0, ignoringElidableConstructorCall(expr().bind("arg"))))))); ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; EXPECT_EQ(Expected, Pred(*StmtMatch->Result.Nodes.getNodeAs("arg"))) << "Snippet: " << Snippet; } TEST(SourceCodeBuildersTest, needParensAfterUnaryOperator) { testPredicate(needParensAfterUnaryOperator, "3 + 5;", true); testPredicate(needParensAfterUnaryOperator, "true ? 3 : 5;", true); testPredicate(needParensAfterUnaryOperator, "S(3) + S(5);", true); testPredicate(needParensAfterUnaryOperator, "int x; x;", false); testPredicate(needParensAfterUnaryOperator, "int(3.0);", false); testPredicate(needParensAfterUnaryOperator, "void f(); f();", false); testPredicate(needParensAfterUnaryOperator, "int a[3]; a[0];", false); testPredicate(needParensAfterUnaryOperator, "S x; x.Field;", false); testPredicate(needParensAfterUnaryOperator, "int x = 1; --x;", false); testPredicate(needParensAfterUnaryOperator, "int x = 1; -x;", false); } TEST(SourceCodeBuildersTest, needParensAfterUnaryOperatorInImplicitConversion) { // The binary operation will be embedded in various implicit // expressions. Verify they are ignored. testPredicateOnArg(needParensAfterUnaryOperator, "void f(S); f(3 + 5);", true); } TEST(SourceCodeBuildersTest, mayEverNeedParens) { testPredicate(mayEverNeedParens, "3 + 5;", true); testPredicate(mayEverNeedParens, "true ? 3 : 5;", true); testPredicate(mayEverNeedParens, "int x = 1; --x;", true); testPredicate(mayEverNeedParens, "int x = 1; -x;", true); testPredicate(mayEverNeedParens, "int x; x;", false); testPredicate(mayEverNeedParens, "int(3.0);", false); testPredicate(mayEverNeedParens, "void f(); f();", false); testPredicate(mayEverNeedParens, "int a[3]; a[0];", false); testPredicate(mayEverNeedParens, "S x; x.Field;", false); } TEST(SourceCodeBuildersTest, mayEverNeedParensInImplictConversion) { // The binary operation will be embedded in various implicit // expressions. Verify they are ignored. testPredicateOnArg(mayEverNeedParens, "void f(S); f(3 + 5);", true); } TEST(SourceCodeBuildersTest, isKnownPointerLikeTypeUniquePtr) { std::string Snippet = "std::unique_ptr P; P;"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("ty")))); ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; EXPECT_TRUE( isKnownPointerLikeType(*StmtMatch->Result.Nodes.getNodeAs("ty"), *StmtMatch->Result.Context)) << "Snippet: " << Snippet; } TEST(SourceCodeBuildersTest, isKnownPointerLikeTypeSharedPtr) { std::string Snippet = "std::shared_ptr P; P;"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("ty")))); ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; EXPECT_TRUE( isKnownPointerLikeType(*StmtMatch->Result.Nodes.getNodeAs("ty"), *StmtMatch->Result.Context)) << "Snippet: " << Snippet; } TEST(SourceCodeBuildersTest, isKnownPointerLikeTypeUnknownTypeFalse) { std::string Snippet = "Smart P; P;"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("ty")))); ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; EXPECT_FALSE( isKnownPointerLikeType(*StmtMatch->Result.Nodes.getNodeAs("ty"), *StmtMatch->Result.Context)) << "Snippet: " << Snippet; } TEST(SourceCodeBuildersTest, isKnownPointerLikeTypeNormalTypeFalse) { std::string Snippet = "int *P; P;"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("ty")))); ASSERT_TRUE(StmtMatch) << "Snippet: " << Snippet; EXPECT_FALSE( isKnownPointerLikeType(*StmtMatch->Result.Nodes.getNodeAs("ty"), *StmtMatch->Result.Context)) << "Snippet: " << Snippet; } static void testBuilder( llvm::Optional (*Builder)(const Expr &, const ASTContext &), StringRef Snippet, StringRef Expected) { auto StmtMatch = matchStmt(Snippet, expr().bind("expr")); ASSERT_TRUE(StmtMatch); EXPECT_THAT(Builder(*StmtMatch->Result.Nodes.getNodeAs("expr"), *StmtMatch->Result.Context), ValueIs(std::string(Expected))); } static void testBuildAccess(StringRef Snippet, StringRef Expected, PLTClass C = PLTClass::Pointer) { auto StmtMatch = matchStmt(Snippet, expr().bind("expr")); ASSERT_TRUE(StmtMatch); EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs("expr"), *StmtMatch->Result.Context, C), ValueIs(std::string(Expected))); } TEST(SourceCodeBuildersTest, BuildParensUnaryOp) { testBuilder(buildParens, "-4;", "(-4)"); } TEST(SourceCodeBuildersTest, BuildParensBinOp) { testBuilder(buildParens, "4 + 4;", "(4 + 4)"); } TEST(SourceCodeBuildersTest, BuildParensValue) { testBuilder(buildParens, "4;", "4"); } TEST(SourceCodeBuildersTest, BuildParensSubscript) { testBuilder(buildParens, "int a[3]; a[0];", "a[0]"); } TEST(SourceCodeBuildersTest, BuildParensCall) { testBuilder(buildParens, "int f(int); f(4);", "f(4)"); } TEST(SourceCodeBuildersTest, BuildAddressOfValue) { testBuilder(buildAddressOf, "S x; x;", "&x"); } TEST(SourceCodeBuildersTest, BuildAddressOfPointerDereference) { testBuilder(buildAddressOf, "S *x; *x;", "x"); } TEST(SourceCodeBuildersTest, BuildAddressOfPointerDereferenceIgnoresParens) { testBuilder(buildAddressOf, "S *x; *(x);", "x"); } TEST(SourceCodeBuildersTest, BuildAddressOfBinaryOperation) { testBuilder(buildAddressOf, "S x; x + x;", "&(x + x)"); } TEST(SourceCodeBuildersTest, BuildAddressOfImplicitThis) { StringRef Snippet = R"cc( struct Struct { void foo() {} void bar() { foo(); } }; )cc"; auto StmtMatch = matchStmt( Snippet, cxxMemberCallExpr(onImplicitObjectArgument(cxxThisExpr().bind("expr")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT(buildAddressOf(*StmtMatch->Result.Nodes.getNodeAs("expr"), *StmtMatch->Result.Context), ValueIs(std::string("this"))); } TEST(SourceCodeBuildersTest, BuildDereferencePointer) { testBuilder(buildDereference, "S *x; x;", "*x"); } TEST(SourceCodeBuildersTest, BuildDereferenceValueAddress) { testBuilder(buildDereference, "S x; &x;", "x"); } TEST(SourceCodeBuildersTest, BuildDereferenceValueAddressIgnoresParens) { testBuilder(buildDereference, "S x; &(x);", "x"); } TEST(SourceCodeBuildersTest, BuildDereferenceBinaryOperation) { testBuilder(buildDereference, "S *x; x + 1;", "*(x + 1)"); } TEST(SourceCodeBuildersTest, BuildDotValue) { testBuilder(buildDot, "S x; x;", "x."); } TEST(SourceCodeBuildersTest, BuildDotPointerDereference) { testBuilder(buildDot, "S *x; *x;", "x->"); } TEST(SourceCodeBuildersTest, BuildDotPointerDereferenceIgnoresParens) { testBuilder(buildDot, "S *x; *(x);", "x->"); } TEST(SourceCodeBuildersTest, BuildDotBinaryOperation) { testBuilder(buildDot, "S x; x + x;", "(x + x)."); } TEST(SourceCodeBuildersTest, BuildDotPointerDereferenceExprWithParens) { testBuilder(buildDot, "S *x; *(x + 1);", "(x + 1)->"); } TEST(SourceCodeBuildersTest, BuildArrowPointer) { testBuilder(buildArrow, "S *x; x;", "x->"); } TEST(SourceCodeBuildersTest, BuildArrowValueAddress) { testBuilder(buildArrow, "S x; &x;", "x."); } TEST(SourceCodeBuildersTest, BuildArrowValueAddressIgnoresParens) { testBuilder(buildArrow, "S x; &(x);", "x."); } TEST(SourceCodeBuildersTest, BuildArrowBinaryOperation) { testBuilder(buildArrow, "S *x; x + 1;", "(x + 1)->"); } TEST(SourceCodeBuildersTest, BuildArrowValueAddressWithParens) { testBuilder(buildArrow, "S x; &(true ? x : x);", "(true ? x : x)."); } TEST(SourceCodeBuildersTest, BuildAccessValue) { testBuildAccess("S x; x;", "x."); } TEST(SourceCodeBuildersTest, BuildAccessPointerDereference) { testBuildAccess("S *x; *x;", "x->"); } TEST(SourceCodeBuildersTest, BuildAccessPointerDereferenceIgnoresParens) { testBuildAccess("S *x; *(x);", "x->"); } TEST(SourceCodeBuildersTest, BuildAccessValueBinaryOperation) { testBuildAccess("S x; x + x;", "(x + x)."); } TEST(SourceCodeBuildersTest, BuildAccessPointerDereferenceExprWithParens) { testBuildAccess("S *x; *(x + 1);", "(x + 1)->"); } TEST(SourceCodeBuildersTest, BuildAccessPointer) { testBuildAccess("S *x; x;", "x->"); } TEST(SourceCodeBuildersTest, BuildAccessValueAddress) { testBuildAccess("S x; &x;", "x."); } TEST(SourceCodeBuildersTest, BuildAccessValueAddressIgnoresParens) { testBuildAccess("S x; &(x);", "x."); } TEST(SourceCodeBuildersTest, BuildAccessPointerBinaryOperation) { testBuildAccess("S *x; x + 1;", "(x + 1)->"); } TEST(SourceCodeBuildersTest, BuildAccessValueAddressWithParens) { testBuildAccess("S x; &(true ? x : x);", "(true ? x : x)."); } TEST(SourceCodeBuildersTest, BuildAccessSmartPointer) { testBuildAccess("std::unique_ptr x; x;", "x->"); } TEST(SourceCodeBuildersTest, BuildAccessSmartPointerAsValue) { testBuildAccess("std::unique_ptr x; x;", "x.", PLTClass::Value); } TEST(SourceCodeBuildersTest, BuildAccessSmartPointerDeref) { testBuildAccess("std::unique_ptr x; *x;", "x->"); } TEST(SourceCodeBuildersTest, BuildAccessSmartPointerDerefAsValue) { testBuildAccess("std::unique_ptr x; *x;", "(*x).", PLTClass::Value); } TEST(SourceCodeBuildersTest, BuildAccessSmartPointerMemberCall) { StringRef Snippet = R"cc( Smart x; x->Field; )cc"; auto StmtMatch = matchStmt(Snippet, memberExpr(hasObjectExpression(expr().bind("expr")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs("expr"), *StmtMatch->Result.Context), ValueIs(std::string("x->"))); } TEST(SourceCodeBuildersTest, BuildAccessIgnoreImplicit) { StringRef Snippet = R"cc( S x; A *a; a = &x; )cc"; auto StmtMatch = matchStmt(Snippet, binaryOperator(isAssignmentOperator(), hasRHS(expr().bind("expr")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs("expr"), *StmtMatch->Result.Context), ValueIs(std::string("x."))); } TEST(SourceCodeBuildersTest, BuildAccessImplicitThis) { StringRef Snippet = R"cc( struct Struct { void foo() {} void bar() { foo(); } }; )cc"; auto StmtMatch = matchStmt( Snippet, cxxMemberCallExpr(onImplicitObjectArgument(cxxThisExpr().bind("expr")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs("expr"), *StmtMatch->Result.Context), ValueIs(std::string())); } TEST(SourceCodeBuildersTest, BuildAccessImplicitThisIgnoreImplicitCasts) { StringRef Snippet = "struct B : public A { void f() { super(); } };"; auto StmtMatch = matchStmt( Snippet, cxxMemberCallExpr(onImplicitObjectArgument(expr().bind("expr")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT(buildAccess(*StmtMatch->Result.Nodes.getNodeAs("expr"), *StmtMatch->Result.Context), ValueIs(std::string())); } } // namespace