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:
Artem Dergachev 2017-04-05 15:06:17 +00:00
parent de87d145cc
commit c4aee48b9f
5 changed files with 696 additions and 823 deletions

View File

@ -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,213 +150,105 @@ 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
/// 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
/// the given Decl.
void analyzeCodeBody(const Decl *D);
/// Constrains the given list of clone groups with the given constraint.
/// \brief Stores the CloneSignature to allow future querying.
void add(const StmtSequence &S, const CloneSignature &Signature);
/// \brief Searches the provided statements for clones.
///
/// 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);
}
/// \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);
/// 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.
/// \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
/// \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.
@ -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

View File

@ -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
/// \brief 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;
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.

View File

@ -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

View File

@ -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