//===- unittest/Tooling/StencilTest.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/Stencil.h" #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Expr.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Tooling/FixIt.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/Error.h" #include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using namespace clang; using namespace transformer; using namespace ast_matchers; namespace { using ::llvm::Failed; using ::llvm::HasValue; using ::llvm::StringError; using ::testing::AllOf; using ::testing::HasSubstr; using MatchResult = MatchFinder::MatchResult; // Create a valid translation-unit from a statement. static std::string wrapSnippet(StringRef ExtraPreface, StringRef StatementCode) { constexpr char Preface[] = R"cc( namespace N { class C {}; } namespace { class AnonC {}; } struct S { int Field; }; struct Smart { S* operator->() const; S& operator*() const; }; )cc"; return (Preface + ExtraPreface + "auto stencil_test_snippet = []{" + StatementCode + "};") .str(); } static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) { return varDecl(hasName("stencil_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`. // `ExtraPreface` (optionally) adds extra decls to the TU, before the code. static llvm::Optional matchStmt(StringRef StatementCode, StatementMatcher Matcher, StringRef ExtraPreface = "") { auto AstUnit = tooling::buildASTFromCodeWithArgs( wrapSnippet(ExtraPreface, 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)}; } class StencilTest : public ::testing::Test { protected: // Verifies that the given stencil fails when evaluated on a valid match // result. Binds a statement to "stmt", a (non-member) ctor-initializer to // "init", an expression to "expr" and a (nameless) declaration to "decl". void testError(const Stencil &Stencil, ::testing::Matcher Matcher) { const std::string Snippet = R"cc( struct A {}; class F : public A { public: F(int) {} }; F(1); )cc"; auto StmtMatch = matchStmt( Snippet, stmt(hasDescendant( cxxConstructExpr( hasDeclaration(decl(hasDescendant(cxxCtorInitializer( isBaseInitializer()) .bind("init"))) .bind("decl"))) .bind("expr"))) .bind("stmt")); ASSERT_TRUE(StmtMatch); if (auto ResultOrErr = Stencil->eval(StmtMatch->Result)) { ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr; } else { auto Err = llvm::handleErrors(ResultOrErr.takeError(), [&Matcher](const StringError &Err) { EXPECT_THAT(Err.getMessage(), Matcher); }); if (Err) { ADD_FAILURE() << "Unhandled error: " << llvm::toString(std::move(Err)); } } } // Tests failures caused by references to unbound nodes. `unbound_id` is the // id that will cause the failure. void testUnboundNodeError(const Stencil &Stencil, StringRef UnboundId) { testError(Stencil, AllOf(HasSubstr(std::string(UnboundId)), HasSubstr("not bound"))); } }; TEST_F(StencilTest, SingleStatement) { StringRef Condition("C"), Then("T"), Else("E"); const std::string Snippet = R"cc( if (true) return 1; else return 0; )cc"; auto StmtMatch = matchStmt( Snippet, ifStmt(hasCondition(expr().bind(Condition)), hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else)))); ASSERT_TRUE(StmtMatch); // Invert the if-then-else. auto Stencil = cat("if (!", node(std::string(Condition)), ") ", statement(std::string(Else)), " else ", statement(std::string(Then))); EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("if (!true) return 0; else return 1;")); } TEST_F(StencilTest, UnboundNode) { const std::string Snippet = R"cc( if (true) return 1; else return 0; )cc"; auto StmtMatch = matchStmt(Snippet, ifStmt(hasCondition(stmt().bind("a1")), hasThen(stmt().bind("a2")))); ASSERT_TRUE(StmtMatch); auto Stencil = cat("if(!", node("a1"), ") ", node("UNBOUND"), ";"); auto ResultOrErr = Stencil->eval(StmtMatch->Result); EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError())) << "Expected unbound node, got " << *ResultOrErr; } // Tests that a stencil with a single parameter (`Id`) evaluates to the expected // string, when `Id` is bound to the expression-statement in `Snippet`. void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil, StringRef Expected) { auto StmtMatch = matchStmt(Snippet, expr().bind(Id)); ASSERT_TRUE(StmtMatch); EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue(std::string(Expected))); } void testFailure(StringRef Id, StringRef Snippet, const Stencil &Stencil, testing::Matcher MessageMatcher) { auto StmtMatch = matchStmt(Snippet, expr().bind(Id)); ASSERT_TRUE(StmtMatch); EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), Failed(testing::Property( &StringError::getMessage, MessageMatcher))); } TEST_F(StencilTest, SelectionOp) { StringRef Id = "id"; testExpr(Id, "3;", cat(node(std::string(Id))), "3"); } TEST_F(StencilTest, IfBoundOpBound) { StringRef Id = "id"; testExpr(Id, "3;", ifBound(Id, cat("5"), cat("7")), "5"); } TEST_F(StencilTest, IfBoundOpUnbound) { StringRef Id = "id"; testExpr(Id, "3;", ifBound("other", cat("5"), cat("7")), "7"); } TEST_F(StencilTest, ExpressionOpNoParens) { StringRef Id = "id"; testExpr(Id, "3;", expression(Id), "3"); } // Don't parenthesize a parens expression. TEST_F(StencilTest, ExpressionOpNoParensParens) { StringRef Id = "id"; testExpr(Id, "(3);", expression(Id), "(3)"); } TEST_F(StencilTest, ExpressionOpBinaryOpParens) { StringRef Id = "id"; testExpr(Id, "3+4;", expression(Id), "(3+4)"); } // `expression` shares code with other ops, so we get sufficient coverage of the // error handling code with this test. If that changes in the future, more error // tests should be added. TEST_F(StencilTest, ExpressionOpUnbound) { StringRef Id = "id"; testFailure(Id, "3;", expression("ACACA"), AllOf(HasSubstr("ACACA"), HasSubstr("not bound"))); } TEST_F(StencilTest, DerefPointer) { StringRef Id = "id"; testExpr(Id, "int *x; x;", deref(Id), "*x"); } TEST_F(StencilTest, DerefBinOp) { StringRef Id = "id"; testExpr(Id, "int *x; x + 1;", deref(Id), "*(x + 1)"); } TEST_F(StencilTest, DerefAddressExpr) { StringRef Id = "id"; testExpr(Id, "int x; &x;", deref(Id), "x"); } TEST_F(StencilTest, AddressOfValue) { StringRef Id = "id"; testExpr(Id, "int x; x;", addressOf(Id), "&x"); } TEST_F(StencilTest, AddressOfDerefExpr) { StringRef Id = "id"; testExpr(Id, "int *x; *x;", addressOf(Id), "x"); } TEST_F(StencilTest, MaybeDerefValue) { StringRef Id = "id"; testExpr(Id, "int x; x;", maybeDeref(Id), "x"); } TEST_F(StencilTest, MaybeDerefPointer) { StringRef Id = "id"; testExpr(Id, "int *x; x;", maybeDeref(Id), "*x"); } TEST_F(StencilTest, MaybeDerefBinOp) { StringRef Id = "id"; testExpr(Id, "int *x; x + 1;", maybeDeref(Id), "*(x + 1)"); } TEST_F(StencilTest, MaybeDerefAddressExpr) { StringRef Id = "id"; testExpr(Id, "int x; &x;", maybeDeref(Id), "x"); } TEST_F(StencilTest, MaybeDerefSmartPointer) { StringRef Id = "id"; std::string Snippet = R"cc( Smart x; x; )cc"; testExpr(Id, Snippet, maybeDeref(Id), "*x"); } // Tests that unique_ptr specifically is handled. TEST_F(StencilTest, MaybeDerefSmartPointerUniquePtr) { StringRef Id = "id"; // We deliberately specify `unique_ptr` as empty to verify that it matches // because of its name, rather than its contents. StringRef ExtraPreface = "namespace std { template class unique_ptr {}; }\n"; StringRef Snippet = R"cc( std::unique_ptr x; x; )cc"; auto StmtMatch = matchStmt(Snippet, expr().bind(Id), ExtraPreface); ASSERT_TRUE(StmtMatch); EXPECT_THAT_EXPECTED(maybeDeref(Id)->eval(StmtMatch->Result), HasValue(std::string("*x"))); } TEST_F(StencilTest, MaybeDerefSmartPointerFromMemberExpr) { StringRef Id = "id"; std::string Snippet = "Smart x; x->Field;"; auto StmtMatch = matchStmt(Snippet, memberExpr(hasObjectExpression(expr().bind(Id)))); ASSERT_TRUE(StmtMatch); const Stencil Stencil = maybeDeref(Id); EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("*x")); } TEST_F(StencilTest, MaybeAddressOfPointer) { StringRef Id = "id"; testExpr(Id, "int *x; x;", maybeAddressOf(Id), "x"); } TEST_F(StencilTest, MaybeAddressOfValue) { StringRef Id = "id"; testExpr(Id, "int x; x;", addressOf(Id), "&x"); } TEST_F(StencilTest, MaybeAddressOfBinOp) { StringRef Id = "id"; testExpr(Id, "int x; x + 1;", maybeAddressOf(Id), "&(x + 1)"); } TEST_F(StencilTest, MaybeAddressOfDerefExpr) { StringRef Id = "id"; testExpr(Id, "int *x; *x;", addressOf(Id), "x"); } TEST_F(StencilTest, MaybeAddressOfSmartPointer) { StringRef Id = "id"; testExpr(Id, "Smart x; x;", maybeAddressOf(Id), "x"); } TEST_F(StencilTest, MaybeAddressOfSmartPointerFromMemberCall) { StringRef Id = "id"; std::string Snippet = "Smart x; x->Field;"; auto StmtMatch = matchStmt(Snippet, memberExpr(hasObjectExpression(expr().bind(Id)))); ASSERT_TRUE(StmtMatch); const Stencil Stencil = maybeAddressOf(Id); EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("x")); } TEST_F(StencilTest, MaybeAddressOfSmartPointerDerefNoCancel) { StringRef Id = "id"; testExpr(Id, "Smart x; *x;", maybeAddressOf(Id), "&*x"); } TEST_F(StencilTest, AccessOpValue) { StringRef Snippet = R"cc( S x; x; )cc"; StringRef Id = "id"; testExpr(Id, Snippet, access(Id, "field"), "x.field"); } TEST_F(StencilTest, AccessOpValueExplicitText) { StringRef Snippet = R"cc( S x; x; )cc"; StringRef Id = "id"; testExpr(Id, Snippet, access(Id, cat("field")), "x.field"); } TEST_F(StencilTest, AccessOpValueAddress) { StringRef Snippet = R"cc( S x; &x; )cc"; StringRef Id = "id"; testExpr(Id, Snippet, access(Id, "field"), "x.field"); } TEST_F(StencilTest, AccessOpPointer) { StringRef Snippet = R"cc( S *x; x; )cc"; StringRef Id = "id"; testExpr(Id, Snippet, access(Id, "field"), "x->field"); } TEST_F(StencilTest, AccessOpPointerDereference) { StringRef Snippet = R"cc( S *x; *x; )cc"; StringRef Id = "id"; testExpr(Id, Snippet, access(Id, "field"), "x->field"); } TEST_F(StencilTest, AccessOpExplicitThis) { using clang::ast_matchers::hasObjectExpression; using clang::ast_matchers::memberExpr; // Set up the code so we can bind to a use of this. StringRef Snippet = R"cc( class C { public: int x; int foo() { return this->x; } }; )cc"; auto StmtMatch = matchStmt( Snippet, traverse(TK_AsIs, returnStmt(hasReturnValue(ignoringImplicit(memberExpr( hasObjectExpression(expr().bind("obj")))))))); ASSERT_TRUE(StmtMatch); const Stencil Stencil = access("obj", "field"); EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("this->field")); } TEST_F(StencilTest, AccessOpImplicitThis) { using clang::ast_matchers::hasObjectExpression; using clang::ast_matchers::memberExpr; // Set up the code so we can bind to a use of (implicit) this. StringRef Snippet = R"cc( class C { public: int x; int foo() { return x; } }; )cc"; auto StmtMatch = matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr( hasObjectExpression(expr().bind("obj"))))))); ASSERT_TRUE(StmtMatch); const Stencil Stencil = access("obj", "field"); EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("field")); } TEST_F(StencilTest, DescribeType) { std::string Snippet = "int *x; x;"; std::string Expected = "int *"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), HasValue(std::string(Expected))); } TEST_F(StencilTest, DescribeSugaredType) { std::string Snippet = "using Ty = int; Ty *x; x;"; std::string Expected = "Ty *"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), HasValue(std::string(Expected))); } TEST_F(StencilTest, DescribeDeclType) { std::string Snippet = "S s; s;"; std::string Expected = "S"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), HasValue(std::string(Expected))); } TEST_F(StencilTest, DescribeQualifiedType) { std::string Snippet = "N::C c; c;"; std::string Expected = "N::C"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), HasValue(std::string(Expected))); } TEST_F(StencilTest, DescribeUnqualifiedType) { std::string Snippet = "using N::C; C c; c;"; std::string Expected = "N::C"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), HasValue(std::string(Expected))); } TEST_F(StencilTest, DescribeAnonNamespaceType) { std::string Snippet = "AnonC c; c;"; std::string Expected = "(anonymous namespace)::AnonC"; auto StmtMatch = matchStmt(Snippet, declRefExpr(hasType(qualType().bind("type")))); ASSERT_TRUE(StmtMatch); EXPECT_THAT_EXPECTED(describe("type")->eval(StmtMatch->Result), HasValue(std::string(Expected))); } TEST_F(StencilTest, RunOp) { StringRef Id = "id"; auto SimpleFn = [Id](const MatchResult &R) { return std::string(R.Nodes.getNodeAs(Id) != nullptr ? "Bound" : "Unbound"); }; testExpr(Id, "3;", run(SimpleFn), "Bound"); } TEST_F(StencilTest, CatOfMacroRangeSucceeds) { StringRef Snippet = R"cpp( #define MACRO 3.77 double foo(double d); foo(MACRO);)cpp"; auto StmtMatch = matchStmt(Snippet, callExpr(callee(functionDecl(hasName("foo"))), argumentCountIs(1), hasArgument(0, expr().bind("arg")))); ASSERT_TRUE(StmtMatch); Stencil S = cat(node("arg")); EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("MACRO")); } TEST_F(StencilTest, CatOfMacroArgRangeSucceeds) { StringRef Snippet = R"cpp( #define MACRO(a, b) a + b MACRO(2, 3);)cpp"; auto StmtMatch = matchStmt(Snippet, binaryOperator(hasRHS(expr().bind("rhs")))); ASSERT_TRUE(StmtMatch); Stencil S = cat(node("rhs")); EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("3")); } TEST_F(StencilTest, CatOfMacroArgSubRangeSucceeds) { StringRef Snippet = R"cpp( #define MACRO(a, b) a + b int foo(int); MACRO(2, foo(3));)cpp"; auto StmtMatch = matchStmt( Snippet, binaryOperator(hasRHS(callExpr( callee(functionDecl(hasName("foo"))), argumentCountIs(1), hasArgument(0, expr().bind("arg")))))); ASSERT_TRUE(StmtMatch); Stencil S = cat(node("arg")); EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("3")); } TEST_F(StencilTest, CatOfInvalidRangeFails) { StringRef Snippet = R"cpp( #define MACRO (3.77) double foo(double d); foo(MACRO);)cpp"; auto StmtMatch = matchStmt(Snippet, callExpr(callee(functionDecl(hasName("foo"))), argumentCountIs(1), hasArgument(0, expr().bind("arg")))); ASSERT_TRUE(StmtMatch); Stencil S = cat(node("arg")); Expected Result = S->eval(StmtMatch->Result); ASSERT_THAT_EXPECTED(Result, Failed()); llvm::handleAllErrors(Result.takeError(), [](const llvm::StringError &E) { EXPECT_THAT(E.getMessage(), AllOf(HasSubstr("selected range"), HasSubstr("macro expansion"))); }); } // The `StencilToStringTest` tests verify that the string representation of the // stencil combinator matches (as best possible) the spelling of the // combinator's construction. Exceptions include those combinators that have no // explicit spelling (like raw text) and those supporting non-printable // arguments (like `run`, `selection`). TEST(StencilToStringTest, RawTextOp) { auto S = cat("foo bar baz"); StringRef Expected = R"("foo bar baz")"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, RawTextOpEscaping) { auto S = cat("foo \"bar\" baz\\n"); StringRef Expected = R"("foo \"bar\" baz\\n")"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, DescribeOp) { auto S = describe("Id"); StringRef Expected = R"repr(describe("Id"))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, DebugPrintNodeOp) { auto S = dPrint("Id"); StringRef Expected = R"repr(dPrint("Id"))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, ExpressionOp) { auto S = expression("Id"); StringRef Expected = R"repr(expression("Id"))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, DerefOp) { auto S = deref("Id"); StringRef Expected = R"repr(deref("Id"))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, AddressOfOp) { auto S = addressOf("Id"); StringRef Expected = R"repr(addressOf("Id"))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, SelectionOp) { auto S1 = cat(node("node1")); EXPECT_EQ(S1->toString(), "selection(...)"); } TEST(StencilToStringTest, AccessOpText) { auto S = access("Id", "memberData"); StringRef Expected = R"repr(access("Id", "memberData"))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, AccessOpSelector) { auto S = access("Id", cat(name("otherId"))); StringRef Expected = R"repr(access("Id", selection(...)))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, AccessOpStencil) { auto S = access("Id", cat("foo_", "bar")); StringRef Expected = R"repr(access("Id", seq("foo_", "bar")))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, IfBoundOp) { auto S = ifBound("Id", cat("trueText"), access("exprId", "memberData")); StringRef Expected = R"repr(ifBound("Id", "trueText", access("exprId", "memberData")))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, RunOp) { auto F1 = [](const MatchResult &R) { return "foo"; }; auto S1 = run(F1); EXPECT_EQ(S1->toString(), "run(...)"); } TEST(StencilToStringTest, Sequence) { auto S = cat("foo", access("x", "m()"), "bar", ifBound("x", cat("t"), access("e", "f"))); StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr" R"repr(ifBound("x", "t", access("e", "f"))))repr"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, SequenceEmpty) { auto S = cat(); StringRef Expected = "seq()"; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, SequenceSingle) { auto S = cat("foo"); StringRef Expected = "\"foo\""; EXPECT_EQ(S->toString(), Expected); } TEST(StencilToStringTest, SequenceFromVector) { auto S = catVector({cat("foo"), access("x", "m()"), cat("bar"), ifBound("x", cat("t"), access("e", "f"))}); StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr" R"repr(ifBound("x", "t", access("e", "f"))))repr"; EXPECT_EQ(S->toString(), Expected); } } // namespace