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