blob: 774184da83f9c5027005f1d34274120d45f71698 [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
119 void testRule(RewriteRule Rule, StringRef Input, StringRef Expected) {
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000120 Transformer T(std::move(Rule), consumer());
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000121 T.registerMatchers(&MatchFinder);
122 compareSnippets(Expected, rewrite(Input));
123 }
124
125 clang::ast_matchers::MatchFinder MatchFinder;
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000126 // Records whether any errors occurred in individual changes.
127 int ErrorCount = 0;
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000128 AtomicChanges Changes;
129
130private:
131 FileContentMappings FileContents = {{"header.h", ""}};
132};
133
134class TransformerTest : public ClangRefactoringTestBase {
135protected:
136 TransformerTest() { appendToHeader(KHeaderContents); }
137};
138
139// Given string s, change strlen($s.c_str()) to $s.size().
140static RewriteRule ruleStrlenSize() {
141 StringRef StringExpr = "strexpr";
142 auto StringType = namedDecl(hasAnyName("::basic_string", "::string"));
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000143 auto R = makeRule(
144 callExpr(callee(functionDecl(hasName("strlen"))),
145 hasArgument(0, cxxMemberCallExpr(
146 on(expr(hasType(isOrPointsTo(StringType)))
147 .bind(StringExpr)),
148 callee(cxxMethodDecl(hasName("c_str")))))),
149 change<clang::Expr>("REPLACED"));
150 R.Explanation = text("Use size() method directly on string.");
151 return R;
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000152}
153
154TEST_F(TransformerTest, StrlenSize) {
155 std::string Input = "int f(string s) { return strlen(s.c_str()); }";
156 std::string Expected = "int f(string s) { return REPLACED; }";
157 testRule(ruleStrlenSize(), Input, Expected);
158}
159
160// Tests that no change is applied when a match is not expected.
161TEST_F(TransformerTest, NoMatch) {
162 std::string Input = "int f(string s) { return s.size(); }";
163 testRule(ruleStrlenSize(), Input, Input);
164}
165
166// Tests that expressions in macro arguments are rewritten (when applicable).
167TEST_F(TransformerTest, StrlenSizeMacro) {
168 std::string Input = R"cc(
169#define ID(e) e
170 int f(string s) { return ID(strlen(s.c_str())); })cc";
171 std::string Expected = R"cc(
172#define ID(e) e
173 int f(string s) { return ID(REPLACED); })cc";
174 testRule(ruleStrlenSize(), Input, Expected);
175}
176
177// Tests replacing an expression.
178TEST_F(TransformerTest, Flag) {
179 StringRef Flag = "flag";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000180 RewriteRule Rule = makeRule(
181 cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(
182 hasName("proto::ProtoCommandLineFlag"))))
183 .bind(Flag)),
184 unless(callee(cxxMethodDecl(hasName("GetProto"))))),
185 change<clang::Expr>(Flag, "EXPR"));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000186
187 std::string Input = R"cc(
188 proto::ProtoCommandLineFlag flag;
189 int x = flag.foo();
190 int y = flag.GetProto().foo();
191 )cc";
192 std::string Expected = R"cc(
193 proto::ProtoCommandLineFlag flag;
194 int x = EXPR.foo();
195 int y = flag.GetProto().foo();
196 )cc";
197
198 testRule(std::move(Rule), Input, Expected);
199}
200
201TEST_F(TransformerTest, NodePartNameNamedDecl) {
202 StringRef Fun = "fun";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000203 RewriteRule Rule =
204 makeRule(functionDecl(hasName("bad")).bind(Fun),
205 change<clang::FunctionDecl>(Fun, NodePart::Name, "good"));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000206
207 std::string Input = R"cc(
208 int bad(int x);
209 int bad(int x) { return x * x; }
210 )cc";
211 std::string Expected = R"cc(
212 int good(int x);
213 int good(int x) { return x * x; }
214 )cc";
215
216 testRule(Rule, Input, Expected);
217}
218
219TEST_F(TransformerTest, NodePartNameDeclRef) {
220 std::string Input = R"cc(
221 template <typename T>
222 T bad(T x) {
223 return x;
224 }
225 int neutral(int x) { return bad<int>(x) * x; }
226 )cc";
227 std::string Expected = R"cc(
228 template <typename T>
229 T bad(T x) {
230 return x;
231 }
232 int neutral(int x) { return good<int>(x) * x; }
233 )cc";
234
235 StringRef Ref = "ref";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000236 testRule(makeRule(declRefExpr(to(functionDecl(hasName("bad")))).bind(Ref),
237 change<clang::Expr>(Ref, NodePart::Name, "good")),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000238 Input, Expected);
239}
240
241TEST_F(TransformerTest, NodePartNameDeclRefFailure) {
242 std::string Input = R"cc(
243 struct Y {
244 int operator*();
245 };
246 int neutral(int x) {
247 Y y;
248 int (Y::*ptr)() = &Y::operator*;
249 return *y + x;
250 }
251 )cc";
252
253 StringRef Ref = "ref";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000254 testRule(makeRule(declRefExpr(to(functionDecl())).bind(Ref),
255 change<clang::Expr>(Ref, NodePart::Name, "good")),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000256 Input, Input);
257}
258
259TEST_F(TransformerTest, NodePartMember) {
260 StringRef E = "expr";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000261 RewriteRule Rule = makeRule(memberExpr(member(hasName("bad"))).bind(E),
262 change<clang::Expr>(E, NodePart::Member, "good"));
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000263
264 std::string Input = R"cc(
265 struct S {
266 int bad;
267 };
268 int g() {
269 S s;
270 return s.bad;
271 }
272 )cc";
273 std::string Expected = R"cc(
274 struct S {
275 int bad;
276 };
277 int g() {
278 S s;
279 return s.good;
280 }
281 )cc";
282
283 testRule(Rule, Input, Expected);
284}
285
286TEST_F(TransformerTest, NodePartMemberQualified) {
287 std::string Input = R"cc(
288 struct S {
289 int bad;
290 int good;
291 };
292 struct T : public S {
293 int bad;
294 };
295 int g() {
296 T t;
297 return t.S::bad;
298 }
299 )cc";
300 std::string Expected = R"cc(
301 struct S {
302 int bad;
303 int good;
304 };
305 struct T : public S {
306 int bad;
307 };
308 int g() {
309 T t;
310 return t.S::good;
311 }
312 )cc";
313
314 StringRef E = "expr";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000315 testRule(makeRule(memberExpr().bind(E),
316 change<clang::Expr>(E, NodePart::Member, "good")),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000317 Input, Expected);
318}
319
320TEST_F(TransformerTest, NodePartMemberMultiToken) {
321 std::string Input = R"cc(
322 struct Y {
323 int operator*();
324 int good();
325 template <typename T> void foo(T t);
326 };
327 int neutral(int x) {
328 Y y;
329 y.template foo<int>(3);
330 return y.operator *();
331 }
332 )cc";
333 std::string Expected = R"cc(
334 struct Y {
335 int operator*();
336 int good();
337 template <typename T> void foo(T t);
338 };
339 int neutral(int x) {
340 Y y;
341 y.template good<int>(3);
342 return y.good();
343 }
344 )cc";
345
346 StringRef MemExpr = "member";
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000347 testRule(makeRule(memberExpr().bind(MemExpr),
348 change<clang::Expr>(MemExpr, NodePart::Member, "good")),
349 Input, Expected);
350}
351
352TEST_F(TransformerTest, MultiChange) {
353 std::string Input = R"cc(
354 void foo() {
355 if (10 > 1.0)
356 log(1) << "oh no!";
357 else
358 log(0) << "ok";
359 }
360 )cc";
361 std::string Expected = R"(
362 void foo() {
363 if (true) { /* then */ }
364 else { /* else */ }
365 }
366 )";
367
368 StringRef C = "C", T = "T", E = "E";
369 testRule(makeRule(ifStmt(hasCondition(expr().bind(C)),
370 hasThen(stmt().bind(T)), hasElse(stmt().bind(E))),
371 {change<Expr>(C, "true"), change<Stmt>(T, "{ /* then */ }"),
372 change<Stmt>(E, "{ /* else */ }")}),
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000373 Input, Expected);
374}
375
376//
377// Negative tests (where we expect no transformation to occur).
378//
379
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000380// Tests for a conflict in edits from a single match for a rule.
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000381TEST_F(TransformerTest, TextGeneratorFailure) {
382 std::string Input = "int conflictOneRule() { return 3 + 7; }";
383 // Try to change the whole binary-operator expression AND one its operands:
384 StringRef O = "O";
385 auto AlwaysFail = [](const ast_matchers::MatchFinder::MatchResult &)
386 -> llvm::Expected<std::string> {
387 return llvm::createStringError(llvm::errc::invalid_argument, "ERROR");
388 };
389 Transformer T(makeRule(binaryOperator().bind(O), change<Expr>(O, AlwaysFail)),
390 consumer());
391 T.registerMatchers(&MatchFinder);
392 EXPECT_FALSE(rewrite(Input));
393 EXPECT_THAT(Changes, IsEmpty());
394 EXPECT_EQ(ErrorCount, 1);
395}
396
397// Tests for a conflict in edits from a single match for a rule.
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000398TEST_F(TransformerTest, OverlappingEditsInRule) {
399 std::string Input = "int conflictOneRule() { return 3 + 7; }";
400 // Try to change the whole binary-operator expression AND one its operands:
401 StringRef O = "O", L = "L";
402 Transformer T(
403 makeRule(binaryOperator(hasLHS(expr().bind(L))).bind(O),
404 {change<Expr>(O, "DELETE_OP"), change<Expr>(L, "DELETE_LHS")}),
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000405 consumer());
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000406 T.registerMatchers(&MatchFinder);
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000407 EXPECT_FALSE(rewrite(Input));
408 EXPECT_THAT(Changes, IsEmpty());
409 EXPECT_EQ(ErrorCount, 1);
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000410}
411
412// Tests for a conflict in edits across multiple matches (of the same rule).
413TEST_F(TransformerTest, OverlappingEditsMultipleMatches) {
414 std::string Input = "int conflictOneRule() { return -7; }";
415 // Try to change the whole binary-operator expression AND one its operands:
416 StringRef E = "E";
417 Transformer T(makeRule(expr().bind(E), change<Expr>(E, "DELETE_EXPR")),
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000418 consumer());
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000419 T.registerMatchers(&MatchFinder);
420 // The rewrite process fails because the changes conflict with each other...
421 EXPECT_FALSE(rewrite(Input));
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000422 // ... but two changes were produced.
423 EXPECT_EQ(Changes.size(), 2u);
424 EXPECT_EQ(ErrorCount, 0);
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000425}
426
427TEST_F(TransformerTest, ErrorOccurredMatchSkipped) {
428 // Syntax error in the function body:
429 std::string Input = "void errorOccurred() { 3 }";
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000430 Transformer T(makeRule(functionDecl(hasName("errorOccurred")),
431 change<Decl>("DELETED;")),
432 consumer());
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000433 T.registerMatchers(&MatchFinder);
434 // The rewrite process itself fails...
435 EXPECT_FALSE(rewrite(Input));
Yitzhak Mandelbaumaecc59c2019-04-30 16:48:33 +0000436 // ... and no changes or errors are produced in the process.
437 EXPECT_THAT(Changes, IsEmpty());
438 EXPECT_EQ(ErrorCount, 0);
Yitzhak Mandelbaumfa1552e2019-04-18 17:52:24 +0000439}
440
Yitzhak Mandelbaumfdd98782019-04-05 15:14:05 +0000441TEST_F(TransformerTest, NoTransformationInMacro) {
442 std::string Input = R"cc(
443#define MACRO(str) strlen((str).c_str())
444 int f(string s) { return MACRO(s); })cc";
445 testRule(ruleStrlenSize(), Input, Input);
446}
447
448// This test handles the corner case where a macro called within another macro
449// expands to matching code, but the matched code is an argument to the nested
450// macro. A simple check of isMacroArgExpansion() vs. isMacroBodyExpansion()
451// will get this wrong, and transform the code. This test verifies that no such
452// transformation occurs.
453TEST_F(TransformerTest, NoTransformationInNestedMacro) {
454 std::string Input = R"cc(
455#define NESTED(e) e
456#define MACRO(str) NESTED(strlen((str).c_str()))
457 int f(string s) { return MACRO(s); })cc";
458 testRule(ruleStrlenSize(), Input, Input);
459}
460} // namespace