blob: 7e8cd6e90bbc9e8fa2763b818f21362b58370b46 [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"
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +000013#include "llvm/Support/Errc.h"
14#include "llvm/Support/Error.h"
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +000015#include "gmock/gmock.h"
16#include "gtest/gtest.h"
17
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +000018using namespace clang;
19using namespace tooling;
20using namespace ast_matchers;
21
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +000022namespace {
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +000023using ::testing::IsEmpty;
24
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +000025constexpr char KHeaderContents[] = R"cc(
26 struct string {
27 string(const char*);
28 char* c_str();
29 int size();
30 };
31 int strlen(const char*);
32
33 namespace proto {
34 struct PCFProto {
35 int foo();
36 };
37 struct ProtoCommandLineFlag : PCFProto {
38 PCFProto& GetProto();
39 };
40 } // namespace proto
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +000041 class Logger {};
42 void operator<<(Logger& l, string msg);
43 Logger& log(int level);
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +000044)cc";
45
46static ast_matchers::internal::Matcher<clang::QualType>
47isOrPointsTo(const clang::ast_matchers::DeclarationMatcher &TypeMatcher) {
48 return anyOf(hasDeclaration(TypeMatcher), pointsTo(TypeMatcher));
49}
50
51static std::string format(StringRef Code) {
52 const std::vector<Range> Ranges(1, Range(0, Code.size()));
53 auto Style = format::getLLVMStyle();
54 const auto Replacements = format::reformat(Style, Code, Ranges);
55 auto Formatted = applyAllReplacements(Code, Replacements);
56 if (!Formatted) {
57 ADD_FAILURE() << "Could not format code: "
58 << llvm::toString(Formatted.takeError());
59 return std::string();
60 }
61 return *Formatted;
62}
63
64static void compareSnippets(StringRef Expected,
65 const llvm::Optional<std::string> &MaybeActual) {
66 ASSERT_TRUE(MaybeActual) << "Rewrite failed. Expecting: " << Expected;
67 auto Actual = *MaybeActual;
68 std::string HL = "#include \"header.h\"\n";
69 auto I = Actual.find(HL);
70 if (I != std::string::npos)
71 Actual.erase(I, HL.size());
72 EXPECT_EQ(format(Expected), format(Actual));
73}
74
75// FIXME: consider separating this class into its own file(s).
76class ClangRefactoringTestBase : public testing::Test {
77protected:
78 void appendToHeader(StringRef S) { FileContents[0].second += S; }
79
80 void addFile(StringRef Filename, StringRef Content) {
81 FileContents.emplace_back(Filename, Content);
82 }
83
84 llvm::Optional<std::string> rewrite(StringRef Input) {
85 std::string Code = ("#include \"header.h\"\n" + Input).str();
86 auto Factory = newFrontendActionFactory(&MatchFinder);
87 if (!runToolOnCodeWithArgs(
88 Factory->create(), Code, std::vector<std::string>(), "input.cc",
89 "clang-tool", std::make_shared<PCHContainerOperations>(),
90 FileContents)) {
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +000091 llvm::errs() << "Running tool failed.\n";
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +000092 return None;
93 }
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +000094 if (ErrorCount != 0) {
95 llvm::errs() << "Generating changes failed.\n";
96 return None;
97 }
98 auto ChangedCode =
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +000099 applyAtomicChanges("input.cc", Code, Changes, ApplyChangesSpec());
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000100 if (!ChangedCode) {
101 llvm::errs() << "Applying changes failed: "
102 << llvm::toString(ChangedCode.takeError()) << "\n";
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000103 return None;
104 }
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000105 return *ChangedCode;
106 }
107
108 Transformer::ChangeConsumer consumer() {
109 return [this](Expected<AtomicChange> C) {
110 if (C) {
111 Changes.push_back(std::move(*C));
112 } else {
113 consumeError(C.takeError());
114 ++ErrorCount;
115 }
116 };
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000117 }
118
Yitzhak Mandelbaum8369a9b2019-05-17 14:23:33 +0000119 template <typename R>
120 void testRule(R Rule, StringRef Input, StringRef Expected) {
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000121 Transformer T(std::move(Rule), consumer());
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000122 T.registerMatchers(&MatchFinder);
123 compareSnippets(Expected, rewrite(Input));
124 }
125
126 clang::ast_matchers::MatchFinder MatchFinder;
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000127 // Records whether any errors occurred in individual changes.
128 int ErrorCount = 0;
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000129 AtomicChanges Changes;
130
131private:
132 FileContentMappings FileContents = {{"header.h", ""}};
133};
134
135class TransformerTest : public ClangRefactoringTestBase {
136protected:
137 TransformerTest() { appendToHeader(KHeaderContents); }
138};
139
140// Given string s, change strlen($s.c_str()) to $s.size().
141static RewriteRule ruleStrlenSize() {
142 StringRef StringExpr = "strexpr";
143 auto StringType = namedDecl(hasAnyName("::basic_string", "::string"));
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000144 auto R = makeRule(
145 callExpr(callee(functionDecl(hasName("strlen"))),
146 hasArgument(0, cxxMemberCallExpr(
147 on(expr(hasType(isOrPointsTo(StringType)))
148 .bind(StringExpr)),
149 callee(cxxMethodDecl(hasName("c_str")))))),
150 change<clang::Expr>("REPLACED"));
Yitzhak Mandelbaum8369a9b2019-05-17 14:23:33 +0000151 R.Cases[0].Explanation = text("Use size() method directly on string.");
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000152 return R;
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000153}
154
155TEST_F(TransformerTest, StrlenSize) {
156 std::string Input = "int f(string s) { return strlen(s.c_str()); }";
157 std::string Expected = "int f(string s) { return REPLACED; }";
158 testRule(ruleStrlenSize(), Input, Expected);
159}
160
161// Tests that no change is applied when a match is not expected.
162TEST_F(TransformerTest, NoMatch) {
163 std::string Input = "int f(string s) { return s.size(); }";
164 testRule(ruleStrlenSize(), Input, Input);
165}
166
167// Tests that expressions in macro arguments are rewritten (when applicable).
168TEST_F(TransformerTest, StrlenSizeMacro) {
169 std::string Input = R"cc(
170#define ID(e) e
171 int f(string s) { return ID(strlen(s.c_str())); })cc";
172 std::string Expected = R"cc(
173#define ID(e) e
174 int f(string s) { return ID(REPLACED); })cc";
175 testRule(ruleStrlenSize(), Input, Expected);
176}
177
178// Tests replacing an expression.
179TEST_F(TransformerTest, Flag) {
180 StringRef Flag = "flag";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000181 RewriteRule Rule = makeRule(
182 cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(
183 hasName("proto::ProtoCommandLineFlag"))))
184 .bind(Flag)),
185 unless(callee(cxxMethodDecl(hasName("GetProto"))))),
186 change<clang::Expr>(Flag, "EXPR"));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000187
188 std::string Input = R"cc(
189 proto::ProtoCommandLineFlag flag;
190 int x = flag.foo();
191 int y = flag.GetProto().foo();
192 )cc";
193 std::string Expected = R"cc(
194 proto::ProtoCommandLineFlag flag;
195 int x = EXPR.foo();
196 int y = flag.GetProto().foo();
197 )cc";
198
199 testRule(std::move(Rule), Input, Expected);
200}
201
202TEST_F(TransformerTest, NodePartNameNamedDecl) {
203 StringRef Fun = "fun";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000204 RewriteRule Rule =
205 makeRule(functionDecl(hasName("bad")).bind(Fun),
206 change<clang::FunctionDecl>(Fun, NodePart::Name, "good"));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000207
208 std::string Input = R"cc(
209 int bad(int x);
210 int bad(int x) { return x * x; }
211 )cc";
212 std::string Expected = R"cc(
213 int good(int x);
214 int good(int x) { return x * x; }
215 )cc";
216
217 testRule(Rule, Input, Expected);
218}
219
220TEST_F(TransformerTest, NodePartNameDeclRef) {
221 std::string Input = R"cc(
222 template <typename T>
223 T bad(T x) {
224 return x;
225 }
226 int neutral(int x) { return bad<int>(x) * x; }
227 )cc";
228 std::string Expected = R"cc(
229 template <typename T>
230 T bad(T x) {
231 return x;
232 }
233 int neutral(int x) { return good<int>(x) * x; }
234 )cc";
235
236 StringRef Ref = "ref";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000237 testRule(makeRule(declRefExpr(to(functionDecl(hasName("bad")))).bind(Ref),
238 change<clang::Expr>(Ref, NodePart::Name, "good")),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000239 Input, Expected);
240}
241
242TEST_F(TransformerTest, NodePartNameDeclRefFailure) {
243 std::string Input = R"cc(
244 struct Y {
245 int operator*();
246 };
247 int neutral(int x) {
248 Y y;
249 int (Y::*ptr)() = &Y::operator*;
250 return *y + x;
251 }
252 )cc";
253
254 StringRef Ref = "ref";
Yitzhak Mandelbauma5dadbe2019-04-30 17:24:36 +0000255 Transformer T(makeRule(declRefExpr(to(functionDecl())).bind(Ref),
256 change<clang::Expr>(Ref, NodePart::Name, "good")),
257 consumer());
258 T.registerMatchers(&MatchFinder);
259 EXPECT_FALSE(rewrite(Input));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000260}
261
262TEST_F(TransformerTest, NodePartMember) {
263 StringRef E = "expr";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000264 RewriteRule Rule = makeRule(memberExpr(member(hasName("bad"))).bind(E),
265 change<clang::Expr>(E, NodePart::Member, "good"));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000266
267 std::string Input = R"cc(
268 struct S {
269 int bad;
270 };
271 int g() {
272 S s;
273 return s.bad;
274 }
275 )cc";
276 std::string Expected = R"cc(
277 struct S {
278 int bad;
279 };
280 int g() {
281 S s;
282 return s.good;
283 }
284 )cc";
285
286 testRule(Rule, Input, Expected);
287}
288
289TEST_F(TransformerTest, NodePartMemberQualified) {
290 std::string Input = R"cc(
291 struct S {
292 int bad;
293 int good;
294 };
295 struct T : public S {
296 int bad;
297 };
298 int g() {
299 T t;
300 return t.S::bad;
301 }
302 )cc";
303 std::string Expected = R"cc(
304 struct S {
305 int bad;
306 int good;
307 };
308 struct T : public S {
309 int bad;
310 };
311 int g() {
312 T t;
313 return t.S::good;
314 }
315 )cc";
316
317 StringRef E = "expr";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000318 testRule(makeRule(memberExpr().bind(E),
319 change<clang::Expr>(E, NodePart::Member, "good")),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000320 Input, Expected);
321}
322
323TEST_F(TransformerTest, NodePartMemberMultiToken) {
324 std::string Input = R"cc(
325 struct Y {
326 int operator*();
327 int good();
328 template <typename T> void foo(T t);
329 };
330 int neutral(int x) {
331 Y y;
332 y.template foo<int>(3);
333 return y.operator *();
334 }
335 )cc";
336 std::string Expected = R"cc(
337 struct Y {
338 int operator*();
339 int good();
340 template <typename T> void foo(T t);
341 };
342 int neutral(int x) {
343 Y y;
344 y.template good<int>(3);
345 return y.good();
346 }
347 )cc";
348
349 StringRef MemExpr = "member";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000350 testRule(makeRule(memberExpr().bind(MemExpr),
351 change<clang::Expr>(MemExpr, NodePart::Member, "good")),
352 Input, Expected);
353}
354
355TEST_F(TransformerTest, MultiChange) {
356 std::string Input = R"cc(
357 void foo() {
358 if (10 > 1.0)
359 log(1) << "oh no!";
360 else
361 log(0) << "ok";
362 }
363 )cc";
364 std::string Expected = R"(
365 void foo() {
366 if (true) { /* then */ }
367 else { /* else */ }
368 }
369 )";
370
371 StringRef C = "C", T = "T", E = "E";
372 testRule(makeRule(ifStmt(hasCondition(expr().bind(C)),
373 hasThen(stmt().bind(T)), hasElse(stmt().bind(E))),
374 {change<Expr>(C, "true"), change<Stmt>(T, "{ /* then */ }"),
375 change<Stmt>(E, "{ /* else */ }")}),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000376 Input, Expected);
377}
378
Yitzhak Mandelbaum8369a9b2019-05-17 14:23:33 +0000379TEST_F(TransformerTest, OrderedRuleUnrelated) {
380 StringRef Flag = "flag";
381 RewriteRule FlagRule = makeRule(
382 cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(
383 hasName("proto::ProtoCommandLineFlag"))))
384 .bind(Flag)),
385 unless(callee(cxxMethodDecl(hasName("GetProto"))))),
386 change<clang::Expr>(Flag, "PROTO"));
387
388 std::string Input = R"cc(
389 proto::ProtoCommandLineFlag flag;
390 int x = flag.foo();
391 int y = flag.GetProto().foo();
392 int f(string s) { return strlen(s.c_str()); }
393 )cc";
394 std::string Expected = R"cc(
395 proto::ProtoCommandLineFlag flag;
396 int x = PROTO.foo();
397 int y = flag.GetProto().foo();
398 int f(string s) { return REPLACED; }
399 )cc";
400
401 testRule(applyFirst({ruleStrlenSize(), FlagRule}), Input, Expected);
402}
403
404// Version of ruleStrlenSizeAny that inserts a method with a different name than
405// ruleStrlenSize, so we can tell their effect apart.
406RewriteRule ruleStrlenSizeDistinct() {
407 StringRef S;
408 return makeRule(
409 callExpr(callee(functionDecl(hasName("strlen"))),
410 hasArgument(0, cxxMemberCallExpr(
411 on(expr().bind(S)),
412 callee(cxxMethodDecl(hasName("c_str")))))),
413 change<clang::Expr>("DISTINCT"));
414}
415
416TEST_F(TransformerTest, OrderedRuleRelated) {
417 std::string Input = R"cc(
418 namespace foo {
419 struct mystring {
420 char* c_str();
421 };
422 int f(mystring s) { return strlen(s.c_str()); }
423 } // namespace foo
424 int g(string s) { return strlen(s.c_str()); }
425 )cc";
426 std::string Expected = R"cc(
427 namespace foo {
428 struct mystring {
429 char* c_str();
430 };
431 int f(mystring s) { return DISTINCT; }
432 } // namespace foo
433 int g(string s) { return REPLACED; }
434 )cc";
435
436 testRule(applyFirst({ruleStrlenSize(), ruleStrlenSizeDistinct()}), Input,
437 Expected);
438}
439
440// Change the order of the rules to get a different result.
441TEST_F(TransformerTest, OrderedRuleRelatedSwapped) {
442 std::string Input = R"cc(
443 namespace foo {
444 struct mystring {
445 char* c_str();
446 };
447 int f(mystring s) { return strlen(s.c_str()); }
448 } // namespace foo
449 int g(string s) { return strlen(s.c_str()); }
450 )cc";
451 std::string Expected = R"cc(
452 namespace foo {
453 struct mystring {
454 char* c_str();
455 };
456 int f(mystring s) { return DISTINCT; }
457 } // namespace foo
458 int g(string s) { return DISTINCT; }
459 )cc";
460
461 testRule(applyFirst({ruleStrlenSizeDistinct(), ruleStrlenSize()}), Input,
462 Expected);
463}
464
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000465//
466// Negative tests (where we expect no transformation to occur).
467//
468
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000469// Tests for a conflict in edits from a single match for a rule.
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000470TEST_F(TransformerTest, TextGeneratorFailure) {
471 std::string Input = "int conflictOneRule() { return 3 + 7; }";
472 // Try to change the whole binary-operator expression AND one its operands:
473 StringRef O = "O";
474 auto AlwaysFail = [](const ast_matchers::MatchFinder::MatchResult &)
475 -> llvm::Expected<std::string> {
476 return llvm::createStringError(llvm::errc::invalid_argument, "ERROR");
477 };
478 Transformer T(makeRule(binaryOperator().bind(O), change<Expr>(O, AlwaysFail)),
479 consumer());
480 T.registerMatchers(&MatchFinder);
481 EXPECT_FALSE(rewrite(Input));
482 EXPECT_THAT(Changes, IsEmpty());
483 EXPECT_EQ(ErrorCount, 1);
484}
485
486// Tests for a conflict in edits from a single match for a rule.
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000487TEST_F(TransformerTest, OverlappingEditsInRule) {
488 std::string Input = "int conflictOneRule() { return 3 + 7; }";
489 // Try to change the whole binary-operator expression AND one its operands:
490 StringRef O = "O", L = "L";
491 Transformer T(
492 makeRule(binaryOperator(hasLHS(expr().bind(L))).bind(O),
493 {change<Expr>(O, "DELETE_OP"), change<Expr>(L, "DELETE_LHS")}),
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000494 consumer());
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000495 T.registerMatchers(&MatchFinder);
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000496 EXPECT_FALSE(rewrite(Input));
497 EXPECT_THAT(Changes, IsEmpty());
498 EXPECT_EQ(ErrorCount, 1);
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000499}
500
501// Tests for a conflict in edits across multiple matches (of the same rule).
502TEST_F(TransformerTest, OverlappingEditsMultipleMatches) {
503 std::string Input = "int conflictOneRule() { return -7; }";
504 // Try to change the whole binary-operator expression AND one its operands:
505 StringRef E = "E";
506 Transformer T(makeRule(expr().bind(E), change<Expr>(E, "DELETE_EXPR")),
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000507 consumer());
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000508 T.registerMatchers(&MatchFinder);
509 // The rewrite process fails because the changes conflict with each other...
510 EXPECT_FALSE(rewrite(Input));
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000511 // ... but two changes were produced.
512 EXPECT_EQ(Changes.size(), 2u);
513 EXPECT_EQ(ErrorCount, 0);
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000514}
515
516TEST_F(TransformerTest, ErrorOccurredMatchSkipped) {
517 // Syntax error in the function body:
518 std::string Input = "void errorOccurred() { 3 }";
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000519 Transformer T(makeRule(functionDecl(hasName("errorOccurred")),
520 change<Decl>("DELETED;")),
521 consumer());
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000522 T.registerMatchers(&MatchFinder);
523 // The rewrite process itself fails...
524 EXPECT_FALSE(rewrite(Input));
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000525 // ... and no changes or errors are produced in the process.
526 EXPECT_THAT(Changes, IsEmpty());
527 EXPECT_EQ(ErrorCount, 0);
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000528}
529
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000530TEST_F(TransformerTest, NoTransformationInMacro) {
531 std::string Input = R"cc(
532#define MACRO(str) strlen((str).c_str())
533 int f(string s) { return MACRO(s); })cc";
534 testRule(ruleStrlenSize(), Input, Input);
535}
536
537// This test handles the corner case where a macro called within another macro
538// expands to matching code, but the matched code is an argument to the nested
539// macro. A simple check of isMacroArgExpansion() vs. isMacroBodyExpansion()
540// will get this wrong, and transform the code. This test verifies that no such
541// transformation occurs.
542TEST_F(TransformerTest, NoTransformationInNestedMacro) {
543 std::string Input = R"cc(
544#define NESTED(e) e
545#define MACRO(str) NESTED(strlen((str).c_str()))
546 int f(string s) { return MACRO(s); })cc";
547 testRule(ruleStrlenSize(), Input, Input);
548}
549} // namespace