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<bool> HtmlDiff("html", 41 cl::desc("Output a side-by-side diff in HTML."), 42 cl::init(false), cl::cat(ClangDiffCategory)); 43 44 static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"), 45 cl::Required, 46 cl::cat(ClangDiffCategory)); 47 48 static cl::opt<std::string> DestinationPath(cl::Positional, 49 cl::desc("<destination>"), 50 cl::Optional, 51 cl::cat(ClangDiffCategory)); 52 53 static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional, 54 cl::init(-1), cl::cat(ClangDiffCategory)); 55 56 static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""), 57 cl::Optional, cl::cat(ClangDiffCategory)); 58 59 static cl::list<std::string> ArgsAfter( 60 "extra-arg", 61 cl::desc("Additional argument to append to the compiler command line"), 62 cl::cat(ClangDiffCategory)); 63 64 static cl::list<std::string> ArgsBefore( 65 "extra-arg-before", 66 cl::desc("Additional argument to prepend to the compiler command line"), 67 cl::cat(ClangDiffCategory)); 68 69 static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) { 70 if (!Compilations) 71 return; 72 auto AdjustingCompilations = 73 llvm::make_unique<ArgumentsAdjustingCompilations>( 74 std::move(Compilations)); 75 AdjustingCompilations->appendArgumentsAdjuster( 76 getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN)); 77 AdjustingCompilations->appendArgumentsAdjuster( 78 getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); 79 Compilations = std::move(AdjustingCompilations); 80 } 81 82 static std::unique_ptr<ASTUnit> 83 getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations, 84 const StringRef Filename) { 85 std::string ErrorMessage; 86 std::unique_ptr<CompilationDatabase> Compilations; 87 if (!CommonCompilations) { 88 Compilations = CompilationDatabase::autoDetectFromSource( 89 BuildPath.empty() ? Filename : BuildPath, ErrorMessage); 90 if (!Compilations) { 91 llvm::errs() 92 << "Error while trying to load a compilation database, running " 93 "without flags.\n" 94 << ErrorMessage; 95 Compilations = 96 llvm::make_unique<clang::tooling::FixedCompilationDatabase>( 97 ".", std::vector<std::string>()); 98 } 99 } 100 addExtraArgs(Compilations); 101 std::array<std::string, 1> Files = {{Filename}}; 102 ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files); 103 std::vector<std::unique_ptr<ASTUnit>> ASTs; 104 Tool.buildASTs(ASTs); 105 if (ASTs.size() != Files.size()) 106 return nullptr; 107 return std::move(ASTs[0]); 108 } 109 110 static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); } 111 112 static const char HtmlDiffHeader[] = R"( 113 <html> 114 <head> 115 <meta charset='utf-8'/> 116 <style> 117 span.d { color: red; } 118 span.u { color: #cc00cc; } 119 span.i { color: green; } 120 span.m { font-weight: bold; } 121 span { font-weight: normal; color: black; } 122 div.code { 123 width: 48%; 124 height: 98%; 125 overflow: scroll; 126 float: left; 127 padding: 0 0 0.5% 0.5%; 128 border: solid 2px LightGrey; 129 border-radius: 5px; 130 } 131 </style> 132 </head> 133 <script type='text/javascript'> 134 highlightStack = [] 135 function clearHighlight() { 136 while (highlightStack.length) { 137 let [l, r] = highlightStack.pop() 138 document.getElementById(l).style.backgroundColor = 'white' 139 document.getElementById(r).style.backgroundColor = 'white' 140 } 141 } 142 function highlight(event) { 143 id = event.target['id'] 144 doHighlight(id) 145 } 146 function doHighlight(id) { 147 clearHighlight() 148 source = document.getElementById(id) 149 if (!source.attributes['tid']) 150 return 151 tid = source.attributes['tid'].value 152 target = document.getElementById(tid) 153 if (!target || source.parentElement && source.parentElement.classList.contains('code')) 154 return 155 source.style.backgroundColor = target.style.backgroundColor = 'lightgrey' 156 highlightStack.push([id, tid]) 157 source.scrollIntoView() 158 target.scrollIntoView() 159 location.hash = '#' + id 160 } 161 function scrollToBoth() { 162 doHighlight(location.hash.substr(1)) 163 } 164 window.onload = scrollToBoth 165 </script> 166 <body> 167 <div onclick='highlight(event)'> 168 )"; 169 170 static void printHtml(raw_ostream &OS, char C) { 171 switch (C) { 172 case '&': 173 OS << "&"; 174 break; 175 case '<': 176 OS << "<"; 177 break; 178 case '>': 179 OS << ">"; 180 break; 181 case '\'': 182 OS << "'"; 183 break; 184 case '"': 185 OS << """; 186 break; 187 default: 188 OS << C; 189 } 190 } 191 192 static void printHtml(raw_ostream &OS, const StringRef Str) { 193 for (char C : Str) 194 printHtml(OS, C); 195 } 196 197 static std::string getChangeKindAbbr(diff::ChangeKind Kind) { 198 switch (Kind) { 199 case diff::None: 200 return ""; 201 case diff::Delete: 202 return "d"; 203 case diff::Update: 204 return "u"; 205 case diff::Insert: 206 return "i"; 207 case diff::Move: 208 return "m"; 209 case diff::UpdateMove: 210 return "u m"; 211 } 212 } 213 214 static unsigned printHtmlForNode(raw_ostream &OS, const diff::ASTDiff &Diff, 215 diff::SyntaxTree &Tree, bool IsLeft, 216 diff::NodeId Id, unsigned Offset) { 217 const diff::Node &Node = Tree.getNode(Id); 218 char MyTag, OtherTag; 219 diff::NodeId LeftId, RightId; 220 diff::NodeId TargetId = Diff.getMapped(Tree, Id); 221 if (IsLeft) { 222 MyTag = 'L'; 223 OtherTag = 'R'; 224 LeftId = Id; 225 RightId = TargetId; 226 } else { 227 MyTag = 'R'; 228 OtherTag = 'L'; 229 LeftId = TargetId; 230 RightId = Id; 231 } 232 unsigned Begin, End; 233 std::tie(Begin, End) = Tree.getSourceRangeOffsets(Node); 234 const SourceManager &SrcMgr = Tree.getASTContext().getSourceManager(); 235 auto Code = SrcMgr.getBuffer(SrcMgr.getMainFileID())->getBuffer(); 236 for (; Offset < Begin; ++Offset) 237 printHtml(OS, Code[Offset]); 238 OS << "<span id='" << MyTag << Id << "' " 239 << "tid='" << OtherTag << TargetId << "' "; 240 OS << "title='"; 241 printHtml(OS, Node.getTypeLabel()); 242 OS << "\n" << LeftId << " -> " << RightId; 243 std::string Value = Tree.getNodeValue(Node); 244 if (!Value.empty()) { 245 OS << "\n"; 246 printHtml(OS, Value); 247 } 248 OS << "'"; 249 if (Node.Change != diff::None) 250 OS << " class='" << getChangeKindAbbr(Node.Change) << "'"; 251 OS << ">"; 252 253 for (diff::NodeId Child : Node.Children) 254 Offset = printHtmlForNode(OS, Diff, Tree, IsLeft, Child, Offset); 255 256 for (; Offset < End; ++Offset) 257 printHtml(OS, Code[Offset]); 258 if (Id == Tree.getRootId()) { 259 End = Code.size(); 260 for (; Offset < End; ++Offset) 261 printHtml(OS, Code[Offset]); 262 } 263 OS << "</span>"; 264 return Offset; 265 } 266 267 static void printJsonString(raw_ostream &OS, const StringRef Str) { 268 for (signed char C : Str) { 269 switch (C) { 270 case '"': 271 OS << R"(\")"; 272 break; 273 case '\\': 274 OS << R"(\\)"; 275 break; 276 case '\n': 277 OS << R"(\n)"; 278 break; 279 case '\t': 280 OS << R"(\t)"; 281 break; 282 default: 283 if ('\x00' <= C && C <= '\x1f') { 284 OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C); 285 } else { 286 OS << C; 287 } 288 } 289 } 290 } 291 292 static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree, 293 diff::NodeId Id) { 294 const diff::Node &N = Tree.getNode(Id); 295 OS << R"("id":)" << int(Id); 296 OS << R"(,"type":")" << N.getTypeLabel() << '"'; 297 auto Offsets = Tree.getSourceRangeOffsets(N); 298 OS << R"(,"begin":)" << Offsets.first; 299 OS << R"(,"end":)" << Offsets.second; 300 std::string Value = Tree.getNodeValue(N); 301 if (!Value.empty()) { 302 OS << R"(,"value":")"; 303 printJsonString(OS, Value); 304 OS << '"'; 305 } 306 } 307 308 static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree, 309 diff::NodeId Id) { 310 const diff::Node &N = Tree.getNode(Id); 311 OS << "{"; 312 printNodeAttributes(OS, Tree, Id); 313 OS << R"(,"children":[)"; 314 if (N.Children.size() > 0) { 315 printNodeAsJson(OS, Tree, N.Children[0]); 316 for (size_t I = 1, E = N.Children.size(); I < E; ++I) { 317 OS << ","; 318 printNodeAsJson(OS, Tree, N.Children[I]); 319 } 320 } 321 OS << "]}"; 322 } 323 324 static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree, 325 diff::NodeId Id) { 326 if (Id.isInvalid()) { 327 OS << "None"; 328 return; 329 } 330 OS << Tree.getNode(Id).getTypeLabel(); 331 std::string Value = Tree.getNodeValue(Id); 332 if (!Value.empty()) 333 OS << ": " << Value; 334 OS << "(" << Id << ")"; 335 } 336 337 static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) { 338 for (diff::NodeId Id : Tree) { 339 for (int I = 0; I < Tree.getNode(Id).Depth; ++I) 340 OS << " "; 341 printNode(OS, Tree, Id); 342 OS << "\n"; 343 } 344 } 345 346 static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff, 347 diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree, 348 diff::NodeId Dst) { 349 const diff::Node &DstNode = DstTree.getNode(Dst); 350 diff::NodeId Src = Diff.getMapped(DstTree, Dst); 351 switch (DstNode.Change) { 352 case diff::None: 353 break; 354 case diff::Delete: 355 llvm_unreachable("The destination tree can't have deletions."); 356 case diff::Update: 357 OS << "Update "; 358 printNode(OS, SrcTree, Src); 359 OS << " to " << DstTree.getNodeValue(Dst) << "\n"; 360 break; 361 case diff::Insert: 362 case diff::Move: 363 case diff::UpdateMove: 364 if (DstNode.Change == diff::Insert) 365 OS << "Insert"; 366 else if (DstNode.Change == diff::Move) 367 OS << "Move"; 368 else if (DstNode.Change == diff::UpdateMove) 369 OS << "Update and Move"; 370 OS << " "; 371 printNode(OS, DstTree, Dst); 372 OS << " into "; 373 printNode(OS, DstTree, DstNode.Parent); 374 OS << " at " << DstTree.findPositionInParent(Dst) << "\n"; 375 break; 376 } 377 } 378 379 int main(int argc, const char **argv) { 380 std::string ErrorMessage; 381 std::unique_ptr<CompilationDatabase> CommonCompilations = 382 FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); 383 if (!CommonCompilations && !ErrorMessage.empty()) 384 llvm::errs() << ErrorMessage; 385 cl::HideUnrelatedOptions(ClangDiffCategory); 386 if (!cl::ParseCommandLineOptions(argc, argv)) { 387 cl::PrintOptionValues(); 388 return 1; 389 } 390 391 addExtraArgs(CommonCompilations); 392 393 if (ASTDump || ASTDumpJson) { 394 if (!DestinationPath.empty()) { 395 llvm::errs() << "Error: Please specify exactly one filename.\n"; 396 return 1; 397 } 398 std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath); 399 if (!AST) 400 return 1; 401 diff::SyntaxTree Tree(AST->getASTContext()); 402 if (ASTDump) { 403 printTree(llvm::outs(), Tree); 404 return 0; 405 } 406 llvm::outs() << R"({"filename":")"; 407 printJsonString(llvm::outs(), SourcePath); 408 llvm::outs() << R"(","root":)"; 409 printNodeAsJson(llvm::outs(), Tree, Tree.getRootId()); 410 llvm::outs() << "}\n"; 411 return 0; 412 } 413 414 if (DestinationPath.empty()) { 415 llvm::errs() << "Error: Exactly two paths are required.\n"; 416 return 1; 417 } 418 419 std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath); 420 std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath); 421 if (!Src || !Dst) 422 return 1; 423 424 diff::ComparisonOptions Options; 425 if (MaxSize != -1) 426 Options.MaxSize = MaxSize; 427 diff::SyntaxTree SrcTree(Src->getASTContext()); 428 diff::SyntaxTree DstTree(Dst->getASTContext()); 429 diff::ASTDiff Diff(SrcTree, DstTree, Options); 430 431 if (HtmlDiff) { 432 llvm::outs() << HtmlDiffHeader << "<pre>"; 433 llvm::outs() << "<div id='L' class='code'>"; 434 printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0); 435 llvm::outs() << "</div>"; 436 llvm::outs() << "<div id='R' class='code'>"; 437 printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(), 438 0); 439 llvm::outs() << "</div>"; 440 llvm::outs() << "</pre></div></body></html>\n"; 441 return 0; 442 } 443 444 for (diff::NodeId Dst : DstTree) { 445 diff::NodeId Src = Diff.getMapped(DstTree, Dst); 446 if (PrintMatches && Src.isValid()) { 447 llvm::outs() << "Match "; 448 printNode(llvm::outs(), SrcTree, Src); 449 llvm::outs() << " to "; 450 printNode(llvm::outs(), DstTree, Dst); 451 llvm::outs() << "\n"; 452 } 453 printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst); 454 } 455 for (diff::NodeId Src : SrcTree) { 456 if (Diff.getMapped(SrcTree, Src).isInvalid()) { 457 llvm::outs() << "Delete "; 458 printNode(llvm::outs(), SrcTree, Src); 459 llvm::outs() << "\n"; 460 } 461 } 462 463 return 0; 464 } 465