409 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			409 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
//===---- OverlappingReplacementsTest.cpp - clang-tidy --------------------===//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#include "ClangTidyTest.h"
 | 
						|
#include "clang/AST/RecursiveASTVisitor.h"
 | 
						|
#include "gtest/gtest.h"
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace tidy {
 | 
						|
namespace test {
 | 
						|
namespace {
 | 
						|
 | 
						|
const char BoundDecl[] = "decl";
 | 
						|
const char BoundIf[] = "if";
 | 
						|
 | 
						|
// We define a reduced set of very small checks that allow to test different
 | 
						|
// overlapping situations (no overlapping, replacements partially overlap, etc),
 | 
						|
// as well as different kinds of diagnostics (one check produces several errors,
 | 
						|
// several replacement ranges in an error, etc).
 | 
						|
class UseCharCheck : public ClangTidyCheck {
 | 
						|
public:
 | 
						|
  UseCharCheck(StringRef CheckName, ClangTidyContext *Context)
 | 
						|
      : ClangTidyCheck(CheckName, Context) {}
 | 
						|
  void registerMatchers(ast_matchers::MatchFinder *Finder) override {
 | 
						|
    using namespace ast_matchers;
 | 
						|
    Finder->addMatcher(varDecl(hasType(isInteger())).bind(BoundDecl), this);
 | 
						|
  }
 | 
						|
  void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
 | 
						|
    auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
 | 
						|
    diag(VD->getBeginLoc(), "use char") << FixItHint::CreateReplacement(
 | 
						|
        CharSourceRange::getTokenRange(VD->getBeginLoc(), VD->getBeginLoc()),
 | 
						|
        "char");
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class IfFalseCheck : public ClangTidyCheck {
 | 
						|
public:
 | 
						|
  IfFalseCheck(StringRef CheckName, ClangTidyContext *Context)
 | 
						|
      : ClangTidyCheck(CheckName, Context) {}
 | 
						|
  void registerMatchers(ast_matchers::MatchFinder *Finder) override {
 | 
						|
    using namespace ast_matchers;
 | 
						|
    Finder->addMatcher(ifStmt().bind(BoundIf), this);
 | 
						|
  }
 | 
						|
  void check(const ast_matchers::MatchFinder::MatchResult &Result) override {
 | 
						|
    auto *If = Result.Nodes.getNodeAs<IfStmt>(BoundIf);
 | 
						|
    auto *Cond = If->getCond();
 | 
						|
    SourceRange Range = Cond->getSourceRange();
 | 
						|
    if (auto *D = If->getConditionVariable()) {
 | 
						|
      Range = SourceRange(D->getBeginLoc(), D->getEndLoc());
 | 
						|
    }
 | 
						|
    diag(Range.getBegin(), "the cake is a lie") << FixItHint::CreateReplacement(
 | 
						|
        CharSourceRange::getTokenRange(Range), "false");
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class RefactorCheck : public ClangTidyCheck {
 | 
						|
public:
 | 
						|
  RefactorCheck(StringRef CheckName, ClangTidyContext *Context)
 | 
						|
      : ClangTidyCheck(CheckName, Context), NamePattern("::$") {}
 | 
						|
  RefactorCheck(StringRef CheckName, ClangTidyContext *Context,
 | 
						|
                StringRef NamePattern)
 | 
						|
      : ClangTidyCheck(CheckName, Context), NamePattern(NamePattern) {}
 | 
						|
  virtual std::string newName(StringRef OldName) = 0;
 | 
						|
 | 
						|
  void registerMatchers(ast_matchers::MatchFinder *Finder) final {
 | 
						|
    using namespace ast_matchers;
 | 
						|
    Finder->addMatcher(varDecl(matchesName(NamePattern)).bind(BoundDecl), this);
 | 
						|
  }
 | 
						|
 | 
						|
  void check(const ast_matchers::MatchFinder::MatchResult &Result) final {
 | 
						|
    auto *VD = Result.Nodes.getNodeAs<VarDecl>(BoundDecl);
 | 
						|
    std::string NewName = newName(VD->getName());
 | 
						|
 | 
						|
    auto Diag = diag(VD->getLocation(), "refactor %0 into %1")
 | 
						|
                << VD->getName() << NewName
 | 
						|
                << FixItHint::CreateReplacement(
 | 
						|
                       CharSourceRange::getTokenRange(VD->getLocation(),
 | 
						|
                                                      VD->getLocation()),
 | 
						|
                       NewName);
 | 
						|
 | 
						|
    class UsageVisitor : public RecursiveASTVisitor<UsageVisitor> {
 | 
						|
    public:
 | 
						|
      UsageVisitor(const ValueDecl *VD, StringRef NewName,
 | 
						|
                   DiagnosticBuilder &Diag)
 | 
						|
          : VD(VD), NewName(NewName), Diag(Diag) {}
 | 
						|
      bool VisitDeclRefExpr(DeclRefExpr *E) {
 | 
						|
        if (const ValueDecl *D = E->getDecl()) {
 | 
						|
          if (VD->getCanonicalDecl() == D->getCanonicalDecl()) {
 | 
						|
            Diag << FixItHint::CreateReplacement(
 | 
						|
                CharSourceRange::getTokenRange(E->getSourceRange()), NewName);
 | 
						|
          }
 | 
						|
        }
 | 
						|
        return RecursiveASTVisitor<UsageVisitor>::VisitDeclRefExpr(E);
 | 
						|
      }
 | 
						|
 | 
						|
    private:
 | 
						|
      const ValueDecl *VD;
 | 
						|
      StringRef NewName;
 | 
						|
      DiagnosticBuilder &Diag;
 | 
						|
    };
 | 
						|
 | 
						|
    UsageVisitor(VD, NewName, Diag)
 | 
						|
        .TraverseDecl(Result.Context->getTranslationUnitDecl());
 | 
						|
  }
 | 
						|
 | 
						|
protected:
 | 
						|
  const std::string NamePattern;
 | 
						|
};
 | 
						|
 | 
						|
class StartsWithPotaCheck : public RefactorCheck {
 | 
						|
public:
 | 
						|
  StartsWithPotaCheck(StringRef CheckName, ClangTidyContext *Context)
 | 
						|
      : RefactorCheck(CheckName, Context, "::pota") {}
 | 
						|
 | 
						|
  std::string newName(StringRef OldName) override {
 | 
						|
    return "toma" + OldName.substr(4).str();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class EndsWithTatoCheck : public RefactorCheck {
 | 
						|
public:
 | 
						|
  EndsWithTatoCheck(StringRef CheckName, ClangTidyContext *Context)
 | 
						|
      : RefactorCheck(CheckName, Context, "tato$") {}
 | 
						|
 | 
						|
  std::string newName(StringRef OldName) override {
 | 
						|
    return OldName.substr(0, OldName.size() - 4).str() + "melo";
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
TEST(OverlappingReplacementsTest, UseCharCheckTest) {
 | 
						|
  const char Code[] =
 | 
						|
      R"(void f() {
 | 
						|
  int a = 0;
 | 
						|
  if (int b = 0) {
 | 
						|
    int c = a;
 | 
						|
  }
 | 
						|
})";
 | 
						|
 | 
						|
  const char CharFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  char a = 0;
 | 
						|
  if (char b = 0) {
 | 
						|
    char c = a;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  EXPECT_EQ(CharFix, runCheckOnCode<UseCharCheck>(Code));
 | 
						|
}
 | 
						|
 | 
						|
TEST(OverlappingReplacementsTest, IfFalseCheckTest) {
 | 
						|
  const char Code[] =
 | 
						|
      R"(void f() {
 | 
						|
  int potato = 0;
 | 
						|
  if (int b = 0) {
 | 
						|
    int c = potato;
 | 
						|
  } else if (true) {
 | 
						|
    int d = 0;
 | 
						|
  }
 | 
						|
})";
 | 
						|
 | 
						|
  const char IfFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  int potato = 0;
 | 
						|
  if (false) {
 | 
						|
    int c = potato;
 | 
						|
  } else if (false) {
 | 
						|
    int d = 0;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  EXPECT_EQ(IfFix, runCheckOnCode<IfFalseCheck>(Code));
 | 
						|
}
 | 
						|
 | 
						|
TEST(OverlappingReplacementsTest, StartsWithCheckTest) {
 | 
						|
  const char Code[] =
 | 
						|
      R"(void f() {
 | 
						|
  int a = 0;
 | 
						|
  int potato = 0;
 | 
						|
  if (int b = 0) {
 | 
						|
    int c = potato;
 | 
						|
  } else if (true) {
 | 
						|
    int d = 0;
 | 
						|
  }
 | 
						|
})";
 | 
						|
 | 
						|
  const char StartsFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  int a = 0;
 | 
						|
  int tomato = 0;
 | 
						|
  if (int b = 0) {
 | 
						|
    int c = tomato;
 | 
						|
  } else if (true) {
 | 
						|
    int d = 0;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  EXPECT_EQ(StartsFix, runCheckOnCode<StartsWithPotaCheck>(Code));
 | 
						|
}
 | 
						|
 | 
						|
TEST(OverlappingReplacementsTest, EndsWithCheckTest) {
 | 
						|
  const char Code[] =
 | 
						|
      R"(void f() {
 | 
						|
  int a = 0;
 | 
						|
  int potato = 0;
 | 
						|
  if (int b = 0) {
 | 
						|
    int c = potato;
 | 
						|
  } else if (true) {
 | 
						|
    int d = 0;
 | 
						|
  }
 | 
						|
})";
 | 
						|
 | 
						|
  const char EndsFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  int a = 0;
 | 
						|
  int pomelo = 0;
 | 
						|
  if (int b = 0) {
 | 
						|
    int c = pomelo;
 | 
						|
  } else if (true) {
 | 
						|
    int d = 0;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  EXPECT_EQ(EndsFix, runCheckOnCode<EndsWithTatoCheck>(Code));
 | 
						|
}
 | 
						|
 | 
						|
TEST(OverlappingReplacementTest, ReplacementsDoNotOverlap) {
 | 
						|
  std::string Res;
 | 
						|
  const char Code[] =
 | 
						|
      R"(void f() {
 | 
						|
  int potassium = 0;
 | 
						|
  if (true) {
 | 
						|
    int Potato = potassium;
 | 
						|
  }
 | 
						|
})";
 | 
						|
 | 
						|
  const char CharIfFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  char potassium = 0;
 | 
						|
  if (false) {
 | 
						|
    char Potato = potassium;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
 | 
						|
  EXPECT_EQ(CharIfFix, Res);
 | 
						|
 | 
						|
  const char StartsEndsFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  int tomassium = 0;
 | 
						|
  if (true) {
 | 
						|
    int Pomelo = tomassium;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
 | 
						|
  EXPECT_EQ(StartsEndsFix, Res);
 | 
						|
 | 
						|
  const char CharIfStartsEndsFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  char tomassium = 0;
 | 
						|
  if (false) {
 | 
						|
    char Pomelo = tomassium;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck,
 | 
						|
                       EndsWithTatoCheck>(Code);
 | 
						|
  EXPECT_EQ(CharIfStartsEndsFix, Res);
 | 
						|
}
 | 
						|
 | 
						|
TEST(OverlappingReplacementsTest, ReplacementInsideOtherReplacement) {
 | 
						|
  std::string Res;
 | 
						|
  const char Code[] =
 | 
						|
      R"(void f() {
 | 
						|
  if (char potato = 0) {
 | 
						|
  } else if (int a = 0) {
 | 
						|
    char potato = 0;
 | 
						|
    if (potato) potato;
 | 
						|
  }
 | 
						|
})";
 | 
						|
 | 
						|
  // Apply the UseCharCheck together with the IfFalseCheck.
 | 
						|
  //
 | 
						|
  // The 'If' fix contains the other, so that is the one that has to be applied.
 | 
						|
  // } else if (int a = 0) {
 | 
						|
  //            ^^^ -> char
 | 
						|
  //            ~~~~~~~~~ -> false
 | 
						|
  const char CharIfFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  if (false) {
 | 
						|
  } else if (false) {
 | 
						|
    char potato = 0;
 | 
						|
    if (false) potato;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  Res = runCheckOnCode<UseCharCheck, IfFalseCheck>(Code);
 | 
						|
  EXPECT_EQ(CharIfFix, Res);
 | 
						|
  Res = runCheckOnCode<IfFalseCheck, UseCharCheck>(Code);
 | 
						|
  EXPECT_EQ(CharIfFix, Res);
 | 
						|
 | 
						|
  // Apply the IfFalseCheck with the StartsWithPotaCheck.
 | 
						|
  //
 | 
						|
  // The 'If' replacement is bigger here.
 | 
						|
  // if (char potato = 0) {
 | 
						|
  //          ^^^^^^ -> tomato
 | 
						|
  //     ~~~~~~~~~~~~~~~ -> false
 | 
						|
  //
 | 
						|
  // But the refactoring is the one that contains the other here:
 | 
						|
  // char potato = 0;
 | 
						|
  //      ^^^^^^ -> tomato
 | 
						|
  // if (potato) potato;
 | 
						|
  //     ^^^^^^  ^^^^^^ -> tomato, tomato
 | 
						|
  //     ~~~~~~ -> false
 | 
						|
  const char IfStartsFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  if (false) {
 | 
						|
  } else if (false) {
 | 
						|
    char tomato = 0;
 | 
						|
    if (tomato) tomato;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
 | 
						|
  EXPECT_EQ(IfStartsFix, Res);
 | 
						|
  Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck>(Code);
 | 
						|
  EXPECT_EQ(IfStartsFix, Res);
 | 
						|
}
 | 
						|
 | 
						|
TEST(OverlappingReplacements, TwoReplacementsInsideOne) {
 | 
						|
  std::string Res;
 | 
						|
  const char Code[] =
 | 
						|
      R"(void f() {
 | 
						|
  if (int potato = 0) {
 | 
						|
    int a = 0;
 | 
						|
  }
 | 
						|
})";
 | 
						|
 | 
						|
  // The two smallest replacements should not be applied.
 | 
						|
  // if (int potato = 0) {
 | 
						|
  //         ^^^^^^ -> tomato
 | 
						|
  //     *** -> char
 | 
						|
  //     ~~~~~~~~~~~~~~ -> false
 | 
						|
  // But other errors from the same checks should not be affected.
 | 
						|
  //   int a = 0;
 | 
						|
  //   *** -> char
 | 
						|
  const char Fix[] =
 | 
						|
      R"(void f() {
 | 
						|
  if (false) {
 | 
						|
    char a = 0;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  Res = runCheckOnCode<UseCharCheck, IfFalseCheck, StartsWithPotaCheck>(Code);
 | 
						|
  EXPECT_EQ(Fix, Res);
 | 
						|
  Res = runCheckOnCode<StartsWithPotaCheck, IfFalseCheck, UseCharCheck>(Code);
 | 
						|
  EXPECT_EQ(Fix, Res);
 | 
						|
}
 | 
						|
 | 
						|
TEST(OverlappingReplacementsTest,
 | 
						|
     ApplyAtMostOneOfTheChangesWhenPartialOverlapping) {
 | 
						|
  std::string Res;
 | 
						|
  const char Code[] =
 | 
						|
      R"(void f() {
 | 
						|
  if (int potato = 0) {
 | 
						|
    int a = potato;
 | 
						|
  }
 | 
						|
})";
 | 
						|
 | 
						|
  // These two replacements overlap, but none of them is completely contained
 | 
						|
  // inside the other.
 | 
						|
  // if (int potato = 0) {
 | 
						|
  //         ^^^^^^ -> tomato
 | 
						|
  //     ~~~~~~~~~~~~~~ -> false
 | 
						|
  //   int a = potato;
 | 
						|
  //           ^^^^^^ -> tomato
 | 
						|
  //
 | 
						|
  // The 'StartsWithPotaCheck' fix has endpoints inside the 'IfFalseCheck' fix,
 | 
						|
  // so it is going to be set as inapplicable. The 'if' fix will be applied.
 | 
						|
  const char IfFix[] =
 | 
						|
      R"(void f() {
 | 
						|
  if (false) {
 | 
						|
    int a = potato;
 | 
						|
  }
 | 
						|
})";
 | 
						|
  Res = runCheckOnCode<IfFalseCheck, StartsWithPotaCheck>(Code);
 | 
						|
  EXPECT_EQ(IfFix, Res);
 | 
						|
}
 | 
						|
 | 
						|
TEST(OverlappingReplacementsTest, TwoErrorsHavePerfectOverlapping) {
 | 
						|
  std::string Res;
 | 
						|
  const char Code[] =
 | 
						|
      R"(void f() {
 | 
						|
  int potato = 0;
 | 
						|
  potato += potato * potato;
 | 
						|
  if (char a = potato) potato;
 | 
						|
})";
 | 
						|
 | 
						|
  // StartsWithPotaCheck will try to refactor 'potato' into 'tomato', and
 | 
						|
  // EndsWithTatoCheck will try to use 'pomelo'. Both fixes have the same set of
 | 
						|
  // ranges. This is a corner case of one error completely containing another:
 | 
						|
  // the other completely contains the first one as well. Both errors are
 | 
						|
  // discarded.
 | 
						|
 | 
						|
  Res = runCheckOnCode<StartsWithPotaCheck, EndsWithTatoCheck>(Code);
 | 
						|
  EXPECT_EQ(Code, Res);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace test
 | 
						|
} // namespace tidy
 | 
						|
} // namespace clang
 |