Skip to content

Instantly share code, notes, and snippets.

@Teemperor
Created March 30, 2017 13:54
Show Gist options
  • Save Teemperor/0fb0d22e62f9e21bec8072068cbc7a05 to your computer and use it in GitHub Desktop.
Save Teemperor/0fb0d22e62f9e21bec8072068cbc7a05 to your computer and use it in GitHub Desktop.
// Example of using the CloneDetector for finding changes in source files.
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Analysis/CloneDetection.h"
#include "clang/Tooling/Tooling.h"
#include <iostream>
// Utility class that pushes each declaration in the given TU to
// the CloneDetector instance.
class CloneDetectionVisitor
: public RecursiveASTVisitor<CloneDetectionVisitor> {
CloneDetector &Detector;
public:
explicit CloneDetectionVisitor(CloneDetector &D) : Detector(D) {}
bool VisitFunctionDecl(FunctionDecl *D) {
if (!D->isImplicit())
Detector.analyzeCodeBody(D);
return true;
}
};
// Below are some custom constraints that we use when searching for
// the clones that are relevant for the diff tool.
// Ensures that all clone groups consist of clones that are in different
// translation units because we only care what changed between those
// two files.
class DifferentTUConstraint {
public:
void constrain(std::vector<CloneDetector::CloneGroup> &CloneGroups) {
CloneConstraint::splitCloneGroups(
CloneGroups, [](const StmtSequence &A, const StmtSequence &B) {
return &A.getASTContext() != &B.getASTContext();
});
}
};
// Gets the start line number of a StmtSeq. Just a utility method because we want to print the line numbers.
int getStartLineNumber(const StmtSequence& Seq) {
bool Invalid;
auto Line = Seq.getASTContext().getSourceManager().getSpellingLineNumber(Seq.front()->getLocStart(), &Invalid);
assert(!Invalid);
return Line;
}
// Filters out all clones that are on the same source code lines. Later we can filter by what lines are changed
// by the diff or something like that.
class DifferentLineConstraint {
public:
void constrain(std::vector<CloneDetector::CloneGroup> &CloneGroups) {
CloneConstraint::splitCloneGroups(
CloneGroups, [](const StmtSequence &A, const StmtSequence &B) {
return getStartLineNumber(A) != getStartLineNumber(B);
});
}
};
// We only care for pairs of clones. So we make this constraint and filter by clone groups
// that aren't pairs.
class ExactGroupSizeConstraint {
unsigned GroupSize;
public:
ExactGroupSizeConstraint(unsigned MinGroupSize = 2)
: GroupSize(MinGroupSize) {}
void constrain(std::vector<CloneDetector::CloneGroup> &CloneGroups) {
CloneConstraint::filterGroups(CloneGroups,
[this](const CloneDetector::CloneGroup &A) {
return A.size() != GroupSize;
});
}
};
int main() {
// Hardcode the two files before and after the diff.
// The old file.
auto ASTUnit1 =
clang::tooling::buildASTFromCode(
"int main(int argc, const char **argv) {\n"
" switch (argc) {\n"
" }\n"
" if (argc > 2) {\n"
" return 1;\n"
" }\n"
" while (false);\n"
" int funkyVariable = 1;\n"
" funkyVariable++;\n"
"}");
auto TU1 = ASTUnit1->getASTContext().getTranslationUnitDecl();
// The new file.
auto ASTUnit2 =
clang::tooling::buildASTFromCode(
"int main(int argc, const char **argv) {\n"
" if (argc > 2) {\n"
" return 1;\n"
" }\n"
" switch (argc) {\n"
" }\n"
" while (false);\n"
" int funkyVariable = 1;\n"
" funkyVariable++;\n"
"}");
auto TU2 = ASTUnit2->getASTContext().getTranslationUnitDecl();
// Instantiate a clone detector that stores the data.
CloneDetector Detector;
// Push all the old and new translation unit into the detector.
CloneDetectionVisitor Visitor(Detector);
Visitor.TraverseTranslationUnitDecl(TU1);
Visitor.TraverseTranslationUnitDecl(TU2);
// Look with the help of our constraints from above for things
// that have changed.
std::vector<CloneDetector::CloneGroup> CloneGroups;
Detector.findClones(CloneGroups,
// This hashes each stmt in the TU.
// Note: Ignores variable names.
RecursiveCloneTypeIIConstraint(),
// Apply our three custom constraints.
DifferentTUConstraint(),
DifferentLineConstraint(),
ExactGroupSizeConstraint(2),
// We only care about the largest change. That is, if the if
// statement is moving, all his children are also moving.
// This filters everything out beside the largest change.
OnlyLargestCloneConstraint());
// Print the detected changes.
for (const auto& CloneGroup : CloneGroups) {
std::cerr << "Change: " << CloneGroup.front().front()->getStmtClassName();
std::cerr << " moved from line " << getStartLineNumber(CloneGroup.front()) << " to line " << getStartLineNumber(CloneGroup.back());
std::cerr << "\n";
}
// Console-Output:
// Change: SwitchStmt moved from line 2 to line 5
// Change: IfStmt moved from line 4 to line 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment