1a75b2cacSAlex Lorenz //===- ClangDiff.cpp - compare source files by AST nodes ------*- C++ -*- -===// 2a75b2cacSAlex Lorenz // 3a75b2cacSAlex Lorenz // The LLVM Compiler Infrastructure 4a75b2cacSAlex Lorenz // 5a75b2cacSAlex Lorenz // This file is distributed under the University of Illinois Open Source 6a75b2cacSAlex Lorenz // License. See LICENSE.TXT for details. 7a75b2cacSAlex Lorenz // 8a75b2cacSAlex Lorenz //===----------------------------------------------------------------------===// 9a75b2cacSAlex Lorenz // 10a75b2cacSAlex Lorenz // This file implements a tool for syntax tree based comparison using 11a75b2cacSAlex Lorenz // Tooling/ASTDiff. 12a75b2cacSAlex Lorenz // 13a75b2cacSAlex Lorenz //===----------------------------------------------------------------------===// 14a75b2cacSAlex Lorenz 15a75b2cacSAlex Lorenz #include "clang/Tooling/ASTDiff/ASTDiff.h" 16a75b2cacSAlex Lorenz #include "clang/Tooling/CommonOptionsParser.h" 17a75b2cacSAlex Lorenz #include "clang/Tooling/Tooling.h" 18a75b2cacSAlex Lorenz #include "llvm/Support/CommandLine.h" 19a75b2cacSAlex Lorenz 20a75b2cacSAlex Lorenz using namespace llvm; 21a75b2cacSAlex Lorenz using namespace clang; 22a75b2cacSAlex Lorenz using namespace clang::tooling; 23a75b2cacSAlex Lorenz 24a75b2cacSAlex Lorenz static cl::OptionCategory ClangDiffCategory("clang-diff options"); 25a75b2cacSAlex Lorenz 26a75b2cacSAlex Lorenz static cl::opt<bool> 27914a958eSJohannes Altmanninger ASTDump("ast-dump", 28a75b2cacSAlex Lorenz cl::desc("Print the internal representation of the AST as JSON."), 29a75b2cacSAlex Lorenz cl::init(false), cl::cat(ClangDiffCategory)); 30a75b2cacSAlex Lorenz 31a75b2cacSAlex Lorenz static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"), 32a75b2cacSAlex Lorenz cl::Required, 33a75b2cacSAlex Lorenz cl::cat(ClangDiffCategory)); 34a75b2cacSAlex Lorenz 35a75b2cacSAlex Lorenz static cl::opt<std::string> DestinationPath(cl::Positional, 36a75b2cacSAlex Lorenz cl::desc("<destination>"), 37a75b2cacSAlex Lorenz cl::Optional, 38a75b2cacSAlex Lorenz cl::cat(ClangDiffCategory)); 39a75b2cacSAlex Lorenz 40849f20e4SJohannes Altmanninger static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional, 41849f20e4SJohannes Altmanninger cl::init(-1), cl::cat(ClangDiffCategory)); 42849f20e4SJohannes Altmanninger 43849f20e4SJohannes Altmanninger static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""), 44849f20e4SJohannes Altmanninger cl::Optional, cl::cat(ClangDiffCategory)); 45849f20e4SJohannes Altmanninger 46849f20e4SJohannes Altmanninger static cl::list<std::string> ArgsAfter( 47849f20e4SJohannes Altmanninger "extra-arg", 48849f20e4SJohannes Altmanninger cl::desc("Additional argument to append to the compiler command line"), 49849f20e4SJohannes Altmanninger cl::cat(ClangDiffCategory)); 50849f20e4SJohannes Altmanninger 51849f20e4SJohannes Altmanninger static cl::list<std::string> ArgsBefore( 52849f20e4SJohannes Altmanninger "extra-arg-before", 53849f20e4SJohannes Altmanninger cl::desc("Additional argument to prepend to the compiler command line"), 54849f20e4SJohannes Altmanninger cl::cat(ClangDiffCategory)); 55849f20e4SJohannes Altmanninger 56849f20e4SJohannes Altmanninger static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) { 57849f20e4SJohannes Altmanninger if (!Compilations) 58849f20e4SJohannes Altmanninger return; 59849f20e4SJohannes Altmanninger auto AdjustingCompilations = 60849f20e4SJohannes Altmanninger llvm::make_unique<ArgumentsAdjustingCompilations>( 61849f20e4SJohannes Altmanninger std::move(Compilations)); 62849f20e4SJohannes Altmanninger AdjustingCompilations->appendArgumentsAdjuster( 63849f20e4SJohannes Altmanninger getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN)); 64849f20e4SJohannes Altmanninger AdjustingCompilations->appendArgumentsAdjuster( 65849f20e4SJohannes Altmanninger getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); 66849f20e4SJohannes Altmanninger Compilations = std::move(AdjustingCompilations); 67849f20e4SJohannes Altmanninger } 68849f20e4SJohannes Altmanninger 69849f20e4SJohannes Altmanninger static std::unique_ptr<ASTUnit> 70849f20e4SJohannes Altmanninger getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations, 71849f20e4SJohannes Altmanninger const StringRef Filename) { 72a75b2cacSAlex Lorenz std::string ErrorMessage; 73a75b2cacSAlex Lorenz std::unique_ptr<CompilationDatabase> Compilations; 74849f20e4SJohannes Altmanninger if (!CommonCompilations) { 75849f20e4SJohannes Altmanninger Compilations = CompilationDatabase::autoDetectFromSource( 76849f20e4SJohannes Altmanninger BuildPath.empty() ? Filename : BuildPath, ErrorMessage); 77a75b2cacSAlex Lorenz if (!Compilations) { 78a75b2cacSAlex Lorenz llvm::errs() 79a75b2cacSAlex Lorenz << "Error while trying to load a compilation database, running " 80a75b2cacSAlex Lorenz "without flags.\n" 81a75b2cacSAlex Lorenz << ErrorMessage; 82849f20e4SJohannes Altmanninger Compilations = 83849f20e4SJohannes Altmanninger llvm::make_unique<clang::tooling::FixedCompilationDatabase>( 84a75b2cacSAlex Lorenz ".", std::vector<std::string>()); 85a75b2cacSAlex Lorenz } 86849f20e4SJohannes Altmanninger } 87849f20e4SJohannes Altmanninger addExtraArgs(Compilations); 88a75b2cacSAlex Lorenz std::array<std::string, 1> Files = {{Filename}}; 89849f20e4SJohannes Altmanninger ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files); 90a75b2cacSAlex Lorenz std::vector<std::unique_ptr<ASTUnit>> ASTs; 91a75b2cacSAlex Lorenz Tool.buildASTs(ASTs); 92a75b2cacSAlex Lorenz if (ASTs.size() != Files.size()) 93a75b2cacSAlex Lorenz return nullptr; 94a75b2cacSAlex Lorenz return std::move(ASTs[0]); 95a75b2cacSAlex Lorenz } 96a75b2cacSAlex Lorenz 97*0da12c84SJohannes Altmanninger static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); } 98*0da12c84SJohannes Altmanninger 99*0da12c84SJohannes Altmanninger static void printJsonString(raw_ostream &OS, const StringRef Str) { 100*0da12c84SJohannes Altmanninger for (char C : Str) { 101*0da12c84SJohannes Altmanninger switch (C) { 102*0da12c84SJohannes Altmanninger case '"': 103*0da12c84SJohannes Altmanninger OS << R"(\")"; 104*0da12c84SJohannes Altmanninger break; 105*0da12c84SJohannes Altmanninger case '\\': 106*0da12c84SJohannes Altmanninger OS << R"(\\)"; 107*0da12c84SJohannes Altmanninger break; 108*0da12c84SJohannes Altmanninger case '\n': 109*0da12c84SJohannes Altmanninger OS << R"(\n)"; 110*0da12c84SJohannes Altmanninger break; 111*0da12c84SJohannes Altmanninger case '\t': 112*0da12c84SJohannes Altmanninger OS << R"(\t)"; 113*0da12c84SJohannes Altmanninger break; 114*0da12c84SJohannes Altmanninger default: 115*0da12c84SJohannes Altmanninger if ('\x00' <= C && C <= '\x1f') { 116*0da12c84SJohannes Altmanninger OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C); 117*0da12c84SJohannes Altmanninger } else { 118*0da12c84SJohannes Altmanninger OS << C; 119*0da12c84SJohannes Altmanninger } 120*0da12c84SJohannes Altmanninger } 121*0da12c84SJohannes Altmanninger } 122*0da12c84SJohannes Altmanninger } 123*0da12c84SJohannes Altmanninger 124*0da12c84SJohannes Altmanninger static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree, 125*0da12c84SJohannes Altmanninger diff::NodeId Id) { 126*0da12c84SJohannes Altmanninger const diff::Node &N = Tree.getNode(Id); 127*0da12c84SJohannes Altmanninger OS << R"("id":)" << int(Id); 128*0da12c84SJohannes Altmanninger OS << R"(,"type":")" << N.getTypeLabel() << '"'; 129*0da12c84SJohannes Altmanninger auto Offsets = Tree.getSourceRangeOffsets(N); 130*0da12c84SJohannes Altmanninger OS << R"(,"begin":)" << Offsets.first; 131*0da12c84SJohannes Altmanninger OS << R"(,"end":)" << Offsets.second; 132*0da12c84SJohannes Altmanninger std::string Value = Tree.getNodeValue(N.ASTNode); 133*0da12c84SJohannes Altmanninger if (!Value.empty()) { 134*0da12c84SJohannes Altmanninger OS << R"(,"value":")"; 135*0da12c84SJohannes Altmanninger printJsonString(OS, Value); 136*0da12c84SJohannes Altmanninger OS << '"'; 137*0da12c84SJohannes Altmanninger } 138*0da12c84SJohannes Altmanninger } 139*0da12c84SJohannes Altmanninger 140*0da12c84SJohannes Altmanninger static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree, 141*0da12c84SJohannes Altmanninger diff::NodeId Id) { 142*0da12c84SJohannes Altmanninger const diff::Node &N = Tree.getNode(Id); 143*0da12c84SJohannes Altmanninger OS << "{"; 144*0da12c84SJohannes Altmanninger printNodeAttributes(OS, Tree, Id); 145*0da12c84SJohannes Altmanninger OS << R"(,"children":[)"; 146*0da12c84SJohannes Altmanninger if (N.Children.size() > 0) { 147*0da12c84SJohannes Altmanninger printNodeAsJson(OS, Tree, N.Children[0]); 148*0da12c84SJohannes Altmanninger for (size_t I = 1, E = N.Children.size(); I < E; ++I) { 149*0da12c84SJohannes Altmanninger OS << ","; 150*0da12c84SJohannes Altmanninger printNodeAsJson(OS, Tree, N.Children[I]); 151*0da12c84SJohannes Altmanninger } 152*0da12c84SJohannes Altmanninger } 153*0da12c84SJohannes Altmanninger OS << "]}"; 154*0da12c84SJohannes Altmanninger } 155*0da12c84SJohannes Altmanninger 156a75b2cacSAlex Lorenz int main(int argc, const char **argv) { 157849f20e4SJohannes Altmanninger std::string ErrorMessage; 158849f20e4SJohannes Altmanninger std::unique_ptr<CompilationDatabase> CommonCompilations = 159849f20e4SJohannes Altmanninger FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); 160849f20e4SJohannes Altmanninger if (!CommonCompilations && !ErrorMessage.empty()) 161849f20e4SJohannes Altmanninger llvm::errs() << ErrorMessage; 162a75b2cacSAlex Lorenz cl::HideUnrelatedOptions(ClangDiffCategory); 163a75b2cacSAlex Lorenz if (!cl::ParseCommandLineOptions(argc, argv)) { 164a75b2cacSAlex Lorenz cl::PrintOptionValues(); 165a75b2cacSAlex Lorenz return 1; 166a75b2cacSAlex Lorenz } 167a75b2cacSAlex Lorenz 168849f20e4SJohannes Altmanninger addExtraArgs(CommonCompilations); 169849f20e4SJohannes Altmanninger 170914a958eSJohannes Altmanninger if (ASTDump) { 171a75b2cacSAlex Lorenz if (!DestinationPath.empty()) { 172a75b2cacSAlex Lorenz llvm::errs() << "Error: Please specify exactly one filename.\n"; 173a75b2cacSAlex Lorenz return 1; 174a75b2cacSAlex Lorenz } 175849f20e4SJohannes Altmanninger std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath); 176a75b2cacSAlex Lorenz if (!AST) 177a75b2cacSAlex Lorenz return 1; 178a75b2cacSAlex Lorenz diff::SyntaxTree Tree(AST->getASTContext()); 179*0da12c84SJohannes Altmanninger llvm::outs() << R"({"filename":")"; 180*0da12c84SJohannes Altmanninger printJsonString(llvm::outs(), SourcePath); 181*0da12c84SJohannes Altmanninger llvm::outs() << R"(","root":)"; 182*0da12c84SJohannes Altmanninger printNodeAsJson(llvm::outs(), Tree, Tree.getRootId()); 183*0da12c84SJohannes Altmanninger llvm::outs() << "}\n"; 184a75b2cacSAlex Lorenz return 0; 185a75b2cacSAlex Lorenz } 186a75b2cacSAlex Lorenz 187a75b2cacSAlex Lorenz if (DestinationPath.empty()) { 188a75b2cacSAlex Lorenz llvm::errs() << "Error: Exactly two paths are required.\n"; 189a75b2cacSAlex Lorenz return 1; 190a75b2cacSAlex Lorenz } 191a75b2cacSAlex Lorenz 192849f20e4SJohannes Altmanninger std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath); 193849f20e4SJohannes Altmanninger std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath); 194a75b2cacSAlex Lorenz if (!Src || !Dst) 195a75b2cacSAlex Lorenz return 1; 196a75b2cacSAlex Lorenz 197a75b2cacSAlex Lorenz diff::ComparisonOptions Options; 198849f20e4SJohannes Altmanninger if (MaxSize != -1) 199849f20e4SJohannes Altmanninger Options.MaxSize = MaxSize; 200a75b2cacSAlex Lorenz diff::SyntaxTree SrcTree(Src->getASTContext()); 201a75b2cacSAlex Lorenz diff::SyntaxTree DstTree(Dst->getASTContext()); 202ebcb773fSVlad Tsyrklevich diff::ASTDiff DiffTool(SrcTree, DstTree, Options); 203ebcb773fSVlad Tsyrklevich for (const auto &Match : DiffTool.getMatches()) 204ebcb773fSVlad Tsyrklevich DiffTool.printMatch(llvm::outs(), Match); 205ebcb773fSVlad Tsyrklevich for (const auto &Change : DiffTool.getChanges()) 206ebcb773fSVlad Tsyrklevich DiffTool.printChange(llvm::outs(), Change); 207a75b2cacSAlex Lorenz 208a75b2cacSAlex Lorenz return 0; 209a75b2cacSAlex Lorenz } 210