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