xref: /llvm-project/clang/tools/clang-diff/ClangDiff.cpp (revision 683876ca6d75d95ebf83fd569f1ef0b8e07fc068)
1a75b2cacSAlex Lorenz //===- ClangDiff.cpp - compare source files by AST nodes ------*- C++ -*- -===//
2a75b2cacSAlex Lorenz //
3a75b2cacSAlex Lorenz //                     The LLVM Compiler Infrastructure
4a75b2cacSAlex Lorenz //
5a75b2cacSAlex Lorenz // This file is distributed under the University of Illinois Open Source
6a75b2cacSAlex Lorenz // License. See LICENSE.TXT for details.
7a75b2cacSAlex Lorenz //
8a75b2cacSAlex Lorenz //===----------------------------------------------------------------------===//
9a75b2cacSAlex Lorenz //
10a75b2cacSAlex Lorenz // This file implements a tool for syntax tree based comparison using
11a75b2cacSAlex Lorenz // Tooling/ASTDiff.
12a75b2cacSAlex Lorenz //
13a75b2cacSAlex Lorenz //===----------------------------------------------------------------------===//
14a75b2cacSAlex Lorenz 
15a75b2cacSAlex Lorenz #include "clang/Tooling/ASTDiff/ASTDiff.h"
16a75b2cacSAlex Lorenz #include "clang/Tooling/CommonOptionsParser.h"
17a75b2cacSAlex Lorenz #include "clang/Tooling/Tooling.h"
18a75b2cacSAlex Lorenz #include "llvm/Support/CommandLine.h"
19a75b2cacSAlex Lorenz 
20a75b2cacSAlex Lorenz using namespace llvm;
21a75b2cacSAlex Lorenz using namespace clang;
22a75b2cacSAlex Lorenz using namespace clang::tooling;
23a75b2cacSAlex Lorenz 
24a75b2cacSAlex Lorenz static cl::OptionCategory ClangDiffCategory("clang-diff options");
25a75b2cacSAlex Lorenz 
26a75b2cacSAlex Lorenz static cl::opt<bool>
27914a958eSJohannes Altmanninger     ASTDump("ast-dump",
28a1d2b5d5SJohannes Altmanninger             cl::desc("Print the internal representation of the AST."),
29a1d2b5d5SJohannes Altmanninger             cl::init(false), cl::cat(ClangDiffCategory));
30a1d2b5d5SJohannes Altmanninger 
31a1d2b5d5SJohannes Altmanninger static cl::opt<bool> ASTDumpJson(
32a1d2b5d5SJohannes Altmanninger     "ast-dump-json",
33a75b2cacSAlex Lorenz     cl::desc("Print the internal representation of the AST as JSON."),
34a75b2cacSAlex Lorenz     cl::init(false), cl::cat(ClangDiffCategory));
35a75b2cacSAlex Lorenz 
36*683876caSJohannes Altmanninger static cl::opt<bool>
37*683876caSJohannes Altmanninger     PrintMatches("dump-matches", cl::desc("Print the matched nodes."),
38*683876caSJohannes Altmanninger                  cl::init(false), cl::cat(ClangDiffCategory));
39*683876caSJohannes Altmanninger 
40a75b2cacSAlex Lorenz static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"),
41a75b2cacSAlex Lorenz                                        cl::Required,
42a75b2cacSAlex Lorenz                                        cl::cat(ClangDiffCategory));
43a75b2cacSAlex Lorenz 
44a75b2cacSAlex Lorenz static cl::opt<std::string> DestinationPath(cl::Positional,
45a75b2cacSAlex Lorenz                                             cl::desc("<destination>"),
46a75b2cacSAlex Lorenz                                             cl::Optional,
47a75b2cacSAlex Lorenz                                             cl::cat(ClangDiffCategory));
48a75b2cacSAlex Lorenz 
49849f20e4SJohannes Altmanninger static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional,
50849f20e4SJohannes Altmanninger                             cl::init(-1), cl::cat(ClangDiffCategory));
51849f20e4SJohannes Altmanninger 
52849f20e4SJohannes Altmanninger static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""),
53849f20e4SJohannes Altmanninger                                       cl::Optional, cl::cat(ClangDiffCategory));
54849f20e4SJohannes Altmanninger 
55849f20e4SJohannes Altmanninger static cl::list<std::string> ArgsAfter(
56849f20e4SJohannes Altmanninger     "extra-arg",
57849f20e4SJohannes Altmanninger     cl::desc("Additional argument to append to the compiler command line"),
58849f20e4SJohannes Altmanninger     cl::cat(ClangDiffCategory));
59849f20e4SJohannes Altmanninger 
60849f20e4SJohannes Altmanninger static cl::list<std::string> ArgsBefore(
61849f20e4SJohannes Altmanninger     "extra-arg-before",
62849f20e4SJohannes Altmanninger     cl::desc("Additional argument to prepend to the compiler command line"),
63849f20e4SJohannes Altmanninger     cl::cat(ClangDiffCategory));
64849f20e4SJohannes Altmanninger 
65849f20e4SJohannes Altmanninger static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) {
66849f20e4SJohannes Altmanninger   if (!Compilations)
67849f20e4SJohannes Altmanninger     return;
68849f20e4SJohannes Altmanninger   auto AdjustingCompilations =
69849f20e4SJohannes Altmanninger       llvm::make_unique<ArgumentsAdjustingCompilations>(
70849f20e4SJohannes Altmanninger           std::move(Compilations));
71849f20e4SJohannes Altmanninger   AdjustingCompilations->appendArgumentsAdjuster(
72849f20e4SJohannes Altmanninger       getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN));
73849f20e4SJohannes Altmanninger   AdjustingCompilations->appendArgumentsAdjuster(
74849f20e4SJohannes Altmanninger       getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END));
75849f20e4SJohannes Altmanninger   Compilations = std::move(AdjustingCompilations);
76849f20e4SJohannes Altmanninger }
77849f20e4SJohannes Altmanninger 
78849f20e4SJohannes Altmanninger static std::unique_ptr<ASTUnit>
79849f20e4SJohannes Altmanninger getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations,
80849f20e4SJohannes Altmanninger        const StringRef Filename) {
81a75b2cacSAlex Lorenz   std::string ErrorMessage;
82a75b2cacSAlex Lorenz   std::unique_ptr<CompilationDatabase> Compilations;
83849f20e4SJohannes Altmanninger   if (!CommonCompilations) {
84849f20e4SJohannes Altmanninger     Compilations = CompilationDatabase::autoDetectFromSource(
85849f20e4SJohannes Altmanninger         BuildPath.empty() ? Filename : BuildPath, ErrorMessage);
86a75b2cacSAlex Lorenz     if (!Compilations) {
87a75b2cacSAlex Lorenz       llvm::errs()
88a75b2cacSAlex Lorenz           << "Error while trying to load a compilation database, running "
89a75b2cacSAlex Lorenz              "without flags.\n"
90a75b2cacSAlex Lorenz           << ErrorMessage;
91849f20e4SJohannes Altmanninger       Compilations =
92849f20e4SJohannes Altmanninger           llvm::make_unique<clang::tooling::FixedCompilationDatabase>(
93a75b2cacSAlex Lorenz               ".", std::vector<std::string>());
94a75b2cacSAlex Lorenz     }
95849f20e4SJohannes Altmanninger   }
96849f20e4SJohannes Altmanninger   addExtraArgs(Compilations);
97a75b2cacSAlex Lorenz   std::array<std::string, 1> Files = {{Filename}};
98849f20e4SJohannes Altmanninger   ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files);
99a75b2cacSAlex Lorenz   std::vector<std::unique_ptr<ASTUnit>> ASTs;
100a75b2cacSAlex Lorenz   Tool.buildASTs(ASTs);
101a75b2cacSAlex Lorenz   if (ASTs.size() != Files.size())
102a75b2cacSAlex Lorenz     return nullptr;
103a75b2cacSAlex Lorenz   return std::move(ASTs[0]);
104a75b2cacSAlex Lorenz }
105a75b2cacSAlex Lorenz 
1060da12c84SJohannes Altmanninger static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); }
1070da12c84SJohannes Altmanninger 
1080da12c84SJohannes Altmanninger static void printJsonString(raw_ostream &OS, const StringRef Str) {
1090da12c84SJohannes Altmanninger   for (char C : Str) {
1100da12c84SJohannes Altmanninger     switch (C) {
1110da12c84SJohannes Altmanninger     case '"':
1120da12c84SJohannes Altmanninger       OS << R"(\")";
1130da12c84SJohannes Altmanninger       break;
1140da12c84SJohannes Altmanninger     case '\\':
1150da12c84SJohannes Altmanninger       OS << R"(\\)";
1160da12c84SJohannes Altmanninger       break;
1170da12c84SJohannes Altmanninger     case '\n':
1180da12c84SJohannes Altmanninger       OS << R"(\n)";
1190da12c84SJohannes Altmanninger       break;
1200da12c84SJohannes Altmanninger     case '\t':
1210da12c84SJohannes Altmanninger       OS << R"(\t)";
1220da12c84SJohannes Altmanninger       break;
1230da12c84SJohannes Altmanninger     default:
1240da12c84SJohannes Altmanninger       if ('\x00' <= C && C <= '\x1f') {
1250da12c84SJohannes Altmanninger         OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C);
1260da12c84SJohannes Altmanninger       } else {
1270da12c84SJohannes Altmanninger         OS << C;
1280da12c84SJohannes Altmanninger       }
1290da12c84SJohannes Altmanninger     }
1300da12c84SJohannes Altmanninger   }
1310da12c84SJohannes Altmanninger }
1320da12c84SJohannes Altmanninger 
1330da12c84SJohannes Altmanninger static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree,
1340da12c84SJohannes Altmanninger                                 diff::NodeId Id) {
1350da12c84SJohannes Altmanninger   const diff::Node &N = Tree.getNode(Id);
1360da12c84SJohannes Altmanninger   OS << R"("id":)" << int(Id);
1370da12c84SJohannes Altmanninger   OS << R"(,"type":")" << N.getTypeLabel() << '"';
1380da12c84SJohannes Altmanninger   auto Offsets = Tree.getSourceRangeOffsets(N);
1390da12c84SJohannes Altmanninger   OS << R"(,"begin":)" << Offsets.first;
1400da12c84SJohannes Altmanninger   OS << R"(,"end":)" << Offsets.second;
141e0fe5cd4SJohannes Altmanninger   std::string Value = Tree.getNodeValue(N);
1420da12c84SJohannes Altmanninger   if (!Value.empty()) {
1430da12c84SJohannes Altmanninger     OS << R"(,"value":")";
1440da12c84SJohannes Altmanninger     printJsonString(OS, Value);
1450da12c84SJohannes Altmanninger     OS << '"';
1460da12c84SJohannes Altmanninger   }
1470da12c84SJohannes Altmanninger }
1480da12c84SJohannes Altmanninger 
1490da12c84SJohannes Altmanninger static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree,
1500da12c84SJohannes Altmanninger                             diff::NodeId Id) {
1510da12c84SJohannes Altmanninger   const diff::Node &N = Tree.getNode(Id);
1520da12c84SJohannes Altmanninger   OS << "{";
1530da12c84SJohannes Altmanninger   printNodeAttributes(OS, Tree, Id);
1540da12c84SJohannes Altmanninger   OS << R"(,"children":[)";
1550da12c84SJohannes Altmanninger   if (N.Children.size() > 0) {
1560da12c84SJohannes Altmanninger     printNodeAsJson(OS, Tree, N.Children[0]);
1570da12c84SJohannes Altmanninger     for (size_t I = 1, E = N.Children.size(); I < E; ++I) {
1580da12c84SJohannes Altmanninger       OS << ",";
1590da12c84SJohannes Altmanninger       printNodeAsJson(OS, Tree, N.Children[I]);
1600da12c84SJohannes Altmanninger     }
1610da12c84SJohannes Altmanninger   }
1620da12c84SJohannes Altmanninger   OS << "]}";
1630da12c84SJohannes Altmanninger }
1640da12c84SJohannes Altmanninger 
165e0fe5cd4SJohannes Altmanninger static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree,
166e0fe5cd4SJohannes Altmanninger                       diff::NodeId Id) {
167e0fe5cd4SJohannes Altmanninger   if (Id.isInvalid()) {
168e0fe5cd4SJohannes Altmanninger     OS << "None";
169e0fe5cd4SJohannes Altmanninger     return;
170e0fe5cd4SJohannes Altmanninger   }
171e0fe5cd4SJohannes Altmanninger   OS << Tree.getNode(Id).getTypeLabel();
172e0fe5cd4SJohannes Altmanninger   std::string Value = Tree.getNodeValue(Id);
173e0fe5cd4SJohannes Altmanninger   if (!Value.empty())
174e0fe5cd4SJohannes Altmanninger     OS << ": " << Value;
175e0fe5cd4SJohannes Altmanninger   OS << "(" << Id << ")";
176e0fe5cd4SJohannes Altmanninger }
177e0fe5cd4SJohannes Altmanninger 
178a1d2b5d5SJohannes Altmanninger static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) {
179a1d2b5d5SJohannes Altmanninger   for (diff::NodeId Id : Tree) {
180a1d2b5d5SJohannes Altmanninger     for (int I = 0; I < Tree.getNode(Id).Depth; ++I)
181a1d2b5d5SJohannes Altmanninger       OS << " ";
182a1d2b5d5SJohannes Altmanninger     printNode(OS, Tree, Id);
183a1d2b5d5SJohannes Altmanninger     OS << "\n";
184a1d2b5d5SJohannes Altmanninger   }
185a1d2b5d5SJohannes Altmanninger }
186a1d2b5d5SJohannes Altmanninger 
187e0fe5cd4SJohannes Altmanninger static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff,
188e0fe5cd4SJohannes Altmanninger                            diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree,
189e0fe5cd4SJohannes Altmanninger                            diff::NodeId Dst) {
190e0fe5cd4SJohannes Altmanninger   const diff::Node &DstNode = DstTree.getNode(Dst);
191e0fe5cd4SJohannes Altmanninger   diff::NodeId Src = Diff.getMapped(DstTree, Dst);
192e0fe5cd4SJohannes Altmanninger   switch (DstNode.Change) {
193e0fe5cd4SJohannes Altmanninger   case diff::None:
194e0fe5cd4SJohannes Altmanninger     break;
195e0fe5cd4SJohannes Altmanninger   case diff::Delete:
196e0fe5cd4SJohannes Altmanninger     llvm_unreachable("The destination tree can't have deletions.");
197e0fe5cd4SJohannes Altmanninger   case diff::Update:
198e0fe5cd4SJohannes Altmanninger     OS << "Update ";
199e0fe5cd4SJohannes Altmanninger     printNode(OS, SrcTree, Src);
200e0fe5cd4SJohannes Altmanninger     OS << " to " << DstTree.getNodeValue(Dst) << "\n";
201e0fe5cd4SJohannes Altmanninger     break;
202e0fe5cd4SJohannes Altmanninger   case diff::Insert:
203e0fe5cd4SJohannes Altmanninger   case diff::Move:
204e0fe5cd4SJohannes Altmanninger   case diff::UpdateMove:
205e0fe5cd4SJohannes Altmanninger     if (DstNode.Change == diff::Insert)
206e0fe5cd4SJohannes Altmanninger       OS << "Insert";
207e0fe5cd4SJohannes Altmanninger     else if (DstNode.Change == diff::Move)
208e0fe5cd4SJohannes Altmanninger       OS << "Move";
209e0fe5cd4SJohannes Altmanninger     else if (DstNode.Change == diff::UpdateMove)
210e0fe5cd4SJohannes Altmanninger       OS << "Update and Move";
211e0fe5cd4SJohannes Altmanninger     OS << " ";
212e0fe5cd4SJohannes Altmanninger     printNode(OS, DstTree, Dst);
213e0fe5cd4SJohannes Altmanninger     OS << " into ";
214e0fe5cd4SJohannes Altmanninger     printNode(OS, DstTree, DstNode.Parent);
215e0fe5cd4SJohannes Altmanninger     OS << " at " << DstTree.findPositionInParent(Dst) << "\n";
216e0fe5cd4SJohannes Altmanninger     break;
217e0fe5cd4SJohannes Altmanninger   }
218e0fe5cd4SJohannes Altmanninger }
219e0fe5cd4SJohannes Altmanninger 
220a75b2cacSAlex Lorenz int main(int argc, const char **argv) {
221849f20e4SJohannes Altmanninger   std::string ErrorMessage;
222849f20e4SJohannes Altmanninger   std::unique_ptr<CompilationDatabase> CommonCompilations =
223849f20e4SJohannes Altmanninger       FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage);
224849f20e4SJohannes Altmanninger   if (!CommonCompilations && !ErrorMessage.empty())
225849f20e4SJohannes Altmanninger     llvm::errs() << ErrorMessage;
226a75b2cacSAlex Lorenz   cl::HideUnrelatedOptions(ClangDiffCategory);
227a75b2cacSAlex Lorenz   if (!cl::ParseCommandLineOptions(argc, argv)) {
228a75b2cacSAlex Lorenz     cl::PrintOptionValues();
229a75b2cacSAlex Lorenz     return 1;
230a75b2cacSAlex Lorenz   }
231a75b2cacSAlex Lorenz 
232849f20e4SJohannes Altmanninger   addExtraArgs(CommonCompilations);
233849f20e4SJohannes Altmanninger 
234a1d2b5d5SJohannes Altmanninger   if (ASTDump || ASTDumpJson) {
235a75b2cacSAlex Lorenz     if (!DestinationPath.empty()) {
236a75b2cacSAlex Lorenz       llvm::errs() << "Error: Please specify exactly one filename.\n";
237a75b2cacSAlex Lorenz       return 1;
238a75b2cacSAlex Lorenz     }
239849f20e4SJohannes Altmanninger     std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath);
240a75b2cacSAlex Lorenz     if (!AST)
241a75b2cacSAlex Lorenz       return 1;
242a75b2cacSAlex Lorenz     diff::SyntaxTree Tree(AST->getASTContext());
243a1d2b5d5SJohannes Altmanninger     if (ASTDump) {
244a1d2b5d5SJohannes Altmanninger       printTree(llvm::outs(), Tree);
245a1d2b5d5SJohannes Altmanninger       return 0;
246a1d2b5d5SJohannes Altmanninger     }
2470da12c84SJohannes Altmanninger     llvm::outs() << R"({"filename":")";
2480da12c84SJohannes Altmanninger     printJsonString(llvm::outs(), SourcePath);
2490da12c84SJohannes Altmanninger     llvm::outs() << R"(","root":)";
2500da12c84SJohannes Altmanninger     printNodeAsJson(llvm::outs(), Tree, Tree.getRootId());
2510da12c84SJohannes Altmanninger     llvm::outs() << "}\n";
252a75b2cacSAlex Lorenz     return 0;
253a75b2cacSAlex Lorenz   }
254a75b2cacSAlex Lorenz 
255a75b2cacSAlex Lorenz   if (DestinationPath.empty()) {
256a75b2cacSAlex Lorenz     llvm::errs() << "Error: Exactly two paths are required.\n";
257a75b2cacSAlex Lorenz     return 1;
258a75b2cacSAlex Lorenz   }
259a75b2cacSAlex Lorenz 
260849f20e4SJohannes Altmanninger   std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath);
261849f20e4SJohannes Altmanninger   std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath);
262a75b2cacSAlex Lorenz   if (!Src || !Dst)
263a75b2cacSAlex Lorenz     return 1;
264a75b2cacSAlex Lorenz 
265a75b2cacSAlex Lorenz   diff::ComparisonOptions Options;
266849f20e4SJohannes Altmanninger   if (MaxSize != -1)
267849f20e4SJohannes Altmanninger     Options.MaxSize = MaxSize;
268a75b2cacSAlex Lorenz   diff::SyntaxTree SrcTree(Src->getASTContext());
269a75b2cacSAlex Lorenz   diff::SyntaxTree DstTree(Dst->getASTContext());
270e0fe5cd4SJohannes Altmanninger   diff::ASTDiff Diff(SrcTree, DstTree, Options);
271e0fe5cd4SJohannes Altmanninger 
272e0fe5cd4SJohannes Altmanninger   for (diff::NodeId Dst : DstTree) {
273e0fe5cd4SJohannes Altmanninger     diff::NodeId Src = Diff.getMapped(DstTree, Dst);
274*683876caSJohannes Altmanninger     if (PrintMatches && Src.isValid()) {
275e0fe5cd4SJohannes Altmanninger       llvm::outs() << "Match ";
276e0fe5cd4SJohannes Altmanninger       printNode(llvm::outs(), SrcTree, Src);
277e0fe5cd4SJohannes Altmanninger       llvm::outs() << " to ";
278e0fe5cd4SJohannes Altmanninger       printNode(llvm::outs(), DstTree, Dst);
279e0fe5cd4SJohannes Altmanninger       llvm::outs() << "\n";
280e0fe5cd4SJohannes Altmanninger     }
281e0fe5cd4SJohannes Altmanninger     printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst);
282e0fe5cd4SJohannes Altmanninger   }
283e0fe5cd4SJohannes Altmanninger   for (diff::NodeId Src : SrcTree) {
284e0fe5cd4SJohannes Altmanninger     if (Diff.getMapped(SrcTree, Src).isInvalid()) {
285e0fe5cd4SJohannes Altmanninger       llvm::outs() << "Delete ";
286e0fe5cd4SJohannes Altmanninger       printNode(llvm::outs(), SrcTree, Src);
287e0fe5cd4SJohannes Altmanninger       llvm::outs() << "\n";
288e0fe5cd4SJohannes Altmanninger     }
289e0fe5cd4SJohannes Altmanninger   }
290a75b2cacSAlex Lorenz 
291a75b2cacSAlex Lorenz   return 0;
292a75b2cacSAlex Lorenz }
293