xref: /llvm-project/clang/tools/clang-diff/ClangDiff.cpp (revision 46307d7350fb95734cb5a43f5e8cd584b6be574f)
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     let [l, r] = highlightStack.pop()
143     document.getElementById(l).style.backgroundColor = 'white'
144     document.getElementById(r).style.backgroundColor = 'white'
145   }
146 }
147 function highlight(event) {
148   id = event.target['id']
149   doHighlight(id)
150 }
151 function doHighlight(id) {
152   clearHighlight()
153   source = document.getElementById(id)
154   if (!source.attributes['tid'])
155     return
156   tid = source.attributes['tid'].value
157   target = document.getElementById(tid)
158   if (!target || source.parentElement && source.parentElement.classList.contains('code'))
159     return
160   source.style.backgroundColor = target.style.backgroundColor = 'lightgrey'
161   highlightStack.push([id, tid])
162   source.scrollIntoView()
163   target.scrollIntoView()
164   location.hash = '#' + id
165 }
166 function scrollToBoth() {
167   doHighlight(location.hash.substr(1))
168 }
169 window.onload = scrollToBoth
170 </script>
171 <body>
172 <div onclick='highlight(event)'>
173 )";
174 
175 static void printHtml(raw_ostream &OS, char C) {
176   switch (C) {
177   case '&':
178     OS << "&amp;";
179     break;
180   case '<':
181     OS << "&lt;";
182     break;
183   case '>':
184     OS << "&gt;";
185     break;
186   case '\'':
187     OS << "&#x27;";
188     break;
189   case '"':
190     OS << "&quot;";
191     break;
192   default:
193     OS << C;
194   }
195 }
196 
197 static void printHtml(raw_ostream &OS, const StringRef Str) {
198   for (char C : Str)
199     printHtml(OS, C);
200 }
201 
202 static std::string getChangeKindAbbr(diff::ChangeKind Kind) {
203   switch (Kind) {
204   case diff::None:
205     return "";
206   case diff::Delete:
207     return "d";
208   case diff::Update:
209     return "u";
210   case diff::Insert:
211     return "i";
212   case diff::Move:
213     return "m";
214   case diff::UpdateMove:
215     return "u m";
216   }
217   llvm_unreachable("Invalid enumeration value.");
218 }
219 
220 static unsigned printHtmlForNode(raw_ostream &OS, const diff::ASTDiff &Diff,
221                                  diff::SyntaxTree &Tree, bool IsLeft,
222                                  diff::NodeId Id, unsigned Offset) {
223   const diff::Node &Node = Tree.getNode(Id);
224   char MyTag, OtherTag;
225   diff::NodeId LeftId, RightId;
226   diff::NodeId TargetId = Diff.getMapped(Tree, Id);
227   if (IsLeft) {
228     MyTag = 'L';
229     OtherTag = 'R';
230     LeftId = Id;
231     RightId = TargetId;
232   } else {
233     MyTag = 'R';
234     OtherTag = 'L';
235     LeftId = TargetId;
236     RightId = Id;
237   }
238   unsigned Begin, End;
239   std::tie(Begin, End) = Tree.getSourceRangeOffsets(Node);
240   const SourceManager &SrcMgr = Tree.getASTContext().getSourceManager();
241   auto Code = SrcMgr.getBuffer(SrcMgr.getMainFileID())->getBuffer();
242   for (; Offset < Begin; ++Offset)
243     printHtml(OS, Code[Offset]);
244   OS << "<span id='" << MyTag << Id << "' "
245      << "tid='" << OtherTag << TargetId << "' ";
246   OS << "title='";
247   printHtml(OS, Node.getTypeLabel());
248   OS << "\n" << LeftId << " -> " << RightId;
249   std::string Value = Tree.getNodeValue(Node);
250   if (!Value.empty()) {
251     OS << "\n";
252     printHtml(OS, Value);
253   }
254   OS << "'";
255   if (Node.Change != diff::None)
256     OS << " class='" << getChangeKindAbbr(Node.Change) << "'";
257   OS << ">";
258 
259   for (diff::NodeId Child : Node.Children)
260     Offset = printHtmlForNode(OS, Diff, Tree, IsLeft, Child, Offset);
261 
262   for (; Offset < End; ++Offset)
263     printHtml(OS, Code[Offset]);
264   if (Id == Tree.getRootId()) {
265     End = Code.size();
266     for (; Offset < End; ++Offset)
267       printHtml(OS, Code[Offset]);
268   }
269   OS << "</span>";
270   return Offset;
271 }
272 
273 static void printJsonString(raw_ostream &OS, const StringRef Str) {
274   for (signed char C : Str) {
275     switch (C) {
276     case '"':
277       OS << R"(\")";
278       break;
279     case '\\':
280       OS << R"(\\)";
281       break;
282     case '\n':
283       OS << R"(\n)";
284       break;
285     case '\t':
286       OS << R"(\t)";
287       break;
288     default:
289       if ('\x00' <= C && C <= '\x1f') {
290         OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C);
291       } else {
292         OS << C;
293       }
294     }
295   }
296 }
297 
298 static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree,
299                                 diff::NodeId Id) {
300   const diff::Node &N = Tree.getNode(Id);
301   OS << R"("id":)" << int(Id);
302   OS << R"(,"type":")" << N.getTypeLabel() << '"';
303   auto Offsets = Tree.getSourceRangeOffsets(N);
304   OS << R"(,"begin":)" << Offsets.first;
305   OS << R"(,"end":)" << Offsets.second;
306   std::string Value = Tree.getNodeValue(N);
307   if (!Value.empty()) {
308     OS << R"(,"value":")";
309     printJsonString(OS, Value);
310     OS << '"';
311   }
312 }
313 
314 static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree,
315                             diff::NodeId Id) {
316   const diff::Node &N = Tree.getNode(Id);
317   OS << "{";
318   printNodeAttributes(OS, Tree, Id);
319   auto Identifier = N.getIdentifier();
320   auto QualifiedIdentifier = N.getQualifiedIdentifier();
321   if (Identifier) {
322     OS << R"(,"identifier":")";
323     printJsonString(OS, *Identifier);
324     OS << R"(")";
325     if (QualifiedIdentifier && *Identifier != *QualifiedIdentifier) {
326       OS << R"(,"qualified_identifier":")";
327       printJsonString(OS, *QualifiedIdentifier);
328       OS << R"(")";
329     }
330   }
331   OS << R"(,"children":[)";
332   if (N.Children.size() > 0) {
333     printNodeAsJson(OS, Tree, N.Children[0]);
334     for (size_t I = 1, E = N.Children.size(); I < E; ++I) {
335       OS << ",";
336       printNodeAsJson(OS, Tree, N.Children[I]);
337     }
338   }
339   OS << "]}";
340 }
341 
342 static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree,
343                       diff::NodeId Id) {
344   if (Id.isInvalid()) {
345     OS << "None";
346     return;
347   }
348   OS << Tree.getNode(Id).getTypeLabel();
349   std::string Value = Tree.getNodeValue(Id);
350   if (!Value.empty())
351     OS << ": " << Value;
352   OS << "(" << Id << ")";
353 }
354 
355 static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) {
356   for (diff::NodeId Id : Tree) {
357     for (int I = 0; I < Tree.getNode(Id).Depth; ++I)
358       OS << " ";
359     printNode(OS, Tree, Id);
360     OS << "\n";
361   }
362 }
363 
364 static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff,
365                            diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree,
366                            diff::NodeId Dst) {
367   const diff::Node &DstNode = DstTree.getNode(Dst);
368   diff::NodeId Src = Diff.getMapped(DstTree, Dst);
369   switch (DstNode.Change) {
370   case diff::None:
371     break;
372   case diff::Delete:
373     llvm_unreachable("The destination tree can't have deletions.");
374   case diff::Update:
375     OS << "Update ";
376     printNode(OS, SrcTree, Src);
377     OS << " to " << DstTree.getNodeValue(Dst) << "\n";
378     break;
379   case diff::Insert:
380   case diff::Move:
381   case diff::UpdateMove:
382     if (DstNode.Change == diff::Insert)
383       OS << "Insert";
384     else if (DstNode.Change == diff::Move)
385       OS << "Move";
386     else if (DstNode.Change == diff::UpdateMove)
387       OS << "Update and Move";
388     OS << " ";
389     printNode(OS, DstTree, Dst);
390     OS << " into ";
391     printNode(OS, DstTree, DstNode.Parent);
392     OS << " at " << DstTree.findPositionInParent(Dst) << "\n";
393     break;
394   }
395 }
396 
397 int main(int argc, const char **argv) {
398   std::string ErrorMessage;
399   std::unique_ptr<CompilationDatabase> CommonCompilations =
400       FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage);
401   if (!CommonCompilations && !ErrorMessage.empty())
402     llvm::errs() << ErrorMessage;
403   cl::HideUnrelatedOptions(ClangDiffCategory);
404   if (!cl::ParseCommandLineOptions(argc, argv)) {
405     cl::PrintOptionValues();
406     return 1;
407   }
408 
409   addExtraArgs(CommonCompilations);
410 
411   if (ASTDump || ASTDumpJson) {
412     if (!DestinationPath.empty()) {
413       llvm::errs() << "Error: Please specify exactly one filename.\n";
414       return 1;
415     }
416     std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath);
417     if (!AST)
418       return 1;
419     diff::SyntaxTree Tree(AST->getASTContext());
420     if (ASTDump) {
421       printTree(llvm::outs(), Tree);
422       return 0;
423     }
424     llvm::outs() << R"({"filename":")";
425     printJsonString(llvm::outs(), SourcePath);
426     llvm::outs() << R"(","root":)";
427     printNodeAsJson(llvm::outs(), Tree, Tree.getRootId());
428     llvm::outs() << "}\n";
429     return 0;
430   }
431 
432   if (DestinationPath.empty()) {
433     llvm::errs() << "Error: Exactly two paths are required.\n";
434     return 1;
435   }
436 
437   std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath);
438   std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath);
439   if (!Src || !Dst)
440     return 1;
441 
442   diff::ComparisonOptions Options;
443   if (MaxSize != -1)
444     Options.MaxSize = MaxSize;
445   if (!StopAfter.empty()) {
446     if (StopAfter == "topdown")
447       Options.StopAfterTopDown = true;
448     else if (StopAfter != "bottomup") {
449       llvm::errs() << "Error: Invalid argument for -stop-after\n";
450       return 1;
451     }
452   }
453   diff::SyntaxTree SrcTree(Src->getASTContext());
454   diff::SyntaxTree DstTree(Dst->getASTContext());
455   diff::ASTDiff Diff(SrcTree, DstTree, Options);
456 
457   if (HtmlDiff) {
458     llvm::outs() << HtmlDiffHeader << "<pre>";
459     llvm::outs() << "<div id='L' class='code'>";
460     printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0);
461     llvm::outs() << "</div>";
462     llvm::outs() << "<div id='R' class='code'>";
463     printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(),
464                      0);
465     llvm::outs() << "</div>";
466     llvm::outs() << "</pre></div></body></html>\n";
467     return 0;
468   }
469 
470   for (diff::NodeId Dst : DstTree) {
471     diff::NodeId Src = Diff.getMapped(DstTree, Dst);
472     if (PrintMatches && Src.isValid()) {
473       llvm::outs() << "Match ";
474       printNode(llvm::outs(), SrcTree, Src);
475       llvm::outs() << " to ";
476       printNode(llvm::outs(), DstTree, Dst);
477       llvm::outs() << "\n";
478     }
479     printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst);
480   }
481   for (diff::NodeId Src : SrcTree) {
482     if (Diff.getMapped(SrcTree, Src).isInvalid()) {
483       llvm::outs() << "Delete ";
484       printNode(llvm::outs(), SrcTree, Src);
485       llvm::outs() << "\n";
486     }
487   }
488 
489   return 0;
490 }
491