Created
March 30, 2017 13:54
-
-
Save Teemperor/0fb0d22e62f9e21bec8072068cbc7a05 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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