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