[analyzer] Add a modular constraint system to the CloneDetector

A big part of the clone detection code is functionality for filtering clones and
clone groups based on different criteria. So far this filtering process was
hardcoded into the CloneDetector class, which made it hard to understand and,
ultimately, to extend.

This patch splits the CloneDetector's logic into a sequence of reusable
constraints that are used for filtering clone groups. These constraints
can be turned on and off and reodreder at will, and new constraints are easy
to implement if necessary.

Unit tests are added for the new constraint interface.

This is a refactoring patch - no functional change intended.

Patch by Raphael Isemann!

Differential Revision: https://reviews.llvm.org/D23418

llvm-svn: 299544
This commit is contained in:
Artem Dergachev 2017-04-05 14:17:36 +00:00
parent a16ab5aee5
commit f8b4fc38fd
5 changed files with 830 additions and 703 deletions

View File

@ -16,9 +16,7 @@
#define LLVM_CLANG_AST_CLONEDETECTION_H
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/SmallVector.h"
#include <vector>
namespace clang {
@ -29,7 +27,7 @@ class VarDecl;
class ASTContext;
class CompoundStmt;
/// \brief Identifies a list of statements.
/// 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.
@ -39,8 +37,8 @@ class StmtSequence {
/// Stmt, then S is a pointer to this Stmt.
const Stmt *S;
/// The related ASTContext for S.
ASTContext *Context;
/// The declaration that contains the statements.
const Decl *D;
/// If EndIndex is non-zero, then S is a CompoundStmt and this StmtSequence
/// instance is representing the CompoundStmt children inside the array
@ -49,7 +47,7 @@ class StmtSequence {
unsigned EndIndex;
public:
/// \brief Constructs a StmtSequence holding multiple statements.
/// 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
@ -57,20 +55,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 Context The ASTContext for the given CompoundStmt.
/// \param Decl The Decl containing this Stmt.
/// \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, ASTContext &Context,
unsigned StartIndex, unsigned EndIndex);
StmtSequence(const CompoundStmt *Stmt, const Decl *D, unsigned StartIndex,
unsigned EndIndex);
/// \brief Constructs a StmtSequence holding a single statement.
/// Constructs a StmtSequence holding a single statement.
///
/// \param Stmt An arbitrary Stmt.
/// \param Context The ASTContext for the given Stmt.
StmtSequence(const Stmt *Stmt, ASTContext &Context);
/// \param Decl The Decl containing this Stmt.
StmtSequence(const Stmt *Stmt, const Decl *D);
/// \brief Constructs an empty StmtSequence.
/// Constructs an empty StmtSequence.
StmtSequence();
typedef const Stmt *const *iterator;
@ -110,9 +108,12 @@ public:
bool empty() const { return size() == 0; }
/// Returns the related ASTContext for the stored Stmts.
ASTContext &getASTContext() const {
assert(Context);
return *Context;
ASTContext &getASTContext() const;
/// Returns the declaration that contains the stored Stmts.
const Decl *getContainingDecl() const {
assert(D);
return D;
}
/// Returns true if this objects holds a list of statements.
@ -150,105 +151,213 @@ public:
bool contains(const StmtSequence &Other) const;
};
/// \brief Searches for clones in source code.
/// Searches for similar subtrees in the AST.
///
/// 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 .
/// 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.
///
/// 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:
typedef unsigned DataPiece;
/// A collection of StmtSequences that share an arbitrary property.
typedef llvm::SmallVector<StmtSequence, 8> CloneGroup;
/// 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;
/// \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;
/// \brief Creates an empty CloneSignature without any data.
CloneSignature() : Complexity(1) {}
CloneSignature(llvm::hash_code Hash, unsigned Complexity)
: Hash(Hash), Complexity(Complexity) {}
};
/// 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;
CloneGroup() {}
CloneGroup(const StmtSequence &Seq, CloneSignature Signature)
: Signature(Signature) {
Sequences.push_back(Seq);
}
/// \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;
}
};
/// \brief Generates and stores search data for all statements in the body of
/// 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.
/// Constrains the given list of clone groups with the given constraint.
///
/// \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);
/// 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 Describes two clones that reference their variables in a different
/// pattern which could indicate a programming error.
/// 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...);
}
/// 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) {}
};
/// 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;
/// 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);
/// Adds each referenced variable from the given statement.
void addVariables(const Stmt *S);
public:
/// Creates an VariablePattern object with information about the given
/// StmtSequence.
VariablePattern(const StmtSequence &Sequence) {
for (const Stmt *S : Sequence)
addVariables(S);
}
/// Describes two clones that reference their variables in a different pattern
/// which could indicate a programming error.
struct SuspiciousClonePair {
/// \brief Utility class holding the relevant information about a single
/// 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.
@ -270,17 +379,37 @@ public:
SuspiciousCloneInfo SecondCloneInfo;
};
/// \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);
/// 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);
};
private:
/// Stores all encountered StmtSequences alongside their CloneSignature.
std::vector<std::pair<CloneSignature, StmtSequence>> Sequences;
/// Ensures that all clones reference variables in the same pattern.
struct MatchingVariablePatternConstraint {
void constrain(std::vector<CloneDetector::CloneGroup> &CloneGroups);
};
} // end namespace clang

File diff suppressed because it is too large Load Diff

View File

@ -38,14 +38,15 @@ public:
void checkEndOfTranslationUnit(const TranslationUnitDecl *TU,
AnalysisManager &Mgr, BugReporter &BR) const;
/// \brief Reports all clones to the user.
/// Reports all clones to the user.
void reportClones(BugReporter &BR, AnalysisManager &Mgr,
int MinComplexity) const;
std::vector<CloneDetector::CloneGroup> &CloneGroups) const;
/// \brief Reports only suspicious clones to the user along with informaton
/// 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;
void reportSuspiciousClones(
BugReporter &BR, AnalysisManager &Mgr,
std::vector<CloneDetector::CloneGroup> &CloneGroups) const;
};
} // end anonymous namespace
@ -72,11 +73,30 @@ void CloneChecker::checkEndOfTranslationUnit(const TranslationUnitDecl *TU,
bool ReportNormalClones = Mgr.getAnalyzerOptions().getBooleanOption(
"ReportNormalClones", true, this);
if (ReportSuspiciousClones)
reportSuspiciousClones(BR, Mgr, MinComplexity);
// 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;
if (ReportNormalClones)
reportClones(BR, Mgr, MinComplexity);
Detector.findClones(AllCloneGroups, RecursiveCloneTypeIIConstraint(),
MinComplexityConstraint(MinComplexity),
MinGroupSizeConstraint(2), OnlyLargestCloneConstraint());
if (ReportSuspiciousClones)
reportSuspiciousClones(BR, Mgr, AllCloneGroups);
// 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);
}
static PathDiagnosticLocation makeLocation(const StmtSequence &S,
@ -87,37 +107,55 @@ static PathDiagnosticLocation makeLocation(const StmtSequence &S,
Mgr.getAnalysisDeclContext(ACtx.getTranslationUnitDecl()));
}
void CloneChecker::reportClones(BugReporter &BR, AnalysisManager &Mgr,
int MinComplexity) const {
std::vector<CloneDetector::CloneGroup> CloneGroups;
Detector.findClones(CloneGroups, MinComplexity);
void CloneChecker::reportClones(
BugReporter &BR, AnalysisManager &Mgr,
std::vector<CloneDetector::CloneGroup> &CloneGroups) const {
if (!BT_Exact)
BT_Exact.reset(new BugType(this, "Exact code clone", "Code clone"));
for (CloneDetector::CloneGroup &Group : CloneGroups) {
for (const 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.Sequences.front(), Mgr));
R->addRange(Group.Sequences.front().getSourceRange());
auto R = llvm::make_unique<BugReport>(*BT_Exact, "Duplicate code detected",
makeLocation(Group.front(), Mgr));
R->addRange(Group.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());
for (unsigned i = 1; i < Group.size(); ++i)
R->addNote("Similar code here", makeLocation(Group[i], Mgr),
Group[i].getSourceRange());
BR.emitReport(std::move(R));
}
}
void CloneChecker::reportSuspiciousClones(BugReporter &BR,
AnalysisManager &Mgr,
int MinComplexity) const {
void CloneChecker::reportSuspiciousClones(
BugReporter &BR, AnalysisManager &Mgr,
std::vector<CloneDetector::CloneGroup> &CloneGroups) const {
std::vector<VariablePattern::SuspiciousClonePair> Pairs;
std::vector<CloneDetector::SuspiciousClonePair> Clones;
Detector.findSuspiciousClones(Clones, MinComplexity);
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;
}
}
}
}
if (!BT_Suspicious)
BT_Suspicious.reset(
@ -128,7 +166,7 @@ void CloneChecker::reportSuspiciousClones(BugReporter &BR,
AnalysisDeclContext *ADC =
Mgr.getAnalysisDeclContext(ACtx.getTranslationUnitDecl());
for (CloneDetector::SuspiciousClonePair &Pair : Clones) {
for (VariablePattern::SuspiciousClonePair &Pair : Pairs) {
// 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.

View File

@ -2,11 +2,12 @@ set(LLVM_LINK_COMPONENTS
Support
)
add_clang_unittest(CFGTests
add_clang_unittest(ClangAnalysisTests
CFGTest.cpp
CloneDetectionTest.cpp
)
target_link_libraries(CFGTests
target_link_libraries(ClangAnalysisTests
clangAnalysis
clangAST
clangASTMatchers

View File

@ -0,0 +1,110 @@
//===- 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