xref: /llvm-project/clang/tools/clang-diff/ClangDiff.cpp (revision 683876ca6d75d95ebf83fd569f1ef0b8e07fc068)
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<std::string> SourcePath(cl::Positional, cl::desc("<source>"),
41                                        cl::Required,
42                                        cl::cat(ClangDiffCategory));
43 
44 static cl::opt<std::string> DestinationPath(cl::Positional,
45                                             cl::desc("<destination>"),
46                                             cl::Optional,
47                                             cl::cat(ClangDiffCategory));
48 
49 static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional,
50                             cl::init(-1), cl::cat(ClangDiffCategory));
51 
52 static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""),
53                                       cl::Optional, cl::cat(ClangDiffCategory));
54 
55 static cl::list<std::string> ArgsAfter(
56     "extra-arg",
57     cl::desc("Additional argument to append to the compiler command line"),
58     cl::cat(ClangDiffCategory));
59 
60 static cl::list<std::string> ArgsBefore(
61     "extra-arg-before",
62     cl::desc("Additional argument to prepend to the compiler command line"),
63     cl::cat(ClangDiffCategory));
64 
65 static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) {
66   if (!Compilations)
67     return;
68   auto AdjustingCompilations =
69       llvm::make_unique<ArgumentsAdjustingCompilations>(
70           std::move(Compilations));
71   AdjustingCompilations->appendArgumentsAdjuster(
72       getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN));
73   AdjustingCompilations->appendArgumentsAdjuster(
74       getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END));
75   Compilations = std::move(AdjustingCompilations);
76 }
77 
78 static std::unique_ptr<ASTUnit>
79 getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations,
80        const StringRef Filename) {
81   std::string ErrorMessage;
82   std::unique_ptr<CompilationDatabase> Compilations;
83   if (!CommonCompilations) {
84     Compilations = CompilationDatabase::autoDetectFromSource(
85         BuildPath.empty() ? Filename : BuildPath, ErrorMessage);
86     if (!Compilations) {
87       llvm::errs()
88           << "Error while trying to load a compilation database, running "
89              "without flags.\n"
90           << ErrorMessage;
91       Compilations =
92           llvm::make_unique<clang::tooling::FixedCompilationDatabase>(
93               ".", std::vector<std::string>());
94     }
95   }
96   addExtraArgs(Compilations);
97   std::array<std::string, 1> Files = {{Filename}};
98   ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files);
99   std::vector<std::unique_ptr<ASTUnit>> ASTs;
100   Tool.buildASTs(ASTs);
101   if (ASTs.size() != Files.size())
102     return nullptr;
103   return std::move(ASTs[0]);
104 }
105 
106 static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); }
107 
108 static void printJsonString(raw_ostream &OS, const StringRef Str) {
109   for (char C : Str) {
110     switch (C) {
111     case '"':
112       OS << R"(\")";
113       break;
114     case '\\':
115       OS << R"(\\)";
116       break;
117     case '\n':
118       OS << R"(\n)";
119       break;
120     case '\t':
121       OS << R"(\t)";
122       break;
123     default:
124       if ('\x00' <= C && C <= '\x1f') {
125         OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C);
126       } else {
127         OS << C;
128       }
129     }
130   }
131 }
132 
133 static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree,
134                                 diff::NodeId Id) {
135   const diff::Node &N = Tree.getNode(Id);
136   OS << R"("id":)" << int(Id);
137   OS << R"(,"type":")" << N.getTypeLabel() << '"';
138   auto Offsets = Tree.getSourceRangeOffsets(N);
139   OS << R"(,"begin":)" << Offsets.first;
140   OS << R"(,"end":)" << Offsets.second;
141   std::string Value = Tree.getNodeValue(N);
142   if (!Value.empty()) {
143     OS << R"(,"value":")";
144     printJsonString(OS, Value);
145     OS << '"';
146   }
147 }
148 
149 static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree,
150                             diff::NodeId Id) {
151   const diff::Node &N = Tree.getNode(Id);
152   OS << "{";
153   printNodeAttributes(OS, Tree, Id);
154   OS << R"(,"children":[)";
155   if (N.Children.size() > 0) {
156     printNodeAsJson(OS, Tree, N.Children[0]);
157     for (size_t I = 1, E = N.Children.size(); I < E; ++I) {
158       OS << ",";
159       printNodeAsJson(OS, Tree, N.Children[I]);
160     }
161   }
162   OS << "]}";
163 }
164 
165 static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree,
166                       diff::NodeId Id) {
167   if (Id.isInvalid()) {
168     OS << "None";
169     return;
170   }
171   OS << Tree.getNode(Id).getTypeLabel();
172   std::string Value = Tree.getNodeValue(Id);
173   if (!Value.empty())
174     OS << ": " << Value;
175   OS << "(" << Id << ")";
176 }
177 
178 static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) {
179   for (diff::NodeId Id : Tree) {
180     for (int I = 0; I < Tree.getNode(Id).Depth; ++I)
181       OS << " ";
182     printNode(OS, Tree, Id);
183     OS << "\n";
184   }
185 }
186 
187 static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff,
188                            diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree,
189                            diff::NodeId Dst) {
190   const diff::Node &DstNode = DstTree.getNode(Dst);
191   diff::NodeId Src = Diff.getMapped(DstTree, Dst);
192   switch (DstNode.Change) {
193   case diff::None:
194     break;
195   case diff::Delete:
196     llvm_unreachable("The destination tree can't have deletions.");
197   case diff::Update:
198     OS << "Update ";
199     printNode(OS, SrcTree, Src);
200     OS << " to " << DstTree.getNodeValue(Dst) << "\n";
201     break;
202   case diff::Insert:
203   case diff::Move:
204   case diff::UpdateMove:
205     if (DstNode.Change == diff::Insert)
206       OS << "Insert";
207     else if (DstNode.Change == diff::Move)
208       OS << "Move";
209     else if (DstNode.Change == diff::UpdateMove)
210       OS << "Update and Move";
211     OS << " ";
212     printNode(OS, DstTree, Dst);
213     OS << " into ";
214     printNode(OS, DstTree, DstNode.Parent);
215     OS << " at " << DstTree.findPositionInParent(Dst) << "\n";
216     break;
217   }
218 }
219 
220 int main(int argc, const char **argv) {
221   std::string ErrorMessage;
222   std::unique_ptr<CompilationDatabase> CommonCompilations =
223       FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage);
224   if (!CommonCompilations && !ErrorMessage.empty())
225     llvm::errs() << ErrorMessage;
226   cl::HideUnrelatedOptions(ClangDiffCategory);
227   if (!cl::ParseCommandLineOptions(argc, argv)) {
228     cl::PrintOptionValues();
229     return 1;
230   }
231 
232   addExtraArgs(CommonCompilations);
233 
234   if (ASTDump || ASTDumpJson) {
235     if (!DestinationPath.empty()) {
236       llvm::errs() << "Error: Please specify exactly one filename.\n";
237       return 1;
238     }
239     std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath);
240     if (!AST)
241       return 1;
242     diff::SyntaxTree Tree(AST->getASTContext());
243     if (ASTDump) {
244       printTree(llvm::outs(), Tree);
245       return 0;
246     }
247     llvm::outs() << R"({"filename":")";
248     printJsonString(llvm::outs(), SourcePath);
249     llvm::outs() << R"(","root":)";
250     printNodeAsJson(llvm::outs(), Tree, Tree.getRootId());
251     llvm::outs() << "}\n";
252     return 0;
253   }
254 
255   if (DestinationPath.empty()) {
256     llvm::errs() << "Error: Exactly two paths are required.\n";
257     return 1;
258   }
259 
260   std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath);
261   std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath);
262   if (!Src || !Dst)
263     return 1;
264 
265   diff::ComparisonOptions Options;
266   if (MaxSize != -1)
267     Options.MaxSize = MaxSize;
268   diff::SyntaxTree SrcTree(Src->getASTContext());
269   diff::SyntaxTree DstTree(Dst->getASTContext());
270   diff::ASTDiff Diff(SrcTree, DstTree, Options);
271 
272   for (diff::NodeId Dst : DstTree) {
273     diff::NodeId Src = Diff.getMapped(DstTree, Dst);
274     if (PrintMatches && Src.isValid()) {
275       llvm::outs() << "Match ";
276       printNode(llvm::outs(), SrcTree, Src);
277       llvm::outs() << " to ";
278       printNode(llvm::outs(), DstTree, Dst);
279       llvm::outs() << "\n";
280     }
281     printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst);
282   }
283   for (diff::NodeId Src : SrcTree) {
284     if (Diff.getMapped(SrcTree, Src).isInvalid()) {
285       llvm::outs() << "Delete ";
286       printNode(llvm::outs(), SrcTree, Src);
287       llvm::outs() << "\n";
288     }
289   }
290 
291   return 0;
292 }
293