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 llvm_unreachable("Invalid enumeration value."); 213 } 214 215 static unsigned printHtmlForNode(raw_ostream &OS, const diff::ASTDiff &Diff, 216 diff::SyntaxTree &Tree, bool IsLeft, 217 diff::NodeId Id, unsigned Offset) { 218 const diff::Node &Node = Tree.getNode(Id); 219 char MyTag, OtherTag; 220 diff::NodeId LeftId, RightId; 221 diff::NodeId TargetId = Diff.getMapped(Tree, Id); 222 if (IsLeft) { 223 MyTag = 'L'; 224 OtherTag = 'R'; 225 LeftId = Id; 226 RightId = TargetId; 227 } else { 228 MyTag = 'R'; 229 OtherTag = 'L'; 230 LeftId = TargetId; 231 RightId = Id; 232 } 233 unsigned Begin, End; 234 std::tie(Begin, End) = Tree.getSourceRangeOffsets(Node); 235 const SourceManager &SrcMgr = Tree.getASTContext().getSourceManager(); 236 auto Code = SrcMgr.getBuffer(SrcMgr.getMainFileID())->getBuffer(); 237 for (; Offset < Begin; ++Offset) 238 printHtml(OS, Code[Offset]); 239 OS << "<span id='" << MyTag << Id << "' " 240 << "tid='" << OtherTag << TargetId << "' "; 241 OS << "title='"; 242 printHtml(OS, Node.getTypeLabel()); 243 OS << "\n" << LeftId << " -> " << RightId; 244 std::string Value = Tree.getNodeValue(Node); 245 if (!Value.empty()) { 246 OS << "\n"; 247 printHtml(OS, Value); 248 } 249 OS << "'"; 250 if (Node.Change != diff::None) 251 OS << " class='" << getChangeKindAbbr(Node.Change) << "'"; 252 OS << ">"; 253 254 for (diff::NodeId Child : Node.Children) 255 Offset = printHtmlForNode(OS, Diff, Tree, IsLeft, Child, Offset); 256 257 for (; Offset < End; ++Offset) 258 printHtml(OS, Code[Offset]); 259 if (Id == Tree.getRootId()) { 260 End = Code.size(); 261 for (; Offset < End; ++Offset) 262 printHtml(OS, Code[Offset]); 263 } 264 OS << "</span>"; 265 return Offset; 266 } 267 268 static void printJsonString(raw_ostream &OS, const StringRef Str) { 269 for (signed char C : Str) { 270 switch (C) { 271 case '"': 272 OS << R"(\")"; 273 break; 274 case '\\': 275 OS << R"(\\)"; 276 break; 277 case '\n': 278 OS << R"(\n)"; 279 break; 280 case '\t': 281 OS << R"(\t)"; 282 break; 283 default: 284 if ('\x00' <= C && C <= '\x1f') { 285 OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C); 286 } else { 287 OS << C; 288 } 289 } 290 } 291 } 292 293 static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree, 294 diff::NodeId Id) { 295 const diff::Node &N = Tree.getNode(Id); 296 OS << R"("id":)" << int(Id); 297 OS << R"(,"type":")" << N.getTypeLabel() << '"'; 298 auto Offsets = Tree.getSourceRangeOffsets(N); 299 OS << R"(,"begin":)" << Offsets.first; 300 OS << R"(,"end":)" << Offsets.second; 301 std::string Value = Tree.getNodeValue(N); 302 if (!Value.empty()) { 303 OS << R"(,"value":")"; 304 printJsonString(OS, Value); 305 OS << '"'; 306 } 307 } 308 309 static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree, 310 diff::NodeId Id) { 311 const diff::Node &N = Tree.getNode(Id); 312 OS << "{"; 313 printNodeAttributes(OS, Tree, Id); 314 OS << R"(,"children":[)"; 315 if (N.Children.size() > 0) { 316 printNodeAsJson(OS, Tree, N.Children[0]); 317 for (size_t I = 1, E = N.Children.size(); I < E; ++I) { 318 OS << ","; 319 printNodeAsJson(OS, Tree, N.Children[I]); 320 } 321 } 322 OS << "]}"; 323 } 324 325 static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree, 326 diff::NodeId Id) { 327 if (Id.isInvalid()) { 328 OS << "None"; 329 return; 330 } 331 OS << Tree.getNode(Id).getTypeLabel(); 332 std::string Value = Tree.getNodeValue(Id); 333 if (!Value.empty()) 334 OS << ": " << Value; 335 OS << "(" << Id << ")"; 336 } 337 338 static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) { 339 for (diff::NodeId Id : Tree) { 340 for (int I = 0; I < Tree.getNode(Id).Depth; ++I) 341 OS << " "; 342 printNode(OS, Tree, Id); 343 OS << "\n"; 344 } 345 } 346 347 static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff, 348 diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree, 349 diff::NodeId Dst) { 350 const diff::Node &DstNode = DstTree.getNode(Dst); 351 diff::NodeId Src = Diff.getMapped(DstTree, Dst); 352 switch (DstNode.Change) { 353 case diff::None: 354 break; 355 case diff::Delete: 356 llvm_unreachable("The destination tree can't have deletions."); 357 case diff::Update: 358 OS << "Update "; 359 printNode(OS, SrcTree, Src); 360 OS << " to " << DstTree.getNodeValue(Dst) << "\n"; 361 break; 362 case diff::Insert: 363 case diff::Move: 364 case diff::UpdateMove: 365 if (DstNode.Change == diff::Insert) 366 OS << "Insert"; 367 else if (DstNode.Change == diff::Move) 368 OS << "Move"; 369 else if (DstNode.Change == diff::UpdateMove) 370 OS << "Update and Move"; 371 OS << " "; 372 printNode(OS, DstTree, Dst); 373 OS << " into "; 374 printNode(OS, DstTree, DstNode.Parent); 375 OS << " at " << DstTree.findPositionInParent(Dst) << "\n"; 376 break; 377 } 378 } 379 380 int main(int argc, const char **argv) { 381 std::string ErrorMessage; 382 std::unique_ptr<CompilationDatabase> CommonCompilations = 383 FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); 384 if (!CommonCompilations && !ErrorMessage.empty()) 385 llvm::errs() << ErrorMessage; 386 cl::HideUnrelatedOptions(ClangDiffCategory); 387 if (!cl::ParseCommandLineOptions(argc, argv)) { 388 cl::PrintOptionValues(); 389 return 1; 390 } 391 392 addExtraArgs(CommonCompilations); 393 394 if (ASTDump || ASTDumpJson) { 395 if (!DestinationPath.empty()) { 396 llvm::errs() << "Error: Please specify exactly one filename.\n"; 397 return 1; 398 } 399 std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath); 400 if (!AST) 401 return 1; 402 diff::SyntaxTree Tree(AST->getASTContext()); 403 if (ASTDump) { 404 printTree(llvm::outs(), Tree); 405 return 0; 406 } 407 llvm::outs() << R"({"filename":")"; 408 printJsonString(llvm::outs(), SourcePath); 409 llvm::outs() << R"(","root":)"; 410 printNodeAsJson(llvm::outs(), Tree, Tree.getRootId()); 411 llvm::outs() << "}\n"; 412 return 0; 413 } 414 415 if (DestinationPath.empty()) { 416 llvm::errs() << "Error: Exactly two paths are required.\n"; 417 return 1; 418 } 419 420 std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath); 421 std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath); 422 if (!Src || !Dst) 423 return 1; 424 425 diff::ComparisonOptions Options; 426 if (MaxSize != -1) 427 Options.MaxSize = MaxSize; 428 diff::SyntaxTree SrcTree(Src->getASTContext()); 429 diff::SyntaxTree DstTree(Dst->getASTContext()); 430 diff::ASTDiff Diff(SrcTree, DstTree, Options); 431 432 if (HtmlDiff) { 433 llvm::outs() << HtmlDiffHeader << "<pre>"; 434 llvm::outs() << "<div id='L' class='code'>"; 435 printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0); 436 llvm::outs() << "</div>"; 437 llvm::outs() << "<div id='R' class='code'>"; 438 printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(), 439 0); 440 llvm::outs() << "</div>"; 441 llvm::outs() << "</pre></div></body></html>\n"; 442 return 0; 443 } 444 445 for (diff::NodeId Dst : DstTree) { 446 diff::NodeId Src = Diff.getMapped(DstTree, Dst); 447 if (PrintMatches && Src.isValid()) { 448 llvm::outs() << "Match "; 449 printNode(llvm::outs(), SrcTree, Src); 450 llvm::outs() << " to "; 451 printNode(llvm::outs(), DstTree, Dst); 452 llvm::outs() << "\n"; 453 } 454 printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst); 455 } 456 for (diff::NodeId Src : SrcTree) { 457 if (Diff.getMapped(SrcTree, Src).isInvalid()) { 458 llvm::outs() << "Delete "; 459 printNode(llvm::outs(), SrcTree, Src); 460 llvm::outs() << "\n"; 461 } 462 } 463 464 return 0; 465 } 466