blob: b35c195fc7c7e8a4703c3eec44e343417f09d015 [file] [log] [blame]
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +00001//===- unittest/Tooling/TransformerTest.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/Transformer.h"
10
11#include "clang/ASTMatchers/ASTMatchers.h"
12#include "clang/Tooling/Tooling.h"
13#include "gmock/gmock.h"
14#include "gtest/gtest.h"
15
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +000016using namespace clang;
17using namespace tooling;
18using namespace ast_matchers;
19
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +000020namespace {
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +000021constexpr char KHeaderContents[] = R"cc(
22 struct string {
23 string(const char*);
24 char* c_str();
25 int size();
26 };
27 int strlen(const char*);
28
29 namespace proto {
30 struct PCFProto {
31 int foo();
32 };
33 struct ProtoCommandLineFlag : PCFProto {
34 PCFProto& GetProto();
35 };
36 } // namespace proto
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +000037 class Logger {};
38 void operator<<(Logger& l, string msg);
39 Logger& log(int level);
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +000040)cc";
41
42static ast_matchers::internal::Matcher<clang::QualType>
43isOrPointsTo(const clang::ast_matchers::DeclarationMatcher &TypeMatcher) {
44 return anyOf(hasDeclaration(TypeMatcher), pointsTo(TypeMatcher));
45}
46
47static std::string format(StringRef Code) {
48 const std::vector<Range> Ranges(1, Range(0, Code.size()));
49 auto Style = format::getLLVMStyle();
50 const auto Replacements = format::reformat(Style, Code, Ranges);
51 auto Formatted = applyAllReplacements(Code, Replacements);
52 if (!Formatted) {
53 ADD_FAILURE() << "Could not format code: "
54 << llvm::toString(Formatted.takeError());
55 return std::string();
56 }
57 return *Formatted;
58}
59
60static void compareSnippets(StringRef Expected,
61 const llvm::Optional<std::string> &MaybeActual) {
62 ASSERT_TRUE(MaybeActual) << "Rewrite failed. Expecting: " << Expected;
63 auto Actual = *MaybeActual;
64 std::string HL = "#include \"header.h\"\n";
65 auto I = Actual.find(HL);
66 if (I != std::string::npos)
67 Actual.erase(I, HL.size());
68 EXPECT_EQ(format(Expected), format(Actual));
69}
70
71// FIXME: consider separating this class into its own file(s).
72class ClangRefactoringTestBase : public testing::Test {
73protected:
74 void appendToHeader(StringRef S) { FileContents[0].second += S; }
75
76 void addFile(StringRef Filename, StringRef Content) {
77 FileContents.emplace_back(Filename, Content);
78 }
79
80 llvm::Optional<std::string> rewrite(StringRef Input) {
81 std::string Code = ("#include \"header.h\"\n" + Input).str();
82 auto Factory = newFrontendActionFactory(&MatchFinder);
83 if (!runToolOnCodeWithArgs(
84 Factory->create(), Code, std::vector<std::string>(), "input.cc",
85 "clang-tool", std::make_shared<PCHContainerOperations>(),
86 FileContents)) {
87 return None;
88 }
89 auto ChangedCodeOrErr =
90 applyAtomicChanges("input.cc", Code, Changes, ApplyChangesSpec());
91 if (auto Err = ChangedCodeOrErr.takeError()) {
92 llvm::errs() << "Change failed: " << llvm::toString(std::move(Err))
93 << "\n";
94 return None;
95 }
96 return *ChangedCodeOrErr;
97 }
98
99 void testRule(RewriteRule Rule, StringRef Input, StringRef Expected) {
100 Transformer T(std::move(Rule),
101 [this](const AtomicChange &C) { Changes.push_back(C); });
102 T.registerMatchers(&MatchFinder);
103 compareSnippets(Expected, rewrite(Input));
104 }
105
106 clang::ast_matchers::MatchFinder MatchFinder;
107 AtomicChanges Changes;
108
109private:
110 FileContentMappings FileContents = {{"header.h", ""}};
111};
112
113class TransformerTest : public ClangRefactoringTestBase {
114protected:
115 TransformerTest() { appendToHeader(KHeaderContents); }
116};
117
118// Given string s, change strlen($s.c_str()) to $s.size().
119static RewriteRule ruleStrlenSize() {
120 StringRef StringExpr = "strexpr";
121 auto StringType = namedDecl(hasAnyName("::basic_string", "::string"));
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000122 auto R = makeRule(
123 callExpr(callee(functionDecl(hasName("strlen"))),
124 hasArgument(0, cxxMemberCallExpr(
125 on(expr(hasType(isOrPointsTo(StringType)))
126 .bind(StringExpr)),
127 callee(cxxMethodDecl(hasName("c_str")))))),
128 change<clang::Expr>("REPLACED"));
129 R.Explanation = text("Use size() method directly on string.");
130 return R;
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000131}
132
133TEST_F(TransformerTest, StrlenSize) {
134 std::string Input = "int f(string s) { return strlen(s.c_str()); }";
135 std::string Expected = "int f(string s) { return REPLACED; }";
136 testRule(ruleStrlenSize(), Input, Expected);
137}
138
139// Tests that no change is applied when a match is not expected.
140TEST_F(TransformerTest, NoMatch) {
141 std::string Input = "int f(string s) { return s.size(); }";
142 testRule(ruleStrlenSize(), Input, Input);
143}
144
145// Tests that expressions in macro arguments are rewritten (when applicable).
146TEST_F(TransformerTest, StrlenSizeMacro) {
147 std::string Input = R"cc(
148#define ID(e) e
149 int f(string s) { return ID(strlen(s.c_str())); })cc";
150 std::string Expected = R"cc(
151#define ID(e) e
152 int f(string s) { return ID(REPLACED); })cc";
153 testRule(ruleStrlenSize(), Input, Expected);
154}
155
156// Tests replacing an expression.
157TEST_F(TransformerTest, Flag) {
158 StringRef Flag = "flag";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000159 RewriteRule Rule = makeRule(
160 cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(
161 hasName("proto::ProtoCommandLineFlag"))))
162 .bind(Flag)),
163 unless(callee(cxxMethodDecl(hasName("GetProto"))))),
164 change<clang::Expr>(Flag, "EXPR"));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000165
166 std::string Input = R"cc(
167 proto::ProtoCommandLineFlag flag;
168 int x = flag.foo();
169 int y = flag.GetProto().foo();
170 )cc";
171 std::string Expected = R"cc(
172 proto::ProtoCommandLineFlag flag;
173 int x = EXPR.foo();
174 int y = flag.GetProto().foo();
175 )cc";
176
177 testRule(std::move(Rule), Input, Expected);
178}
179
180TEST_F(TransformerTest, NodePartNameNamedDecl) {
181 StringRef Fun = "fun";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000182 RewriteRule Rule =
183 makeRule(functionDecl(hasName("bad")).bind(Fun),
184 change<clang::FunctionDecl>(Fun, NodePart::Name, "good"));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000185
186 std::string Input = R"cc(
187 int bad(int x);
188 int bad(int x) { return x * x; }
189 )cc";
190 std::string Expected = R"cc(
191 int good(int x);
192 int good(int x) { return x * x; }
193 )cc";
194
195 testRule(Rule, Input, Expected);
196}
197
198TEST_F(TransformerTest, NodePartNameDeclRef) {
199 std::string Input = R"cc(
200 template <typename T>
201 T bad(T x) {
202 return x;
203 }
204 int neutral(int x) { return bad<int>(x) * x; }
205 )cc";
206 std::string Expected = R"cc(
207 template <typename T>
208 T bad(T x) {
209 return x;
210 }
211 int neutral(int x) { return good<int>(x) * x; }
212 )cc";
213
214 StringRef Ref = "ref";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000215 testRule(makeRule(declRefExpr(to(functionDecl(hasName("bad")))).bind(Ref),
216 change<clang::Expr>(Ref, NodePart::Name, "good")),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000217 Input, Expected);
218}
219
220TEST_F(TransformerTest, NodePartNameDeclRefFailure) {
221 std::string Input = R"cc(
222 struct Y {
223 int operator*();
224 };
225 int neutral(int x) {
226 Y y;
227 int (Y::*ptr)() = &Y::operator*;
228 return *y + x;
229 }
230 )cc";
231
232 StringRef Ref = "ref";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000233 testRule(makeRule(declRefExpr(to(functionDecl())).bind(Ref),
234 change<clang::Expr>(Ref, NodePart::Name, "good")),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000235 Input, Input);
236}
237
238TEST_F(TransformerTest, NodePartMember) {
239 StringRef E = "expr";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000240 RewriteRule Rule = makeRule(memberExpr(member(hasName("bad"))).bind(E),
241 change<clang::Expr>(E, NodePart::Member, "good"));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000242
243 std::string Input = R"cc(
244 struct S {
245 int bad;
246 };
247 int g() {
248 S s;
249 return s.bad;
250 }
251 )cc";
252 std::string Expected = R"cc(
253 struct S {
254 int bad;
255 };
256 int g() {
257 S s;
258 return s.good;
259 }
260 )cc";
261
262 testRule(Rule, Input, Expected);
263}
264
265TEST_F(TransformerTest, NodePartMemberQualified) {
266 std::string Input = R"cc(
267 struct S {
268 int bad;
269 int good;
270 };
271 struct T : public S {
272 int bad;
273 };
274 int g() {
275 T t;
276 return t.S::bad;
277 }
278 )cc";
279 std::string Expected = R"cc(
280 struct S {
281 int bad;
282 int good;
283 };
284 struct T : public S {
285 int bad;
286 };
287 int g() {
288 T t;
289 return t.S::good;
290 }
291 )cc";
292
293 StringRef E = "expr";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000294 testRule(makeRule(memberExpr().bind(E),
295 change<clang::Expr>(E, NodePart::Member, "good")),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000296 Input, Expected);
297}
298
299TEST_F(TransformerTest, NodePartMemberMultiToken) {
300 std::string Input = R"cc(
301 struct Y {
302 int operator*();
303 int good();
304 template <typename T> void foo(T t);
305 };
306 int neutral(int x) {
307 Y y;
308 y.template foo<int>(3);
309 return y.operator *();
310 }
311 )cc";
312 std::string Expected = R"cc(
313 struct Y {
314 int operator*();
315 int good();
316 template <typename T> void foo(T t);
317 };
318 int neutral(int x) {
319 Y y;
320 y.template good<int>(3);
321 return y.good();
322 }
323 )cc";
324
325 StringRef MemExpr = "member";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000326 testRule(makeRule(memberExpr().bind(MemExpr),
327 change<clang::Expr>(MemExpr, NodePart::Member, "good")),
328 Input, Expected);
329}
330
331TEST_F(TransformerTest, MultiChange) {
332 std::string Input = R"cc(
333 void foo() {
334 if (10 > 1.0)
335 log(1) << "oh no!";
336 else
337 log(0) << "ok";
338 }
339 )cc";
340 std::string Expected = R"(
341 void foo() {
342 if (true) { /* then */ }
343 else { /* else */ }
344 }
345 )";
346
347 StringRef C = "C", T = "T", E = "E";
348 testRule(makeRule(ifStmt(hasCondition(expr().bind(C)),
349 hasThen(stmt().bind(T)), hasElse(stmt().bind(E))),
350 {change<Expr>(C, "true"), change<Stmt>(T, "{ /* then */ }"),
351 change<Stmt>(E, "{ /* else */ }")}),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000352 Input, Expected);
353}
354
355//
356// Negative tests (where we expect no transformation to occur).
357//
358
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000359// Tests for a conflict in edits from a single match for a rule.
360TEST_F(TransformerTest, OverlappingEditsInRule) {
361 std::string Input = "int conflictOneRule() { return 3 + 7; }";
362 // Try to change the whole binary-operator expression AND one its operands:
363 StringRef O = "O", L = "L";
364 Transformer T(
365 makeRule(binaryOperator(hasLHS(expr().bind(L))).bind(O),
366 {change<Expr>(O, "DELETE_OP"), change<Expr>(L, "DELETE_LHS")}),
367 [this](const AtomicChange &C) { Changes.push_back(C); });
368 T.registerMatchers(&MatchFinder);
369 // The rewrite process fails...
370 EXPECT_TRUE(rewrite(Input));
371 // ... but one AtomicChange was consumed:
372 ASSERT_EQ(Changes.size(), 1);
373 EXPECT_TRUE(Changes[0].hasError());
374}
375
376// Tests for a conflict in edits across multiple matches (of the same rule).
377TEST_F(TransformerTest, OverlappingEditsMultipleMatches) {
378 std::string Input = "int conflictOneRule() { return -7; }";
379 // Try to change the whole binary-operator expression AND one its operands:
380 StringRef E = "E";
381 Transformer T(makeRule(expr().bind(E), change<Expr>(E, "DELETE_EXPR")),
382 [this](const AtomicChange &C) { Changes.push_back(C); });
383 T.registerMatchers(&MatchFinder);
384 // The rewrite process fails because the changes conflict with each other...
385 EXPECT_FALSE(rewrite(Input));
386 // ... but all changes are (individually) fine:
387 ASSERT_EQ(Changes.size(), 2);
388 EXPECT_FALSE(Changes[0].hasError());
389 EXPECT_FALSE(Changes[1].hasError());
390}
391
392TEST_F(TransformerTest, ErrorOccurredMatchSkipped) {
393 // Syntax error in the function body:
394 std::string Input = "void errorOccurred() { 3 }";
395 Transformer T(
396 makeRule(functionDecl(hasName("errorOccurred")), change<Decl>("DELETED;")),
397 [this](const AtomicChange &C) { Changes.push_back(C); });
398 T.registerMatchers(&MatchFinder);
399 // The rewrite process itself fails...
400 EXPECT_FALSE(rewrite(Input));
401 // ... and no changes are produced in the process.
402 EXPECT_THAT(Changes, ::testing::IsEmpty());
403}
404
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000405TEST_F(TransformerTest, NoTransformationInMacro) {
406 std::string Input = R"cc(
407#define MACRO(str) strlen((str).c_str())
408 int f(string s) { return MACRO(s); })cc";
409 testRule(ruleStrlenSize(), Input, Input);
410}
411
412// This test handles the corner case where a macro called within another macro
413// expands to matching code, but the matched code is an argument to the nested
414// macro. A simple check of isMacroArgExpansion() vs. isMacroBodyExpansion()
415// will get this wrong, and transform the code. This test verifies that no such
416// transformation occurs.
417TEST_F(TransformerTest, NoTransformationInNestedMacro) {
418 std::string Input = R"cc(
419#define NESTED(e) e
420#define MACRO(str) NESTED(strlen((str).c_str()))
421 int f(string s) { return MACRO(s); })cc";
422 testRule(ruleStrlenSize(), Input, Input);
423}
424} // namespace