1 //===- unittest/Tooling/StencilTest.cpp -----------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "clang/Tooling/Transformer/Stencil.h" 10 #include "clang/ASTMatchers/ASTMatchers.h" 11 #include "clang/Tooling/FixIt.h" 12 #include "clang/Tooling/Tooling.h" 13 #include "llvm/Support/Error.h" 14 #include "llvm/Testing/Support/Error.h" 15 #include "gmock/gmock.h" 16 #include "gtest/gtest.h" 17 18 using namespace clang; 19 using namespace transformer; 20 using namespace ast_matchers; 21 22 namespace { 23 using ::llvm::Failed; 24 using ::llvm::HasValue; 25 using ::llvm::StringError; 26 using ::testing::AllOf; 27 using ::testing::Eq; 28 using ::testing::HasSubstr; 29 using MatchResult = MatchFinder::MatchResult; 30 31 // Create a valid translation-unit from a statement. 32 static std::string wrapSnippet(StringRef StatementCode) { 33 return ("struct S { int field; }; auto stencil_test_snippet = []{" + 34 StatementCode + "};") 35 .str(); 36 } 37 38 static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) { 39 return varDecl(hasName("stencil_test_snippet"), 40 hasDescendant(compoundStmt(hasAnySubstatement(Matcher)))); 41 } 42 43 struct TestMatch { 44 // The AST unit from which `result` is built. We bundle it because it backs 45 // the result. Users are not expected to access it. 46 std::unique_ptr<ASTUnit> AstUnit; 47 // The result to use in the test. References `ast_unit`. 48 MatchResult Result; 49 }; 50 51 // Matches `Matcher` against the statement `StatementCode` and returns the 52 // result. Handles putting the statement inside a function and modifying the 53 // matcher correspondingly. `Matcher` should match one of the statements in 54 // `StatementCode` exactly -- that is, produce exactly one match. However, 55 // `StatementCode` may contain other statements not described by `Matcher`. 56 static llvm::Optional<TestMatch> matchStmt(StringRef StatementCode, 57 StatementMatcher Matcher) { 58 auto AstUnit = tooling::buildASTFromCode(wrapSnippet(StatementCode)); 59 if (AstUnit == nullptr) { 60 ADD_FAILURE() << "AST construction failed"; 61 return llvm::None; 62 } 63 ASTContext &Context = AstUnit->getASTContext(); 64 auto Matches = ast_matchers::match(wrapMatcher(Matcher), Context); 65 // We expect a single, exact match for the statement. 66 if (Matches.size() != 1) { 67 ADD_FAILURE() << "Wrong number of matches: " << Matches.size(); 68 return llvm::None; 69 } 70 return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)}; 71 } 72 73 class StencilTest : public ::testing::Test { 74 protected: 75 // Verifies that the given stencil fails when evaluated on a valid match 76 // result. Binds a statement to "stmt", a (non-member) ctor-initializer to 77 // "init", an expression to "expr" and a (nameless) declaration to "decl". 78 void testError(const Stencil &Stencil, 79 ::testing::Matcher<std::string> Matcher) { 80 const std::string Snippet = R"cc( 81 struct A {}; 82 class F : public A { 83 public: 84 F(int) {} 85 }; 86 F(1); 87 )cc"; 88 auto StmtMatch = matchStmt( 89 Snippet, 90 stmt(hasDescendant( 91 cxxConstructExpr( 92 hasDeclaration(decl(hasDescendant(cxxCtorInitializer( 93 isBaseInitializer()) 94 .bind("init"))) 95 .bind("decl"))) 96 .bind("expr"))) 97 .bind("stmt")); 98 ASSERT_TRUE(StmtMatch); 99 if (auto ResultOrErr = Stencil.eval(StmtMatch->Result)) { 100 ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr; 101 } else { 102 auto Err = llvm::handleErrors(ResultOrErr.takeError(), 103 [&Matcher](const StringError &Err) { 104 EXPECT_THAT(Err.getMessage(), Matcher); 105 }); 106 if (Err) { 107 ADD_FAILURE() << "Unhandled error: " << llvm::toString(std::move(Err)); 108 } 109 } 110 } 111 112 // Tests failures caused by references to unbound nodes. `unbound_id` is the 113 // id that will cause the failure. 114 void testUnboundNodeError(const Stencil &Stencil, StringRef UnboundId) { 115 testError(Stencil, AllOf(HasSubstr(UnboundId), HasSubstr("not bound"))); 116 } 117 }; 118 119 TEST_F(StencilTest, SingleStatement) { 120 StringRef Condition("C"), Then("T"), Else("E"); 121 const std::string Snippet = R"cc( 122 if (true) 123 return 1; 124 else 125 return 0; 126 )cc"; 127 auto StmtMatch = matchStmt( 128 Snippet, ifStmt(hasCondition(expr().bind(Condition)), 129 hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else)))); 130 ASSERT_TRUE(StmtMatch); 131 // Invert the if-then-else. 132 auto Stencil = cat("if (!", node(Condition), ") ", statement(Else), " else ", 133 statement(Then)); 134 EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), 135 HasValue("if (!true) return 0; else return 1;")); 136 } 137 138 TEST_F(StencilTest, SingleStatementCallOperator) { 139 StringRef Condition("C"), Then("T"), Else("E"); 140 const std::string Snippet = R"cc( 141 if (true) 142 return 1; 143 else 144 return 0; 145 )cc"; 146 auto StmtMatch = matchStmt( 147 Snippet, ifStmt(hasCondition(expr().bind(Condition)), 148 hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else)))); 149 ASSERT_TRUE(StmtMatch); 150 // Invert the if-then-else. 151 Stencil S = cat("if (!", node(Condition), ") ", statement(Else), " else ", 152 statement(Then)); 153 EXPECT_THAT_EXPECTED(S(StmtMatch->Result), 154 HasValue("if (!true) return 0; else return 1;")); 155 } 156 157 TEST_F(StencilTest, UnboundNode) { 158 const std::string Snippet = R"cc( 159 if (true) 160 return 1; 161 else 162 return 0; 163 )cc"; 164 auto StmtMatch = matchStmt(Snippet, ifStmt(hasCondition(stmt().bind("a1")), 165 hasThen(stmt().bind("a2")))); 166 ASSERT_TRUE(StmtMatch); 167 auto Stencil = cat("if(!", node("a1"), ") ", node("UNBOUND"), ";"); 168 auto ResultOrErr = Stencil.eval(StmtMatch->Result); 169 EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError())) 170 << "Expected unbound node, got " << *ResultOrErr; 171 } 172 173 // Tests that a stencil with a single parameter (`Id`) evaluates to the expected 174 // string, when `Id` is bound to the expression-statement in `Snippet`. 175 void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil, 176 StringRef Expected) { 177 auto StmtMatch = matchStmt(Snippet, expr().bind(Id)); 178 ASSERT_TRUE(StmtMatch); 179 EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), HasValue(Expected)); 180 } 181 182 void testFailure(StringRef Id, StringRef Snippet, const Stencil &Stencil, 183 testing::Matcher<std::string> MessageMatcher) { 184 auto StmtMatch = matchStmt(Snippet, expr().bind(Id)); 185 ASSERT_TRUE(StmtMatch); 186 EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), 187 Failed<StringError>(testing::Property( 188 &StringError::getMessage, MessageMatcher))); 189 } 190 191 TEST_F(StencilTest, SelectionOp) { 192 StringRef Id = "id"; 193 testExpr(Id, "3;", cat(node(Id)), "3"); 194 } 195 196 TEST_F(StencilTest, IfBoundOpBound) { 197 StringRef Id = "id"; 198 testExpr(Id, "3;", cat(ifBound(Id, text("5"), text("7"))), "5"); 199 } 200 201 TEST_F(StencilTest, IfBoundOpUnbound) { 202 StringRef Id = "id"; 203 testExpr(Id, "3;", cat(ifBound("other", text("5"), text("7"))), "7"); 204 } 205 206 TEST_F(StencilTest, ExpressionOpNoParens) { 207 StringRef Id = "id"; 208 testExpr(Id, "3;", cat(expression(Id)), "3"); 209 } 210 211 // Don't parenthesize a parens expression. 212 TEST_F(StencilTest, ExpressionOpNoParensParens) { 213 StringRef Id = "id"; 214 testExpr(Id, "(3);", cat(expression(Id)), "(3)"); 215 } 216 217 TEST_F(StencilTest, ExpressionOpBinaryOpParens) { 218 StringRef Id = "id"; 219 testExpr(Id, "3+4;", cat(expression(Id)), "(3+4)"); 220 } 221 222 // `expression` shares code with other ops, so we get sufficient coverage of the 223 // error handling code with this test. If that changes in the future, more error 224 // tests should be added. 225 TEST_F(StencilTest, ExpressionOpUnbound) { 226 StringRef Id = "id"; 227 testFailure(Id, "3;", cat(expression("ACACA")), 228 AllOf(HasSubstr("ACACA"), HasSubstr("not bound"))); 229 } 230 231 TEST_F(StencilTest, DerefPointer) { 232 StringRef Id = "id"; 233 testExpr(Id, "int *x; x;", cat(deref(Id)), "*x"); 234 } 235 236 TEST_F(StencilTest, DerefBinOp) { 237 StringRef Id = "id"; 238 testExpr(Id, "int *x; x + 1;", cat(deref(Id)), "*(x + 1)"); 239 } 240 241 TEST_F(StencilTest, DerefAddressExpr) { 242 StringRef Id = "id"; 243 testExpr(Id, "int x; &x;", cat(deref(Id)), "x"); 244 } 245 246 TEST_F(StencilTest, AddressOfValue) { 247 StringRef Id = "id"; 248 testExpr(Id, "int x; x;", cat(addressOf(Id)), "&x"); 249 } 250 251 TEST_F(StencilTest, AddressOfDerefExpr) { 252 StringRef Id = "id"; 253 testExpr(Id, "int *x; *x;", cat(addressOf(Id)), "x"); 254 } 255 256 TEST_F(StencilTest, AccessOpValue) { 257 StringRef Snippet = R"cc( 258 S x; 259 x; 260 )cc"; 261 StringRef Id = "id"; 262 testExpr(Id, Snippet, cat(access(Id, "field")), "x.field"); 263 } 264 265 TEST_F(StencilTest, AccessOpValueExplicitText) { 266 StringRef Snippet = R"cc( 267 S x; 268 x; 269 )cc"; 270 StringRef Id = "id"; 271 testExpr(Id, Snippet, cat(access(Id, text("field"))), "x.field"); 272 } 273 274 TEST_F(StencilTest, AccessOpValueAddress) { 275 StringRef Snippet = R"cc( 276 S x; 277 &x; 278 )cc"; 279 StringRef Id = "id"; 280 testExpr(Id, Snippet, cat(access(Id, "field")), "x.field"); 281 } 282 283 TEST_F(StencilTest, AccessOpPointer) { 284 StringRef Snippet = R"cc( 285 S *x; 286 x; 287 )cc"; 288 StringRef Id = "id"; 289 testExpr(Id, Snippet, cat(access(Id, "field")), "x->field"); 290 } 291 292 TEST_F(StencilTest, AccessOpPointerDereference) { 293 StringRef Snippet = R"cc( 294 S *x; 295 *x; 296 )cc"; 297 StringRef Id = "id"; 298 testExpr(Id, Snippet, cat(access(Id, "field")), "x->field"); 299 } 300 301 TEST_F(StencilTest, AccessOpExplicitThis) { 302 using clang::ast_matchers::hasObjectExpression; 303 using clang::ast_matchers::memberExpr; 304 305 // Set up the code so we can bind to a use of this. 306 StringRef Snippet = R"cc( 307 class C { 308 public: 309 int x; 310 int foo() { return this->x; } 311 }; 312 )cc"; 313 auto StmtMatch = 314 matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr( 315 hasObjectExpression(expr().bind("obj"))))))); 316 ASSERT_TRUE(StmtMatch); 317 const Stencil Stencil = cat(access("obj", "field")); 318 EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), 319 HasValue("this->field")); 320 } 321 322 TEST_F(StencilTest, AccessOpImplicitThis) { 323 using clang::ast_matchers::hasObjectExpression; 324 using clang::ast_matchers::memberExpr; 325 326 // Set up the code so we can bind to a use of (implicit) this. 327 StringRef Snippet = R"cc( 328 class C { 329 public: 330 int x; 331 int foo() { return x; } 332 }; 333 )cc"; 334 auto StmtMatch = 335 matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr( 336 hasObjectExpression(expr().bind("obj"))))))); 337 ASSERT_TRUE(StmtMatch); 338 const Stencil Stencil = cat(access("obj", "field")); 339 EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), HasValue("field")); 340 } 341 342 TEST_F(StencilTest, RunOp) { 343 StringRef Id = "id"; 344 auto SimpleFn = [Id](const MatchResult &R) { 345 return std::string(R.Nodes.getNodeAs<Stmt>(Id) != nullptr ? "Bound" 346 : "Unbound"); 347 }; 348 testExpr(Id, "3;", cat(run(SimpleFn)), "Bound"); 349 } 350 351 TEST(StencilToStringTest, RawTextOp) { 352 auto S = cat("foo bar baz"); 353 StringRef Expected = R"("foo bar baz")"; 354 EXPECT_EQ(S.toString(), Expected); 355 } 356 357 TEST(StencilToStringTest, RawTextOpEscaping) { 358 auto S = cat("foo \"bar\" baz\\n"); 359 StringRef Expected = R"("foo \"bar\" baz\\n")"; 360 EXPECT_EQ(S.toString(), Expected); 361 } 362 363 TEST(StencilToStringTest, DebugPrintNodeOp) { 364 auto S = cat(dPrint("Id")); 365 StringRef Expected = R"repr(dPrint("Id"))repr"; 366 EXPECT_EQ(S.toString(), Expected); 367 } 368 369 TEST(StencilToStringTest, ExpressionOp) { 370 auto S = cat(expression("Id")); 371 StringRef Expected = R"repr(expression("Id"))repr"; 372 EXPECT_EQ(S.toString(), Expected); 373 } 374 375 TEST(StencilToStringTest, DerefOp) { 376 auto S = cat(deref("Id")); 377 StringRef Expected = R"repr(deref("Id"))repr"; 378 EXPECT_EQ(S.toString(), Expected); 379 } 380 381 TEST(StencilToStringTest, AddressOfOp) { 382 auto S = cat(addressOf("Id")); 383 StringRef Expected = R"repr(addressOf("Id"))repr"; 384 EXPECT_EQ(S.toString(), Expected); 385 } 386 387 TEST(StencilToStringTest, SelectionOp) { 388 auto S1 = cat(node("node1")); 389 EXPECT_EQ(S1.toString(), "selection(...)"); 390 } 391 392 TEST(StencilToStringTest, AccessOp) { 393 auto S = cat(access("Id", text("memberData"))); 394 StringRef Expected = R"repr(access("Id", "memberData"))repr"; 395 EXPECT_EQ(S.toString(), Expected); 396 } 397 398 TEST(StencilToStringTest, AccessOpStencilPart) { 399 auto S = cat(access("Id", access("subId", "memberData"))); 400 StringRef Expected = R"repr(access("Id", access("subId", "memberData")))repr"; 401 EXPECT_EQ(S.toString(), Expected); 402 } 403 404 TEST(StencilToStringTest, IfBoundOp) { 405 auto S = cat(ifBound("Id", text("trueText"), access("exprId", "memberData"))); 406 StringRef Expected = 407 R"repr(ifBound("Id", "trueText", access("exprId", "memberData")))repr"; 408 EXPECT_EQ(S.toString(), Expected); 409 } 410 411 TEST(StencilToStringTest, RunOp) { 412 auto F1 = [](const MatchResult &R) { return "foo"; }; 413 auto S1 = cat(run(F1)); 414 EXPECT_EQ(S1.toString(), "run(...)"); 415 } 416 417 TEST(StencilToStringTest, MultipleOp) { 418 auto S = cat("foo", access("x", "m()"), "bar", 419 ifBound("x", text("t"), access("e", "f"))); 420 StringRef Expected = R"repr("foo", access("x", "m()"), "bar", )repr" 421 R"repr(ifBound("x", "t", access("e", "f")))repr"; 422 EXPECT_EQ(S.toString(), Expected); 423 } 424 } // namespace 425