1 //===---- OverlappingReplacementsTest.cpp - clang-tidy --------------------===//
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 "ClangTidyTest.h"
10 #include "clang/AST/RecursiveASTVisitor.h"
11 #include "gtest/gtest.h"
12 
13 namespace clang {
14 namespace tidy {
15 namespace test {
16 namespace {
17 
18 const char BoundDecl[] = "decl";
19 const char BoundIf[] = "if";
20 
21 // We define a reduced set of very small checks that allow to test different
22 // overlapping situations (no overlapping, replacements partially overlap, etc),
23 // as well as different kinds of diagnostics (one check produces several errors,
24 // several replacement ranges in an error, etc).
25 class UseCharCheck : public ClangTidyCheck {
26 public:
UseCharCheck(StringRef CheckName,ClangTidyContext * Context)27   UseCharCheck(StringRef CheckName, ClangTidyContext *Context)
28       : ClangTidyCheck(CheckName, Context) {}
registerMatchers(ast_matchers::MatchFinder * Finder)29   void registerMatchers(ast_matchers::MatchFinder *Finder) override {
30     using namespace ast_matchers;
31     Finder->addMatcher(varDecl(hasType(isInteger())).bind(BoundDecl), this);
32   }
check(const ast_matchers::MatchFinder::MatchResult & Result)33   void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
34     auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
35     diag(VD->getBeginLoc(), "use char") << FixItHint::CreateReplacement(
36         CharSourceRange::getTokenRange(VD->getBeginLoc(), VD->getBeginLoc()),
37         "char");
38   }
39 };
40 
41 class IfFalseCheck : public ClangTidyCheck {
42 public:
IfFalseCheck(StringRef CheckName,ClangTidyContext * Context)43   IfFalseCheck(StringRef CheckName, ClangTidyContext *Context)
44       : ClangTidyCheck(CheckName, Context) {}
registerMatchers(ast_matchers::MatchFinder * Finder)45   void registerMatchers(ast_matchers::MatchFinder *Finder) override {
46     using namespace ast_matchers;
47     Finder->addMatcher(ifStmt().bind(BoundIf), this);
48   }
check(const ast_matchers::MatchFinder::MatchResult & Result)49   void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
50     auto *If = Result.Nodes.getNodeAs<IfStmt>(BoundIf);
51     auto *Cond = If->getCond();
52     SourceRange Range = Cond->getSourceRange();
53     if (auto *D = If->getConditionVariable()) {
54       Range = SourceRange(D->getBeginLoc(), D->getEndLoc());
55     }
56     diag(Range.getBegin(), "the cake is a lie") << FixItHint::CreateReplacement(
57         CharSourceRange::getTokenRange(Range), "false");
58   }
59 };
60 
61 class RefactorCheck : public ClangTidyCheck {
62 public:
RefactorCheck(StringRef CheckName,ClangTidyContext * Context)63   RefactorCheck(StringRef CheckName, ClangTidyContext *Context)
64       : ClangTidyCheck(CheckName, Context), NamePattern("::$") {}
RefactorCheck(StringRef CheckName,ClangTidyContext * Context,StringRef NamePattern)65   RefactorCheck(StringRef CheckName, ClangTidyContext *Context,
66                 StringRef NamePattern)
67       : ClangTidyCheck(CheckName, Context), NamePattern(NamePattern) {}
68   virtual std::string newName(StringRef OldName) = 0;
69 
registerMatchers(ast_matchers::MatchFinder * Finder)70   void registerMatchers(ast_matchers::MatchFinder *Finder) final {
71     using namespace ast_matchers;
72     Finder->addMatcher(varDecl(matchesName(NamePattern)).bind(BoundDecl), this);
73   }
74 
check(const ast_matchers::MatchFinder::MatchResult & Result)75   void check(const ast_matchers::MatchFinder::MatchResult &Result) final {
76     auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
77     std::string NewName = newName(VD->getName());
78 
79     auto Diag = diag(VD->getLocation(), "refactor %0 into %1")
80                 << VD->getName() << NewName
81                 << FixItHint::CreateReplacement(
82                        CharSourceRange::getTokenRange(VD->getLocation(),
83                                                       VD->getLocation()),
84                        NewName);
85 
86     class UsageVisitor : public RecursiveASTVisitor<UsageVisitor> {
87     public:
88       UsageVisitor(const ValueDecl *VD, StringRef NewName,
89                    DiagnosticBuilder &Diag)
90           : VD(VD), NewName(NewName), Diag(Diag) {}
91       bool VisitDeclRefExpr(DeclRefExpr *E) {
92         if (const ValueDecl *D = E->getDecl()) {
93           if (VD->getCanonicalDecl() == D->getCanonicalDecl()) {
94             Diag << FixItHint::CreateReplacement(
95                 CharSourceRange::getTokenRange(E->getSourceRange()), NewName);
96           }
97         }
98         return RecursiveASTVisitor<UsageVisitor>::VisitDeclRefExpr(E);
99       }
100 
101     private:
102       const ValueDecl *VD;
103       StringRef NewName;
104       DiagnosticBuilder &Diag;
105     };
106 
107     UsageVisitor(VD, NewName, Diag)
108         .TraverseDecl(Result.Context->getTranslationUnitDecl());
109   }
110 
111 protected:
112   const std::string NamePattern;
113 };
114 
115 class StartsWithPotaCheck : public RefactorCheck {
116 public:
StartsWithPotaCheck(StringRef CheckName,ClangTidyContext * Context)117   StartsWithPotaCheck(StringRef CheckName, ClangTidyContext *Context)
118       : RefactorCheck(CheckName, Context, "::pota") {}
119 
newName(StringRef OldName)120   std::string newName(StringRef OldName) override {
121     return "toma" + OldName.substr(4).str();
122   }
123 };
124 
125 class EndsWithTatoCheck : public RefactorCheck {
126 public:
EndsWithTatoCheck(StringRef CheckName,ClangTidyContext * Context)127   EndsWithTatoCheck(StringRef CheckName, ClangTidyContext *Context)
128       : RefactorCheck(CheckName, Context, "tato$") {}
129 
newName(StringRef OldName)130   std::string newName(StringRef OldName) override {
131     return OldName.substr(0, OldName.size() - 4).str() + "melo";
132   }
133 };
134 
135 } // namespace
136 
TEST(OverlappingReplacementsTest,UseCharCheckTest)137 TEST(OverlappingReplacementsTest, UseCharCheckTest) {
138   const char Code[] =
139       R"(void f() {
140   int a = 0;
141   if (int b = 0) {
142     int c = a;
143   }
144 })";
145 
146   const char CharFix[] =
147       R"(void f() {
148   char a = 0;
149   if (char b = 0) {
150     char c = a;
151   }
152 })";
153   EXPECT_EQ(CharFix, runCheckOnCode<UseCharCheck>(Code));
154 }
155 
TEST(OverlappingReplacementsTest,IfFalseCheckTest)156 TEST(OverlappingReplacementsTest, IfFalseCheckTest) {
157   const char Code[] =
158       R"(void f() {
159   int potato = 0;
160   if (int b = 0) {
161     int c = potato;
162   } else if (true) {
163     int d = 0;
164   }
165 })";
166 
167   const char IfFix[] =
168       R"(void f() {
169   int potato = 0;
170   if (false) {
171     int c = potato;
172   } else if (false) {
173     int d = 0;
174   }
175 })";
176   EXPECT_EQ(IfFix, runCheckOnCode<IfFalseCheck>(Code));
177 }
178 
TEST(OverlappingReplacementsTest,StartsWithCheckTest)179 TEST(OverlappingReplacementsTest, StartsWithCheckTest) {
180   const char Code[] =
181       R"(void f() {
182   int a = 0;
183   int potato = 0;
184   if (int b = 0) {
185     int c = potato;
186   } else if (true) {
187     int d = 0;
188   }
189 })";
190 
191   const char StartsFix[] =
192       R"(void f() {
193   int a = 0;
194   int tomato = 0;
195   if (int b = 0) {
196     int c = tomato;
197   } else if (true) {
198     int d = 0;
199   }
200 })";
201   EXPECT_EQ(StartsFix, runCheckOnCode<StartsWithPotaCheck>(Code));
202 }
203 
TEST(OverlappingReplacementsTest,EndsWithCheckTest)204 TEST(OverlappingReplacementsTest, EndsWithCheckTest) {
205   const char Code[] =
206       R"(void f() {
207   int a = 0;
208   int potato = 0;
209   if (int b = 0) {
210     int c = potato;
211   } else if (true) {
212     int d = 0;
213   }
214 })";
215 
216   const char EndsFix[] =
217       R"(void f() {
218   int a = 0;
219   int pomelo = 0;
220   if (int b = 0) {
221     int c = pomelo;
222   } else if (true) {
223     int d = 0;
224   }
225 })";
226   EXPECT_EQ(EndsFix, runCheckOnCode<EndsWithTatoCheck>(Code));
227 }
228 
TEST(OverlappingReplacementTest,ReplacementsDoNotOverlap)229 TEST(OverlappingReplacementTest, ReplacementsDoNotOverlap) {
230   std::string Res;
231   const char Code[] =
232       R"(void f() {
233   int potassium = 0;
234   if (true) {
235     int Potato = potassium;
236   }
237 })";
238 
239   const char CharIfFix[] =
240       R"(void f() {
241   char potassium = 0;
242   if (false) {
243     char Potato = potassium;
244   }
245 })";
246   Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
247   EXPECT_EQ(CharIfFix, Res);
248 
249   const char StartsEndsFix[] =
250       R"(void f() {
251   int tomassium = 0;
252   if (true) {
253     int Pomelo = tomassium;
254   }
255 })";
256   Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
257   EXPECT_EQ(StartsEndsFix, Res);
258 
259   const char CharIfStartsEndsFix[] =
260       R"(void f() {
261   char tomassium = 0;
262   if (false) {
263     char Pomelo = tomassium;
264   }
265 })";
266   Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck,
267                        EndsWithTatoCheck>(Code);
268   EXPECT_EQ(CharIfStartsEndsFix, Res);
269 }
270 
TEST(OverlappingReplacementsTest,ReplacementInsideOtherReplacement)271 TEST(OverlappingReplacementsTest, ReplacementInsideOtherReplacement) {
272   std::string Res;
273   const char Code[] =
274       R"(void f() {
275   if (char potato = 0) {
276   } else if (int a = 0) {
277     char potato = 0;
278     if (potato) potato;
279   }
280 })";
281 
282   // Apply the UseCharCheck together with the IfFalseCheck.
283   //
284   // The 'If' fix contains the other, so that is the one that has to be applied.
285   // } else if (int a = 0) {
286   //            ^^^ -> char
287   //            ~~~~~~~~~ -> false
288   const char CharIfFix[] =
289       R"(void f() {
290   if (false) {
291   } else if (false) {
292     char potato = 0;
293     if (false) potato;
294   }
295 })";
296   Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
297   EXPECT_EQ(CharIfFix, Res);
298   Res = runCheckOnCode<IfFalseCheck, UseCharCheck>(Code);
299   EXPECT_EQ(CharIfFix, Res);
300 
301   // Apply the IfFalseCheck with the StartsWithPotaCheck.
302   //
303   // The 'If' replacement is bigger here.
304   // if (char potato = 0) {
305   //          ^^^^^^ -> tomato
306   //     ~~~~~~~~~~~~~~~ -> false
307   //
308   // But the refactoring is the one that contains the other here:
309   // char potato = 0;
310   //      ^^^^^^ -> tomato
311   // if (potato) potato;
312   //     ^^^^^^  ^^^^^^ -> tomato, tomato
313   //     ~~~~~~ -> false
314   const char IfStartsFix[] =
315       R"(void f() {
316   if (false) {
317   } else if (false) {
318     char tomato = 0;
319     if (tomato) tomato;
320   }
321 })";
322   Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
323   EXPECT_EQ(IfStartsFix, Res);
324   Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);
325   EXPECT_EQ(IfStartsFix, Res);
326 }
327 
TEST(OverlappingReplacements,TwoReplacementsInsideOne)328 TEST(OverlappingReplacements, TwoReplacementsInsideOne) {
329   std::string Res;
330   const char Code[] =
331       R"(void f() {
332   if (int potato = 0) {
333     int a = 0;
334   }
335 })";
336 
337   // The two smallest replacements should not be applied.
338   // if (int potato = 0) {
339   //         ^^^^^^ -> tomato
340   //     *** -> char
341   //     ~~~~~~~~~~~~~~ -> false
342   // But other errors from the same checks should not be affected.
343   //   int a = 0;
344   //   *** -> char
345   const char Fix[] =
346       R"(void f() {
347   if (false) {
348     char a = 0;
349   }
350 })";
351   Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck>(Code);
352   EXPECT_EQ(Fix, Res);
353   Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck, UseCharCheck>(Code);
354   EXPECT_EQ(Fix, Res);
355 }
356 
TEST(OverlappingReplacementsTest,ApplyAtMostOneOfTheChangesWhenPartialOverlapping)357 TEST(OverlappingReplacementsTest,
358      ApplyAtMostOneOfTheChangesWhenPartialOverlapping) {
359   std::string Res;
360   const char Code[] =
361       R"(void f() {
362   if (int potato = 0) {
363     int a = potato;
364   }
365 })";
366 
367   // These two replacements overlap, but none of them is completely contained
368   // inside the other.
369   // if (int potato = 0) {
370   //         ^^^^^^ -> tomato
371   //     ~~~~~~~~~~~~~~ -> false
372   //   int a = potato;
373   //           ^^^^^^ -> tomato
374   //
375   // The 'StartsWithPotaCheck' fix has endpoints inside the 'IfFalseCheck' fix,
376   // so it is going to be set as inapplicable. The 'if' fix will be applied.
377   const char IfFix[] =
378       R"(void f() {
379   if (false) {
380     int a = potato;
381   }
382 })";
383   Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
384   EXPECT_EQ(IfFix, Res);
385 }
386 
TEST(OverlappingReplacementsTest,TwoErrorsHavePerfectOverlapping)387 TEST(OverlappingReplacementsTest, TwoErrorsHavePerfectOverlapping) {
388   std::string Res;
389   const char Code[] =
390       R"(void f() {
391   int potato = 0;
392   potato += potato * potato;
393   if (char a = potato) potato;
394 })";
395 
396   // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', and
397   // EndsWithTatoCheck will try to use 'pomelo'. Both fixes have the same set of
398   // ranges. This is a corner case of one error completely containing another:
399   // the other completely contains the first one as well. Both errors are
400   // discarded.
401 
402   Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
403   EXPECT_EQ(Code, Res);
404 }
405 
406 } // namespace test
407 } // namespace tidy
408 } // namespace clang
409