1 | //===- 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/Transformer/Transformer.h" |
10 | #include "clang/ASTMatchers/ASTMatchers.h" |
11 | #include "clang/Tooling/Tooling.h" |
12 | #include "clang/Tooling/Transformer/RangeSelector.h" |
13 | #include "clang/Tooling/Transformer/RewriteRule.h" |
14 | #include "clang/Tooling/Transformer/Stencil.h" |
15 | #include "llvm/ADT/STLExtras.h" |
16 | #include "llvm/Support/Errc.h" |
17 | #include "llvm/Support/Error.h" |
18 | #include "gmock/gmock.h" |
19 | #include "gtest/gtest.h" |
20 | #include <optional> |
21 | |
22 | using namespace clang; |
23 | using namespace tooling; |
24 | using namespace ast_matchers; |
25 | namespace { |
26 | using ::clang::transformer::addInclude; |
27 | using ::clang::transformer::applyFirst; |
28 | using ::clang::transformer::before; |
29 | using ::clang::transformer::cat; |
30 | using ::clang::transformer::changeTo; |
31 | using ::clang::transformer::editList; |
32 | using ::clang::transformer::makeRule; |
33 | using ::clang::transformer::member; |
34 | using ::clang::transformer::name; |
35 | using ::clang::transformer::node; |
36 | using ::clang::transformer::noEdits; |
37 | using ::clang::transformer::remove; |
38 | using ::clang::transformer::rewriteDescendants; |
39 | using ::clang::transformer::RewriteRule; |
40 | using ::clang::transformer::RewriteRuleWith; |
41 | using ::clang::transformer::statement; |
42 | using ::testing::ElementsAre; |
43 | using ::testing::IsEmpty; |
44 | using ::testing::ResultOf; |
45 | using ::testing::UnorderedElementsAre; |
46 | |
47 | constexpr char [] = R"cc( |
48 | struct string { |
49 | string(const char*); |
50 | char* c_str(); |
51 | int size(); |
52 | }; |
53 | int strlen(const char*); |
54 | |
55 | namespace proto { |
56 | struct PCFProto { |
57 | int foo(); |
58 | }; |
59 | struct ProtoCommandLineFlag : PCFProto { |
60 | PCFProto& GetProto(); |
61 | }; |
62 | } // namespace proto |
63 | class Logger {}; |
64 | void operator<<(Logger& l, string msg); |
65 | Logger& log(int level); |
66 | )cc" ; |
67 | |
68 | static ast_matchers::internal::Matcher<clang::QualType> |
69 | isOrPointsTo(const clang::ast_matchers::DeclarationMatcher &TypeMatcher) { |
70 | return anyOf(hasDeclaration(InnerMatcher: TypeMatcher), pointsTo(InnerMatcher: TypeMatcher)); |
71 | } |
72 | |
73 | static std::string format(StringRef Code) { |
74 | const std::vector<Range> Ranges(1, Range(0, Code.size())); |
75 | auto Style = format::getLLVMStyle(); |
76 | const auto Replacements = format::reformat(Style, Code, Ranges); |
77 | auto Formatted = applyAllReplacements(Code, Replaces: Replacements); |
78 | if (!Formatted) { |
79 | ADD_FAILURE() << "Could not format code: " |
80 | << llvm::toString(E: Formatted.takeError()); |
81 | return std::string(); |
82 | } |
83 | return *Formatted; |
84 | } |
85 | |
86 | static void compareSnippets(StringRef Expected, |
87 | const std::optional<std::string> &MaybeActual) { |
88 | ASSERT_TRUE(MaybeActual) << "Rewrite failed. Expecting: " << Expected; |
89 | auto Actual = *MaybeActual; |
90 | std::string HL = "#include \"header.h\"\n" ; |
91 | auto I = Actual.find(str: HL); |
92 | if (I != std::string::npos) |
93 | Actual.erase(pos: I, n: HL.size()); |
94 | EXPECT_EQ(format(Expected), format(Actual)); |
95 | } |
96 | |
97 | // FIXME: consider separating this class into its own file(s). |
98 | class ClangRefactoringTestBase : public testing::Test { |
99 | protected: |
100 | void (StringRef S) { FileContents[0].second += S; } |
101 | |
102 | void addFile(StringRef Filename, StringRef Content) { |
103 | FileContents.emplace_back(args: std::string(Filename), args: std::string(Content)); |
104 | } |
105 | |
106 | std::optional<std::string> rewrite(StringRef Input) { |
107 | std::string Code = ("#include \"header.h\"\n" + Input).str(); |
108 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
109 | if (!runToolOnCodeWithArgs( |
110 | ToolAction: Factory->create(), Code, Args: std::vector<std::string>(), FileName: "input.cc" , |
111 | ToolName: "clang-tool" , PCHContainerOps: std::make_shared<PCHContainerOperations>(), |
112 | VirtualMappedFiles: FileContents)) { |
113 | llvm::errs() << "Running tool failed.\n" ; |
114 | return std::nullopt; |
115 | } |
116 | if (ErrorCount != 0) { |
117 | llvm::errs() << "Generating changes failed.\n" ; |
118 | return std::nullopt; |
119 | } |
120 | auto ChangedCode = |
121 | applyAtomicChanges(FilePath: "input.cc" , Code, Changes, Spec: ApplyChangesSpec()); |
122 | if (!ChangedCode) { |
123 | llvm::errs() << "Applying changes failed: " |
124 | << llvm::toString(E: ChangedCode.takeError()) << "\n" ; |
125 | return std::nullopt; |
126 | } |
127 | return *ChangedCode; |
128 | } |
129 | |
130 | Transformer::ChangeSetConsumer consumer() { |
131 | return [this](Expected<MutableArrayRef<AtomicChange>> C) { |
132 | if (C) { |
133 | Changes.insert(position: Changes.end(), first: std::make_move_iterator(i: C->begin()), |
134 | last: std::make_move_iterator(i: C->end())); |
135 | } else { |
136 | // FIXME: stash this error rather than printing. |
137 | llvm::errs() << "Error generating changes: " |
138 | << llvm::toString(E: C.takeError()) << "\n" ; |
139 | ++ErrorCount; |
140 | } |
141 | }; |
142 | } |
143 | |
144 | auto consumerWithStringMetadata() { |
145 | return [this](Expected<TransformerResult<std::string>> C) { |
146 | if (C) { |
147 | Changes.insert(position: Changes.end(), |
148 | first: std::make_move_iterator(i: C->Changes.begin()), |
149 | last: std::make_move_iterator(i: C->Changes.end())); |
150 | StringMetadata.push_back(x: std::move(C->Metadata)); |
151 | } else { |
152 | // FIXME: stash this error rather than printing. |
153 | llvm::errs() << "Error generating changes: " |
154 | << llvm::toString(E: C.takeError()) << "\n" ; |
155 | ++ErrorCount; |
156 | } |
157 | }; |
158 | } |
159 | |
160 | void testRule(RewriteRule Rule, StringRef Input, StringRef Expected) { |
161 | Transformers.push_back( |
162 | x: std::make_unique<Transformer>(args: std::move(Rule), args: consumer())); |
163 | Transformers.back()->registerMatchers(MatchFinder: &MatchFinder); |
164 | compareSnippets(Expected, MaybeActual: rewrite(Input)); |
165 | } |
166 | |
167 | void testRule(RewriteRuleWith<std::string> Rule, StringRef Input, |
168 | StringRef Expected) { |
169 | Transformers.push_back(x: std::make_unique<Transformer>( |
170 | args: std::move(Rule), args: consumerWithStringMetadata())); |
171 | Transformers.back()->registerMatchers(MatchFinder: &MatchFinder); |
172 | compareSnippets(Expected, MaybeActual: rewrite(Input)); |
173 | } |
174 | |
175 | void testRuleFailure(RewriteRule Rule, StringRef Input) { |
176 | Transformers.push_back( |
177 | x: std::make_unique<Transformer>(args: std::move(Rule), args: consumer())); |
178 | Transformers.back()->registerMatchers(MatchFinder: &MatchFinder); |
179 | ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code" ; |
180 | } |
181 | |
182 | void testRuleFailure(RewriteRuleWith<std::string> Rule, StringRef Input) { |
183 | Transformers.push_back(x: std::make_unique<Transformer>( |
184 | args: std::move(Rule), args: consumerWithStringMetadata())); |
185 | Transformers.back()->registerMatchers(MatchFinder: &MatchFinder); |
186 | ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code" ; |
187 | } |
188 | |
189 | // Transformers are referenced by MatchFinder. |
190 | std::vector<std::unique_ptr<Transformer>> Transformers; |
191 | clang::ast_matchers::MatchFinder MatchFinder; |
192 | // Records whether any errors occurred in individual changes. |
193 | int ErrorCount = 0; |
194 | AtomicChanges Changes; |
195 | std::vector<std::string> StringMetadata; |
196 | |
197 | private: |
198 | FileContentMappings FileContents = {{"header.h" , "" }}; |
199 | }; |
200 | |
201 | class TransformerTest : public ClangRefactoringTestBase { |
202 | protected: |
203 | TransformerTest() { appendToHeader(S: KHeaderContents); } |
204 | }; |
205 | |
206 | // Given string s, change strlen($s.c_str()) to REPLACED. |
207 | static RewriteRuleWith<std::string> ruleStrlenSize() { |
208 | StringRef StringExpr = "strexpr" ; |
209 | auto StringType = namedDecl(hasAnyName("::basic_string" , "::string" )); |
210 | auto R = makeRule( |
211 | M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "strlen" ))), |
212 | hasArgument(N: 0, InnerMatcher: cxxMemberCallExpr( |
213 | on(InnerMatcher: expr(hasType(InnerMatcher: isOrPointsTo(TypeMatcher: StringType))) |
214 | .bind(ID: StringExpr)), |
215 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "c_str" )))))), |
216 | Edits: changeTo(Replacement: cat(Parts: "REPLACED" )), Metadata: cat(Parts: "Use size() method directly on string." )); |
217 | return R; |
218 | } |
219 | |
220 | TEST_F(TransformerTest, StrlenSize) { |
221 | std::string Input = "int f(string s) { return strlen(s.c_str()); }" ; |
222 | std::string Expected = "int f(string s) { return REPLACED; }" ; |
223 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
224 | } |
225 | |
226 | // Tests that no change is applied when a match is not expected. |
227 | TEST_F(TransformerTest, NoMatch) { |
228 | std::string Input = "int f(string s) { return s.size(); }" ; |
229 | testRule(Rule: ruleStrlenSize(), Input, Expected: Input); |
230 | } |
231 | |
232 | // Tests replacing an expression. |
233 | TEST_F(TransformerTest, Flag) { |
234 | StringRef Flag = "flag" ; |
235 | RewriteRule Rule = makeRule( |
236 | M: cxxMemberCallExpr(on(InnerMatcher: expr(hasType(InnerMatcher: cxxRecordDecl( |
237 | hasName(Name: "proto::ProtoCommandLineFlag" )))) |
238 | .bind(ID: Flag)), |
239 | unless(callee(InnerMatcher: cxxMethodDecl(hasName(Name: "GetProto" ))))), |
240 | Edits: changeTo(Target: node(ID: std::string(Flag)), Replacement: cat(Parts: "EXPR" ))); |
241 | |
242 | std::string Input = R"cc( |
243 | proto::ProtoCommandLineFlag flag; |
244 | int x = flag.foo(); |
245 | int y = flag.GetProto().foo(); |
246 | )cc" ; |
247 | std::string Expected = R"cc( |
248 | proto::ProtoCommandLineFlag flag; |
249 | int x = EXPR.foo(); |
250 | int y = flag.GetProto().foo(); |
251 | )cc" ; |
252 | |
253 | testRule(Rule: std::move(Rule), Input, Expected); |
254 | } |
255 | |
256 | TEST_F(TransformerTest, AddIncludeQuoted) { |
257 | RewriteRule Rule = |
258 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f" )))), |
259 | Edits: {addInclude(Header: "clang/OtherLib.h" ), changeTo(Replacement: cat(Parts: "other()" ))}); |
260 | |
261 | std::string Input = R"cc( |
262 | int f(int x); |
263 | int h(int x) { return f(x); } |
264 | )cc" ; |
265 | std::string Expected = R"cc(#include "clang/OtherLib.h" |
266 | |
267 | int f(int x); |
268 | int h(int x) { return other(); } |
269 | )cc" ; |
270 | |
271 | testRule(Rule, Input, Expected); |
272 | } |
273 | |
274 | TEST_F(TransformerTest, AddIncludeAngled) { |
275 | RewriteRule Rule = makeRule( |
276 | M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f" )))), |
277 | Edits: {addInclude(Header: "clang/OtherLib.h" , Format: transformer::IncludeFormat::Angled), |
278 | changeTo(Replacement: cat(Parts: "other()" ))}); |
279 | |
280 | std::string Input = R"cc( |
281 | int f(int x); |
282 | int h(int x) { return f(x); } |
283 | )cc" ; |
284 | std::string Expected = R"cc(#include <clang/OtherLib.h> |
285 | |
286 | int f(int x); |
287 | int h(int x) { return other(); } |
288 | )cc" ; |
289 | |
290 | testRule(Rule, Input, Expected); |
291 | } |
292 | |
293 | TEST_F(TransformerTest, AddIncludeQuotedForRule) { |
294 | RewriteRule Rule = makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f" )))), |
295 | Edits: changeTo(Replacement: cat(Parts: "other()" ))); |
296 | addInclude(Rule, Header: "clang/OtherLib.h" ); |
297 | |
298 | std::string Input = R"cc( |
299 | int f(int x); |
300 | int h(int x) { return f(x); } |
301 | )cc" ; |
302 | std::string Expected = R"cc(#include "clang/OtherLib.h" |
303 | |
304 | int f(int x); |
305 | int h(int x) { return other(); } |
306 | )cc" ; |
307 | |
308 | testRule(Rule, Input, Expected); |
309 | } |
310 | |
311 | TEST_F(TransformerTest, AddIncludeAngledForRule) { |
312 | RewriteRule Rule = makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f" )))), |
313 | Edits: changeTo(Replacement: cat(Parts: "other()" ))); |
314 | addInclude(Rule, Header: "clang/OtherLib.h" , Format: transformer::IncludeFormat::Angled); |
315 | |
316 | std::string Input = R"cc( |
317 | int f(int x); |
318 | int h(int x) { return f(x); } |
319 | )cc" ; |
320 | std::string Expected = R"cc(#include <clang/OtherLib.h> |
321 | |
322 | int f(int x); |
323 | int h(int x) { return other(); } |
324 | )cc" ; |
325 | |
326 | testRule(Rule, Input, Expected); |
327 | } |
328 | |
329 | TEST_F(TransformerTest, NodePartNameNamedDecl) { |
330 | StringRef Fun = "fun" ; |
331 | RewriteRule Rule = makeRule(M: functionDecl(hasName(Name: "bad" )).bind(ID: Fun), |
332 | Edits: changeTo(Target: name(ID: std::string(Fun)), Replacement: cat(Parts: "good" ))); |
333 | |
334 | std::string Input = R"cc( |
335 | int bad(int x); |
336 | int bad(int x) { return x * x; } |
337 | )cc" ; |
338 | std::string Expected = R"cc( |
339 | int good(int x); |
340 | int good(int x) { return x * x; } |
341 | )cc" ; |
342 | |
343 | testRule(Rule, Input, Expected); |
344 | } |
345 | |
346 | TEST_F(TransformerTest, NodePartNameDeclRef) { |
347 | std::string Input = R"cc( |
348 | template <typename T> |
349 | T bad(T x) { |
350 | return x; |
351 | } |
352 | int neutral(int x) { return bad<int>(x) * x; } |
353 | )cc" ; |
354 | std::string Expected = R"cc( |
355 | template <typename T> |
356 | T bad(T x) { |
357 | return x; |
358 | } |
359 | int neutral(int x) { return good<int>(x) * x; } |
360 | )cc" ; |
361 | |
362 | StringRef Ref = "ref" ; |
363 | testRule(Rule: makeRule(M: declRefExpr(to(InnerMatcher: functionDecl(hasName(Name: "bad" )))).bind(ID: Ref), |
364 | Edits: changeTo(Target: name(ID: std::string(Ref)), Replacement: cat(Parts: "good" ))), |
365 | Input, Expected); |
366 | } |
367 | |
368 | TEST_F(TransformerTest, NodePartNameDeclRefFailure) { |
369 | std::string Input = R"cc( |
370 | struct Y { |
371 | int operator*(); |
372 | }; |
373 | int neutral(int x) { |
374 | Y y; |
375 | int (Y::*ptr)() = &Y::operator*; |
376 | return *y + x; |
377 | } |
378 | )cc" ; |
379 | |
380 | StringRef Ref = "ref" ; |
381 | Transformer T(makeRule(M: declRefExpr(to(InnerMatcher: functionDecl())).bind(ID: Ref), |
382 | Edits: changeTo(Target: name(ID: std::string(Ref)), Replacement: cat(Parts: "good" ))), |
383 | consumer()); |
384 | T.registerMatchers(MatchFinder: &MatchFinder); |
385 | EXPECT_FALSE(rewrite(Input)); |
386 | } |
387 | |
388 | TEST_F(TransformerTest, NodePartMember) { |
389 | StringRef E = "expr" ; |
390 | RewriteRule Rule = |
391 | makeRule(M: memberExpr(clang::ast_matchers::member(InnerMatcher: hasName(Name: "bad" ))).bind(ID: E), |
392 | Edits: changeTo(Target: member(ID: std::string(E)), Replacement: cat(Parts: "good" ))); |
393 | |
394 | std::string Input = R"cc( |
395 | struct S { |
396 | int bad; |
397 | }; |
398 | int g() { |
399 | S s; |
400 | return s.bad; |
401 | } |
402 | )cc" ; |
403 | std::string Expected = R"cc( |
404 | struct S { |
405 | int bad; |
406 | }; |
407 | int g() { |
408 | S s; |
409 | return s.good; |
410 | } |
411 | )cc" ; |
412 | |
413 | testRule(Rule, Input, Expected); |
414 | } |
415 | |
416 | TEST_F(TransformerTest, NodePartMemberQualified) { |
417 | std::string Input = R"cc( |
418 | struct S { |
419 | int bad; |
420 | int good; |
421 | }; |
422 | struct T : public S { |
423 | int bad; |
424 | }; |
425 | int g() { |
426 | T t; |
427 | return t.S::bad; |
428 | } |
429 | )cc" ; |
430 | std::string Expected = R"cc( |
431 | struct S { |
432 | int bad; |
433 | int good; |
434 | }; |
435 | struct T : public S { |
436 | int bad; |
437 | }; |
438 | int g() { |
439 | T t; |
440 | return t.S::good; |
441 | } |
442 | )cc" ; |
443 | |
444 | StringRef E = "expr" ; |
445 | testRule(Rule: makeRule(M: memberExpr().bind(ID: E), |
446 | Edits: changeTo(Target: member(ID: std::string(E)), Replacement: cat(Parts: "good" ))), |
447 | Input, Expected); |
448 | } |
449 | |
450 | TEST_F(TransformerTest, NodePartMemberMultiToken) { |
451 | std::string Input = R"cc( |
452 | struct Y { |
453 | int operator*(); |
454 | int good(); |
455 | template <typename T> void foo(T t); |
456 | }; |
457 | int neutral(int x) { |
458 | Y y; |
459 | y.template foo<int>(3); |
460 | return y.operator *(); |
461 | } |
462 | )cc" ; |
463 | std::string Expected = R"cc( |
464 | struct Y { |
465 | int operator*(); |
466 | int good(); |
467 | template <typename T> void foo(T t); |
468 | }; |
469 | int neutral(int x) { |
470 | Y y; |
471 | y.template good<int>(3); |
472 | return y.good(); |
473 | } |
474 | )cc" ; |
475 | |
476 | StringRef MemExpr = "member" ; |
477 | testRule(Rule: makeRule(M: memberExpr().bind(ID: MemExpr), |
478 | Edits: changeTo(Target: member(ID: std::string(MemExpr)), Replacement: cat(Parts: "good" ))), |
479 | Input, Expected); |
480 | } |
481 | |
482 | TEST_F(TransformerTest, NoEdits) { |
483 | using transformer::noEdits; |
484 | std::string Input = "int f(int x) { return x; }" ; |
485 | testRule(Rule: makeRule(M: returnStmt().bind(ID: "return" ), Edits: noEdits()), Input, Expected: Input); |
486 | } |
487 | |
488 | TEST_F(TransformerTest, NoopEdit) { |
489 | using transformer::node; |
490 | using transformer::noopEdit; |
491 | std::string Input = "int f(int x) { return x; }" ; |
492 | testRule(Rule: makeRule(M: returnStmt().bind(ID: "return" ), Edits: noopEdit(Anchor: node(ID: "return" ))), |
493 | Input, Expected: Input); |
494 | } |
495 | |
496 | TEST_F(TransformerTest, IfBound2Args) { |
497 | using transformer::ifBound; |
498 | std::string Input = "int f(int x) { return x; }" ; |
499 | std::string Expected = "int f(int x) { CHANGE; }" ; |
500 | testRule(Rule: makeRule(M: returnStmt().bind(ID: "return" ), |
501 | Edits: ifBound(ID: "return" , TrueEdit: changeTo(Replacement: cat(Parts: "CHANGE;" )))), |
502 | Input, Expected); |
503 | } |
504 | |
505 | TEST_F(TransformerTest, IfBound3Args) { |
506 | using transformer::ifBound; |
507 | std::string Input = "int f(int x) { return x; }" ; |
508 | std::string Expected = "int f(int x) { CHANGE; }" ; |
509 | testRule(Rule: makeRule(M: returnStmt().bind(ID: "return" ), |
510 | Edits: ifBound(ID: "nothing" , TrueEdit: changeTo(Replacement: cat(Parts: "ERROR" )), |
511 | FalseEdit: changeTo(Replacement: cat(Parts: "CHANGE;" )))), |
512 | Input, Expected); |
513 | } |
514 | |
515 | TEST_F(TransformerTest, ShrinkTo) { |
516 | using transformer::shrinkTo; |
517 | std::string Input = "int f(int x) { return x; }" ; |
518 | std::string Expected = "return x;" ; |
519 | testRule(Rule: makeRule(M: functionDecl(hasDescendant(returnStmt().bind(ID: "return" ))) |
520 | .bind(ID: "function" ), |
521 | Edits: shrinkTo(outer: node(ID: "function" ), inner: node(ID: "return" ))), |
522 | Input, Expected); |
523 | } |
524 | |
525 | // Rewrite various Stmts inside a Decl. |
526 | TEST_F(TransformerTest, RewriteDescendantsDeclChangeStmt) { |
527 | std::string Input = |
528 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
529 | std::string Expected = |
530 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
531 | auto InlineX = |
532 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
533 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" )).bind(ID: "fun" ), |
534 | Edits: rewriteDescendants(NodeId: "fun" , Rule: InlineX)), |
535 | Input, Expected); |
536 | } |
537 | |
538 | // Rewrite various TypeLocs inside a Decl. |
539 | TEST_F(TransformerTest, RewriteDescendantsDeclChangeTypeLoc) { |
540 | std::string Input = "int f(int *x) { return *x; }" ; |
541 | std::string Expected = "char f(char *x) { return *x; }" ; |
542 | auto IntToChar = makeRule(M: typeLoc(loc(InnerMatcher: qualType(isInteger(), builtinType()))), |
543 | Edits: changeTo(Replacement: cat(Parts: "char" ))); |
544 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" )).bind(ID: "fun" ), |
545 | Edits: rewriteDescendants(NodeId: "fun" , Rule: IntToChar)), |
546 | Input, Expected); |
547 | } |
548 | |
549 | TEST_F(TransformerTest, RewriteDescendantsStmt) { |
550 | // Add an unrelated definition to the header that also has a variable named |
551 | // "x", to test that the rewrite is limited to the scope we intend. |
552 | appendToHeader(S: R"cc(int g(int x) { return x; })cc" ); |
553 | std::string Input = |
554 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
555 | std::string Expected = |
556 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
557 | auto InlineX = |
558 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
559 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" ), hasBody(InnerMatcher: stmt().bind(ID: "body" ))), |
560 | Edits: rewriteDescendants(NodeId: "body" , Rule: InlineX)), |
561 | Input, Expected); |
562 | } |
563 | |
564 | TEST_F(TransformerTest, RewriteDescendantsStmtWithAdditionalChange) { |
565 | std::string Input = |
566 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
567 | std::string Expected = |
568 | "int newName(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
569 | auto InlineX = |
570 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
571 | testRule( |
572 | Rule: makeRule( |
573 | M: functionDecl(hasName(Name: "f" ), hasBody(InnerMatcher: stmt().bind(ID: "body" ))).bind(ID: "f" ), |
574 | Edits: flatten(Edits: changeTo(Target: name(ID: "f" ), Replacement: cat(Parts: "newName" )), |
575 | Edits: rewriteDescendants(NodeId: "body" , Rule: InlineX))), |
576 | Input, Expected); |
577 | } |
578 | |
579 | TEST_F(TransformerTest, RewriteDescendantsTypeLoc) { |
580 | std::string Input = "int f(int *x) { return *x; }" ; |
581 | std::string Expected = "int f(char *x) { return *x; }" ; |
582 | auto IntToChar = |
583 | makeRule(M: typeLoc(loc(InnerMatcher: qualType(isInteger(), builtinType()))).bind(ID: "loc" ), |
584 | Edits: changeTo(Replacement: cat(Parts: "char" ))); |
585 | testRule( |
586 | Rule: makeRule(M: functionDecl(hasName(Name: "f" ), |
587 | hasParameter(N: 0, InnerMatcher: varDecl(hasTypeLoc( |
588 | Inner: typeLoc().bind(ID: "parmType" ))))), |
589 | Edits: rewriteDescendants(NodeId: "parmType" , Rule: IntToChar)), |
590 | Input, Expected); |
591 | } |
592 | |
593 | TEST_F(TransformerTest, RewriteDescendantsReferToParentBinding) { |
594 | std::string Input = |
595 | "int f(int p) { int y = p; { int z = p * p; } return p; }" ; |
596 | std::string Expected = |
597 | "int f(int p) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
598 | std::string VarId = "var" ; |
599 | auto InlineVar = makeRule(M: declRefExpr(to(InnerMatcher: varDecl(equalsBoundNode(ID: VarId)))), |
600 | Edits: changeTo(Replacement: cat(Parts: "3" ))); |
601 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" ), |
602 | hasParameter(N: 0, InnerMatcher: varDecl().bind(ID: VarId))) |
603 | .bind(ID: "fun" ), |
604 | Edits: rewriteDescendants(NodeId: "fun" , Rule: InlineVar)), |
605 | Input, Expected); |
606 | } |
607 | |
608 | TEST_F(TransformerTest, RewriteDescendantsUnboundNode) { |
609 | std::string Input = |
610 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
611 | auto InlineX = |
612 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
613 | Transformer T(makeRule(M: functionDecl(hasName(Name: "f" )), |
614 | Edits: rewriteDescendants(NodeId: "UNBOUND" , Rule: InlineX)), |
615 | consumer()); |
616 | T.registerMatchers(MatchFinder: &MatchFinder); |
617 | EXPECT_FALSE(rewrite(Input)); |
618 | EXPECT_THAT(Changes, IsEmpty()); |
619 | EXPECT_EQ(ErrorCount, 1); |
620 | } |
621 | |
622 | TEST_F(TransformerTest, RewriteDescendantsInvalidNodeType) { |
623 | std::string Input = |
624 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
625 | auto IntToChar = |
626 | makeRule(M: qualType(isInteger(), builtinType()), Edits: changeTo(Replacement: cat(Parts: "char" ))); |
627 | Transformer T( |
628 | makeRule(M: functionDecl( |
629 | hasName(Name: "f" ), |
630 | hasParameter(N: 0, InnerMatcher: varDecl(hasType(InnerMatcher: qualType().bind(ID: "type" ))))), |
631 | Edits: rewriteDescendants(NodeId: "type" , Rule: IntToChar)), |
632 | consumer()); |
633 | T.registerMatchers(MatchFinder: &MatchFinder); |
634 | EXPECT_FALSE(rewrite(Input)); |
635 | EXPECT_THAT(Changes, IsEmpty()); |
636 | EXPECT_EQ(ErrorCount, 1); |
637 | } |
638 | |
639 | // |
640 | // We include one test per typed overload. We don't test extensively since that |
641 | // is already covered by the tests above. |
642 | // |
643 | |
644 | TEST_F(TransformerTest, RewriteDescendantsTypedStmt) { |
645 | // Add an unrelated definition to the header that also has a variable named |
646 | // "x", to test that the rewrite is limited to the scope we intend. |
647 | appendToHeader(S: R"cc(int g(int x) { return x; })cc" ); |
648 | std::string Input = |
649 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
650 | std::string Expected = |
651 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
652 | auto InlineX = |
653 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
654 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" ), hasBody(InnerMatcher: stmt().bind(ID: "body" ))), |
655 | Edits: [&InlineX](const MatchFinder::MatchResult &R) { |
656 | const auto *Node = R.Nodes.getNodeAs<Stmt>(ID: "body" ); |
657 | assert(Node != nullptr && "body must be bound" ); |
658 | return transformer::detail::rewriteDescendants( |
659 | Node: *Node, Rule: InlineX, Result: R); |
660 | }), |
661 | Input, Expected); |
662 | } |
663 | |
664 | TEST_F(TransformerTest, RewriteDescendantsTypedDecl) { |
665 | std::string Input = |
666 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
667 | std::string Expected = |
668 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
669 | auto InlineX = |
670 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
671 | testRule(Rule: makeRule(M: functionDecl(hasName(Name: "f" )).bind(ID: "fun" ), |
672 | Edits: [&InlineX](const MatchFinder::MatchResult &R) { |
673 | const auto *Node = R.Nodes.getNodeAs<Decl>(ID: "fun" ); |
674 | assert(Node != nullptr && "fun must be bound" ); |
675 | return transformer::detail::rewriteDescendants( |
676 | Node: *Node, Rule: InlineX, Result: R); |
677 | }), |
678 | Input, Expected); |
679 | } |
680 | |
681 | TEST_F(TransformerTest, RewriteDescendantsTypedTypeLoc) { |
682 | std::string Input = "int f(int *x) { return *x; }" ; |
683 | std::string Expected = "int f(char *x) { return *x; }" ; |
684 | auto IntToChar = |
685 | makeRule(M: typeLoc(loc(InnerMatcher: qualType(isInteger(), builtinType()))).bind(ID: "loc" ), |
686 | Edits: changeTo(Replacement: cat(Parts: "char" ))); |
687 | testRule( |
688 | Rule: makeRule( |
689 | M: functionDecl( |
690 | hasName(Name: "f" ), |
691 | hasParameter(N: 0, InnerMatcher: varDecl(hasTypeLoc(Inner: typeLoc().bind(ID: "parmType" ))))), |
692 | Edits: [&IntToChar](const MatchFinder::MatchResult &R) { |
693 | const auto *Node = R.Nodes.getNodeAs<TypeLoc>(ID: "parmType" ); |
694 | assert(Node != nullptr && "parmType must be bound" ); |
695 | return transformer::detail::rewriteDescendants(Node: *Node, Rule: IntToChar, Result: R); |
696 | }), |
697 | Input, Expected); |
698 | } |
699 | |
700 | TEST_F(TransformerTest, RewriteDescendantsTypedDynTyped) { |
701 | // Add an unrelated definition to the header that also has a variable named |
702 | // "x", to test that the rewrite is limited to the scope we intend. |
703 | appendToHeader(S: R"cc(int g(int x) { return x; })cc" ); |
704 | std::string Input = |
705 | "int f(int x) { int y = x; { int z = x * x; } return x; }" ; |
706 | std::string Expected = |
707 | "int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }" ; |
708 | auto InlineX = |
709 | makeRule(M: declRefExpr(to(InnerMatcher: varDecl(hasName(Name: "x" )))), Edits: changeTo(Replacement: cat(Parts: "3" ))); |
710 | testRule( |
711 | Rule: makeRule(M: functionDecl(hasName(Name: "f" ), hasBody(InnerMatcher: stmt().bind(ID: "body" ))), |
712 | Edits: [&InlineX](const MatchFinder::MatchResult &R) { |
713 | auto It = R.Nodes.getMap().find(x: "body" ); |
714 | assert(It != R.Nodes.getMap().end() && "body must be bound" ); |
715 | return transformer::detail::rewriteDescendants(It->second, |
716 | InlineX, R); |
717 | }), |
718 | Input, Expected); |
719 | } |
720 | |
721 | TEST_F(TransformerTest, InsertBeforeEdit) { |
722 | std::string Input = R"cc( |
723 | int f() { |
724 | return 7; |
725 | } |
726 | )cc" ; |
727 | std::string Expected = R"cc( |
728 | int f() { |
729 | int y = 3; |
730 | return 7; |
731 | } |
732 | )cc" ; |
733 | |
734 | StringRef Ret = "return" ; |
735 | testRule( |
736 | Rule: makeRule(M: returnStmt().bind(ID: Ret), |
737 | Edits: insertBefore(S: statement(ID: std::string(Ret)), Replacement: cat(Parts: "int y = 3;" ))), |
738 | Input, Expected); |
739 | } |
740 | |
741 | TEST_F(TransformerTest, InsertAfterEdit) { |
742 | std::string Input = R"cc( |
743 | int f() { |
744 | int x = 5; |
745 | return 7; |
746 | } |
747 | )cc" ; |
748 | std::string Expected = R"cc( |
749 | int f() { |
750 | int x = 5; |
751 | int y = 3; |
752 | return 7; |
753 | } |
754 | )cc" ; |
755 | |
756 | StringRef Decl = "decl" ; |
757 | testRule( |
758 | Rule: makeRule(M: declStmt().bind(ID: Decl), |
759 | Edits: insertAfter(S: statement(ID: std::string(Decl)), Replacement: cat(Parts: "int y = 3;" ))), |
760 | Input, Expected); |
761 | } |
762 | |
763 | TEST_F(TransformerTest, RemoveEdit) { |
764 | std::string Input = R"cc( |
765 | int f() { |
766 | int x = 5; |
767 | return 7; |
768 | } |
769 | )cc" ; |
770 | std::string Expected = R"cc( |
771 | int f() { |
772 | return 7; |
773 | } |
774 | )cc" ; |
775 | |
776 | StringRef Decl = "decl" ; |
777 | testRule( |
778 | Rule: makeRule(M: declStmt().bind(ID: Decl), Edits: remove(S: statement(ID: std::string(Decl)))), |
779 | Input, Expected); |
780 | } |
781 | |
782 | TEST_F(TransformerTest, WithMetadata) { |
783 | auto makeMetadata = [](const MatchFinder::MatchResult &R) -> llvm::Any { |
784 | int N = |
785 | R.Nodes.getNodeAs<IntegerLiteral>(ID: "int" )->getValue().getLimitedValue(); |
786 | return N; |
787 | }; |
788 | |
789 | std::string Input = R"cc( |
790 | int f() { |
791 | int x = 5; |
792 | return 7; |
793 | } |
794 | )cc" ; |
795 | |
796 | Transformer T( |
797 | makeRule( |
798 | M: declStmt(containsDeclaration(N: 0, InnerMatcher: varDecl(hasInitializer( |
799 | InnerMatcher: integerLiteral().bind(ID: "int" ))))) |
800 | .bind(ID: "decl" ), |
801 | Edits: withMetadata(Edit: remove(S: statement(ID: std::string("decl" ))), Metadata: makeMetadata)), |
802 | consumer()); |
803 | T.registerMatchers(MatchFinder: &MatchFinder); |
804 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
805 | EXPECT_TRUE(runToolOnCodeWithArgs( |
806 | Factory->create(), Input, std::vector<std::string>(), "input.cc" , |
807 | "clang-tool" , std::make_shared<PCHContainerOperations>(), {})); |
808 | ASSERT_EQ(Changes.size(), 1u); |
809 | const llvm::Any &Metadata = Changes[0].getMetadata(); |
810 | ASSERT_TRUE(llvm::any_cast<int>(&Metadata)); |
811 | EXPECT_THAT(llvm::any_cast<int>(Metadata), 5); |
812 | } |
813 | |
814 | TEST_F(TransformerTest, MultiChange) { |
815 | std::string Input = R"cc( |
816 | void foo() { |
817 | if (10 > 1.0) |
818 | log(1) << "oh no!"; |
819 | else |
820 | log(0) << "ok"; |
821 | } |
822 | )cc" ; |
823 | std::string Expected = R"( |
824 | void foo() { |
825 | if (true) { /* then */ } |
826 | else { /* else */ } |
827 | } |
828 | )" ; |
829 | |
830 | StringRef C = "C" , T = "T" , E = "E" ; |
831 | testRule( |
832 | Rule: makeRule(M: ifStmt(hasCondition(InnerMatcher: expr().bind(ID: C)), hasThen(InnerMatcher: stmt().bind(ID: T)), |
833 | hasElse(InnerMatcher: stmt().bind(ID: E))), |
834 | Edits: {changeTo(Target: node(ID: std::string(C)), Replacement: cat(Parts: "true" )), |
835 | changeTo(Target: statement(ID: std::string(T)), Replacement: cat(Parts: "{ /* then */ }" )), |
836 | changeTo(Target: statement(ID: std::string(E)), Replacement: cat(Parts: "{ /* else */ }" ))}), |
837 | Input, Expected); |
838 | } |
839 | |
840 | TEST_F(TransformerTest, EditList) { |
841 | std::string Input = R"cc( |
842 | void foo() { |
843 | if (10 > 1.0) |
844 | log(1) << "oh no!"; |
845 | else |
846 | log(0) << "ok"; |
847 | } |
848 | )cc" ; |
849 | std::string Expected = R"( |
850 | void foo() { |
851 | if (true) { /* then */ } |
852 | else { /* else */ } |
853 | } |
854 | )" ; |
855 | |
856 | StringRef C = "C" , T = "T" , E = "E" ; |
857 | testRule(Rule: makeRule(M: ifStmt(hasCondition(InnerMatcher: expr().bind(ID: C)), |
858 | hasThen(InnerMatcher: stmt().bind(ID: T)), hasElse(InnerMatcher: stmt().bind(ID: E))), |
859 | Edits: editList(Edits: {changeTo(Target: node(ID: std::string(C)), Replacement: cat(Parts: "true" )), |
860 | changeTo(Target: statement(ID: std::string(T)), |
861 | Replacement: cat(Parts: "{ /* then */ }" )), |
862 | changeTo(Target: statement(ID: std::string(E)), |
863 | Replacement: cat(Parts: "{ /* else */ }" ))})), |
864 | Input, Expected); |
865 | } |
866 | |
867 | TEST_F(TransformerTest, Flatten) { |
868 | std::string Input = R"cc( |
869 | void foo() { |
870 | if (10 > 1.0) |
871 | log(1) << "oh no!"; |
872 | else |
873 | log(0) << "ok"; |
874 | } |
875 | )cc" ; |
876 | std::string Expected = R"( |
877 | void foo() { |
878 | if (true) { /* then */ } |
879 | else { /* else */ } |
880 | } |
881 | )" ; |
882 | |
883 | StringRef C = "C" , T = "T" , E = "E" ; |
884 | testRule( |
885 | Rule: makeRule( |
886 | M: ifStmt(hasCondition(InnerMatcher: expr().bind(ID: C)), hasThen(InnerMatcher: stmt().bind(ID: T)), |
887 | hasElse(InnerMatcher: stmt().bind(ID: E))), |
888 | Edits: flatten(Edits: changeTo(Target: node(ID: std::string(C)), Replacement: cat(Parts: "true" )), |
889 | Edits: changeTo(Target: statement(ID: std::string(T)), Replacement: cat(Parts: "{ /* then */ }" )), |
890 | Edits: changeTo(Target: statement(ID: std::string(E)), Replacement: cat(Parts: "{ /* else */ }" )))), |
891 | Input, Expected); |
892 | } |
893 | |
894 | TEST_F(TransformerTest, FlattenWithMixedArgs) { |
895 | using clang::transformer::editList; |
896 | std::string Input = R"cc( |
897 | void foo() { |
898 | if (10 > 1.0) |
899 | log(1) << "oh no!"; |
900 | else |
901 | log(0) << "ok"; |
902 | } |
903 | )cc" ; |
904 | std::string Expected = R"( |
905 | void foo() { |
906 | if (true) { /* then */ } |
907 | else { /* else */ } |
908 | } |
909 | )" ; |
910 | |
911 | StringRef C = "C" , T = "T" , E = "E" ; |
912 | testRule(Rule: makeRule(M: ifStmt(hasCondition(InnerMatcher: expr().bind(ID: C)), |
913 | hasThen(InnerMatcher: stmt().bind(ID: T)), hasElse(InnerMatcher: stmt().bind(ID: E))), |
914 | Edits: flatten(Edits: changeTo(Target: node(ID: std::string(C)), Replacement: cat(Parts: "true" )), |
915 | Edits: edit(E: changeTo(Target: statement(ID: std::string(T)), |
916 | Replacement: cat(Parts: "{ /* then */ }" ))), |
917 | Edits: editList(Edits: {changeTo(Target: statement(ID: std::string(E)), |
918 | Replacement: cat(Parts: "{ /* else */ }" ))}))), |
919 | Input, Expected); |
920 | } |
921 | |
922 | TEST_F(TransformerTest, OrderedRuleUnrelated) { |
923 | StringRef Flag = "flag" ; |
924 | RewriteRuleWith<std::string> FlagRule = makeRule( |
925 | M: cxxMemberCallExpr(on(InnerMatcher: expr(hasType(InnerMatcher: cxxRecordDecl( |
926 | hasName(Name: "proto::ProtoCommandLineFlag" )))) |
927 | .bind(ID: Flag)), |
928 | unless(callee(InnerMatcher: cxxMethodDecl(hasName(Name: "GetProto" ))))), |
929 | Edits: changeTo(Target: node(ID: std::string(Flag)), Replacement: cat(Parts: "PROTO" )), Metadata: cat(Parts: "" )); |
930 | |
931 | std::string Input = R"cc( |
932 | proto::ProtoCommandLineFlag flag; |
933 | int x = flag.foo(); |
934 | int y = flag.GetProto().foo(); |
935 | int f(string s) { return strlen(s.c_str()); } |
936 | )cc" ; |
937 | std::string Expected = R"cc( |
938 | proto::ProtoCommandLineFlag flag; |
939 | int x = PROTO.foo(); |
940 | int y = flag.GetProto().foo(); |
941 | int f(string s) { return REPLACED; } |
942 | )cc" ; |
943 | |
944 | testRule(Rule: applyFirst(Rules: {ruleStrlenSize(), FlagRule}), Input, Expected); |
945 | } |
946 | |
947 | TEST_F(TransformerTest, OrderedRuleRelated) { |
948 | std::string Input = R"cc( |
949 | void f1(); |
950 | void f2(); |
951 | void call_f1() { f1(); } |
952 | void call_f2() { f2(); } |
953 | )cc" ; |
954 | std::string Expected = R"cc( |
955 | void f1(); |
956 | void f2(); |
957 | void call_f1() { REPLACE_F1; } |
958 | void call_f2() { REPLACE_F1_OR_F2; } |
959 | )cc" ; |
960 | |
961 | RewriteRule ReplaceF1 = |
962 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f1" )))), |
963 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1" ))); |
964 | RewriteRule ReplaceF1OrF2 = |
965 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasAnyName("f1" , "f2" )))), |
966 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1_OR_F2" ))); |
967 | testRule(Rule: applyFirst(Rules: {ReplaceF1, ReplaceF1OrF2}), Input, Expected); |
968 | } |
969 | |
970 | // Change the order of the rules to get a different result. When `ReplaceF1OrF2` |
971 | // comes first, it applies for both uses, so `ReplaceF1` never applies. |
972 | TEST_F(TransformerTest, OrderedRuleRelatedSwapped) { |
973 | std::string Input = R"cc( |
974 | void f1(); |
975 | void f2(); |
976 | void call_f1() { f1(); } |
977 | void call_f2() { f2(); } |
978 | )cc" ; |
979 | std::string Expected = R"cc( |
980 | void f1(); |
981 | void f2(); |
982 | void call_f1() { REPLACE_F1_OR_F2; } |
983 | void call_f2() { REPLACE_F1_OR_F2; } |
984 | )cc" ; |
985 | |
986 | RewriteRule ReplaceF1 = |
987 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f1" )))), |
988 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1" ))); |
989 | RewriteRule ReplaceF1OrF2 = |
990 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasAnyName("f1" , "f2" )))), |
991 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1_OR_F2" ))); |
992 | testRule(Rule: applyFirst(Rules: {ReplaceF1OrF2, ReplaceF1}), Input, Expected); |
993 | } |
994 | |
995 | // Verify that a set of rules whose matchers have different base kinds works |
996 | // properly, including that `applyFirst` produces multiple matchers. We test |
997 | // two different kinds of rules: Expr and Decl. We place the Decl rule in the |
998 | // middle to test that `buildMatchers` works even when the kinds aren't grouped |
999 | // together. |
1000 | TEST_F(TransformerTest, OrderedRuleMultipleKinds) { |
1001 | std::string Input = R"cc( |
1002 | void f1(); |
1003 | void f2(); |
1004 | void call_f1() { f1(); } |
1005 | void call_f2() { f2(); } |
1006 | )cc" ; |
1007 | std::string Expected = R"cc( |
1008 | void f1(); |
1009 | void DECL_RULE(); |
1010 | void call_f1() { REPLACE_F1; } |
1011 | void call_f2() { REPLACE_F1_OR_F2; } |
1012 | )cc" ; |
1013 | |
1014 | RewriteRule ReplaceF1 = |
1015 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f1" )))), |
1016 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1" ))); |
1017 | RewriteRule ReplaceF1OrF2 = |
1018 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasAnyName("f1" , "f2" )))), |
1019 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1_OR_F2" ))); |
1020 | RewriteRule DeclRule = makeRule(M: functionDecl(hasName(Name: "f2" )).bind(ID: "fun" ), |
1021 | Edits: changeTo(Target: name(ID: "fun" ), Replacement: cat(Parts: "DECL_RULE" ))); |
1022 | |
1023 | RewriteRule Rule = applyFirst(Rules: {ReplaceF1, DeclRule, ReplaceF1OrF2}); |
1024 | EXPECT_EQ(transformer::detail::buildMatchers(Rule).size(), 2UL); |
1025 | testRule(Rule, Input, Expected); |
1026 | } |
1027 | |
1028 | // Verifies that a rule with a top-level matcher for an implicit node (like |
1029 | // `implicitCastExpr`) works correctly -- the implicit nodes are not skipped. |
1030 | TEST_F(TransformerTest, OrderedRuleImplicitMatched) { |
1031 | std::string Input = R"cc( |
1032 | void f1(); |
1033 | int f2(); |
1034 | void call_f1() { f1(); } |
1035 | float call_f2() { return f2(); } |
1036 | )cc" ; |
1037 | std::string Expected = R"cc( |
1038 | void f1(); |
1039 | int f2(); |
1040 | void call_f1() { REPLACE_F1; } |
1041 | float call_f2() { return REPLACE_F2; } |
1042 | )cc" ; |
1043 | |
1044 | RewriteRule ReplaceF1 = |
1045 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "f1" )))), |
1046 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F1" ))); |
1047 | RewriteRule ReplaceF2 = |
1048 | makeRule(M: implicitCastExpr(hasSourceExpression(InnerMatcher: callExpr())), |
1049 | Edits: changeTo(Replacement: cat(Parts: "REPLACE_F2" ))); |
1050 | testRule(Rule: applyFirst(Rules: {ReplaceF1, ReplaceF2}), Input, Expected); |
1051 | } |
1052 | |
1053 | // |
1054 | // Negative tests (where we expect no transformation to occur). |
1055 | // |
1056 | |
1057 | // Tests for a conflict in edits from a single match for a rule. |
1058 | TEST_F(TransformerTest, TextGeneratorFailure) { |
1059 | std::string Input = "int conflictOneRule() { return 3 + 7; }" ; |
1060 | // Try to change the whole binary-operator expression AND one its operands: |
1061 | StringRef O = "O" ; |
1062 | class AlwaysFail : public transformer::MatchComputation<std::string> { |
1063 | llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &, |
1064 | std::string *) const override { |
1065 | return llvm::createStringError(EC: llvm::errc::invalid_argument, Msg: "ERROR" ); |
1066 | } |
1067 | std::string toString() const override { return "AlwaysFail" ; } |
1068 | }; |
1069 | Transformer T( |
1070 | makeRule(M: binaryOperator().bind(ID: O), |
1071 | Edits: changeTo(Target: node(ID: std::string(O)), Replacement: std::make_shared<AlwaysFail>())), |
1072 | consumer()); |
1073 | T.registerMatchers(MatchFinder: &MatchFinder); |
1074 | EXPECT_FALSE(rewrite(Input)); |
1075 | EXPECT_THAT(Changes, IsEmpty()); |
1076 | EXPECT_EQ(ErrorCount, 1); |
1077 | } |
1078 | |
1079 | // Tests for a conflict in edits from a single match for a rule. |
1080 | TEST_F(TransformerTest, OverlappingEditsInRule) { |
1081 | std::string Input = "int conflictOneRule() { return 3 + 7; }" ; |
1082 | // Try to change the whole binary-operator expression AND one its operands: |
1083 | StringRef O = "O" , L = "L" ; |
1084 | Transformer T(makeRule(M: binaryOperator(hasLHS(InnerMatcher: expr().bind(ID: L))).bind(ID: O), |
1085 | Edits: {changeTo(Target: node(ID: std::string(O)), Replacement: cat(Parts: "DELETE_OP" )), |
1086 | changeTo(Target: node(ID: std::string(L)), Replacement: cat(Parts: "DELETE_LHS" ))}), |
1087 | consumer()); |
1088 | T.registerMatchers(MatchFinder: &MatchFinder); |
1089 | EXPECT_FALSE(rewrite(Input)); |
1090 | EXPECT_THAT(Changes, IsEmpty()); |
1091 | EXPECT_EQ(ErrorCount, 1); |
1092 | } |
1093 | |
1094 | // Tests for a conflict in edits across multiple matches (of the same rule). |
1095 | TEST_F(TransformerTest, OverlappingEditsMultipleMatches) { |
1096 | std::string Input = "int conflictOneRule() { return -7; }" ; |
1097 | // Try to change the whole binary-operator expression AND one its operands: |
1098 | StringRef E = "E" ; |
1099 | Transformer T(makeRule(M: expr().bind(ID: E), |
1100 | Edits: changeTo(Target: node(ID: std::string(E)), Replacement: cat(Parts: "DELETE_EXPR" ))), |
1101 | consumer()); |
1102 | T.registerMatchers(MatchFinder: &MatchFinder); |
1103 | // The rewrite process fails because the changes conflict with each other... |
1104 | EXPECT_FALSE(rewrite(Input)); |
1105 | // ... but two changes were produced. |
1106 | EXPECT_EQ(Changes.size(), 2u); |
1107 | EXPECT_EQ(ErrorCount, 0); |
1108 | } |
1109 | |
1110 | TEST_F(TransformerTest, ErrorOccurredMatchSkipped) { |
1111 | // Syntax error in the function body: |
1112 | std::string Input = "void errorOccurred() { 3 }" ; |
1113 | Transformer T(makeRule(M: functionDecl(hasName(Name: "errorOccurred" )), |
1114 | Edits: changeTo(Replacement: cat(Parts: "DELETED;" ))), |
1115 | consumer()); |
1116 | T.registerMatchers(MatchFinder: &MatchFinder); |
1117 | // The rewrite process itself fails... |
1118 | EXPECT_FALSE(rewrite(Input)); |
1119 | // ... and no changes or errors are produced in the process. |
1120 | EXPECT_THAT(Changes, IsEmpty()); |
1121 | EXPECT_EQ(ErrorCount, 0); |
1122 | } |
1123 | |
1124 | TEST_F(TransformerTest, ImplicitNodes_ConstructorDecl) { |
1125 | |
1126 | std::string OtherStructPrefix = R"cpp( |
1127 | struct Other { |
1128 | )cpp" ; |
1129 | std::string OtherStructSuffix = "};" ; |
1130 | |
1131 | std::string CopyableStructName = "struct Copyable" ; |
1132 | std::string BrokenStructName = "struct explicit Copyable" ; |
1133 | |
1134 | std::string CodeSuffix = R"cpp( |
1135 | { |
1136 | Other m_i; |
1137 | Copyable(); |
1138 | }; |
1139 | )cpp" ; |
1140 | |
1141 | std::string CopyCtor = "Other(const Other&) = default;" ; |
1142 | std::string ExplicitCopyCtor = "explicit Other(const Other&) = default;" ; |
1143 | std::string BrokenExplicitCopyCtor = |
1144 | "explicit explicit explicit Other(const Other&) = default;" ; |
1145 | |
1146 | std::string RewriteInput = OtherStructPrefix + CopyCtor + OtherStructSuffix + |
1147 | CopyableStructName + CodeSuffix; |
1148 | std::string ExpectedRewriteOutput = OtherStructPrefix + ExplicitCopyCtor + |
1149 | OtherStructSuffix + CopyableStructName + |
1150 | CodeSuffix; |
1151 | std::string BrokenRewriteOutput = OtherStructPrefix + BrokenExplicitCopyCtor + |
1152 | OtherStructSuffix + BrokenStructName + |
1153 | CodeSuffix; |
1154 | |
1155 | auto MatchedRecord = |
1156 | cxxConstructorDecl(isCopyConstructor()).bind(ID: "copyConstructor" ); |
1157 | |
1158 | auto RewriteRule = |
1159 | changeTo(Target: before(Selector: node(ID: "copyConstructor" )), Replacement: cat(Parts: "explicit " )); |
1160 | |
1161 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedRecord), |
1162 | Edits&: RewriteRule), |
1163 | Input: RewriteInput, Expected: ExpectedRewriteOutput); |
1164 | |
1165 | testRule(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedRecord), Edits&: RewriteRule), |
1166 | Input: RewriteInput, Expected: BrokenRewriteOutput); |
1167 | } |
1168 | |
1169 | TEST_F(TransformerTest, ImplicitNodes_RangeFor) { |
1170 | |
1171 | std::string CodePrefix = R"cpp( |
1172 | struct Container |
1173 | { |
1174 | int* begin() const; |
1175 | int* end() const; |
1176 | int* cbegin() const; |
1177 | int* cend() const; |
1178 | }; |
1179 | |
1180 | void foo() |
1181 | { |
1182 | const Container c; |
1183 | )cpp" ; |
1184 | |
1185 | std::string BeginCallBefore = " c.begin();" ; |
1186 | std::string BeginCallAfter = " c.cbegin();" ; |
1187 | |
1188 | std::string ForLoop = "for (auto i : c)" ; |
1189 | std::string BrokenForLoop = "for (auto i :.cbegin() c)" ; |
1190 | |
1191 | std::string CodeSuffix = R"cpp( |
1192 | { |
1193 | } |
1194 | } |
1195 | )cpp" ; |
1196 | |
1197 | std::string RewriteInput = |
1198 | CodePrefix + BeginCallBefore + ForLoop + CodeSuffix; |
1199 | std::string ExpectedRewriteOutput = |
1200 | CodePrefix + BeginCallAfter + ForLoop + CodeSuffix; |
1201 | std::string BrokenRewriteOutput = |
1202 | CodePrefix + BeginCallAfter + BrokenForLoop + CodeSuffix; |
1203 | |
1204 | auto MatchedRecord = |
1205 | cxxMemberCallExpr(on(InnerMatcher: expr(hasType(InnerMatcher: qualType(isConstQualified(), |
1206 | hasDeclaration(InnerMatcher: cxxRecordDecl( |
1207 | hasName(Name: "Container" )))))) |
1208 | .bind(ID: "callTarget" )), |
1209 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "begin" )))) |
1210 | .bind(ID: "constBeginCall" ); |
1211 | |
1212 | auto RewriteRule = |
1213 | changeTo(Target: node(ID: "constBeginCall" ), Replacement: cat(Parts: name(ID: "callTarget" ), Parts: ".cbegin()" )); |
1214 | |
1215 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedRecord), |
1216 | Edits&: RewriteRule), |
1217 | Input: RewriteInput, Expected: ExpectedRewriteOutput); |
1218 | |
1219 | testRule(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedRecord), Edits&: RewriteRule), |
1220 | Input: RewriteInput, Expected: BrokenRewriteOutput); |
1221 | } |
1222 | |
1223 | TEST_F(TransformerTest, ImplicitNodes_ForStmt) { |
1224 | |
1225 | std::string CodePrefix = R"cpp( |
1226 | struct NonTrivial { |
1227 | NonTrivial() {} |
1228 | NonTrivial(NonTrivial&) {} |
1229 | NonTrivial& operator=(NonTrivial const&) { return *this; } |
1230 | |
1231 | ~NonTrivial() {} |
1232 | }; |
1233 | |
1234 | struct ContainsArray { |
1235 | NonTrivial arr[2]; |
1236 | ContainsArray& operator=(ContainsArray const&) = default; |
1237 | }; |
1238 | |
1239 | void testIt() |
1240 | { |
1241 | ContainsArray ca1; |
1242 | ContainsArray ca2; |
1243 | ca2 = ca1; |
1244 | )cpp" ; |
1245 | |
1246 | auto CodeSuffix = "}" ; |
1247 | |
1248 | auto LoopBody = R"cpp( |
1249 | { |
1250 | |
1251 | } |
1252 | )cpp" ; |
1253 | |
1254 | auto RawLoop = "for (auto i = 0; i != 5; ++i)" ; |
1255 | |
1256 | auto RangeLoop = "for (auto i : boost::irange(5))" ; |
1257 | |
1258 | // Expect to rewrite the raw loop to the ranged loop. |
1259 | // This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs |
1260 | // mode also matches the hidden for loop generated in the copy assignment |
1261 | // operator of ContainsArray. Transformer then fails to transform the code at |
1262 | // all. |
1263 | |
1264 | auto RewriteInput = |
1265 | CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix; |
1266 | |
1267 | auto RewriteOutput = |
1268 | CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix; |
1269 | |
1270 | auto MatchedLoop = forStmt( |
1271 | has(declStmt(hasSingleDecl( |
1272 | InnerMatcher: varDecl(hasInitializer(InnerMatcher: integerLiteral(equals(Value: 0)))).bind(ID: "loopVar" )))), |
1273 | has(binaryOperator(hasOperatorName(Name: "!=" ), |
1274 | hasLHS(InnerMatcher: ignoringImplicit(InnerMatcher: declRefExpr( |
1275 | to(InnerMatcher: varDecl(equalsBoundNode(ID: "loopVar" )))))), |
1276 | hasRHS(InnerMatcher: expr().bind(ID: "upperBoundExpr" )))), |
1277 | has(unaryOperator(hasOperatorName(Name: "++" ), |
1278 | hasUnaryOperand(InnerMatcher: declRefExpr( |
1279 | to(InnerMatcher: varDecl(equalsBoundNode(ID: "loopVar" )))))) |
1280 | .bind(ID: "incrementOp" ))); |
1281 | |
1282 | auto RewriteRule = |
1283 | changeTo(Target: transformer::enclose(Begin: node(ID: "loopVar" ), End: node(ID: "incrementOp" )), |
1284 | Replacement: cat(Parts: "auto " , Parts: name(ID: "loopVar" ), Parts: " : boost::irange(" , |
1285 | Parts: node(ID: "upperBoundExpr" ), Parts: ")" )); |
1286 | |
1287 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedLoop), |
1288 | Edits&: RewriteRule), |
1289 | Input: RewriteInput, Expected: RewriteOutput); |
1290 | |
1291 | testRuleFailure(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedLoop), Edits&: RewriteRule), |
1292 | Input: RewriteInput); |
1293 | } |
1294 | |
1295 | TEST_F(TransformerTest, ImplicitNodes_ForStmt2) { |
1296 | |
1297 | std::string CodePrefix = R"cpp( |
1298 | struct NonTrivial { |
1299 | NonTrivial() {} |
1300 | NonTrivial(NonTrivial&) {} |
1301 | NonTrivial& operator=(NonTrivial const&) { return *this; } |
1302 | |
1303 | ~NonTrivial() {} |
1304 | }; |
1305 | |
1306 | struct ContainsArray { |
1307 | NonTrivial arr[2]; |
1308 | ContainsArray& operator=(ContainsArray const&) = default; |
1309 | }; |
1310 | |
1311 | void testIt() |
1312 | { |
1313 | ContainsArray ca1; |
1314 | ContainsArray ca2; |
1315 | ca2 = ca1; |
1316 | )cpp" ; |
1317 | |
1318 | auto CodeSuffix = "}" ; |
1319 | |
1320 | auto LoopBody = R"cpp( |
1321 | { |
1322 | |
1323 | } |
1324 | )cpp" ; |
1325 | |
1326 | auto RawLoop = "for (auto i = 0; i != 5; ++i)" ; |
1327 | |
1328 | auto RangeLoop = "for (auto i : boost::irange(5))" ; |
1329 | |
1330 | // Expect to rewrite the raw loop to the ranged loop. |
1331 | // This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs |
1332 | // mode also matches the hidden for loop generated in the copy assignment |
1333 | // operator of ContainsArray. Transformer then fails to transform the code at |
1334 | // all. |
1335 | |
1336 | auto RewriteInput = |
1337 | CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix; |
1338 | |
1339 | auto RewriteOutput = |
1340 | CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix; |
1341 | auto MatchedLoop = forStmt( |
1342 | hasLoopInit(InnerMatcher: declStmt(hasSingleDecl( |
1343 | InnerMatcher: varDecl(hasInitializer(InnerMatcher: integerLiteral(equals(Value: 0)))).bind(ID: "loopVar" )))), |
1344 | hasCondition(InnerMatcher: binaryOperator(hasOperatorName(Name: "!=" ), |
1345 | hasLHS(InnerMatcher: ignoringImplicit(InnerMatcher: declRefExpr(to( |
1346 | InnerMatcher: varDecl(equalsBoundNode(ID: "loopVar" )))))), |
1347 | hasRHS(InnerMatcher: expr().bind(ID: "upperBoundExpr" )))), |
1348 | hasIncrement(InnerMatcher: unaryOperator(hasOperatorName(Name: "++" ), |
1349 | hasUnaryOperand(InnerMatcher: declRefExpr( |
1350 | to(InnerMatcher: varDecl(equalsBoundNode(ID: "loopVar" )))))) |
1351 | .bind(ID: "incrementOp" ))); |
1352 | |
1353 | auto RewriteRule = |
1354 | changeTo(Target: transformer::enclose(Begin: node(ID: "loopVar" ), End: node(ID: "incrementOp" )), |
1355 | Replacement: cat(Parts: "auto " , Parts: name(ID: "loopVar" ), Parts: " : boost::irange(" , |
1356 | Parts: node(ID: "upperBoundExpr" ), Parts: ")" )); |
1357 | |
1358 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedLoop), |
1359 | Edits&: RewriteRule), |
1360 | Input: RewriteInput, Expected: RewriteOutput); |
1361 | |
1362 | testRuleFailure(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedLoop), Edits&: RewriteRule), |
1363 | Input: RewriteInput); |
1364 | } |
1365 | |
1366 | TEST_F(TransformerTest, TemplateInstantiation) { |
1367 | |
1368 | std::string NonTemplatesInput = R"cpp( |
1369 | struct S { |
1370 | int m_i; |
1371 | }; |
1372 | )cpp" ; |
1373 | std::string NonTemplatesExpected = R"cpp( |
1374 | struct S { |
1375 | safe_int m_i; |
1376 | }; |
1377 | )cpp" ; |
1378 | |
1379 | std::string TemplatesInput = R"cpp( |
1380 | template<typename T> |
1381 | struct TemplStruct { |
1382 | TemplStruct() {} |
1383 | ~TemplStruct() {} |
1384 | |
1385 | private: |
1386 | T m_t; |
1387 | }; |
1388 | |
1389 | void instantiate() |
1390 | { |
1391 | TemplStruct<int> ti; |
1392 | } |
1393 | )cpp" ; |
1394 | |
1395 | auto MatchedField = fieldDecl(hasType(InnerMatcher: asString(Name: "int" ))).bind(ID: "theField" ); |
1396 | |
1397 | // Changes the 'int' in 'S', but not the 'T' in 'TemplStruct': |
1398 | testRule(Rule: makeRule(M: traverse(TK: TK_IgnoreUnlessSpelledInSource, InnerMatcher: MatchedField), |
1399 | Edits: changeTo(Replacement: cat(Parts: "safe_int " , Parts: name(ID: "theField" ), Parts: ";" ))), |
1400 | Input: NonTemplatesInput + TemplatesInput, |
1401 | Expected: NonTemplatesExpected + TemplatesInput); |
1402 | |
1403 | // In AsIs mode, template instantiations are modified, which is |
1404 | // often not desired: |
1405 | |
1406 | std::string IncorrectTemplatesExpected = R"cpp( |
1407 | template<typename T> |
1408 | struct TemplStruct { |
1409 | TemplStruct() {} |
1410 | ~TemplStruct() {} |
1411 | |
1412 | private: |
1413 | safe_int m_t; |
1414 | }; |
1415 | |
1416 | void instantiate() |
1417 | { |
1418 | TemplStruct<int> ti; |
1419 | } |
1420 | )cpp" ; |
1421 | |
1422 | // Changes the 'int' in 'S', and (incorrectly) the 'T' in 'TemplStruct': |
1423 | testRule(Rule: makeRule(M: traverse(TK: TK_AsIs, InnerMatcher: MatchedField), |
1424 | Edits: changeTo(Replacement: cat(Parts: "safe_int " , Parts: name(ID: "theField" ), Parts: ";" ))), |
1425 | |
1426 | Input: NonTemplatesInput + TemplatesInput, |
1427 | Expected: NonTemplatesExpected + IncorrectTemplatesExpected); |
1428 | } |
1429 | |
1430 | // Transformation of macro source text when the change encompasses the entirety |
1431 | // of the expanded text. |
1432 | TEST_F(TransformerTest, SimpleMacro) { |
1433 | std::string Input = R"cc( |
1434 | #define ZERO 0 |
1435 | int f(string s) { return ZERO; } |
1436 | )cc" ; |
1437 | std::string Expected = R"cc( |
1438 | #define ZERO 0 |
1439 | int f(string s) { return 999; } |
1440 | )cc" ; |
1441 | |
1442 | StringRef zero = "zero" ; |
1443 | RewriteRule R = makeRule(M: integerLiteral(equals(Value: 0)).bind(ID: zero), |
1444 | Edits: changeTo(Target: node(ID: std::string(zero)), Replacement: cat(Parts: "999" ))); |
1445 | testRule(Rule: R, Input, Expected); |
1446 | } |
1447 | |
1448 | // Transformation of macro source text when the change encompasses the entirety |
1449 | // of the expanded text, for the case of function-style macros. |
1450 | TEST_F(TransformerTest, FunctionMacro) { |
1451 | std::string Input = R"cc( |
1452 | #define MACRO(str) strlen((str).c_str()) |
1453 | int f(string s) { return MACRO(s); } |
1454 | )cc" ; |
1455 | std::string Expected = R"cc( |
1456 | #define MACRO(str) strlen((str).c_str()) |
1457 | int f(string s) { return REPLACED; } |
1458 | )cc" ; |
1459 | |
1460 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
1461 | } |
1462 | |
1463 | // Tests that expressions in macro arguments can be rewritten. |
1464 | TEST_F(TransformerTest, MacroArg) { |
1465 | std::string Input = R"cc( |
1466 | #define PLUS(e) e + 1 |
1467 | int f(string s) { return PLUS(strlen(s.c_str())); } |
1468 | )cc" ; |
1469 | std::string Expected = R"cc( |
1470 | #define PLUS(e) e + 1 |
1471 | int f(string s) { return PLUS(REPLACED); } |
1472 | )cc" ; |
1473 | |
1474 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
1475 | } |
1476 | |
1477 | // Tests that expressions in macro arguments can be rewritten, even when the |
1478 | // macro call occurs inside another macro's definition. |
1479 | TEST_F(TransformerTest, MacroArgInMacroDef) { |
1480 | std::string Input = R"cc( |
1481 | #define NESTED(e) e |
1482 | #define MACRO(str) NESTED(strlen((str).c_str())) |
1483 | int f(string s) { return MACRO(s); } |
1484 | )cc" ; |
1485 | std::string Expected = R"cc( |
1486 | #define NESTED(e) e |
1487 | #define MACRO(str) NESTED(strlen((str).c_str())) |
1488 | int f(string s) { return REPLACED; } |
1489 | )cc" ; |
1490 | |
1491 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
1492 | } |
1493 | |
1494 | // Tests the corner case of the identity macro, specifically that it is |
1495 | // discarded in the rewrite rather than preserved (like PLUS is preserved in the |
1496 | // previous test). This behavior is of dubious value (and marked with a FIXME |
1497 | // in the code), but we test it to verify (and demonstrate) how this case is |
1498 | // handled. |
1499 | TEST_F(TransformerTest, IdentityMacro) { |
1500 | std::string Input = R"cc( |
1501 | #define ID(e) e |
1502 | int f(string s) { return ID(strlen(s.c_str())); } |
1503 | )cc" ; |
1504 | std::string Expected = R"cc( |
1505 | #define ID(e) e |
1506 | int f(string s) { return REPLACED; } |
1507 | )cc" ; |
1508 | |
1509 | testRule(Rule: ruleStrlenSize(), Input, Expected); |
1510 | } |
1511 | |
1512 | // Tests that two changes in a single macro expansion do not lead to conflicts |
1513 | // in applying the changes. |
1514 | TEST_F(TransformerTest, TwoChangesInOneMacroExpansion) { |
1515 | std::string Input = R"cc( |
1516 | #define PLUS(a,b) (a) + (b) |
1517 | int f() { return PLUS(3, 4); } |
1518 | )cc" ; |
1519 | std::string Expected = R"cc( |
1520 | #define PLUS(a,b) (a) + (b) |
1521 | int f() { return PLUS(LIT, LIT); } |
1522 | )cc" ; |
1523 | |
1524 | testRule(Rule: makeRule(M: integerLiteral(), Edits: changeTo(Replacement: cat(Parts: "LIT" ))), Input, Expected); |
1525 | } |
1526 | |
1527 | // Tests case where the rule's match spans both source from the macro and its |
1528 | // arg, with the begin location (the "anchor") being the arg. |
1529 | TEST_F(TransformerTest, MatchSpansMacroTextButChangeDoesNot) { |
1530 | std::string Input = R"cc( |
1531 | #define PLUS_ONE(a) a + 1 |
1532 | int f() { return PLUS_ONE(3); } |
1533 | )cc" ; |
1534 | std::string Expected = R"cc( |
1535 | #define PLUS_ONE(a) a + 1 |
1536 | int f() { return PLUS_ONE(LIT); } |
1537 | )cc" ; |
1538 | |
1539 | StringRef E = "expr" ; |
1540 | testRule(Rule: makeRule(M: binaryOperator(hasLHS(InnerMatcher: expr().bind(ID: E))), |
1541 | Edits: changeTo(Target: node(ID: std::string(E)), Replacement: cat(Parts: "LIT" ))), |
1542 | Input, Expected); |
1543 | } |
1544 | |
1545 | // Tests case where the rule's match spans both source from the macro and its |
1546 | // arg, with the begin location (the "anchor") being inside the macro. |
1547 | TEST_F(TransformerTest, MatchSpansMacroTextButChangeDoesNotAnchoredInMacro) { |
1548 | std::string Input = R"cc( |
1549 | #define PLUS_ONE(a) 1 + a |
1550 | int f() { return PLUS_ONE(3); } |
1551 | )cc" ; |
1552 | std::string Expected = R"cc( |
1553 | #define PLUS_ONE(a) 1 + a |
1554 | int f() { return PLUS_ONE(LIT); } |
1555 | )cc" ; |
1556 | |
1557 | StringRef E = "expr" ; |
1558 | testRule(Rule: makeRule(M: binaryOperator(hasRHS(InnerMatcher: expr().bind(ID: E))), |
1559 | Edits: changeTo(Target: node(ID: std::string(E)), Replacement: cat(Parts: "LIT" ))), |
1560 | Input, Expected); |
1561 | } |
1562 | |
1563 | // No rewrite is applied when the changed text does not encompass the entirety |
1564 | // of the expanded text. That is, the edit would have to be applied to the |
1565 | // macro's definition to succeed and editing the expansion point would not |
1566 | // suffice. |
1567 | TEST_F(TransformerTest, NoPartialRewriteOMacroExpansion) { |
1568 | std::string Input = R"cc( |
1569 | #define ZERO_PLUS 0 + 3 |
1570 | int f(string s) { return ZERO_PLUS; })cc" ; |
1571 | |
1572 | StringRef zero = "zero" ; |
1573 | RewriteRule R = makeRule(M: integerLiteral(equals(Value: 0)).bind(ID: zero), |
1574 | Edits: changeTo(Target: node(ID: std::string(zero)), Replacement: cat(Parts: "0" ))); |
1575 | testRule(Rule: R, Input, Expected: Input); |
1576 | } |
1577 | |
1578 | // This test handles the corner case where a macro expands within another macro |
1579 | // to matching code, but that code is an argument to the nested macro call. A |
1580 | // simple check of isMacroArgExpansion() vs. isMacroBodyExpansion() will get |
1581 | // this wrong, and transform the code. |
1582 | TEST_F(TransformerTest, NoPartialRewriteOfMacroExpansionForMacroArgs) { |
1583 | std::string Input = R"cc( |
1584 | #define NESTED(e) e |
1585 | #define MACRO(str) 1 + NESTED(strlen((str).c_str())) |
1586 | int f(string s) { return MACRO(s); } |
1587 | )cc" ; |
1588 | |
1589 | testRule(Rule: ruleStrlenSize(), Input, Expected: Input); |
1590 | } |
1591 | |
1592 | #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST |
1593 | // Verifies that `Type` and `QualType` are not allowed as top-level matchers in |
1594 | // rules. |
1595 | TEST(TransformerDeathTest, OrderedRuleTypes) { |
1596 | RewriteRule QualTypeRule = makeRule(M: qualType(), Edits: changeTo(Replacement: cat(Parts: "Q" ))); |
1597 | EXPECT_DEATH(transformer::detail::buildMatchers(QualTypeRule), |
1598 | "Matcher must be.*node matcher" ); |
1599 | |
1600 | RewriteRule TypeRule = makeRule(M: arrayType(), Edits: changeTo(Replacement: cat(Parts: "T" ))); |
1601 | EXPECT_DEATH(transformer::detail::buildMatchers(TypeRule), |
1602 | "Matcher must be.*node matcher" ); |
1603 | } |
1604 | #endif |
1605 | |
1606 | // Edits are able to span multiple files; in this case, a header and an |
1607 | // implementation file. |
1608 | TEST_F(TransformerTest, MultipleFiles) { |
1609 | std::string = R"cc(void RemoveThisFunction();)cc" ; |
1610 | std::string Source = R"cc(#include "input.h" |
1611 | void RemoveThisFunction();)cc" ; |
1612 | Transformer T( |
1613 | makeRule(M: functionDecl(hasName(Name: "RemoveThisFunction" )), Edits: changeTo(Replacement: cat(Parts: "" ))), |
1614 | consumer()); |
1615 | T.registerMatchers(MatchFinder: &MatchFinder); |
1616 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
1617 | EXPECT_TRUE(runToolOnCodeWithArgs( |
1618 | Factory->create(), Source, std::vector<std::string>(), "input.cc" , |
1619 | "clang-tool" , std::make_shared<PCHContainerOperations>(), |
1620 | {{"input.h" , Header}})); |
1621 | |
1622 | llvm::sort(C&: Changes, Comp: [](const AtomicChange &L, const AtomicChange &R) { |
1623 | return L.getFilePath() < R.getFilePath(); |
1624 | }); |
1625 | |
1626 | ASSERT_EQ(llvm::sys::path::convert_to_slash(Changes[0].getFilePath()), |
1627 | "./input.h" ); |
1628 | EXPECT_THAT(Changes[0].getInsertedHeaders(), IsEmpty()); |
1629 | EXPECT_THAT(Changes[0].getRemovedHeaders(), IsEmpty()); |
1630 | llvm::Expected<std::string> UpdatedCode = |
1631 | clang::tooling::applyAllReplacements(Code: Header, |
1632 | Replaces: Changes[0].getReplacements()); |
1633 | ASSERT_TRUE(static_cast<bool>(UpdatedCode)) |
1634 | << "Could not update code: " << llvm::toString(E: UpdatedCode.takeError()); |
1635 | EXPECT_EQ(format(*UpdatedCode), "" ); |
1636 | |
1637 | ASSERT_EQ(Changes[1].getFilePath(), "input.cc" ); |
1638 | EXPECT_THAT(Changes[1].getInsertedHeaders(), IsEmpty()); |
1639 | EXPECT_THAT(Changes[1].getRemovedHeaders(), IsEmpty()); |
1640 | UpdatedCode = clang::tooling::applyAllReplacements( |
1641 | Code: Source, Replaces: Changes[1].getReplacements()); |
1642 | ASSERT_TRUE(static_cast<bool>(UpdatedCode)) |
1643 | << "Could not update code: " << llvm::toString(E: UpdatedCode.takeError()); |
1644 | EXPECT_EQ(format(*UpdatedCode), format("#include \"input.h\"\n" )); |
1645 | } |
1646 | |
1647 | TEST_F(TransformerTest, AddIncludeMultipleFiles) { |
1648 | std::string = R"cc(void RemoveThisFunction();)cc" ; |
1649 | std::string Source = R"cc(#include "input.h" |
1650 | void Foo() {RemoveThisFunction();})cc" ; |
1651 | Transformer T( |
1652 | makeRule(M: callExpr(callee( |
1653 | InnerMatcher: functionDecl(hasName(Name: "RemoveThisFunction" )).bind(ID: "fun" ))), |
1654 | Edits: addInclude(Target: node(ID: "fun" ), Header: "header.h" )), |
1655 | consumer()); |
1656 | T.registerMatchers(MatchFinder: &MatchFinder); |
1657 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
1658 | EXPECT_TRUE(runToolOnCodeWithArgs( |
1659 | Factory->create(), Source, std::vector<std::string>(), "input.cc" , |
1660 | "clang-tool" , std::make_shared<PCHContainerOperations>(), |
1661 | {{"input.h" , Header}})); |
1662 | |
1663 | ASSERT_EQ(Changes.size(), 1U); |
1664 | ASSERT_EQ(llvm::sys::path::convert_to_slash(Changes[0].getFilePath()), |
1665 | "./input.h" ); |
1666 | EXPECT_THAT(Changes[0].getInsertedHeaders(), ElementsAre("header.h" )); |
1667 | EXPECT_THAT(Changes[0].getRemovedHeaders(), IsEmpty()); |
1668 | llvm::Expected<std::string> UpdatedCode = |
1669 | clang::tooling::applyAllReplacements(Code: Header, |
1670 | Replaces: Changes[0].getReplacements()); |
1671 | ASSERT_TRUE(static_cast<bool>(UpdatedCode)) |
1672 | << "Could not update code: " << llvm::toString(E: UpdatedCode.takeError()); |
1673 | EXPECT_EQ(format(*UpdatedCode), format(Header)); |
1674 | } |
1675 | |
1676 | // A single change set can span multiple files. |
1677 | TEST_F(TransformerTest, MultiFileEdit) { |
1678 | // NB: The fixture is unused for this test, but kept for the test suite name. |
1679 | std::string = R"cc(void Func(int id);)cc" ; |
1680 | std::string Source = R"cc(#include "input.h" |
1681 | void Caller() { |
1682 | int id = 0; |
1683 | Func(id); |
1684 | })cc" ; |
1685 | int ErrorCount = 0; |
1686 | std::vector<AtomicChanges> ChangeSets; |
1687 | clang::ast_matchers::MatchFinder MatchFinder; |
1688 | Transformer T( |
1689 | makeRule(M: callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "Func" ))), |
1690 | forEachArgumentWithParam(ArgMatcher: expr().bind(ID: "arg" ), |
1691 | ParamMatcher: parmVarDecl().bind(ID: "param" ))), |
1692 | Edits: {changeTo(Target: node(ID: "arg" ), Replacement: cat(Parts: "ARG" )), |
1693 | changeTo(Target: node(ID: "param" ), Replacement: cat(Parts: "PARAM" ))}), |
1694 | [&](Expected<MutableArrayRef<AtomicChange>> Changes) { |
1695 | if (Changes) |
1696 | ChangeSets.push_back(x: AtomicChanges(Changes->begin(), Changes->end())); |
1697 | else |
1698 | ++ErrorCount; |
1699 | }); |
1700 | T.registerMatchers(MatchFinder: &MatchFinder); |
1701 | auto Factory = newFrontendActionFactory(ConsumerFactory: &MatchFinder); |
1702 | EXPECT_TRUE(runToolOnCodeWithArgs( |
1703 | Factory->create(), Source, std::vector<std::string>(), "input.cc" , |
1704 | "clang-tool" , std::make_shared<PCHContainerOperations>(), |
1705 | {{"input.h" , Header}})); |
1706 | |
1707 | auto GetPathWithSlashes = [](const AtomicChange &C) { |
1708 | return llvm::sys::path::convert_to_slash(path: C.getFilePath()); |
1709 | }; |
1710 | |
1711 | EXPECT_EQ(ErrorCount, 0); |
1712 | EXPECT_THAT(ChangeSets, UnorderedElementsAre(UnorderedElementsAre( |
1713 | ResultOf(GetPathWithSlashes, "input.cc" ), |
1714 | ResultOf(GetPathWithSlashes, "./input.h" )))); |
1715 | } |
1716 | |
1717 | TEST_F(TransformerTest, GeneratesMetadata) { |
1718 | std::string Input = R"cc(int target = 0;)cc" ; |
1719 | std::string Expected = R"cc(REPLACE)cc" ; |
1720 | RewriteRuleWith<std::string> Rule = makeRule( |
1721 | M: varDecl(hasName(Name: "target" )), Edits: changeTo(Replacement: cat(Parts: "REPLACE" )), Metadata: cat(Parts: "METADATA" )); |
1722 | testRule(Rule: std::move(Rule), Input, Expected); |
1723 | EXPECT_EQ(ErrorCount, 0); |
1724 | EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA" )); |
1725 | } |
1726 | |
1727 | TEST_F(TransformerTest, GeneratesMetadataWithNoEdits) { |
1728 | std::string Input = R"cc(int target = 0;)cc" ; |
1729 | RewriteRuleWith<std::string> Rule = makeRule( |
1730 | M: varDecl(hasName(Name: "target" )).bind(ID: "var" ), Edits: noEdits(), Metadata: cat(Parts: "METADATA" )); |
1731 | testRule(Rule: std::move(Rule), Input, Expected: Input); |
1732 | EXPECT_EQ(ErrorCount, 0); |
1733 | EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA" )); |
1734 | } |
1735 | |
1736 | TEST_F(TransformerTest, PropagateMetadataErrors) { |
1737 | class AlwaysFail : public transformer::MatchComputation<std::string> { |
1738 | llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &, |
1739 | std::string *) const override { |
1740 | return llvm::createStringError(EC: llvm::errc::invalid_argument, Msg: "ERROR" ); |
1741 | } |
1742 | std::string toString() const override { return "AlwaysFail" ; } |
1743 | }; |
1744 | std::string Input = R"cc(int target = 0;)cc" ; |
1745 | RewriteRuleWith<std::string> Rule = makeRule<std::string>( |
1746 | M: varDecl(hasName(Name: "target" )).bind(ID: "var" ), Edits: changeTo(Replacement: cat(Parts: "REPLACE" )), |
1747 | Metadata: std::make_shared<AlwaysFail>()); |
1748 | testRuleFailure(Rule: std::move(Rule), Input); |
1749 | EXPECT_EQ(ErrorCount, 1); |
1750 | } |
1751 | |
1752 | } // namespace |
1753 | |