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/Refactoring/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 tooling;
20 using namespace ast_matchers;
21 
22 namespace {
23 using ::llvm::HasValue;
24 using ::testing::AllOf;
25 using ::testing::Eq;
26 using ::testing::HasSubstr;
27 using MatchResult = MatchFinder::MatchResult;
28 using stencil::access;
29 using stencil::cat;
30 using stencil::dPrint;
31 using stencil::ifBound;
32 using stencil::run;
33 using stencil::text;
34 
35 // Create a valid translation-unit from a statement.
36 static std::string wrapSnippet(StringRef StatementCode) {
37   return ("struct S { int field; }; auto stencil_test_snippet = []{" +
38           StatementCode + "};")
39       .str();
40 }
41 
42 static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) {
43   return varDecl(hasName("stencil_test_snippet"),
44                  hasDescendant(compoundStmt(hasAnySubstatement(Matcher))));
45 }
46 
47 struct TestMatch {
48   // The AST unit from which `result` is built. We bundle it because it backs
49   // the result. Users are not expected to access it.
50   std::unique_ptr<ASTUnit> AstUnit;
51   // The result to use in the test. References `ast_unit`.
52   MatchResult Result;
53 };
54 
55 // Matches `Matcher` against the statement `StatementCode` and returns the
56 // result. Handles putting the statement inside a function and modifying the
57 // matcher correspondingly. `Matcher` should match one of the statements in
58 // `StatementCode` exactly -- that is, produce exactly one match. However,
59 // `StatementCode` may contain other statements not described by `Matcher`.
60 static llvm::Optional<TestMatch> matchStmt(StringRef StatementCode,
61                                            StatementMatcher Matcher) {
62   auto AstUnit = buildASTFromCode(wrapSnippet(StatementCode));
63   if (AstUnit == nullptr) {
64     ADD_FAILURE() << "AST construction failed";
65     return llvm::None;
66   }
67   ASTContext &Context = AstUnit->getASTContext();
68   auto Matches = ast_matchers::match(wrapMatcher(Matcher), Context);
69   // We expect a single, exact match for the statement.
70   if (Matches.size() != 1) {
71     ADD_FAILURE() << "Wrong number of matches: " << Matches.size();
72     return llvm::None;
73   }
74   return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)};
75 }
76 
77 class StencilTest : public ::testing::Test {
78 protected:
79   // Verifies that the given stencil fails when evaluated on a valid match
80   // result. Binds a statement to "stmt", a (non-member) ctor-initializer to
81   // "init", an expression to "expr" and a (nameless) declaration to "decl".
82   void testError(const Stencil &Stencil,
83                  ::testing::Matcher<std::string> Matcher) {
84     const std::string Snippet = R"cc(
85       struct A {};
86       class F : public A {
87        public:
88         F(int) {}
89       };
90       F(1);
91     )cc";
92     auto StmtMatch = matchStmt(
93         Snippet,
94         stmt(hasDescendant(
95                  cxxConstructExpr(
96                      hasDeclaration(decl(hasDescendant(cxxCtorInitializer(
97                                                            isBaseInitializer())
98                                                            .bind("init")))
99                                         .bind("decl")))
100                      .bind("expr")))
101             .bind("stmt"));
102     ASSERT_TRUE(StmtMatch);
103     if (auto ResultOrErr = Stencil.eval(StmtMatch->Result)) {
104       ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr;
105     } else {
106       auto Err = llvm::handleErrors(ResultOrErr.takeError(),
107                                     [&Matcher](const llvm::StringError &Err) {
108                                       EXPECT_THAT(Err.getMessage(), Matcher);
109                                     });
110       if (Err) {
111         ADD_FAILURE() << "Unhandled error: " << llvm::toString(std::move(Err));
112       }
113     }
114   }
115 
116   // Tests failures caused by references to unbound nodes. `unbound_id` is the
117   // id that will cause the failure.
118   void testUnboundNodeError(const Stencil &Stencil, StringRef UnboundId) {
119     testError(Stencil, AllOf(HasSubstr(UnboundId), HasSubstr("not bound")));
120   }
121 };
122 
123 TEST_F(StencilTest, SingleStatement) {
124   StringRef Condition("C"), Then("T"), Else("E");
125   const std::string Snippet = R"cc(
126     if (true)
127       return 1;
128     else
129       return 0;
130   )cc";
131   auto StmtMatch = matchStmt(
132       Snippet, ifStmt(hasCondition(expr().bind(Condition)),
133                       hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else))));
134   ASSERT_TRUE(StmtMatch);
135   // Invert the if-then-else.
136   auto Stencil = cat("if (!", node(Condition), ") ", statement(Else), " else ",
137                      statement(Then));
138   EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result),
139                        HasValue("if (!true) return 0; else return 1;"));
140 }
141 
142 TEST_F(StencilTest, SingleStatementCallOperator) {
143   StringRef Condition("C"), Then("T"), Else("E");
144   const std::string Snippet = R"cc(
145     if (true)
146       return 1;
147     else
148       return 0;
149   )cc";
150   auto StmtMatch = matchStmt(
151       Snippet, ifStmt(hasCondition(expr().bind(Condition)),
152                       hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else))));
153   ASSERT_TRUE(StmtMatch);
154   // Invert the if-then-else.
155   Stencil S = cat("if (!", node(Condition), ") ", statement(Else), " else ",
156                   statement(Then));
157   EXPECT_THAT_EXPECTED(S(StmtMatch->Result),
158                        HasValue("if (!true) return 0; else return 1;"));
159 }
160 
161 TEST_F(StencilTest, UnboundNode) {
162   const std::string Snippet = R"cc(
163     if (true)
164       return 1;
165     else
166       return 0;
167   )cc";
168   auto StmtMatch = matchStmt(Snippet, ifStmt(hasCondition(stmt().bind("a1")),
169                                              hasThen(stmt().bind("a2"))));
170   ASSERT_TRUE(StmtMatch);
171   auto Stencil = cat("if(!", node("a1"), ") ", node("UNBOUND"), ";");
172   auto ResultOrErr = Stencil.eval(StmtMatch->Result);
173   EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError()))
174       << "Expected unbound node, got " << *ResultOrErr;
175 }
176 
177 // Tests that a stencil with a single parameter (`Id`) evaluates to the expected
178 // string, when `Id` is bound to the expression-statement in `Snippet`.
179 void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil,
180               StringRef Expected) {
181   auto StmtMatch = matchStmt(Snippet, expr().bind(Id));
182   ASSERT_TRUE(StmtMatch);
183   EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), HasValue(Expected));
184 }
185 
186 TEST_F(StencilTest, SelectionOp) {
187   StringRef Id = "id";
188   testExpr(Id, "3;", cat(node(Id)), "3");
189 }
190 
191 TEST_F(StencilTest, IfBoundOpBound) {
192   StringRef Id = "id";
193   testExpr(Id, "3;", cat(ifBound(Id, text("5"), text("7"))), "5");
194 }
195 
196 TEST_F(StencilTest, IfBoundOpUnbound) {
197   StringRef Id = "id";
198   testExpr(Id, "3;", cat(ifBound("other", text("5"), text("7"))), "7");
199 }
200 
201 TEST_F(StencilTest, AccessOpValue) {
202   StringRef Snippet = R"cc(
203     S x;
204     x;
205   )cc";
206   StringRef Id = "id";
207   testExpr(Id, Snippet, cat(access(Id, "field")), "x.field");
208 }
209 
210 TEST_F(StencilTest, AccessOpValueExplicitText) {
211   StringRef Snippet = R"cc(
212     S x;
213     x;
214   )cc";
215   StringRef Id = "id";
216   testExpr(Id, Snippet, cat(access(Id, text("field"))), "x.field");
217 }
218 
219 TEST_F(StencilTest, AccessOpValueAddress) {
220   StringRef Snippet = R"cc(
221     S x;
222     &x;
223   )cc";
224   StringRef Id = "id";
225   testExpr(Id, Snippet, cat(access(Id, "field")), "x.field");
226 }
227 
228 TEST_F(StencilTest, AccessOpPointer) {
229   StringRef Snippet = R"cc(
230     S *x;
231     x;
232   )cc";
233   StringRef Id = "id";
234   testExpr(Id, Snippet, cat(access(Id, "field")), "x->field");
235 }
236 
237 TEST_F(StencilTest, AccessOpPointerDereference) {
238   StringRef Snippet = R"cc(
239     S *x;
240     *x;
241   )cc";
242   StringRef Id = "id";
243   testExpr(Id, Snippet, cat(access(Id, "field")), "x->field");
244 }
245 
246 TEST_F(StencilTest, AccessOpExplicitThis) {
247   using clang::ast_matchers::hasObjectExpression;
248   using clang::ast_matchers::memberExpr;
249 
250   // Set up the code so we can bind to a use of this.
251   StringRef Snippet = R"cc(
252     class C {
253      public:
254       int x;
255       int foo() { return this->x; }
256     };
257   )cc";
258   auto StmtMatch =
259       matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr(
260                              hasObjectExpression(expr().bind("obj")))))));
261   ASSERT_TRUE(StmtMatch);
262   const Stencil Stencil = cat(access("obj", "field"));
263   EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result),
264                        HasValue("this->field"));
265 }
266 
267 TEST_F(StencilTest, AccessOpImplicitThis) {
268   using clang::ast_matchers::hasObjectExpression;
269   using clang::ast_matchers::memberExpr;
270 
271   // Set up the code so we can bind to a use of (implicit) this.
272   StringRef Snippet = R"cc(
273     class C {
274      public:
275       int x;
276       int foo() { return x; }
277     };
278   )cc";
279   auto StmtMatch =
280       matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr(
281                              hasObjectExpression(expr().bind("obj")))))));
282   ASSERT_TRUE(StmtMatch);
283   const Stencil Stencil = cat(access("obj", "field"));
284   EXPECT_THAT_EXPECTED(Stencil.eval(StmtMatch->Result), HasValue("field"));
285 }
286 
287 TEST_F(StencilTest, RunOp) {
288   StringRef Id = "id";
289   auto SimpleFn = [Id](const MatchResult &R) {
290     return std::string(R.Nodes.getNodeAs<Stmt>(Id) != nullptr ? "Bound"
291                                                               : "Unbound");
292   };
293   testExpr(Id, "3;", cat(run(SimpleFn)), "Bound");
294 }
295 
296 TEST(StencilEqualityTest, Equality) {
297   auto Lhs = cat("foo", dPrint("dprint_id"));
298   auto Rhs = cat("foo", dPrint("dprint_id"));
299   EXPECT_EQ(Lhs, Rhs);
300 }
301 
302 TEST(StencilEqualityTest, InEqualityDifferentOrdering) {
303   auto Lhs = cat("foo", dPrint("node"));
304   auto Rhs = cat(dPrint("node"), "foo");
305   EXPECT_NE(Lhs, Rhs);
306 }
307 
308 TEST(StencilEqualityTest, InEqualityDifferentSizes) {
309   auto Lhs = cat("foo", dPrint("node"), "bar", "baz");
310   auto Rhs = cat("foo", dPrint("node"), "bar");
311   EXPECT_NE(Lhs, Rhs);
312 }
313 
314 // node is opaque and therefore cannot be examined for equality.
315 TEST(StencilEqualityTest, InEqualitySelection) {
316   auto S1 = cat(node("node"));
317   auto S2 = cat(node("node"));
318   EXPECT_NE(S1, S2);
319 }
320 
321 // `run` is opaque.
322 TEST(StencilEqualityTest, InEqualityRun) {
323   auto F = [](const MatchResult &R) { return "foo"; };
324   auto S1 = cat(run(F));
325   auto S2 = cat(run(F));
326   EXPECT_NE(S1, S2);
327 }
328 } // namespace
329