xref: /llvm-project/clang/tools/clang-diff/ClangDiff.cpp (revision e0fe5cd4f3369a827e6789e526dbe45f672e125c)
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",
28a75b2cacSAlex Lorenz             cl::desc("Print the internal representation of the AST as JSON."),
29a75b2cacSAlex Lorenz             cl::init(false), cl::cat(ClangDiffCategory));
30a75b2cacSAlex Lorenz 
31a75b2cacSAlex Lorenz static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"),
32a75b2cacSAlex Lorenz                                        cl::Required,
33a75b2cacSAlex Lorenz                                        cl::cat(ClangDiffCategory));
34a75b2cacSAlex Lorenz 
35a75b2cacSAlex Lorenz static cl::opt<std::string> DestinationPath(cl::Positional,
36a75b2cacSAlex Lorenz                                             cl::desc("<destination>"),
37a75b2cacSAlex Lorenz                                             cl::Optional,
38a75b2cacSAlex Lorenz                                             cl::cat(ClangDiffCategory));
39a75b2cacSAlex Lorenz 
40849f20e4SJohannes Altmanninger static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional,
41849f20e4SJohannes Altmanninger                             cl::init(-1), cl::cat(ClangDiffCategory));
42849f20e4SJohannes Altmanninger 
43849f20e4SJohannes Altmanninger static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""),
44849f20e4SJohannes Altmanninger                                       cl::Optional, cl::cat(ClangDiffCategory));
45849f20e4SJohannes Altmanninger 
46849f20e4SJohannes Altmanninger static cl::list<std::string> ArgsAfter(
47849f20e4SJohannes Altmanninger     "extra-arg",
48849f20e4SJohannes Altmanninger     cl::desc("Additional argument to append to the compiler command line"),
49849f20e4SJohannes Altmanninger     cl::cat(ClangDiffCategory));
50849f20e4SJohannes Altmanninger 
51849f20e4SJohannes Altmanninger static cl::list<std::string> ArgsBefore(
52849f20e4SJohannes Altmanninger     "extra-arg-before",
53849f20e4SJohannes Altmanninger     cl::desc("Additional argument to prepend to the compiler command line"),
54849f20e4SJohannes Altmanninger     cl::cat(ClangDiffCategory));
55849f20e4SJohannes Altmanninger 
56849f20e4SJohannes Altmanninger static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) {
57849f20e4SJohannes Altmanninger   if (!Compilations)
58849f20e4SJohannes Altmanninger     return;
59849f20e4SJohannes Altmanninger   auto AdjustingCompilations =
60849f20e4SJohannes Altmanninger       llvm::make_unique<ArgumentsAdjustingCompilations>(
61849f20e4SJohannes Altmanninger           std::move(Compilations));
62849f20e4SJohannes Altmanninger   AdjustingCompilations->appendArgumentsAdjuster(
63849f20e4SJohannes Altmanninger       getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN));
64849f20e4SJohannes Altmanninger   AdjustingCompilations->appendArgumentsAdjuster(
65849f20e4SJohannes Altmanninger       getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END));
66849f20e4SJohannes Altmanninger   Compilations = std::move(AdjustingCompilations);
67849f20e4SJohannes Altmanninger }
68849f20e4SJohannes Altmanninger 
69849f20e4SJohannes Altmanninger static std::unique_ptr<ASTUnit>
70849f20e4SJohannes Altmanninger getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations,
71849f20e4SJohannes Altmanninger        const StringRef Filename) {
72a75b2cacSAlex Lorenz   std::string ErrorMessage;
73a75b2cacSAlex Lorenz   std::unique_ptr<CompilationDatabase> Compilations;
74849f20e4SJohannes Altmanninger   if (!CommonCompilations) {
75849f20e4SJohannes Altmanninger     Compilations = CompilationDatabase::autoDetectFromSource(
76849f20e4SJohannes Altmanninger         BuildPath.empty() ? Filename : BuildPath, ErrorMessage);
77a75b2cacSAlex Lorenz     if (!Compilations) {
78a75b2cacSAlex Lorenz       llvm::errs()
79a75b2cacSAlex Lorenz           << "Error while trying to load a compilation database, running "
80a75b2cacSAlex Lorenz              "without flags.\n"
81a75b2cacSAlex Lorenz           << ErrorMessage;
82849f20e4SJohannes Altmanninger       Compilations =
83849f20e4SJohannes Altmanninger           llvm::make_unique<clang::tooling::FixedCompilationDatabase>(
84a75b2cacSAlex Lorenz               ".", std::vector<std::string>());
85a75b2cacSAlex Lorenz     }
86849f20e4SJohannes Altmanninger   }
87849f20e4SJohannes Altmanninger   addExtraArgs(Compilations);
88a75b2cacSAlex Lorenz   std::array<std::string, 1> Files = {{Filename}};
89849f20e4SJohannes Altmanninger   ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files);
90a75b2cacSAlex Lorenz   std::vector<std::unique_ptr<ASTUnit>> ASTs;
91a75b2cacSAlex Lorenz   Tool.buildASTs(ASTs);
92a75b2cacSAlex Lorenz   if (ASTs.size() != Files.size())
93a75b2cacSAlex Lorenz     return nullptr;
94a75b2cacSAlex Lorenz   return std::move(ASTs[0]);
95a75b2cacSAlex Lorenz }
96a75b2cacSAlex Lorenz 
970da12c84SJohannes Altmanninger static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); }
980da12c84SJohannes Altmanninger 
990da12c84SJohannes Altmanninger static void printJsonString(raw_ostream &OS, const StringRef Str) {
1000da12c84SJohannes Altmanninger   for (char C : Str) {
1010da12c84SJohannes Altmanninger     switch (C) {
1020da12c84SJohannes Altmanninger     case '"':
1030da12c84SJohannes Altmanninger       OS << R"(\")";
1040da12c84SJohannes Altmanninger       break;
1050da12c84SJohannes Altmanninger     case '\\':
1060da12c84SJohannes Altmanninger       OS << R"(\\)";
1070da12c84SJohannes Altmanninger       break;
1080da12c84SJohannes Altmanninger     case '\n':
1090da12c84SJohannes Altmanninger       OS << R"(\n)";
1100da12c84SJohannes Altmanninger       break;
1110da12c84SJohannes Altmanninger     case '\t':
1120da12c84SJohannes Altmanninger       OS << R"(\t)";
1130da12c84SJohannes Altmanninger       break;
1140da12c84SJohannes Altmanninger     default:
1150da12c84SJohannes Altmanninger       if ('\x00' <= C && C <= '\x1f') {
1160da12c84SJohannes Altmanninger         OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C);
1170da12c84SJohannes Altmanninger       } else {
1180da12c84SJohannes Altmanninger         OS << C;
1190da12c84SJohannes Altmanninger       }
1200da12c84SJohannes Altmanninger     }
1210da12c84SJohannes Altmanninger   }
1220da12c84SJohannes Altmanninger }
1230da12c84SJohannes Altmanninger 
1240da12c84SJohannes Altmanninger static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree,
1250da12c84SJohannes Altmanninger                                 diff::NodeId Id) {
1260da12c84SJohannes Altmanninger   const diff::Node &N = Tree.getNode(Id);
1270da12c84SJohannes Altmanninger   OS << R"("id":)" << int(Id);
1280da12c84SJohannes Altmanninger   OS << R"(,"type":")" << N.getTypeLabel() << '"';
1290da12c84SJohannes Altmanninger   auto Offsets = Tree.getSourceRangeOffsets(N);
1300da12c84SJohannes Altmanninger   OS << R"(,"begin":)" << Offsets.first;
1310da12c84SJohannes Altmanninger   OS << R"(,"end":)" << Offsets.second;
132*e0fe5cd4SJohannes Altmanninger   std::string Value = Tree.getNodeValue(N);
1330da12c84SJohannes Altmanninger   if (!Value.empty()) {
1340da12c84SJohannes Altmanninger     OS << R"(,"value":")";
1350da12c84SJohannes Altmanninger     printJsonString(OS, Value);
1360da12c84SJohannes Altmanninger     OS << '"';
1370da12c84SJohannes Altmanninger   }
1380da12c84SJohannes Altmanninger }
1390da12c84SJohannes Altmanninger 
1400da12c84SJohannes Altmanninger static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree,
1410da12c84SJohannes Altmanninger                             diff::NodeId Id) {
1420da12c84SJohannes Altmanninger   const diff::Node &N = Tree.getNode(Id);
1430da12c84SJohannes Altmanninger   OS << "{";
1440da12c84SJohannes Altmanninger   printNodeAttributes(OS, Tree, Id);
1450da12c84SJohannes Altmanninger   OS << R"(,"children":[)";
1460da12c84SJohannes Altmanninger   if (N.Children.size() > 0) {
1470da12c84SJohannes Altmanninger     printNodeAsJson(OS, Tree, N.Children[0]);
1480da12c84SJohannes Altmanninger     for (size_t I = 1, E = N.Children.size(); I < E; ++I) {
1490da12c84SJohannes Altmanninger       OS << ",";
1500da12c84SJohannes Altmanninger       printNodeAsJson(OS, Tree, N.Children[I]);
1510da12c84SJohannes Altmanninger     }
1520da12c84SJohannes Altmanninger   }
1530da12c84SJohannes Altmanninger   OS << "]}";
1540da12c84SJohannes Altmanninger }
1550da12c84SJohannes Altmanninger 
156*e0fe5cd4SJohannes Altmanninger static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree,
157*e0fe5cd4SJohannes Altmanninger                       diff::NodeId Id) {
158*e0fe5cd4SJohannes Altmanninger   if (Id.isInvalid()) {
159*e0fe5cd4SJohannes Altmanninger     OS << "None";
160*e0fe5cd4SJohannes Altmanninger     return;
161*e0fe5cd4SJohannes Altmanninger   }
162*e0fe5cd4SJohannes Altmanninger   OS << Tree.getNode(Id).getTypeLabel();
163*e0fe5cd4SJohannes Altmanninger   std::string Value = Tree.getNodeValue(Id);
164*e0fe5cd4SJohannes Altmanninger   if (!Value.empty())
165*e0fe5cd4SJohannes Altmanninger     OS << ": " << Value;
166*e0fe5cd4SJohannes Altmanninger   OS << "(" << Id << ")";
167*e0fe5cd4SJohannes Altmanninger }
168*e0fe5cd4SJohannes Altmanninger 
169*e0fe5cd4SJohannes Altmanninger static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff,
170*e0fe5cd4SJohannes Altmanninger                            diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree,
171*e0fe5cd4SJohannes Altmanninger                            diff::NodeId Dst) {
172*e0fe5cd4SJohannes Altmanninger   const diff::Node &DstNode = DstTree.getNode(Dst);
173*e0fe5cd4SJohannes Altmanninger   diff::NodeId Src = Diff.getMapped(DstTree, Dst);
174*e0fe5cd4SJohannes Altmanninger   switch (DstNode.Change) {
175*e0fe5cd4SJohannes Altmanninger   case diff::None:
176*e0fe5cd4SJohannes Altmanninger     break;
177*e0fe5cd4SJohannes Altmanninger   case diff::Delete:
178*e0fe5cd4SJohannes Altmanninger     llvm_unreachable("The destination tree can't have deletions.");
179*e0fe5cd4SJohannes Altmanninger   case diff::Update:
180*e0fe5cd4SJohannes Altmanninger     OS << "Update ";
181*e0fe5cd4SJohannes Altmanninger     printNode(OS, SrcTree, Src);
182*e0fe5cd4SJohannes Altmanninger     OS << " to " << DstTree.getNodeValue(Dst) << "\n";
183*e0fe5cd4SJohannes Altmanninger     break;
184*e0fe5cd4SJohannes Altmanninger   case diff::Insert:
185*e0fe5cd4SJohannes Altmanninger   case diff::Move:
186*e0fe5cd4SJohannes Altmanninger   case diff::UpdateMove:
187*e0fe5cd4SJohannes Altmanninger     if (DstNode.Change == diff::Insert)
188*e0fe5cd4SJohannes Altmanninger       OS << "Insert";
189*e0fe5cd4SJohannes Altmanninger     else if (DstNode.Change == diff::Move)
190*e0fe5cd4SJohannes Altmanninger       OS << "Move";
191*e0fe5cd4SJohannes Altmanninger     else if (DstNode.Change == diff::UpdateMove)
192*e0fe5cd4SJohannes Altmanninger       OS << "Update and Move";
193*e0fe5cd4SJohannes Altmanninger     OS << " ";
194*e0fe5cd4SJohannes Altmanninger     printNode(OS, DstTree, Dst);
195*e0fe5cd4SJohannes Altmanninger     OS << " into ";
196*e0fe5cd4SJohannes Altmanninger     printNode(OS, DstTree, DstNode.Parent);
197*e0fe5cd4SJohannes Altmanninger     OS << " at " << DstTree.findPositionInParent(Dst) << "\n";
198*e0fe5cd4SJohannes Altmanninger     break;
199*e0fe5cd4SJohannes Altmanninger   }
200*e0fe5cd4SJohannes Altmanninger }
201*e0fe5cd4SJohannes Altmanninger 
202a75b2cacSAlex Lorenz int main(int argc, const char **argv) {
203849f20e4SJohannes Altmanninger   std::string ErrorMessage;
204849f20e4SJohannes Altmanninger   std::unique_ptr<CompilationDatabase> CommonCompilations =
205849f20e4SJohannes Altmanninger       FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage);
206849f20e4SJohannes Altmanninger   if (!CommonCompilations && !ErrorMessage.empty())
207849f20e4SJohannes Altmanninger     llvm::errs() << ErrorMessage;
208a75b2cacSAlex Lorenz   cl::HideUnrelatedOptions(ClangDiffCategory);
209a75b2cacSAlex Lorenz   if (!cl::ParseCommandLineOptions(argc, argv)) {
210a75b2cacSAlex Lorenz     cl::PrintOptionValues();
211a75b2cacSAlex Lorenz     return 1;
212a75b2cacSAlex Lorenz   }
213a75b2cacSAlex Lorenz 
214849f20e4SJohannes Altmanninger   addExtraArgs(CommonCompilations);
215849f20e4SJohannes Altmanninger 
216914a958eSJohannes Altmanninger   if (ASTDump) {
217a75b2cacSAlex Lorenz     if (!DestinationPath.empty()) {
218a75b2cacSAlex Lorenz       llvm::errs() << "Error: Please specify exactly one filename.\n";
219a75b2cacSAlex Lorenz       return 1;
220a75b2cacSAlex Lorenz     }
221849f20e4SJohannes Altmanninger     std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath);
222a75b2cacSAlex Lorenz     if (!AST)
223a75b2cacSAlex Lorenz       return 1;
224a75b2cacSAlex Lorenz     diff::SyntaxTree Tree(AST->getASTContext());
2250da12c84SJohannes Altmanninger     llvm::outs() << R"({"filename":")";
2260da12c84SJohannes Altmanninger     printJsonString(llvm::outs(), SourcePath);
2270da12c84SJohannes Altmanninger     llvm::outs() << R"(","root":)";
2280da12c84SJohannes Altmanninger     printNodeAsJson(llvm::outs(), Tree, Tree.getRootId());
2290da12c84SJohannes Altmanninger     llvm::outs() << "}\n";
230a75b2cacSAlex Lorenz     return 0;
231a75b2cacSAlex Lorenz   }
232a75b2cacSAlex Lorenz 
233a75b2cacSAlex Lorenz   if (DestinationPath.empty()) {
234a75b2cacSAlex Lorenz     llvm::errs() << "Error: Exactly two paths are required.\n";
235a75b2cacSAlex Lorenz     return 1;
236a75b2cacSAlex Lorenz   }
237a75b2cacSAlex Lorenz 
238849f20e4SJohannes Altmanninger   std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath);
239849f20e4SJohannes Altmanninger   std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath);
240a75b2cacSAlex Lorenz   if (!Src || !Dst)
241a75b2cacSAlex Lorenz     return 1;
242a75b2cacSAlex Lorenz 
243a75b2cacSAlex Lorenz   diff::ComparisonOptions Options;
244849f20e4SJohannes Altmanninger   if (MaxSize != -1)
245849f20e4SJohannes Altmanninger     Options.MaxSize = MaxSize;
246a75b2cacSAlex Lorenz   diff::SyntaxTree SrcTree(Src->getASTContext());
247a75b2cacSAlex Lorenz   diff::SyntaxTree DstTree(Dst->getASTContext());
248*e0fe5cd4SJohannes Altmanninger   diff::ASTDiff Diff(SrcTree, DstTree, Options);
249*e0fe5cd4SJohannes Altmanninger 
250*e0fe5cd4SJohannes Altmanninger   for (diff::NodeId Dst : DstTree) {
251*e0fe5cd4SJohannes Altmanninger     diff::NodeId Src = Diff.getMapped(DstTree, Dst);
252*e0fe5cd4SJohannes Altmanninger     if (Src.isValid()) {
253*e0fe5cd4SJohannes Altmanninger       llvm::outs() << "Match ";
254*e0fe5cd4SJohannes Altmanninger       printNode(llvm::outs(), SrcTree, Src);
255*e0fe5cd4SJohannes Altmanninger       llvm::outs() << " to ";
256*e0fe5cd4SJohannes Altmanninger       printNode(llvm::outs(), DstTree, Dst);
257*e0fe5cd4SJohannes Altmanninger       llvm::outs() << "\n";
258*e0fe5cd4SJohannes Altmanninger     }
259*e0fe5cd4SJohannes Altmanninger     printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst);
260*e0fe5cd4SJohannes Altmanninger   }
261*e0fe5cd4SJohannes Altmanninger   for (diff::NodeId Src : SrcTree) {
262*e0fe5cd4SJohannes Altmanninger     if (Diff.getMapped(SrcTree, Src).isInvalid()) {
263*e0fe5cd4SJohannes Altmanninger       llvm::outs() << "Delete ";
264*e0fe5cd4SJohannes Altmanninger       printNode(llvm::outs(), SrcTree, Src);
265*e0fe5cd4SJohannes Altmanninger       llvm::outs() << "\n";
266*e0fe5cd4SJohannes Altmanninger     }
267*e0fe5cd4SJohannes Altmanninger   }
268a75b2cacSAlex Lorenz 
269a75b2cacSAlex Lorenz   return 0;
270a75b2cacSAlex Lorenz }
271