1 //===- ClangDiff.cpp - compare source files by AST nodes ------*- C++ -*- -===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This file implements a tool for syntax tree based comparison using 11 // Tooling/ASTDiff. 12 // 13 //===----------------------------------------------------------------------===// 14 15 #include "clang/Tooling/ASTDiff/ASTDiff.h" 16 #include "clang/Tooling/CommonOptionsParser.h" 17 #include "clang/Tooling/Tooling.h" 18 #include "llvm/Support/CommandLine.h" 19 20 using namespace llvm; 21 using namespace clang; 22 using namespace clang::tooling; 23 24 static cl::OptionCategory ClangDiffCategory("clang-diff options"); 25 26 static cl::opt<bool> 27 ASTDump("ast-dump", 28 cl::desc("Print the internal representation of the AST."), 29 cl::init(false), cl::cat(ClangDiffCategory)); 30 31 static cl::opt<bool> ASTDumpJson( 32 "ast-dump-json", 33 cl::desc("Print the internal representation of the AST as JSON."), 34 cl::init(false), cl::cat(ClangDiffCategory)); 35 36 static cl::opt<bool> 37 PrintMatches("dump-matches", cl::desc("Print the matched nodes."), 38 cl::init(false), cl::cat(ClangDiffCategory)); 39 40 static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"), 41 cl::Required, 42 cl::cat(ClangDiffCategory)); 43 44 static cl::opt<std::string> DestinationPath(cl::Positional, 45 cl::desc("<destination>"), 46 cl::Optional, 47 cl::cat(ClangDiffCategory)); 48 49 static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional, 50 cl::init(-1), cl::cat(ClangDiffCategory)); 51 52 static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""), 53 cl::Optional, cl::cat(ClangDiffCategory)); 54 55 static cl::list<std::string> ArgsAfter( 56 "extra-arg", 57 cl::desc("Additional argument to append to the compiler command line"), 58 cl::cat(ClangDiffCategory)); 59 60 static cl::list<std::string> ArgsBefore( 61 "extra-arg-before", 62 cl::desc("Additional argument to prepend to the compiler command line"), 63 cl::cat(ClangDiffCategory)); 64 65 static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) { 66 if (!Compilations) 67 return; 68 auto AdjustingCompilations = 69 llvm::make_unique<ArgumentsAdjustingCompilations>( 70 std::move(Compilations)); 71 AdjustingCompilations->appendArgumentsAdjuster( 72 getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN)); 73 AdjustingCompilations->appendArgumentsAdjuster( 74 getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); 75 Compilations = std::move(AdjustingCompilations); 76 } 77 78 static std::unique_ptr<ASTUnit> 79 getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations, 80 const StringRef Filename) { 81 std::string ErrorMessage; 82 std::unique_ptr<CompilationDatabase> Compilations; 83 if (!CommonCompilations) { 84 Compilations = CompilationDatabase::autoDetectFromSource( 85 BuildPath.empty() ? Filename : BuildPath, ErrorMessage); 86 if (!Compilations) { 87 llvm::errs() 88 << "Error while trying to load a compilation database, running " 89 "without flags.\n" 90 << ErrorMessage; 91 Compilations = 92 llvm::make_unique<clang::tooling::FixedCompilationDatabase>( 93 ".", std::vector<std::string>()); 94 } 95 } 96 addExtraArgs(Compilations); 97 std::array<std::string, 1> Files = {{Filename}}; 98 ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files); 99 std::vector<std::unique_ptr<ASTUnit>> ASTs; 100 Tool.buildASTs(ASTs); 101 if (ASTs.size() != Files.size()) 102 return nullptr; 103 return std::move(ASTs[0]); 104 } 105 106 static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); } 107 108 static void printJsonString(raw_ostream &OS, const StringRef Str) { 109 for (char C : Str) { 110 switch (C) { 111 case '"': 112 OS << R"(\")"; 113 break; 114 case '\\': 115 OS << R"(\\)"; 116 break; 117 case '\n': 118 OS << R"(\n)"; 119 break; 120 case '\t': 121 OS << R"(\t)"; 122 break; 123 default: 124 if ('\x00' <= C && C <= '\x1f') { 125 OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C); 126 } else { 127 OS << C; 128 } 129 } 130 } 131 } 132 133 static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree, 134 diff::NodeId Id) { 135 const diff::Node &N = Tree.getNode(Id); 136 OS << R"("id":)" << int(Id); 137 OS << R"(,"type":")" << N.getTypeLabel() << '"'; 138 auto Offsets = Tree.getSourceRangeOffsets(N); 139 OS << R"(,"begin":)" << Offsets.first; 140 OS << R"(,"end":)" << Offsets.second; 141 std::string Value = Tree.getNodeValue(N); 142 if (!Value.empty()) { 143 OS << R"(,"value":")"; 144 printJsonString(OS, Value); 145 OS << '"'; 146 } 147 } 148 149 static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree, 150 diff::NodeId Id) { 151 const diff::Node &N = Tree.getNode(Id); 152 OS << "{"; 153 printNodeAttributes(OS, Tree, Id); 154 OS << R"(,"children":[)"; 155 if (N.Children.size() > 0) { 156 printNodeAsJson(OS, Tree, N.Children[0]); 157 for (size_t I = 1, E = N.Children.size(); I < E; ++I) { 158 OS << ","; 159 printNodeAsJson(OS, Tree, N.Children[I]); 160 } 161 } 162 OS << "]}"; 163 } 164 165 static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree, 166 diff::NodeId Id) { 167 if (Id.isInvalid()) { 168 OS << "None"; 169 return; 170 } 171 OS << Tree.getNode(Id).getTypeLabel(); 172 std::string Value = Tree.getNodeValue(Id); 173 if (!Value.empty()) 174 OS << ": " << Value; 175 OS << "(" << Id << ")"; 176 } 177 178 static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) { 179 for (diff::NodeId Id : Tree) { 180 for (int I = 0; I < Tree.getNode(Id).Depth; ++I) 181 OS << " "; 182 printNode(OS, Tree, Id); 183 OS << "\n"; 184 } 185 } 186 187 static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff, 188 diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree, 189 diff::NodeId Dst) { 190 const diff::Node &DstNode = DstTree.getNode(Dst); 191 diff::NodeId Src = Diff.getMapped(DstTree, Dst); 192 switch (DstNode.Change) { 193 case diff::None: 194 break; 195 case diff::Delete: 196 llvm_unreachable("The destination tree can't have deletions."); 197 case diff::Update: 198 OS << "Update "; 199 printNode(OS, SrcTree, Src); 200 OS << " to " << DstTree.getNodeValue(Dst) << "\n"; 201 break; 202 case diff::Insert: 203 case diff::Move: 204 case diff::UpdateMove: 205 if (DstNode.Change == diff::Insert) 206 OS << "Insert"; 207 else if (DstNode.Change == diff::Move) 208 OS << "Move"; 209 else if (DstNode.Change == diff::UpdateMove) 210 OS << "Update and Move"; 211 OS << " "; 212 printNode(OS, DstTree, Dst); 213 OS << " into "; 214 printNode(OS, DstTree, DstNode.Parent); 215 OS << " at " << DstTree.findPositionInParent(Dst) << "\n"; 216 break; 217 } 218 } 219 220 int main(int argc, const char **argv) { 221 std::string ErrorMessage; 222 std::unique_ptr<CompilationDatabase> CommonCompilations = 223 FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); 224 if (!CommonCompilations && !ErrorMessage.empty()) 225 llvm::errs() << ErrorMessage; 226 cl::HideUnrelatedOptions(ClangDiffCategory); 227 if (!cl::ParseCommandLineOptions(argc, argv)) { 228 cl::PrintOptionValues(); 229 return 1; 230 } 231 232 addExtraArgs(CommonCompilations); 233 234 if (ASTDump || ASTDumpJson) { 235 if (!DestinationPath.empty()) { 236 llvm::errs() << "Error: Please specify exactly one filename.\n"; 237 return 1; 238 } 239 std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath); 240 if (!AST) 241 return 1; 242 diff::SyntaxTree Tree(AST->getASTContext()); 243 if (ASTDump) { 244 printTree(llvm::outs(), Tree); 245 return 0; 246 } 247 llvm::outs() << R"({"filename":")"; 248 printJsonString(llvm::outs(), SourcePath); 249 llvm::outs() << R"(","root":)"; 250 printNodeAsJson(llvm::outs(), Tree, Tree.getRootId()); 251 llvm::outs() << "}\n"; 252 return 0; 253 } 254 255 if (DestinationPath.empty()) { 256 llvm::errs() << "Error: Exactly two paths are required.\n"; 257 return 1; 258 } 259 260 std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath); 261 std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath); 262 if (!Src || !Dst) 263 return 1; 264 265 diff::ComparisonOptions Options; 266 if (MaxSize != -1) 267 Options.MaxSize = MaxSize; 268 diff::SyntaxTree SrcTree(Src->getASTContext()); 269 diff::SyntaxTree DstTree(Dst->getASTContext()); 270 diff::ASTDiff Diff(SrcTree, DstTree, Options); 271 272 for (diff::NodeId Dst : DstTree) { 273 diff::NodeId Src = Diff.getMapped(DstTree, Dst); 274 if (PrintMatches && Src.isValid()) { 275 llvm::outs() << "Match "; 276 printNode(llvm::outs(), SrcTree, Src); 277 llvm::outs() << " to "; 278 printNode(llvm::outs(), DstTree, Dst); 279 llvm::outs() << "\n"; 280 } 281 printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst); 282 } 283 for (diff::NodeId Src : SrcTree) { 284 if (Diff.getMapped(SrcTree, Src).isInvalid()) { 285 llvm::outs() << "Delete "; 286 printNode(llvm::outs(), SrcTree, Src); 287 llvm::outs() << "\n"; 288 } 289 } 290 291 return 0; 292 } 293