Skip to content

Commit

Permalink
[analyzer] Re-apply r283094 "Improve CloneChecker diagnostics"
Browse files Browse the repository at this point in the history
The parent commit (r283092) was reverted before and now finally landed.


git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@283661 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
haoNoQ committed Oct 8, 2016
1 parent a501e19 commit b1769b7
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 93 deletions.
10 changes: 7 additions & 3 deletions include/clang/Analysis/CloneDetection.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ class StmtSequence {
/// This method should only be called on a non-empty StmtSequence object.
SourceLocation getEndLoc() const;

/// Returns the source range of the whole sequence - from the beginning
/// of the first statement to the end of the last statement.
SourceRange getSourceRange() const;

bool operator==(const StmtSequence &Other) const {
return std::tie(S, StartIndex, EndIndex) ==
std::tie(Other.S, Other.StartIndex, Other.EndIndex);
Expand Down Expand Up @@ -250,14 +254,14 @@ class CloneDetector {
/// The variable which referencing in this clone was against the pattern.
const VarDecl *Variable;
/// Where the variable was referenced.
SourceRange VarRange;
const Stmt *Mention;
/// The variable that should have been referenced to follow the pattern.
/// If Suggestion is a nullptr then it's not possible to fix the pattern
/// by referencing a different variable in this clone.
const VarDecl *Suggestion;
SuspiciousCloneInfo(const VarDecl *Variable, SourceRange Range,
SuspiciousCloneInfo(const VarDecl *Variable, const Stmt *Mention,
const VarDecl *Suggestion)
: Variable(Variable), VarRange(Range), Suggestion(Suggestion) {}
: Variable(Variable), Mention(Mention), Suggestion(Suggestion) {}
SuspiciousCloneInfo() {}
};
/// The first clone in the pair which always has a suggested variable.
Expand Down
26 changes: 15 additions & 11 deletions lib/Analysis/CloneDetection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ SourceLocation StmtSequence::getStartLoc() const {

SourceLocation StmtSequence::getEndLoc() const { return back()->getLocEnd(); }

SourceRange StmtSequence::getSourceRange() const {
return SourceRange(getStartLoc(), getEndLoc());
}

namespace {

/// \brief Analyzes the pattern of the referenced variables in a statement.
Expand All @@ -91,11 +95,11 @@ class VariablePattern {
struct VariableOccurence {
/// The index of the associated VarDecl in the Variables vector.
size_t KindID;
/// The source range in the code where the variable was referenced.
SourceRange Range;
/// The statement in the code where the variable was referenced.
const Stmt *Mention;

VariableOccurence(size_t KindID, SourceRange Range)
: KindID(KindID), Range(Range) {}
VariableOccurence(size_t KindID, const Stmt *Mention)
: KindID(KindID), Mention(Mention) {}
};

/// All occurences of referenced variables in the order of appearance.
Expand All @@ -106,20 +110,20 @@ class VariablePattern {

/// \brief Adds a new variable referenced to this pattern.
/// \param VarDecl The declaration of the variable that is referenced.
/// \param Range The SourceRange where this variable is referenced.
void addVariableOccurence(const VarDecl *VarDecl, SourceRange Range) {
/// \param Mention The SourceRange where this variable is referenced.
void addVariableOccurence(const VarDecl *VarDecl, const Stmt *Mention) {
// First check if we already reference this variable
for (size_t KindIndex = 0; KindIndex < Variables.size(); ++KindIndex) {
if (Variables[KindIndex] == VarDecl) {
// If yes, add a new occurence that points to the existing entry in
// the Variables vector.
Occurences.emplace_back(KindIndex, Range);
Occurences.emplace_back(KindIndex, Mention);
return;
}
}
// If this variable wasn't already referenced, add it to the list of
// referenced variables and add a occurence that points to this new entry.
Occurences.emplace_back(Variables.size(), Range);
Occurences.emplace_back(Variables.size(), Mention);
Variables.push_back(VarDecl);
}

Expand All @@ -134,7 +138,7 @@ class VariablePattern {
// Check if S is a reference to a variable. If yes, add it to the pattern.
if (auto D = dyn_cast<DeclRefExpr>(S)) {
if (auto VD = dyn_cast<VarDecl>(D->getDecl()->getCanonicalDecl()))
addVariableOccurence(VD, D->getSourceRange());
addVariableOccurence(VD, D);
}

// Recursively check all children of the given statement.
Expand Down Expand Up @@ -208,7 +212,7 @@ class VariablePattern {
// Store information about the first clone.
FirstMismatch->FirstCloneInfo =
CloneDetector::SuspiciousClonePair::SuspiciousCloneInfo(
Variables[ThisOccurence.KindID], ThisOccurence.Range,
Variables[ThisOccurence.KindID], ThisOccurence.Mention,
FirstSuggestion);

// Same as above but with the other clone. We do this for both clones as
Expand All @@ -221,7 +225,7 @@ class VariablePattern {
// Store information about the second clone.
FirstMismatch->SecondCloneInfo =
CloneDetector::SuspiciousClonePair::SuspiciousCloneInfo(
Variables[ThisOccurence.KindID], OtherOccurence.Range,
Other.Variables[OtherOccurence.KindID], OtherOccurence.Mention,
SecondSuggestion);

// SuspiciousClonePair guarantees that the first clone always has a
Expand Down
106 changes: 56 additions & 50 deletions lib/StaticAnalyzer/Checkers/CloneChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
#include "ClangSACheckers.h"
#include "clang/Analysis/CloneDetection.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"

using namespace clang;
Expand All @@ -27,6 +29,7 @@ namespace {
class CloneChecker
: public Checker<check::ASTCodeBody, check::EndOfTranslationUnit> {
mutable CloneDetector Detector;
mutable std::unique_ptr<BugType> BT_Exact, BT_Suspicious;

public:
void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr,
Expand All @@ -36,12 +39,12 @@ class CloneChecker
AnalysisManager &Mgr, BugReporter &BR) const;

/// \brief Reports all clones to the user.
void reportClones(SourceManager &SM, AnalysisManager &Mgr,
void reportClones(BugReporter &BR, AnalysisManager &Mgr,
int MinComplexity) const;

/// \brief Reports only suspicious clones to the user along with informaton
/// that explain why they are suspicious.
void reportSuspiciousClones(SourceManager &SM, AnalysisManager &Mgr,
void reportSuspiciousClones(BugReporter &BR, AnalysisManager &Mgr,
int MinComplexity) const;
};
} // end anonymous namespace
Expand Down Expand Up @@ -70,79 +73,82 @@ void CloneChecker::checkEndOfTranslationUnit(const TranslationUnitDecl *TU,
"ReportNormalClones", true, this);

if (ReportSuspiciousClones)
reportSuspiciousClones(BR.getSourceManager(), Mgr, MinComplexity);
reportSuspiciousClones(BR, Mgr, MinComplexity);

if (ReportNormalClones)
reportClones(BR.getSourceManager(), Mgr, MinComplexity);
reportClones(BR, Mgr, MinComplexity);
}

void CloneChecker::reportClones(SourceManager &SM, AnalysisManager &Mgr,
static PathDiagnosticLocation makeLocation(const StmtSequence &S,
AnalysisManager &Mgr) {
ASTContext &ACtx = Mgr.getASTContext();
return PathDiagnosticLocation::createBegin(
S.front(), ACtx.getSourceManager(),
Mgr.getAnalysisDeclContext(ACtx.getTranslationUnitDecl()));
}

void CloneChecker::reportClones(BugReporter &BR, AnalysisManager &Mgr,
int MinComplexity) const {

std::vector<CloneDetector::CloneGroup> CloneGroups;
Detector.findClones(CloneGroups, MinComplexity);

DiagnosticsEngine &DiagEngine = Mgr.getDiagnostic();

unsigned WarnID = DiagEngine.getCustomDiagID(DiagnosticsEngine::Warning,
"Detected code clone.");

unsigned NoteID = DiagEngine.getCustomDiagID(DiagnosticsEngine::Note,
"Related code clone is here.");
if (!BT_Exact)
BT_Exact.reset(new BugType(this, "Exact code clone", "Code clone"));

for (CloneDetector::CloneGroup &Group : CloneGroups) {
// We group the clones by printing the first as a warning and all others
// as a note.
DiagEngine.Report(Group.Sequences.front().getStartLoc(), WarnID);
for (unsigned i = 1; i < Group.Sequences.size(); ++i) {
DiagEngine.Report(Group.Sequences[i].getStartLoc(), NoteID);
}
auto R = llvm::make_unique<BugReport>(
*BT_Exact, "Duplicate code detected",
makeLocation(Group.Sequences.front(), Mgr));
R->addRange(Group.Sequences.front().getSourceRange());

for (unsigned i = 1; i < Group.Sequences.size(); ++i)
R->addNote("Similar code here",
makeLocation(Group.Sequences[i], Mgr),
Group.Sequences[i].getSourceRange());
BR.emitReport(std::move(R));
}
}

void CloneChecker::reportSuspiciousClones(SourceManager &SM,
void CloneChecker::reportSuspiciousClones(BugReporter &BR,
AnalysisManager &Mgr,
int MinComplexity) const {

std::vector<CloneDetector::SuspiciousClonePair> Clones;
Detector.findSuspiciousClones(Clones, MinComplexity);

DiagnosticsEngine &DiagEngine = Mgr.getDiagnostic();

auto SuspiciousCloneWarning = DiagEngine.getCustomDiagID(
DiagnosticsEngine::Warning, "suspicious code clone detected; did you "
"mean to use %0?");

auto RelatedCloneNote = DiagEngine.getCustomDiagID(
DiagnosticsEngine::Note, "suggestion is based on the usage of this "
"variable in a similar piece of code");
if (!BT_Suspicious)
BT_Suspicious.reset(
new BugType(this, "Suspicious code clone", "Code clone"));

auto RelatedSuspiciousCloneNote = DiagEngine.getCustomDiagID(
DiagnosticsEngine::Note, "suggestion is based on the usage of this "
"variable in a similar piece of code; did you "
"mean to use %0?");
ASTContext &ACtx = BR.getContext();
SourceManager &SM = ACtx.getSourceManager();
AnalysisDeclContext *ADC =
Mgr.getAnalysisDeclContext(ACtx.getTranslationUnitDecl());

for (CloneDetector::SuspiciousClonePair &Pair : Clones) {
// The first clone always has a suggestion and we report it to the user
// along with the place where the suggestion should be used.
DiagEngine.Report(Pair.FirstCloneInfo.VarRange.getBegin(),
SuspiciousCloneWarning)
<< Pair.FirstCloneInfo.VarRange << Pair.FirstCloneInfo.Suggestion;

// The second clone can have a suggestion and if there is one, we report
// that suggestion to the user.
if (Pair.SecondCloneInfo.Suggestion) {
DiagEngine.Report(Pair.SecondCloneInfo.VarRange.getBegin(),
RelatedSuspiciousCloneNote)
<< Pair.SecondCloneInfo.VarRange << Pair.SecondCloneInfo.Suggestion;
continue;
}

// If there isn't a suggestion in the second clone, we only inform the
// user where we got the idea that his code could contain an error.
DiagEngine.Report(Pair.SecondCloneInfo.VarRange.getBegin(),
RelatedCloneNote)
<< Pair.SecondCloneInfo.VarRange;
// FIXME: We are ignoring the suggestions currently, because they are
// only 50% accurate (even if the second suggestion is unavailable),
// which may confuse the user.
// Think how to perform more accurate suggestions?

auto R = llvm::make_unique<BugReport>(
*BT_Suspicious,
"Potential copy-paste error; did you really mean to use '" +
Pair.FirstCloneInfo.Variable->getNameAsString() + "' here?",
PathDiagnosticLocation::createBegin(Pair.FirstCloneInfo.Mention, SM,
ADC));
R->addRange(Pair.FirstCloneInfo.Mention->getSourceRange());

R->addNote("Similar code using '" +
Pair.SecondCloneInfo.Variable->getNameAsString() + "' here",
PathDiagnosticLocation::createBegin(Pair.SecondCloneInfo.Mention,
SM, ADC),
Pair.SecondCloneInfo.Mention->getSourceRange());

BR.emitReport(std::move(R));
}
}

Expand Down
4 changes: 2 additions & 2 deletions test/Analysis/copypaste/blocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

void log();

auto BlockA = ^(int a, int b){ // expected-warning{{Detected code clone.}}
auto BlockA = ^(int a, int b){ // expected-warning{{Duplicate code detected}}
log();
if (a > b)
return a;
return b;
};

auto BlockB = ^(int a, int b){ // expected-note{{Related code clone is here.}}
auto BlockB = ^(int a, int b){ // expected-note{{Similar code here}}
log();
if (a > b)
return a;
Expand Down
4 changes: 2 additions & 2 deletions test/Analysis/copypaste/function-try-block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Tests if function try blocks are correctly handled.

void nonCompoundStmt1(int& x)
try { x += 1; } catch(...) { x -= 1; } // expected-warning{{Detected code clone.}}
try { x += 1; } catch(...) { x -= 1; } // expected-warning{{Duplicate code detected}}

void nonCompoundStmt2(int& x)
try { x += 1; } catch(...) { x -= 1; } // expected-note{{Related code clone is here.}}
try { x += 1; } catch(...) { x -= 1; } // expected-note{{Similar code here}}
4 changes: 2 additions & 2 deletions test/Analysis/copypaste/functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@

void log();

int max(int a, int b) { // expected-warning{{Detected code clone.}}
int max(int a, int b) { // expected-warning{{Duplicate code detected}}
log();
if (a > b)
return a;
return b;
}

int maxClone(int x, int y) { // expected-note{{Related code clone is here.}}
int maxClone(int x, int y) { // expected-note{{Similar code here}}
log();
if (x > y)
return x;
Expand Down
8 changes: 4 additions & 4 deletions test/Analysis/copypaste/macro-complexity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
// This confirms that with the current configuration the macro body would be
// considered large enough to pass the MinimumCloneComplexity constraint.

int manualMacro(int a, int b) { // expected-warning{{Detected code clone.}}
int manualMacro(int a, int b) { // expected-warning{{Duplicate code detected}}
return a > b ? -a * a : -b * b;
}

int manualMacroClone(int a, int b) { // expected-note{{Related code clone is here.}}
int manualMacroClone(int a, int b) { // expected-note{{Similar code here}}
return a > b ? -a * a : -b * b;
}

Expand All @@ -41,10 +41,10 @@ int macroClone(int a, int b) {

#define NEG(A) -(A)

int nestedMacros() { // expected-warning{{Detected code clone.}}
int nestedMacros() { // expected-warning{{Duplicate code detected}}
return NEG(NEG(NEG(NEG(NEG(NEG(NEG(NEG(NEG(NEG(1))))))))));
}

int nestedMacrosClone() { // expected-note{{Related code clone is here.}}
int nestedMacrosClone() { // expected-note{{Similar code here}}
return NEG(NEG(NEG(NEG(NEG(NEG(NEG(NEG(NEG(NEG(1))))))))));
}
10 changes: 5 additions & 5 deletions test/Analysis/copypaste/macros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// to have the same complexity value. Macros have smaller complexity values
// and need to be in their own hash group.

int foo(int a) { // expected-warning{{Detected code clone.}}
int foo(int a) { // expected-warning{{Duplicate code detected}}
a = a + 1;
a = a + 1 / 1;
a = a + 1 + 1 + 1;
Expand All @@ -15,7 +15,7 @@ int foo(int a) { // expected-warning{{Detected code clone.}}
return a;
}

int fooClone(int a) { // expected-note{{Related code clone is here.}}
int fooClone(int a) { // expected-note{{Similar code here}}
a = a + 1;
a = a + 1 / 1;
a = a + 1 + 1 + 1;
Expand All @@ -30,7 +30,7 @@ int fooClone(int a) { // expected-note{{Related code clone is here.}}

#define ASSIGN(T, V) T = T + V

int macro(int a) { // expected-warning{{Detected code clone.}}
int macro(int a) { // expected-warning{{Duplicate code detected}}
ASSIGN(a, 1);
ASSIGN(a, 1 / 1);
ASSIGN(a, 1 + 1 + 1);
Expand All @@ -40,7 +40,7 @@ int macro(int a) { // expected-warning{{Detected code clone.}}
return a;
}

int macroClone(int a) { // expected-note{{Related code clone is here.}}
int macroClone(int a) { // expected-note{{Similar code here}}
ASSIGN(a, 1);
ASSIGN(a, 1 / 1);
ASSIGN(a, 1 + 1 + 1);
Expand All @@ -54,7 +54,7 @@ int macroClone(int a) { // expected-note{{Related code clone is here.}}

#define EMPTY

int fooFalsePositiveClone(int a) { // expected-note{{Related code clone is here.}}
int fooFalsePositiveClone(int a) { // expected-note{{Similar code here}}
a = EMPTY a + 1;
a = a + 1 / 1;
a = a + 1 + 1 + 1;
Expand Down
Loading

0 comments on commit b1769b7

Please sign in to comment.