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", 28a1d2b5d5SJohannes Altmanninger cl::desc("Print the internal representation of the AST."), 29a1d2b5d5SJohannes Altmanninger cl::init(false), cl::cat(ClangDiffCategory)); 30a1d2b5d5SJohannes Altmanninger 31a1d2b5d5SJohannes Altmanninger static cl::opt<bool> ASTDumpJson( 32a1d2b5d5SJohannes Altmanninger "ast-dump-json", 33a75b2cacSAlex Lorenz cl::desc("Print the internal representation of the AST as JSON."), 34a75b2cacSAlex Lorenz cl::init(false), cl::cat(ClangDiffCategory)); 35a75b2cacSAlex Lorenz 36*683876caSJohannes Altmanninger static cl::opt<bool> 37*683876caSJohannes Altmanninger PrintMatches("dump-matches", cl::desc("Print the matched nodes."), 38*683876caSJohannes Altmanninger cl::init(false), cl::cat(ClangDiffCategory)); 39*683876caSJohannes Altmanninger 40a75b2cacSAlex Lorenz static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"), 41a75b2cacSAlex Lorenz cl::Required, 42a75b2cacSAlex Lorenz cl::cat(ClangDiffCategory)); 43a75b2cacSAlex Lorenz 44a75b2cacSAlex Lorenz static cl::opt<std::string> DestinationPath(cl::Positional, 45a75b2cacSAlex Lorenz cl::desc("<destination>"), 46a75b2cacSAlex Lorenz cl::Optional, 47a75b2cacSAlex Lorenz cl::cat(ClangDiffCategory)); 48a75b2cacSAlex Lorenz 49849f20e4SJohannes Altmanninger static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional, 50849f20e4SJohannes Altmanninger cl::init(-1), cl::cat(ClangDiffCategory)); 51849f20e4SJohannes Altmanninger 52849f20e4SJohannes Altmanninger static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""), 53849f20e4SJohannes Altmanninger cl::Optional, cl::cat(ClangDiffCategory)); 54849f20e4SJohannes Altmanninger 55849f20e4SJohannes Altmanninger static cl::list<std::string> ArgsAfter( 56849f20e4SJohannes Altmanninger "extra-arg", 57849f20e4SJohannes Altmanninger cl::desc("Additional argument to append to the compiler command line"), 58849f20e4SJohannes Altmanninger cl::cat(ClangDiffCategory)); 59849f20e4SJohannes Altmanninger 60849f20e4SJohannes Altmanninger static cl::list<std::string> ArgsBefore( 61849f20e4SJohannes Altmanninger "extra-arg-before", 62849f20e4SJohannes Altmanninger cl::desc("Additional argument to prepend to the compiler command line"), 63849f20e4SJohannes Altmanninger cl::cat(ClangDiffCategory)); 64849f20e4SJohannes Altmanninger 65849f20e4SJohannes Altmanninger static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) { 66849f20e4SJohannes Altmanninger if (!Compilations) 67849f20e4SJohannes Altmanninger return; 68849f20e4SJohannes Altmanninger auto AdjustingCompilations = 69849f20e4SJohannes Altmanninger llvm::make_unique<ArgumentsAdjustingCompilations>( 70849f20e4SJohannes Altmanninger std::move(Compilations)); 71849f20e4SJohannes Altmanninger AdjustingCompilations->appendArgumentsAdjuster( 72849f20e4SJohannes Altmanninger getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN)); 73849f20e4SJohannes Altmanninger AdjustingCompilations->appendArgumentsAdjuster( 74849f20e4SJohannes Altmanninger getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); 75849f20e4SJohannes Altmanninger Compilations = std::move(AdjustingCompilations); 76849f20e4SJohannes Altmanninger } 77849f20e4SJohannes Altmanninger 78849f20e4SJohannes Altmanninger static std::unique_ptr<ASTUnit> 79849f20e4SJohannes Altmanninger getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations, 80849f20e4SJohannes Altmanninger const StringRef Filename) { 81a75b2cacSAlex Lorenz std::string ErrorMessage; 82a75b2cacSAlex Lorenz std::unique_ptr<CompilationDatabase> Compilations; 83849f20e4SJohannes Altmanninger if (!CommonCompilations) { 84849f20e4SJohannes Altmanninger Compilations = CompilationDatabase::autoDetectFromSource( 85849f20e4SJohannes Altmanninger BuildPath.empty() ? Filename : BuildPath, ErrorMessage); 86a75b2cacSAlex Lorenz if (!Compilations) { 87a75b2cacSAlex Lorenz llvm::errs() 88a75b2cacSAlex Lorenz << "Error while trying to load a compilation database, running " 89a75b2cacSAlex Lorenz "without flags.\n" 90a75b2cacSAlex Lorenz << ErrorMessage; 91849f20e4SJohannes Altmanninger Compilations = 92849f20e4SJohannes Altmanninger llvm::make_unique<clang::tooling::FixedCompilationDatabase>( 93a75b2cacSAlex Lorenz ".", std::vector<std::string>()); 94a75b2cacSAlex Lorenz } 95849f20e4SJohannes Altmanninger } 96849f20e4SJohannes Altmanninger addExtraArgs(Compilations); 97a75b2cacSAlex Lorenz std::array<std::string, 1> Files = {{Filename}}; 98849f20e4SJohannes Altmanninger ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files); 99a75b2cacSAlex Lorenz std::vector<std::unique_ptr<ASTUnit>> ASTs; 100a75b2cacSAlex Lorenz Tool.buildASTs(ASTs); 101a75b2cacSAlex Lorenz if (ASTs.size() != Files.size()) 102a75b2cacSAlex Lorenz return nullptr; 103a75b2cacSAlex Lorenz return std::move(ASTs[0]); 104a75b2cacSAlex Lorenz } 105a75b2cacSAlex Lorenz 1060da12c84SJohannes Altmanninger static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); } 1070da12c84SJohannes Altmanninger 1080da12c84SJohannes Altmanninger static void printJsonString(raw_ostream &OS, const StringRef Str) { 1090da12c84SJohannes Altmanninger for (char C : Str) { 1100da12c84SJohannes Altmanninger switch (C) { 1110da12c84SJohannes Altmanninger case '"': 1120da12c84SJohannes Altmanninger OS << R"(\")"; 1130da12c84SJohannes Altmanninger break; 1140da12c84SJohannes Altmanninger case '\\': 1150da12c84SJohannes Altmanninger OS << R"(\\)"; 1160da12c84SJohannes Altmanninger break; 1170da12c84SJohannes Altmanninger case '\n': 1180da12c84SJohannes Altmanninger OS << R"(\n)"; 1190da12c84SJohannes Altmanninger break; 1200da12c84SJohannes Altmanninger case '\t': 1210da12c84SJohannes Altmanninger OS << R"(\t)"; 1220da12c84SJohannes Altmanninger break; 1230da12c84SJohannes Altmanninger default: 1240da12c84SJohannes Altmanninger if ('\x00' <= C && C <= '\x1f') { 1250da12c84SJohannes Altmanninger OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C); 1260da12c84SJohannes Altmanninger } else { 1270da12c84SJohannes Altmanninger OS << C; 1280da12c84SJohannes Altmanninger } 1290da12c84SJohannes Altmanninger } 1300da12c84SJohannes Altmanninger } 1310da12c84SJohannes Altmanninger } 1320da12c84SJohannes Altmanninger 1330da12c84SJohannes Altmanninger static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree, 1340da12c84SJohannes Altmanninger diff::NodeId Id) { 1350da12c84SJohannes Altmanninger const diff::Node &N = Tree.getNode(Id); 1360da12c84SJohannes Altmanninger OS << R"("id":)" << int(Id); 1370da12c84SJohannes Altmanninger OS << R"(,"type":")" << N.getTypeLabel() << '"'; 1380da12c84SJohannes Altmanninger auto Offsets = Tree.getSourceRangeOffsets(N); 1390da12c84SJohannes Altmanninger OS << R"(,"begin":)" << Offsets.first; 1400da12c84SJohannes Altmanninger OS << R"(,"end":)" << Offsets.second; 141e0fe5cd4SJohannes Altmanninger std::string Value = Tree.getNodeValue(N); 1420da12c84SJohannes Altmanninger if (!Value.empty()) { 1430da12c84SJohannes Altmanninger OS << R"(,"value":")"; 1440da12c84SJohannes Altmanninger printJsonString(OS, Value); 1450da12c84SJohannes Altmanninger OS << '"'; 1460da12c84SJohannes Altmanninger } 1470da12c84SJohannes Altmanninger } 1480da12c84SJohannes Altmanninger 1490da12c84SJohannes Altmanninger static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree, 1500da12c84SJohannes Altmanninger diff::NodeId Id) { 1510da12c84SJohannes Altmanninger const diff::Node &N = Tree.getNode(Id); 1520da12c84SJohannes Altmanninger OS << "{"; 1530da12c84SJohannes Altmanninger printNodeAttributes(OS, Tree, Id); 1540da12c84SJohannes Altmanninger OS << R"(,"children":[)"; 1550da12c84SJohannes Altmanninger if (N.Children.size() > 0) { 1560da12c84SJohannes Altmanninger printNodeAsJson(OS, Tree, N.Children[0]); 1570da12c84SJohannes Altmanninger for (size_t I = 1, E = N.Children.size(); I < E; ++I) { 1580da12c84SJohannes Altmanninger OS << ","; 1590da12c84SJohannes Altmanninger printNodeAsJson(OS, Tree, N.Children[I]); 1600da12c84SJohannes Altmanninger } 1610da12c84SJohannes Altmanninger } 1620da12c84SJohannes Altmanninger OS << "]}"; 1630da12c84SJohannes Altmanninger } 1640da12c84SJohannes Altmanninger 165e0fe5cd4SJohannes Altmanninger static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree, 166e0fe5cd4SJohannes Altmanninger diff::NodeId Id) { 167e0fe5cd4SJohannes Altmanninger if (Id.isInvalid()) { 168e0fe5cd4SJohannes Altmanninger OS << "None"; 169e0fe5cd4SJohannes Altmanninger return; 170e0fe5cd4SJohannes Altmanninger } 171e0fe5cd4SJohannes Altmanninger OS << Tree.getNode(Id).getTypeLabel(); 172e0fe5cd4SJohannes Altmanninger std::string Value = Tree.getNodeValue(Id); 173e0fe5cd4SJohannes Altmanninger if (!Value.empty()) 174e0fe5cd4SJohannes Altmanninger OS << ": " << Value; 175e0fe5cd4SJohannes Altmanninger OS << "(" << Id << ")"; 176e0fe5cd4SJohannes Altmanninger } 177e0fe5cd4SJohannes Altmanninger 178a1d2b5d5SJohannes Altmanninger static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) { 179a1d2b5d5SJohannes Altmanninger for (diff::NodeId Id : Tree) { 180a1d2b5d5SJohannes Altmanninger for (int I = 0; I < Tree.getNode(Id).Depth; ++I) 181a1d2b5d5SJohannes Altmanninger OS << " "; 182a1d2b5d5SJohannes Altmanninger printNode(OS, Tree, Id); 183a1d2b5d5SJohannes Altmanninger OS << "\n"; 184a1d2b5d5SJohannes Altmanninger } 185a1d2b5d5SJohannes Altmanninger } 186a1d2b5d5SJohannes Altmanninger 187e0fe5cd4SJohannes Altmanninger static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff, 188e0fe5cd4SJohannes Altmanninger diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree, 189e0fe5cd4SJohannes Altmanninger diff::NodeId Dst) { 190e0fe5cd4SJohannes Altmanninger const diff::Node &DstNode = DstTree.getNode(Dst); 191e0fe5cd4SJohannes Altmanninger diff::NodeId Src = Diff.getMapped(DstTree, Dst); 192e0fe5cd4SJohannes Altmanninger switch (DstNode.Change) { 193e0fe5cd4SJohannes Altmanninger case diff::None: 194e0fe5cd4SJohannes Altmanninger break; 195e0fe5cd4SJohannes Altmanninger case diff::Delete: 196e0fe5cd4SJohannes Altmanninger llvm_unreachable("The destination tree can't have deletions."); 197e0fe5cd4SJohannes Altmanninger case diff::Update: 198e0fe5cd4SJohannes Altmanninger OS << "Update "; 199e0fe5cd4SJohannes Altmanninger printNode(OS, SrcTree, Src); 200e0fe5cd4SJohannes Altmanninger OS << " to " << DstTree.getNodeValue(Dst) << "\n"; 201e0fe5cd4SJohannes Altmanninger break; 202e0fe5cd4SJohannes Altmanninger case diff::Insert: 203e0fe5cd4SJohannes Altmanninger case diff::Move: 204e0fe5cd4SJohannes Altmanninger case diff::UpdateMove: 205e0fe5cd4SJohannes Altmanninger if (DstNode.Change == diff::Insert) 206e0fe5cd4SJohannes Altmanninger OS << "Insert"; 207e0fe5cd4SJohannes Altmanninger else if (DstNode.Change == diff::Move) 208e0fe5cd4SJohannes Altmanninger OS << "Move"; 209e0fe5cd4SJohannes Altmanninger else if (DstNode.Change == diff::UpdateMove) 210e0fe5cd4SJohannes Altmanninger OS << "Update and Move"; 211e0fe5cd4SJohannes Altmanninger OS << " "; 212e0fe5cd4SJohannes Altmanninger printNode(OS, DstTree, Dst); 213e0fe5cd4SJohannes Altmanninger OS << " into "; 214e0fe5cd4SJohannes Altmanninger printNode(OS, DstTree, DstNode.Parent); 215e0fe5cd4SJohannes Altmanninger OS << " at " << DstTree.findPositionInParent(Dst) << "\n"; 216e0fe5cd4SJohannes Altmanninger break; 217e0fe5cd4SJohannes Altmanninger } 218e0fe5cd4SJohannes Altmanninger } 219e0fe5cd4SJohannes Altmanninger 220a75b2cacSAlex Lorenz int main(int argc, const char **argv) { 221849f20e4SJohannes Altmanninger std::string ErrorMessage; 222849f20e4SJohannes Altmanninger std::unique_ptr<CompilationDatabase> CommonCompilations = 223849f20e4SJohannes Altmanninger FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); 224849f20e4SJohannes Altmanninger if (!CommonCompilations && !ErrorMessage.empty()) 225849f20e4SJohannes Altmanninger llvm::errs() << ErrorMessage; 226a75b2cacSAlex Lorenz cl::HideUnrelatedOptions(ClangDiffCategory); 227a75b2cacSAlex Lorenz if (!cl::ParseCommandLineOptions(argc, argv)) { 228a75b2cacSAlex Lorenz cl::PrintOptionValues(); 229a75b2cacSAlex Lorenz return 1; 230a75b2cacSAlex Lorenz } 231a75b2cacSAlex Lorenz 232849f20e4SJohannes Altmanninger addExtraArgs(CommonCompilations); 233849f20e4SJohannes Altmanninger 234a1d2b5d5SJohannes Altmanninger if (ASTDump || ASTDumpJson) { 235a75b2cacSAlex Lorenz if (!DestinationPath.empty()) { 236a75b2cacSAlex Lorenz llvm::errs() << "Error: Please specify exactly one filename.\n"; 237a75b2cacSAlex Lorenz return 1; 238a75b2cacSAlex Lorenz } 239849f20e4SJohannes Altmanninger std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath); 240a75b2cacSAlex Lorenz if (!AST) 241a75b2cacSAlex Lorenz return 1; 242a75b2cacSAlex Lorenz diff::SyntaxTree Tree(AST->getASTContext()); 243a1d2b5d5SJohannes Altmanninger if (ASTDump) { 244a1d2b5d5SJohannes Altmanninger printTree(llvm::outs(), Tree); 245a1d2b5d5SJohannes Altmanninger return 0; 246a1d2b5d5SJohannes Altmanninger } 2470da12c84SJohannes Altmanninger llvm::outs() << R"({"filename":")"; 2480da12c84SJohannes Altmanninger printJsonString(llvm::outs(), SourcePath); 2490da12c84SJohannes Altmanninger llvm::outs() << R"(","root":)"; 2500da12c84SJohannes Altmanninger printNodeAsJson(llvm::outs(), Tree, Tree.getRootId()); 2510da12c84SJohannes Altmanninger llvm::outs() << "}\n"; 252a75b2cacSAlex Lorenz return 0; 253a75b2cacSAlex Lorenz } 254a75b2cacSAlex Lorenz 255a75b2cacSAlex Lorenz if (DestinationPath.empty()) { 256a75b2cacSAlex Lorenz llvm::errs() << "Error: Exactly two paths are required.\n"; 257a75b2cacSAlex Lorenz return 1; 258a75b2cacSAlex Lorenz } 259a75b2cacSAlex Lorenz 260849f20e4SJohannes Altmanninger std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath); 261849f20e4SJohannes Altmanninger std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath); 262a75b2cacSAlex Lorenz if (!Src || !Dst) 263a75b2cacSAlex Lorenz return 1; 264a75b2cacSAlex Lorenz 265a75b2cacSAlex Lorenz diff::ComparisonOptions Options; 266849f20e4SJohannes Altmanninger if (MaxSize != -1) 267849f20e4SJohannes Altmanninger Options.MaxSize = MaxSize; 268a75b2cacSAlex Lorenz diff::SyntaxTree SrcTree(Src->getASTContext()); 269a75b2cacSAlex Lorenz diff::SyntaxTree DstTree(Dst->getASTContext()); 270e0fe5cd4SJohannes Altmanninger diff::ASTDiff Diff(SrcTree, DstTree, Options); 271e0fe5cd4SJohannes Altmanninger 272e0fe5cd4SJohannes Altmanninger for (diff::NodeId Dst : DstTree) { 273e0fe5cd4SJohannes Altmanninger diff::NodeId Src = Diff.getMapped(DstTree, Dst); 274*683876caSJohannes Altmanninger if (PrintMatches && Src.isValid()) { 275e0fe5cd4SJohannes Altmanninger llvm::outs() << "Match "; 276e0fe5cd4SJohannes Altmanninger printNode(llvm::outs(), SrcTree, Src); 277e0fe5cd4SJohannes Altmanninger llvm::outs() << " to "; 278e0fe5cd4SJohannes Altmanninger printNode(llvm::outs(), DstTree, Dst); 279e0fe5cd4SJohannes Altmanninger llvm::outs() << "\n"; 280e0fe5cd4SJohannes Altmanninger } 281e0fe5cd4SJohannes Altmanninger printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst); 282e0fe5cd4SJohannes Altmanninger } 283e0fe5cd4SJohannes Altmanninger for (diff::NodeId Src : SrcTree) { 284e0fe5cd4SJohannes Altmanninger if (Diff.getMapped(SrcTree, Src).isInvalid()) { 285e0fe5cd4SJohannes Altmanninger llvm::outs() << "Delete "; 286e0fe5cd4SJohannes Altmanninger printNode(llvm::outs(), SrcTree, Src); 287e0fe5cd4SJohannes Altmanninger llvm::outs() << "\n"; 288e0fe5cd4SJohannes Altmanninger } 289e0fe5cd4SJohannes Altmanninger } 290a75b2cacSAlex Lorenz 291a75b2cacSAlex Lorenz return 0; 292a75b2cacSAlex Lorenz } 293