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