xref: /llvm-project/clang/tools/clang-diff/ClangDiff.cpp (revision e1a89fbf6d33b67b702de0042441fc3df4cdfa9f)
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 << "&amp;";
174     break;
175   case '<':
176     OS << "&lt;";
177     break;
178   case '>':
179     OS << "&gt;";
180     break;
181   case '\'':
182     OS << "&#x27;";
183     break;
184   case '"':
185     OS << "&quot;";
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