forked from OSchip/llvm-project
Revert "[analyzer] Add a modular constraint system to the CloneDetector"
This reverts commit r299544. Crashes on tests on some buildbots. llvm-svn: 299550
This commit is contained in:
parent
de87d145cc
commit
c4aee48b9f
|
@ -16,7 +16,9 @@
|
|||
#define LLVM_CLANG_AST_CLONEDETECTION_H
|
||||
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/Hashing.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace clang {
|
||||
|
@ -27,7 +29,7 @@ class VarDecl;
|
|||
class ASTContext;
|
||||
class CompoundStmt;
|
||||
|
||||
/// Identifies a list of statements.
|
||||
/// \brief Identifies a list of statements.
|
||||
///
|
||||
/// Can either identify a single arbitrary Stmt object, a continuous sequence of
|
||||
/// child statements inside a CompoundStmt or no statements at all.
|
||||
|
@ -37,8 +39,8 @@ class StmtSequence {
|
|||
/// Stmt, then S is a pointer to this Stmt.
|
||||
const Stmt *S;
|
||||
|
||||
/// The declaration that contains the statements.
|
||||
const Decl *D;
|
||||
/// The related ASTContext for S.
|
||||
ASTContext *Context;
|
||||
|
||||
/// If EndIndex is non-zero, then S is a CompoundStmt and this StmtSequence
|
||||
/// instance is representing the CompoundStmt children inside the array
|
||||
|
@ -47,7 +49,7 @@ class StmtSequence {
|
|||
unsigned EndIndex;
|
||||
|
||||
public:
|
||||
/// Constructs a StmtSequence holding multiple statements.
|
||||
/// \brief Constructs a StmtSequence holding multiple statements.
|
||||
///
|
||||
/// The resulting StmtSequence identifies a continuous sequence of statements
|
||||
/// in the body of the given CompoundStmt. Which statements of the body should
|
||||
|
@ -55,20 +57,20 @@ public:
|
|||
/// that describe a non-empty sub-array in the body of the given CompoundStmt.
|
||||
///
|
||||
/// \param Stmt A CompoundStmt that contains all statements in its body.
|
||||
/// \param Decl The Decl containing this Stmt.
|
||||
/// \param Context The ASTContext for the given CompoundStmt.
|
||||
/// \param StartIndex The inclusive start index in the children array of
|
||||
/// \p Stmt
|
||||
/// \param EndIndex The exclusive end index in the children array of \p Stmt.
|
||||
StmtSequence(const CompoundStmt *Stmt, const Decl *D, unsigned StartIndex,
|
||||
unsigned EndIndex);
|
||||
StmtSequence(const CompoundStmt *Stmt, ASTContext &Context,
|
||||
unsigned StartIndex, unsigned EndIndex);
|
||||
|
||||
/// Constructs a StmtSequence holding a single statement.
|
||||
/// \brief Constructs a StmtSequence holding a single statement.
|
||||
///
|
||||
/// \param Stmt An arbitrary Stmt.
|
||||
/// \param Decl The Decl containing this Stmt.
|
||||
StmtSequence(const Stmt *Stmt, const Decl *D);
|
||||
/// \param Context The ASTContext for the given Stmt.
|
||||
StmtSequence(const Stmt *Stmt, ASTContext &Context);
|
||||
|
||||
/// Constructs an empty StmtSequence.
|
||||
/// \brief Constructs an empty StmtSequence.
|
||||
StmtSequence();
|
||||
|
||||
typedef const Stmt *const *iterator;
|
||||
|
@ -108,12 +110,9 @@ public:
|
|||
bool empty() const { return size() == 0; }
|
||||
|
||||
/// Returns the related ASTContext for the stored Stmts.
|
||||
ASTContext &getASTContext() const;
|
||||
|
||||
/// Returns the declaration that contains the stored Stmts.
|
||||
const Decl *getContainingDecl() const {
|
||||
assert(D);
|
||||
return D;
|
||||
ASTContext &getASTContext() const {
|
||||
assert(Context);
|
||||
return *Context;
|
||||
}
|
||||
|
||||
/// Returns true if this objects holds a list of statements.
|
||||
|
@ -151,214 +150,106 @@ public:
|
|||
bool contains(const StmtSequence &Other) const;
|
||||
};
|
||||
|
||||
/// Searches for similar subtrees in the AST.
|
||||
/// \brief Searches for clones in source code.
|
||||
///
|
||||
/// First, this class needs several declarations with statement bodies which
|
||||
/// can be passed via analyzeCodeBody. Afterwards all statements can be
|
||||
/// searched for clones by calling findClones with a given list of constraints
|
||||
/// that should specify the wanted properties of the clones.
|
||||
///
|
||||
/// The result of findClones can be further constrained with the constrainClones
|
||||
/// method.
|
||||
/// First, this class needs a translation unit which is passed via
|
||||
/// \p analyzeTranslationUnit . It will then generate and store search data
|
||||
/// for all statements inside the given translation unit.
|
||||
/// Afterwards the generated data can be used to find code clones by calling
|
||||
/// \p findClones .
|
||||
///
|
||||
/// This class only searches for clones in exectuable source code
|
||||
/// (e.g. function bodies). Other clones (e.g. cloned comments or declarations)
|
||||
/// are not supported.
|
||||
class CloneDetector {
|
||||
|
||||
public:
|
||||
/// A collection of StmtSequences that share an arbitrary property.
|
||||
typedef llvm::SmallVector<StmtSequence, 8> CloneGroup;
|
||||
typedef unsigned DataPiece;
|
||||
|
||||
/// Generates and stores search data for all statements in the body of
|
||||
/// the given Decl.
|
||||
void analyzeCodeBody(const Decl *D);
|
||||
/// Holds the data about a StmtSequence that is needed during the search for
|
||||
/// code clones.
|
||||
struct CloneSignature {
|
||||
/// \brief The hash code of the StmtSequence.
|
||||
///
|
||||
/// The initial clone groups that are formed during the search for clones
|
||||
/// consist only of Sequences that share the same hash code. This makes this
|
||||
/// value the central part of this heuristic that is needed to find clones
|
||||
/// in a performant way. For this to work, the type of this variable
|
||||
/// always needs to be small and fast to compare.
|
||||
///
|
||||
/// Also, StmtSequences that are clones of each others have to share
|
||||
/// the same hash code. StmtSequences that are not clones of each other
|
||||
/// shouldn't share the same hash code, but if they do, it will only
|
||||
/// degrade the performance of the hash search but doesn't influence
|
||||
/// the correctness of the result.
|
||||
size_t Hash;
|
||||
|
||||
/// Constrains the given list of clone groups with the given constraint.
|
||||
///
|
||||
/// The constraint is expected to have a method with the signature
|
||||
/// `void constrain(std::vector<CloneDetector::CloneGroup> &Sequences)`
|
||||
/// as this is the interface that the CloneDetector uses for applying the
|
||||
/// constraint. The constraint is supposed to directly modify the passed list
|
||||
/// so that all clones in the list fulfill the specific property this
|
||||
/// constraint ensures.
|
||||
template <typename T>
|
||||
static void constrainClones(std::vector<CloneGroup> &CloneGroups, T C) {
|
||||
C.constrain(CloneGroups);
|
||||
}
|
||||
/// \brief The complexity of the StmtSequence.
|
||||
///
|
||||
/// This value gives an approximation on how many direct or indirect child
|
||||
/// statements are contained in the related StmtSequence. In general, the
|
||||
/// greater this value, the greater the amount of statements. However, this
|
||||
/// is only an approximation and the actual amount of statements can be
|
||||
/// higher or lower than this value. Statements that are generated by the
|
||||
/// compiler (e.g. macro expansions) for example barely influence the
|
||||
/// complexity value.
|
||||
///
|
||||
/// The main purpose of this value is to filter clones that are too small
|
||||
/// and therefore probably not interesting enough for the user.
|
||||
unsigned Complexity;
|
||||
|
||||
/// Constrains the given list of clone groups with the given list of
|
||||
/// constraints.
|
||||
///
|
||||
/// The constraints are applied in sequence in the order in which they are
|
||||
/// passed to this function.
|
||||
template <typename T1, typename... Ts>
|
||||
static void constrainClones(std::vector<CloneGroup> &CloneGroups, T1 C,
|
||||
Ts... ConstraintList) {
|
||||
constrainClones(CloneGroups, C);
|
||||
constrainClones(CloneGroups, ConstraintList...);
|
||||
}
|
||||
/// \brief Creates an empty CloneSignature without any data.
|
||||
CloneSignature() : Complexity(1) {}
|
||||
|
||||
/// Searches for clones in all previously passed statements.
|
||||
/// \param Result Output parameter to which all created clone groups are
|
||||
/// added.
|
||||
/// \param Passes The constraints that should be applied to the result.
|
||||
template <typename... Ts>
|
||||
void findClones(std::vector<CloneGroup> &Result, Ts... ConstraintList) {
|
||||
// The initial assumption is that there is only one clone group and every
|
||||
// statement is a clone of the others. This clone group will then be
|
||||
// split up with the help of the constraints.
|
||||
CloneGroup AllClones;
|
||||
AllClones.reserve(Sequences.size());
|
||||
for (const auto &C : Sequences) {
|
||||
AllClones.push_back(C);
|
||||
}
|
||||
|
||||
Result.push_back(AllClones);
|
||||
|
||||
constrainClones(Result, ConstraintList...);
|
||||
}
|
||||
|
||||
private:
|
||||
CloneGroup Sequences;
|
||||
};
|
||||
|
||||
/// This class is a utility class that contains utility functions for building
|
||||
/// custom constraints.
|
||||
class CloneConstraint {
|
||||
public:
|
||||
/// Removes all groups by using a filter function.
|
||||
/// \param CloneGroups The list of CloneGroups that is supposed to be
|
||||
/// filtered.
|
||||
/// \param Filter The filter function that should return true for all groups
|
||||
/// that should be removed from the list.
|
||||
static void
|
||||
filterGroups(std::vector<CloneDetector::CloneGroup> &CloneGroups,
|
||||
std::function<bool(const CloneDetector::CloneGroup &)> Filter) {
|
||||
CloneGroups.erase(
|
||||
std::remove_if(CloneGroups.begin(), CloneGroups.end(), Filter),
|
||||
CloneGroups.end());
|
||||
}
|
||||
|
||||
/// Splits the given CloneGroups until the given Compare function returns true
|
||||
/// for all clones in a single group.
|
||||
/// \param CloneGroups A list of CloneGroups that should be modified.
|
||||
/// \param Compare The comparison function that all clones are supposed to
|
||||
/// pass. Should return true if and only if two clones belong
|
||||
/// to the same CloneGroup.
|
||||
static void splitCloneGroups(
|
||||
std::vector<CloneDetector::CloneGroup> &CloneGroups,
|
||||
std::function<bool(const StmtSequence &, const StmtSequence &)> Compare);
|
||||
};
|
||||
|
||||
/// Searches all children of the given clones for type II clones (i.e. they are
|
||||
/// identical in every aspect beside the used variable names).
|
||||
class RecursiveCloneTypeIIConstraint {
|
||||
|
||||
/// Generates and saves a hash code for the given Stmt.
|
||||
/// \param S The given Stmt.
|
||||
/// \param D The Decl containing S.
|
||||
/// \param StmtsByHash Output parameter that will contain the hash codes for
|
||||
/// each StmtSequence in the given Stmt.
|
||||
/// \return The hash code of the given Stmt.
|
||||
///
|
||||
/// If the given Stmt is a CompoundStmt, this method will also generate
|
||||
/// hashes for all possible StmtSequences in the children of this Stmt.
|
||||
size_t saveHash(const Stmt *S, const Decl *D,
|
||||
std::vector<std::pair<size_t, StmtSequence>> &StmtsByHash);
|
||||
|
||||
public:
|
||||
void constrain(std::vector<CloneDetector::CloneGroup> &Sequences);
|
||||
};
|
||||
|
||||
/// Ensures that every clone has at least the given complexity.
|
||||
///
|
||||
/// Complexity is here defined as the total amount of children of a statement.
|
||||
/// This constraint assumes the first statement in the group is representative
|
||||
/// for all other statements in the group in terms of complexity.
|
||||
class MinComplexityConstraint {
|
||||
unsigned MinComplexity;
|
||||
|
||||
public:
|
||||
MinComplexityConstraint(unsigned MinComplexity)
|
||||
: MinComplexity(MinComplexity) {}
|
||||
|
||||
size_t calculateStmtComplexity(const StmtSequence &Seq,
|
||||
const std::string &ParentMacroStack = "");
|
||||
|
||||
void constrain(std::vector<CloneDetector::CloneGroup> &CloneGroups) {
|
||||
CloneConstraint::filterGroups(
|
||||
CloneGroups, [this](const CloneDetector::CloneGroup &A) {
|
||||
if (!A.empty())
|
||||
return calculateStmtComplexity(A.front()) < MinComplexity;
|
||||
else
|
||||
return false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/// Ensures that all clone groups contain at least the given amount of clones.
|
||||
class MinGroupSizeConstraint {
|
||||
unsigned MinGroupSize;
|
||||
|
||||
public:
|
||||
MinGroupSizeConstraint(unsigned MinGroupSize = 2)
|
||||
: MinGroupSize(MinGroupSize) {}
|
||||
|
||||
void constrain(std::vector<CloneDetector::CloneGroup> &CloneGroups) {
|
||||
CloneConstraint::filterGroups(CloneGroups,
|
||||
[this](const CloneDetector::CloneGroup &A) {
|
||||
return A.size() < MinGroupSize;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/// Ensures that no clone group fully contains another clone group.
|
||||
struct OnlyLargestCloneConstraint {
|
||||
void constrain(std::vector<CloneDetector::CloneGroup> &Result);
|
||||
};
|
||||
|
||||
/// Analyzes the pattern of the referenced variables in a statement.
|
||||
class VariablePattern {
|
||||
|
||||
/// Describes an occurence of a variable reference in a statement.
|
||||
struct VariableOccurence {
|
||||
/// The index of the associated VarDecl in the Variables vector.
|
||||
size_t KindID;
|
||||
/// The statement in the code where the variable was referenced.
|
||||
const Stmt *Mention;
|
||||
|
||||
VariableOccurence(size_t KindID, const Stmt *Mention)
|
||||
: KindID(KindID), Mention(Mention) {}
|
||||
CloneSignature(llvm::hash_code Hash, unsigned Complexity)
|
||||
: Hash(Hash), Complexity(Complexity) {}
|
||||
};
|
||||
|
||||
/// All occurences of referenced variables in the order of appearance.
|
||||
std::vector<VariableOccurence> Occurences;
|
||||
/// List of referenced variables in the order of appearance.
|
||||
/// Every item in this list is unique.
|
||||
std::vector<const VarDecl *> Variables;
|
||||
/// Holds group of StmtSequences that are clones of each other and the
|
||||
/// complexity value (see CloneSignature::Complexity) that all stored
|
||||
/// StmtSequences have in common.
|
||||
struct CloneGroup {
|
||||
std::vector<StmtSequence> Sequences;
|
||||
CloneSignature Signature;
|
||||
|
||||
/// Adds a new variable referenced to this pattern.
|
||||
/// \param VarDecl The declaration of the variable that is referenced.
|
||||
/// \param Mention The SourceRange where this variable is referenced.
|
||||
void addVariableOccurence(const VarDecl *VarDecl, const Stmt *Mention);
|
||||
CloneGroup() {}
|
||||
|
||||
/// Adds each referenced variable from the given statement.
|
||||
void addVariables(const Stmt *S);
|
||||
CloneGroup(const StmtSequence &Seq, CloneSignature Signature)
|
||||
: Signature(Signature) {
|
||||
Sequences.push_back(Seq);
|
||||
}
|
||||
|
||||
public:
|
||||
/// Creates an VariablePattern object with information about the given
|
||||
/// StmtSequence.
|
||||
VariablePattern(const StmtSequence &Sequence) {
|
||||
for (const Stmt *S : Sequence)
|
||||
addVariables(S);
|
||||
}
|
||||
/// \brief Returns false if and only if this group should be skipped when
|
||||
/// searching for clones.
|
||||
bool isValid() const {
|
||||
// A clone group with only one member makes no sense, so we skip them.
|
||||
return Sequences.size() > 1;
|
||||
}
|
||||
};
|
||||
|
||||
/// Describes two clones that reference their variables in a different pattern
|
||||
/// which could indicate a programming error.
|
||||
/// \brief Generates and stores search data for all statements in the body of
|
||||
/// the given Decl.
|
||||
void analyzeCodeBody(const Decl *D);
|
||||
|
||||
/// \brief Stores the CloneSignature to allow future querying.
|
||||
void add(const StmtSequence &S, const CloneSignature &Signature);
|
||||
|
||||
/// \brief Searches the provided statements for clones.
|
||||
///
|
||||
/// \param Result Output parameter that is filled with a list of found
|
||||
/// clone groups. Each group contains multiple StmtSequences
|
||||
/// that were identified to be clones of each other.
|
||||
/// \param MinGroupComplexity Only return clones which have at least this
|
||||
/// complexity value.
|
||||
/// \param CheckPatterns Returns only clone groups in which the referenced
|
||||
/// variables follow the same pattern.
|
||||
void findClones(std::vector<CloneGroup> &Result, unsigned MinGroupComplexity,
|
||||
bool CheckPatterns = true);
|
||||
|
||||
/// \brief Describes two clones that reference their variables in a different
|
||||
/// pattern which could indicate a programming error.
|
||||
struct SuspiciousClonePair {
|
||||
/// Utility class holding the relevant information about a single
|
||||
/// clone in this pair.
|
||||
/// \brief Utility class holding the relevant information about a single
|
||||
/// clone in this pair.
|
||||
struct SuspiciousCloneInfo {
|
||||
/// The variable which referencing in this clone was against the pattern.
|
||||
const VarDecl *Variable;
|
||||
|
@ -379,37 +270,17 @@ public:
|
|||
SuspiciousCloneInfo SecondCloneInfo;
|
||||
};
|
||||
|
||||
/// Counts the differences between this pattern and the given one.
|
||||
/// \param Other The given VariablePattern to compare with.
|
||||
/// \param FirstMismatch Output parameter that will be filled with information
|
||||
/// about the first difference between the two patterns. This parameter
|
||||
/// can be a nullptr, in which case it will be ignored.
|
||||
/// \return Returns the number of differences between the pattern this object
|
||||
/// is following and the given VariablePattern.
|
||||
///
|
||||
/// For example, the following statements all have the same pattern and this
|
||||
/// function would return zero:
|
||||
///
|
||||
/// if (a < b) return a; return b;
|
||||
/// if (x < y) return x; return y;
|
||||
/// if (u2 < u1) return u2; return u1;
|
||||
///
|
||||
/// But the following statement has a different pattern (note the changed
|
||||
/// variables in the return statements) and would have two differences when
|
||||
/// compared with one of the statements above.
|
||||
///
|
||||
/// if (a < b) return b; return a;
|
||||
///
|
||||
/// This function should only be called if the related statements of the given
|
||||
/// pattern and the statements of this objects are clones of each other.
|
||||
unsigned countPatternDifferences(
|
||||
const VariablePattern &Other,
|
||||
VariablePattern::SuspiciousClonePair *FirstMismatch = nullptr);
|
||||
};
|
||||
/// \brief Searches the provided statements for pairs of clones that don't
|
||||
/// follow the same pattern when referencing variables.
|
||||
/// \param Result Output parameter that will contain the clone pairs.
|
||||
/// \param MinGroupComplexity Only clone pairs in which the clones have at
|
||||
/// least this complexity value.
|
||||
void findSuspiciousClones(std::vector<SuspiciousClonePair> &Result,
|
||||
unsigned MinGroupComplexity);
|
||||
|
||||
/// Ensures that all clones reference variables in the same pattern.
|
||||
struct MatchingVariablePatternConstraint {
|
||||
void constrain(std::vector<CloneDetector::CloneGroup> &CloneGroups);
|
||||
private:
|
||||
/// Stores all encountered StmtSequences alongside their CloneSignature.
|
||||
std::vector<std::pair<CloneSignature, StmtSequence>> Sequences;
|
||||
};
|
||||
|
||||
} // end namespace clang
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -38,15 +38,14 @@ public:
|
|||
void checkEndOfTranslationUnit(const TranslationUnitDecl *TU,
|
||||
AnalysisManager &Mgr, BugReporter &BR) const;
|
||||
|
||||
/// Reports all clones to the user.
|
||||
/// \brief Reports all clones to the user.
|
||||
void reportClones(BugReporter &BR, AnalysisManager &Mgr,
|
||||
std::vector<CloneDetector::CloneGroup> &CloneGroups) const;
|
||||
int MinComplexity) const;
|
||||
|
||||
/// Reports only suspicious clones to the user along with informaton
|
||||
/// that explain why they are suspicious.
|
||||
void reportSuspiciousClones(
|
||||
BugReporter &BR, AnalysisManager &Mgr,
|
||||
std::vector<CloneDetector::CloneGroup> &CloneGroups) const;
|
||||
/// \brief Reports only suspicious clones to the user along with informaton
|
||||
/// that explain why they are suspicious.
|
||||
void reportSuspiciousClones(BugReporter &BR, AnalysisManager &Mgr,
|
||||
int MinComplexity) const;
|
||||
};
|
||||
} // end anonymous namespace
|
||||
|
||||
|
@ -73,30 +72,11 @@ void CloneChecker::checkEndOfTranslationUnit(const TranslationUnitDecl *TU,
|
|||
bool ReportNormalClones = Mgr.getAnalyzerOptions().getBooleanOption(
|
||||
"ReportNormalClones", true, this);
|
||||
|
||||
// Let the CloneDetector create a list of clones from all the analyzed
|
||||
// statements. We don't filter for matching variable patterns at this point
|
||||
// because reportSuspiciousClones() wants to search them for errors.
|
||||
std::vector<CloneDetector::CloneGroup> AllCloneGroups;
|
||||
|
||||
Detector.findClones(AllCloneGroups, RecursiveCloneTypeIIConstraint(),
|
||||
MinComplexityConstraint(MinComplexity),
|
||||
MinGroupSizeConstraint(2), OnlyLargestCloneConstraint());
|
||||
|
||||
if (ReportSuspiciousClones)
|
||||
reportSuspiciousClones(BR, Mgr, AllCloneGroups);
|
||||
reportSuspiciousClones(BR, Mgr, MinComplexity);
|
||||
|
||||
// We are done for this translation unit unless we also need to report normal
|
||||
// clones.
|
||||
if (!ReportNormalClones)
|
||||
return;
|
||||
|
||||
// Now that the suspicious clone detector has checked for pattern errors,
|
||||
// we also filter all clones who don't have matching patterns
|
||||
CloneDetector::constrainClones(AllCloneGroups,
|
||||
MatchingVariablePatternConstraint(),
|
||||
MinGroupSizeConstraint(2));
|
||||
|
||||
reportClones(BR, Mgr, AllCloneGroups);
|
||||
if (ReportNormalClones)
|
||||
reportClones(BR, Mgr, MinComplexity);
|
||||
}
|
||||
|
||||
static PathDiagnosticLocation makeLocation(const StmtSequence &S,
|
||||
|
@ -107,55 +87,37 @@ static PathDiagnosticLocation makeLocation(const StmtSequence &S,
|
|||
Mgr.getAnalysisDeclContext(ACtx.getTranslationUnitDecl()));
|
||||
}
|
||||
|
||||
void CloneChecker::reportClones(
|
||||
BugReporter &BR, AnalysisManager &Mgr,
|
||||
std::vector<CloneDetector::CloneGroup> &CloneGroups) const {
|
||||
void CloneChecker::reportClones(BugReporter &BR, AnalysisManager &Mgr,
|
||||
int MinComplexity) const {
|
||||
|
||||
std::vector<CloneDetector::CloneGroup> CloneGroups;
|
||||
Detector.findClones(CloneGroups, MinComplexity);
|
||||
|
||||
if (!BT_Exact)
|
||||
BT_Exact.reset(new BugType(this, "Exact code clone", "Code clone"));
|
||||
|
||||
for (const CloneDetector::CloneGroup &Group : CloneGroups) {
|
||||
for (CloneDetector::CloneGroup &Group : CloneGroups) {
|
||||
// We group the clones by printing the first as a warning and all others
|
||||
// as a note.
|
||||
auto R = llvm::make_unique<BugReport>(*BT_Exact, "Duplicate code detected",
|
||||
makeLocation(Group.front(), Mgr));
|
||||
R->addRange(Group.front().getSourceRange());
|
||||
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.size(); ++i)
|
||||
R->addNote("Similar code here", makeLocation(Group[i], Mgr),
|
||||
Group[i].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(
|
||||
BugReporter &BR, AnalysisManager &Mgr,
|
||||
std::vector<CloneDetector::CloneGroup> &CloneGroups) const {
|
||||
std::vector<VariablePattern::SuspiciousClonePair> Pairs;
|
||||
void CloneChecker::reportSuspiciousClones(BugReporter &BR,
|
||||
AnalysisManager &Mgr,
|
||||
int MinComplexity) const {
|
||||
|
||||
for (const CloneDetector::CloneGroup &Group : CloneGroups) {
|
||||
for (unsigned i = 0; i < Group.size(); ++i) {
|
||||
VariablePattern PatternA(Group[i]);
|
||||
|
||||
for (unsigned j = i + 1; j < Group.size(); ++j) {
|
||||
VariablePattern PatternB(Group[j]);
|
||||
|
||||
VariablePattern::SuspiciousClonePair ClonePair;
|
||||
// For now, we only report clones which break the variable pattern just
|
||||
// once because multiple differences in a pattern are an indicator that
|
||||
// those differences are maybe intended (e.g. because it's actually a
|
||||
// different algorithm).
|
||||
// FIXME: In very big clones even multiple variables can be unintended,
|
||||
// so replacing this number with a percentage could better handle such
|
||||
// cases. On the other hand it could increase the false-positive rate
|
||||
// for all clones if the percentage is too high.
|
||||
if (PatternA.countPatternDifferences(PatternB, &ClonePair) == 1) {
|
||||
Pairs.push_back(ClonePair);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<CloneDetector::SuspiciousClonePair> Clones;
|
||||
Detector.findSuspiciousClones(Clones, MinComplexity);
|
||||
|
||||
if (!BT_Suspicious)
|
||||
BT_Suspicious.reset(
|
||||
|
@ -166,7 +128,7 @@ void CloneChecker::reportSuspiciousClones(
|
|||
AnalysisDeclContext *ADC =
|
||||
Mgr.getAnalysisDeclContext(ACtx.getTranslationUnitDecl());
|
||||
|
||||
for (VariablePattern::SuspiciousClonePair &Pair : Pairs) {
|
||||
for (CloneDetector::SuspiciousClonePair &Pair : Clones) {
|
||||
// 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.
|
||||
|
|
|
@ -2,12 +2,11 @@ set(LLVM_LINK_COMPONENTS
|
|||
Support
|
||||
)
|
||||
|
||||
add_clang_unittest(ClangAnalysisTests
|
||||
add_clang_unittest(CFGTests
|
||||
CFGTest.cpp
|
||||
CloneDetectionTest.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(ClangAnalysisTests
|
||||
target_link_libraries(CFGTests
|
||||
clangAnalysis
|
||||
clangAST
|
||||
clangASTMatchers
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
//===- unittests/Analysis/CloneDetectionTest.cpp - Clone detection tests --===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/AST/RecursiveASTVisitor.h"
|
||||
#include "clang/Analysis/CloneDetection.h"
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace clang {
|
||||
namespace analysis {
|
||||
namespace {
|
||||
|
||||
class CloneDetectionVisitor
|
||||
: public RecursiveASTVisitor<CloneDetectionVisitor> {
|
||||
|
||||
CloneDetector &Detector;
|
||||
|
||||
public:
|
||||
explicit CloneDetectionVisitor(CloneDetector &D) : Detector(D) {}
|
||||
|
||||
bool VisitFunctionDecl(FunctionDecl *D) {
|
||||
Detector.analyzeCodeBody(D);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/// Example constraint for testing purposes.
|
||||
/// Filters out all statements that are in a function which name starts with
|
||||
/// "bar".
|
||||
class NoBarFunctionConstraint {
|
||||
public:
|
||||
void constrain(std::vector<CloneDetector::CloneGroup> &CloneGroups) {
|
||||
CloneConstraint::splitCloneGroups(
|
||||
CloneGroups, [](const StmtSequence &A, const StmtSequence &B) {
|
||||
// Check if one of the sequences is in a function which name starts
|
||||
// with "bar".
|
||||
for (const StmtSequence &Arg : {A, B}) {
|
||||
if (const auto *D =
|
||||
dyn_cast<const FunctionDecl>(Arg.getContainingDecl())) {
|
||||
if (D->getNameAsString().find("bar") == 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TEST(CloneDetector, NoPostOrderTraversal) {
|
||||
auto ASTUnit =
|
||||
clang::tooling::buildASTFromCode("void foo1(int &a1) { a1++; }\n"
|
||||
"void foo2(int &a2) { a2++; }\n"
|
||||
"void bar1(int &a3) { a3++; }\n"
|
||||
"void bar2(int &a4) { a4++; }\n");
|
||||
auto TU = ASTUnit->getASTContext().getTranslationUnitDecl();
|
||||
|
||||
CloneDetector Detector;
|
||||
// Push all the function bodies into the detector.
|
||||
CloneDetectionVisitor Visitor(Detector);
|
||||
Visitor.TraverseTranslationUnitDecl(TU);
|
||||
|
||||
// Find clones with the usual settings, but but we want to filter out
|
||||
// all statements from functions which names start with "bar".
|
||||
std::vector<CloneDetector::CloneGroup> CloneGroups;
|
||||
Detector.findClones(CloneGroups, NoBarFunctionConstraint(),
|
||||
RecursiveCloneTypeIIConstraint(),
|
||||
MinComplexityConstraint(2), MinGroupSizeConstraint(2),
|
||||
OnlyLargestCloneConstraint());
|
||||
|
||||
ASSERT_EQ(CloneGroups.size(), 1u);
|
||||
ASSERT_EQ(CloneGroups.front().size(), 2u);
|
||||
|
||||
for (auto &Clone : CloneGroups.front()) {
|
||||
const auto ND = dyn_cast<const FunctionDecl>(Clone.getContainingDecl());
|
||||
ASSERT_TRUE(ND != nullptr);
|
||||
// Check that no function name starting with "bar" is in the results...
|
||||
ASSERT_TRUE(ND->getNameAsString().find("bar") != 0);
|
||||
}
|
||||
|
||||
// Retry above's example without the filter...
|
||||
CloneGroups.clear();
|
||||
|
||||
Detector.findClones(CloneGroups, RecursiveCloneTypeIIConstraint(),
|
||||
MinComplexityConstraint(2), MinGroupSizeConstraint(2),
|
||||
OnlyLargestCloneConstraint());
|
||||
ASSERT_EQ(CloneGroups.size(), 1u);
|
||||
ASSERT_EQ(CloneGroups.front().size(), 4u);
|
||||
|
||||
// Count how many functions with the bar prefix we have in the results.
|
||||
int FoundFunctionsWithBarPrefix = 0;
|
||||
for (auto &Clone : CloneGroups.front()) {
|
||||
const auto ND = dyn_cast<const FunctionDecl>(Clone.getContainingDecl());
|
||||
ASSERT_TRUE(ND != nullptr);
|
||||
// This time check that we picked up the bar functions from above
|
||||
if (ND->getNameAsString().find("bar") == 0) {
|
||||
FoundFunctionsWithBarPrefix++;
|
||||
}
|
||||
}
|
||||
// We should have found the two functions bar1 and bar2.
|
||||
ASSERT_EQ(FoundFunctionsWithBarPrefix, 2);
|
||||
}
|
||||
} // namespace
|
||||
} // namespace analysis
|
||||
} // namespace clang
|
Loading…
Reference in New Issue