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::HasSubstr;
28 using MatchResult = MatchFinder::MatchResult;
29 
30 // Create a valid translation-unit from a statement.
31 static std::string wrapSnippet(StringRef StatementCode) {
32   return ("struct S { int field; }; auto stencil_test_snippet = []{" +
33           StatementCode + "};")
34       .str();
35 }
36 
37 static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) {
38   return varDecl(hasName("stencil_test_snippet"),
39                  hasDescendant(compoundStmt(hasAnySubstatement(Matcher))));
40 }
41 
42 struct TestMatch {
43   // The AST unit from which `result` is built. We bundle it because it backs
44   // the result. Users are not expected to access it.
45   std::unique_ptr<ASTUnit> AstUnit;
46   // The result to use in the test. References `ast_unit`.
47   MatchResult Result;
48 };
49 
50 // Matches `Matcher` against the statement `StatementCode` and returns the
51 // result. Handles putting the statement inside a function and modifying the
52 // matcher correspondingly. `Matcher` should match one of the statements in
53 // `StatementCode` exactly -- that is, produce exactly one match. However,
54 // `StatementCode` may contain other statements not described by `Matcher`.
55 static llvm::Optional<TestMatch> matchStmt(StringRef StatementCode,
56                                            StatementMatcher Matcher) {
57   auto AstUnit = tooling::buildASTFromCode(wrapSnippet(StatementCode));
58   if (AstUnit == nullptr) {
59     ADD_FAILURE() << "AST construction failed";
60     return llvm::None;
61   }
62   ASTContext &Context = AstUnit->getASTContext();
63   auto Matches = ast_matchers::match(wrapMatcher(Matcher), Context);
64   // We expect a single, exact match for the statement.
65   if (Matches.size() != 1) {
66     ADD_FAILURE() << "Wrong number of matches: " << Matches.size();
67     return llvm::None;
68   }
69   return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)};
70 }
71 
72 class StencilTest : public ::testing::Test {
73 protected:
74   // Verifies that the given stencil fails when evaluated on a valid match
75   // result. Binds a statement to "stmt", a (non-member) ctor-initializer to
76   // "init", an expression to "expr" and a (nameless) declaration to "decl".
77   void testError(const Stencil &Stencil,
78                  ::testing::Matcher<std::string> Matcher) {
79     const std::string Snippet = R"cc(
80       struct A {};
81       class F : public A {
82        public:
83         F(int) {}
84       };
85       F(1);
86     )cc";
87     auto StmtMatch = matchStmt(
88         Snippet,
89         stmt(hasDescendant(
90                  cxxConstructExpr(
91                      hasDeclaration(decl(hasDescendant(cxxCtorInitializer(
92                                                            isBaseInitializer())
93                                                            .bind("init")))
94                                         .bind("decl")))
95                      .bind("expr")))
96             .bind("stmt"));
97     ASSERT_TRUE(StmtMatch);
98     if (auto ResultOrErr = Stencil->eval(StmtMatch->Result)) {
99       ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr;
100     } else {
101       auto Err = llvm::handleErrors(ResultOrErr.takeError(),
102                                     [&Matcher](const StringError &Err) {
103                                       EXPECT_THAT(Err.getMessage(), Matcher);
104                                     });
105       if (Err) {
106         ADD_FAILURE() << "Unhandled error: " << llvm::toString(std::move(Err));
107       }
108     }
109   }
110 
111   // Tests failures caused by references to unbound nodes. `unbound_id` is the
112   // id that will cause the failure.
113   void testUnboundNodeError(const Stencil &Stencil, StringRef UnboundId) {
114     testError(Stencil,
115               AllOf(HasSubstr(std::string(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 =
133       cat("if (!", node(std::string(Condition)), ") ",
134           statement(std::string(Else)), " else ", statement(std::string(Then)));
135   EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result),
136                        HasValue("if (!true) return 0; else return 1;"));
137 }
138 
139 TEST_F(StencilTest, UnboundNode) {
140   const std::string Snippet = R"cc(
141     if (true)
142       return 1;
143     else
144       return 0;
145   )cc";
146   auto StmtMatch = matchStmt(Snippet, ifStmt(hasCondition(stmt().bind("a1")),
147                                              hasThen(stmt().bind("a2"))));
148   ASSERT_TRUE(StmtMatch);
149   auto Stencil = cat("if(!", node("a1"), ") ", node("UNBOUND"), ";");
150   auto ResultOrErr = Stencil->eval(StmtMatch->Result);
151   EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError()))
152       << "Expected unbound node, got " << *ResultOrErr;
153 }
154 
155 // Tests that a stencil with a single parameter (`Id`) evaluates to the expected
156 // string, when `Id` is bound to the expression-statement in `Snippet`.
157 void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil,
158               StringRef Expected) {
159   auto StmtMatch = matchStmt(Snippet, expr().bind(Id));
160   ASSERT_TRUE(StmtMatch);
161   EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result),
162                        HasValue(std::string(Expected)));
163 }
164 
165 void testFailure(StringRef Id, StringRef Snippet, const Stencil &Stencil,
166                  testing::Matcher<std::string> MessageMatcher) {
167   auto StmtMatch = matchStmt(Snippet, expr().bind(Id));
168   ASSERT_TRUE(StmtMatch);
169   EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result),
170                        Failed<StringError>(testing::Property(
171                            &StringError::getMessage, MessageMatcher)));
172 }
173 
174 TEST_F(StencilTest, SelectionOp) {
175   StringRef Id = "id";
176   testExpr(Id, "3;", cat(node(std::string(Id))), "3");
177 }
178 
179 TEST_F(StencilTest, IfBoundOpBound) {
180   StringRef Id = "id";
181   testExpr(Id, "3;", ifBound(Id, text("5"), text("7")), "5");
182 }
183 
184 TEST_F(StencilTest, IfBoundOpUnbound) {
185   StringRef Id = "id";
186   testExpr(Id, "3;", ifBound("other", text("5"), text("7")), "7");
187 }
188 
189 TEST_F(StencilTest, ExpressionOpNoParens) {
190   StringRef Id = "id";
191   testExpr(Id, "3;", expression(Id), "3");
192 }
193 
194 // Don't parenthesize a parens expression.
195 TEST_F(StencilTest, ExpressionOpNoParensParens) {
196   StringRef Id = "id";
197   testExpr(Id, "(3);", expression(Id), "(3)");
198 }
199 
200 TEST_F(StencilTest, ExpressionOpBinaryOpParens) {
201   StringRef Id = "id";
202   testExpr(Id, "3+4;", expression(Id), "(3+4)");
203 }
204 
205 // `expression` shares code with other ops, so we get sufficient coverage of the
206 // error handling code with this test. If that changes in the future, more error
207 // tests should be added.
208 TEST_F(StencilTest, ExpressionOpUnbound) {
209   StringRef Id = "id";
210   testFailure(Id, "3;", expression("ACACA"),
211               AllOf(HasSubstr("ACACA"), HasSubstr("not bound")));
212 }
213 
214 TEST_F(StencilTest, DerefPointer) {
215   StringRef Id = "id";
216   testExpr(Id, "int *x; x;", deref(Id), "*x");
217 }
218 
219 TEST_F(StencilTest, DerefBinOp) {
220   StringRef Id = "id";
221   testExpr(Id, "int *x; x + 1;", deref(Id), "*(x + 1)");
222 }
223 
224 TEST_F(StencilTest, DerefAddressExpr) {
225   StringRef Id = "id";
226   testExpr(Id, "int x; &x;", deref(Id), "x");
227 }
228 
229 TEST_F(StencilTest, AddressOfValue) {
230   StringRef Id = "id";
231   testExpr(Id, "int x; x;", addressOf(Id), "&x");
232 }
233 
234 TEST_F(StencilTest, AddressOfDerefExpr) {
235   StringRef Id = "id";
236   testExpr(Id, "int *x; *x;", addressOf(Id), "x");
237 }
238 
239 TEST_F(StencilTest, MaybeDerefValue) {
240   StringRef Id = "id";
241   testExpr(Id, "int x; x;", maybeDeref(Id), "x");
242 }
243 
244 TEST_F(StencilTest, MaybeDerefPointer) {
245   StringRef Id = "id";
246   testExpr(Id, "int *x; x;", maybeDeref(Id), "*x");
247 }
248 
249 TEST_F(StencilTest, MaybeDerefBinOp) {
250   StringRef Id = "id";
251   testExpr(Id, "int *x; x + 1;", maybeDeref(Id), "*(x + 1)");
252 }
253 
254 TEST_F(StencilTest, MaybeDerefAddressExpr) {
255   StringRef Id = "id";
256   testExpr(Id, "int x; &x;", maybeDeref(Id), "x");
257 }
258 
259 TEST_F(StencilTest, MaybeAddressOfPointer) {
260   StringRef Id = "id";
261   testExpr(Id, "int *x; x;", maybeAddressOf(Id), "x");
262 }
263 
264 TEST_F(StencilTest, MaybeAddressOfValue) {
265   StringRef Id = "id";
266   testExpr(Id, "int x; x;", addressOf(Id), "&x");
267 }
268 
269 TEST_F(StencilTest, MaybeAddressOfBinOp) {
270   StringRef Id = "id";
271   testExpr(Id, "int x; x + 1;", maybeAddressOf(Id), "&(x + 1)");
272 }
273 
274 TEST_F(StencilTest, MaybeAddressOfDerefExpr) {
275   StringRef Id = "id";
276   testExpr(Id, "int *x; *x;", addressOf(Id), "x");
277 }
278 
279 TEST_F(StencilTest, AccessOpValue) {
280   StringRef Snippet = R"cc(
281     S x;
282     x;
283   )cc";
284   StringRef Id = "id";
285   testExpr(Id, Snippet, access(Id, "field"), "x.field");
286 }
287 
288 TEST_F(StencilTest, AccessOpValueExplicitText) {
289   StringRef Snippet = R"cc(
290     S x;
291     x;
292   )cc";
293   StringRef Id = "id";
294   testExpr(Id, Snippet, access(Id, text("field")), "x.field");
295 }
296 
297 TEST_F(StencilTest, AccessOpValueAddress) {
298   StringRef Snippet = R"cc(
299     S x;
300     &x;
301   )cc";
302   StringRef Id = "id";
303   testExpr(Id, Snippet, access(Id, "field"), "x.field");
304 }
305 
306 TEST_F(StencilTest, AccessOpPointer) {
307   StringRef Snippet = R"cc(
308     S *x;
309     x;
310   )cc";
311   StringRef Id = "id";
312   testExpr(Id, Snippet, access(Id, "field"), "x->field");
313 }
314 
315 TEST_F(StencilTest, AccessOpPointerDereference) {
316   StringRef Snippet = R"cc(
317     S *x;
318     *x;
319   )cc";
320   StringRef Id = "id";
321   testExpr(Id, Snippet, access(Id, "field"), "x->field");
322 }
323 
324 TEST_F(StencilTest, AccessOpExplicitThis) {
325   using clang::ast_matchers::hasObjectExpression;
326   using clang::ast_matchers::memberExpr;
327 
328   // Set up the code so we can bind to a use of this.
329   StringRef Snippet = R"cc(
330     class C {
331      public:
332       int x;
333       int foo() { return this->x; }
334     };
335   )cc";
336   auto StmtMatch =
337       matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr(
338                              hasObjectExpression(expr().bind("obj")))))));
339   ASSERT_TRUE(StmtMatch);
340   const Stencil Stencil = access("obj", "field");
341   EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result),
342                        HasValue("this->field"));
343 }
344 
345 TEST_F(StencilTest, AccessOpImplicitThis) {
346   using clang::ast_matchers::hasObjectExpression;
347   using clang::ast_matchers::memberExpr;
348 
349   // Set up the code so we can bind to a use of (implicit) this.
350   StringRef Snippet = R"cc(
351     class C {
352      public:
353       int x;
354       int foo() { return x; }
355     };
356   )cc";
357   auto StmtMatch =
358       matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr(
359                              hasObjectExpression(expr().bind("obj")))))));
360   ASSERT_TRUE(StmtMatch);
361   const Stencil Stencil = access("obj", "field");
362   EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("field"));
363 }
364 
365 TEST_F(StencilTest, RunOp) {
366   StringRef Id = "id";
367   auto SimpleFn = [Id](const MatchResult &R) {
368     return std::string(R.Nodes.getNodeAs<Stmt>(Id) != nullptr ? "Bound"
369                                                               : "Unbound");
370   };
371   testExpr(Id, "3;", run(SimpleFn), "Bound");
372 }
373 
374 TEST_F(StencilTest, CatOfInvalidRangeFails) {
375   StringRef Snippet = R"cpp(
376 #define MACRO (3.77)
377   double foo(double d);
378   foo(MACRO);)cpp";
379 
380   auto StmtMatch =
381       matchStmt(Snippet, callExpr(callee(functionDecl(hasName("foo"))),
382                                   argumentCountIs(1),
383                                   hasArgument(0, expr().bind("arg"))));
384   ASSERT_TRUE(StmtMatch);
385   Stencil S = cat(node("arg"));
386   EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), Failed<StringError>());
387 }
388 
389 TEST(StencilToStringTest, RawTextOp) {
390   auto S = cat("foo bar baz");
391   StringRef Expected = R"("foo bar baz")";
392   EXPECT_EQ(S->toString(), Expected);
393 }
394 
395 TEST(StencilToStringTest, RawTextOpEscaping) {
396   auto S = cat("foo \"bar\" baz\\n");
397   StringRef Expected = R"("foo \"bar\" baz\\n")";
398   EXPECT_EQ(S->toString(), Expected);
399 }
400 
401 TEST(StencilToStringTest, DebugPrintNodeOp) {
402   auto S = dPrint("Id");
403   StringRef Expected = R"repr(dPrint("Id"))repr";
404   EXPECT_EQ(S->toString(), Expected);
405 }
406 
407 TEST(StencilToStringTest, ExpressionOp) {
408   auto S = expression("Id");
409   StringRef Expected = R"repr(expression("Id"))repr";
410   EXPECT_EQ(S->toString(), Expected);
411 }
412 
413 TEST(StencilToStringTest, DerefOp) {
414   auto S = deref("Id");
415   StringRef Expected = R"repr(deref("Id"))repr";
416   EXPECT_EQ(S->toString(), Expected);
417 }
418 
419 TEST(StencilToStringTest, AddressOfOp) {
420   auto S = addressOf("Id");
421   StringRef Expected = R"repr(addressOf("Id"))repr";
422   EXPECT_EQ(S->toString(), Expected);
423 }
424 
425 TEST(StencilToStringTest, SelectionOp) {
426   auto S1 = cat(node("node1"));
427   EXPECT_EQ(S1->toString(), "selection(...)");
428 }
429 
430 TEST(StencilToStringTest, AccessOpText) {
431   auto S = access("Id", "memberData");
432   StringRef Expected = R"repr(access("Id", "memberData"))repr";
433   EXPECT_EQ(S->toString(), Expected);
434 }
435 
436 TEST(StencilToStringTest, AccessOpSelector) {
437   auto S = access("Id", selection(name("otherId")));
438   StringRef Expected = R"repr(access("Id", selection(...)))repr";
439   EXPECT_EQ(S->toString(), Expected);
440 }
441 
442 TEST(StencilToStringTest, AccessOpStencil) {
443   auto S = access("Id", cat("foo_", "bar"));
444   StringRef Expected = R"repr(access("Id", seq("foo_", "bar")))repr";
445   EXPECT_EQ(S->toString(), Expected);
446 }
447 
448 TEST(StencilToStringTest, IfBoundOp) {
449   auto S = ifBound("Id", text("trueText"), access("exprId", "memberData"));
450   StringRef Expected =
451       R"repr(ifBound("Id", "trueText", access("exprId", "memberData")))repr";
452   EXPECT_EQ(S->toString(), Expected);
453 }
454 
455 TEST(StencilToStringTest, RunOp) {
456   auto F1 = [](const MatchResult &R) { return "foo"; };
457   auto S1 = run(F1);
458   EXPECT_EQ(S1->toString(), "run(...)");
459 }
460 
461 TEST(StencilToStringTest, Sequence) {
462   auto S = cat("foo", access("x", "m()"), "bar",
463                ifBound("x", text("t"), access("e", "f")));
464   StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr"
465                        R"repr(ifBound("x", "t", access("e", "f"))))repr";
466   EXPECT_EQ(S->toString(), Expected);
467 }
468 
469 TEST(StencilToStringTest, SequenceEmpty) {
470   auto S = cat();
471   StringRef Expected = "seq()";
472   EXPECT_EQ(S->toString(), Expected);
473 }
474 
475 TEST(StencilToStringTest, SequenceSingle) {
476   auto S = cat("foo");
477   StringRef Expected = "\"foo\"";
478   EXPECT_EQ(S->toString(), Expected);
479 }
480 
481 TEST(StencilToStringTest, SequenceFromVector) {
482   auto S = catVector({text("foo"), access("x", "m()"), text("bar"),
483                       ifBound("x", text("t"), access("e", "f"))});
484   StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr"
485                        R"repr(ifBound("x", "t", access("e", "f"))))repr";
486   EXPECT_EQ(S->toString(), Expected);
487 }
488 } // namespace
489