//===- ClangDiff.cpp - compare source files by AST nodes ------*- C++ -*- -===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file implements a tool for syntax tree based comparison using // Tooling/ASTDiff. // //===----------------------------------------------------------------------===// #include "clang/Tooling/ASTDiff/ASTDiff.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" using namespace llvm; using namespace clang; using namespace clang::tooling; static cl::OptionCategory ClangDiffCategory("clang-diff options"); static cl::opt ASTDump("ast-dump", cl::desc("Print the internal representation of the AST as JSON."), cl::init(false), cl::cat(ClangDiffCategory)); static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); static cl::opt DestinationPath(cl::Positional, cl::desc(""), cl::Optional, cl::cat(ClangDiffCategory)); static cl::opt MaxSize("s", cl::desc(""), cl::Optional, cl::init(-1), cl::cat(ClangDiffCategory)); static cl::opt BuildPath("p", cl::desc("Build path"), cl::init(""), cl::Optional, cl::cat(ClangDiffCategory)); static cl::list ArgsAfter( "extra-arg", cl::desc("Additional argument to append to the compiler command line"), cl::cat(ClangDiffCategory)); static cl::list ArgsBefore( "extra-arg-before", cl::desc("Additional argument to prepend to the compiler command line"), cl::cat(ClangDiffCategory)); static void addExtraArgs(std::unique_ptr &Compilations) { if (!Compilations) return; auto AdjustingCompilations = llvm::make_unique( std::move(Compilations)); AdjustingCompilations->appendArgumentsAdjuster( getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN)); AdjustingCompilations->appendArgumentsAdjuster( getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); Compilations = std::move(AdjustingCompilations); } static std::unique_ptr getAST(const std::unique_ptr &CommonCompilations, const StringRef Filename) { std::string ErrorMessage; std::unique_ptr Compilations; if (!CommonCompilations) { Compilations = CompilationDatabase::autoDetectFromSource( BuildPath.empty() ? Filename : BuildPath, ErrorMessage); if (!Compilations) { llvm::errs() << "Error while trying to load a compilation database, running " "without flags.\n" << ErrorMessage; Compilations = llvm::make_unique( ".", std::vector()); } } addExtraArgs(Compilations); std::array Files = {{Filename}}; ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files); std::vector> ASTs; Tool.buildASTs(ASTs); if (ASTs.size() != Files.size()) return nullptr; return std::move(ASTs[0]); } static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); } static void printJsonString(raw_ostream &OS, const StringRef Str) { for (char C : Str) { switch (C) { case '"': OS << R"(\")"; break; case '\\': OS << R"(\\)"; break; case '\n': OS << R"(\n)"; break; case '\t': OS << R"(\t)"; break; default: if ('\x00' <= C && C <= '\x1f') { OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C); } else { OS << C; } } } } static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree, diff::NodeId Id) { const diff::Node &N = Tree.getNode(Id); OS << R"("id":)" << int(Id); OS << R"(,"type":")" << N.getTypeLabel() << '"'; auto Offsets = Tree.getSourceRangeOffsets(N); OS << R"(,"begin":)" << Offsets.first; OS << R"(,"end":)" << Offsets.second; std::string Value = Tree.getNodeValue(N.ASTNode); if (!Value.empty()) { OS << R"(,"value":")"; printJsonString(OS, Value); OS << '"'; } } static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree, diff::NodeId Id) { const diff::Node &N = Tree.getNode(Id); OS << "{"; printNodeAttributes(OS, Tree, Id); OS << R"(,"children":[)"; if (N.Children.size() > 0) { printNodeAsJson(OS, Tree, N.Children[0]); for (size_t I = 1, E = N.Children.size(); I < E; ++I) { OS << ","; printNodeAsJson(OS, Tree, N.Children[I]); } } OS << "]}"; } int main(int argc, const char **argv) { std::string ErrorMessage; std::unique_ptr CommonCompilations = FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); if (!CommonCompilations && !ErrorMessage.empty()) llvm::errs() << ErrorMessage; cl::HideUnrelatedOptions(ClangDiffCategory); if (!cl::ParseCommandLineOptions(argc, argv)) { cl::PrintOptionValues(); return 1; } addExtraArgs(CommonCompilations); if (ASTDump) { if (!DestinationPath.empty()) { llvm::errs() << "Error: Please specify exactly one filename.\n"; return 1; } std::unique_ptr AST = getAST(CommonCompilations, SourcePath); if (!AST) return 1; diff::SyntaxTree Tree(AST->getASTContext()); llvm::outs() << R"({"filename":")"; printJsonString(llvm::outs(), SourcePath); llvm::outs() << R"(","root":)"; printNodeAsJson(llvm::outs(), Tree, Tree.getRootId()); llvm::outs() << "}\n"; return 0; } if (DestinationPath.empty()) { llvm::errs() << "Error: Exactly two paths are required.\n"; return 1; } std::unique_ptr Src = getAST(CommonCompilations, SourcePath); std::unique_ptr Dst = getAST(CommonCompilations, DestinationPath); if (!Src || !Dst) return 1; diff::ComparisonOptions Options; if (MaxSize != -1) Options.MaxSize = MaxSize; diff::SyntaxTree SrcTree(Src->getASTContext()); diff::SyntaxTree DstTree(Dst->getASTContext()); diff::ASTDiff DiffTool(SrcTree, DstTree, Options); for (const auto &Match : DiffTool.getMatches()) DiffTool.printMatch(llvm::outs(), Match); for (const auto &Change : DiffTool.getChanges()) DiffTool.printChange(llvm::outs(), Change); return 0; }