diff options
Diffstat (limited to 'tools/clang-diff/ClangDiff.cpp')
-rw-r--r-- | tools/clang-diff/ClangDiff.cpp | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/tools/clang-diff/ClangDiff.cpp b/tools/clang-diff/ClangDiff.cpp new file mode 100644 index 000000000000..4e2150aa457d --- /dev/null +++ b/tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,537 @@ +//===- 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<bool> + ASTDump("ast-dump", + cl::desc("Print the internal representation of the AST."), + cl::init(false), cl::cat(ClangDiffCategory)); + +static cl::opt<bool> ASTDumpJson( + "ast-dump-json", + cl::desc("Print the internal representation of the AST as JSON."), + cl::init(false), cl::cat(ClangDiffCategory)); + +static cl::opt<bool> PrintMatches("dump-matches", + cl::desc("Print the matched nodes."), + cl::init(false), cl::cat(ClangDiffCategory)); + +static cl::opt<bool> HtmlDiff("html", + cl::desc("Output a side-by-side diff in HTML."), + cl::init(false), cl::cat(ClangDiffCategory)); + +static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"), + cl::Required, + cl::cat(ClangDiffCategory)); + +static cl::opt<std::string> DestinationPath(cl::Positional, + cl::desc("<destination>"), + cl::Optional, + cl::cat(ClangDiffCategory)); + +static cl::opt<std::string> StopAfter("stop-diff-after", + cl::desc("<topdown|bottomup>"), + cl::Optional, cl::init(""), + cl::cat(ClangDiffCategory)); + +static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional, + cl::init(-1), cl::cat(ClangDiffCategory)); + +static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""), + cl::Optional, cl::cat(ClangDiffCategory)); + +static cl::list<std::string> ArgsAfter( + "extra-arg", + cl::desc("Additional argument to append to the compiler command line"), + cl::cat(ClangDiffCategory)); + +static cl::list<std::string> ArgsBefore( + "extra-arg-before", + cl::desc("Additional argument to prepend to the compiler command line"), + cl::cat(ClangDiffCategory)); + +static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) { + if (!Compilations) + return; + auto AdjustingCompilations = + llvm::make_unique<ArgumentsAdjustingCompilations>( + std::move(Compilations)); + AdjustingCompilations->appendArgumentsAdjuster( + getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN)); + AdjustingCompilations->appendArgumentsAdjuster( + getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); + Compilations = std::move(AdjustingCompilations); +} + +static std::unique_ptr<ASTUnit> +getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations, + const StringRef Filename) { + std::string ErrorMessage; + std::unique_ptr<CompilationDatabase> 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<clang::tooling::FixedCompilationDatabase>( + ".", std::vector<std::string>()); + } + } + addExtraArgs(Compilations); + std::array<std::string, 1> Files = {{Filename}}; + ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files); + std::vector<std::unique_ptr<ASTUnit>> 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 const char HtmlDiffHeader[] = R"( +<html> +<head> +<meta charset='utf-8'/> +<style> +span.d { color: red; } +span.u { color: #cc00cc; } +span.i { color: green; } +span.m { font-weight: bold; } +span { font-weight: normal; color: black; } +div.code { + width: 48%; + height: 98%; + overflow: scroll; + float: left; + padding: 0 0 0.5% 0.5%; + border: solid 2px LightGrey; + border-radius: 5px; +} +</style> +</head> +<script type='text/javascript'> +highlightStack = [] +function clearHighlight() { + while (highlightStack.length) { + var [l, r] = highlightStack.pop() + document.getElementById(l).style.backgroundColor = 'inherit' + if (r[1] != '-') + document.getElementById(r).style.backgroundColor = 'inherit' + } +} +function highlight(event) { + var id = event.target['id'] + doHighlight(id) +} +function doHighlight(id) { + clearHighlight() + source = document.getElementById(id) + if (!source.attributes['tid']) + return + var mapped = source + while (mapped && mapped.parentElement && mapped.attributes['tid'].value.substr(1) === '-1') + mapped = mapped.parentElement + var tid = null, target = null + if (mapped) { + tid = mapped.attributes['tid'].value + target = document.getElementById(tid) + } + if (source.parentElement && source.parentElement.classList.contains('code')) + return + source.style.backgroundColor = 'lightgrey' + source.scrollIntoView() + if (target) { + if (mapped === source) + target.style.backgroundColor = 'lightgrey' + target.scrollIntoView() + } + highlightStack.push([id, tid]) + location.hash = '#' + id +} +function scrollToBoth() { + doHighlight(location.hash.substr(1)) +} +function changed(elem) { + return elem.classList.length == 0 +} +function nextChangedNode(prefix, increment, number) { + do { + number += increment + var elem = document.getElementById(prefix + number) + } while(elem && !changed(elem)) + return elem ? number : null +} +function handleKey(e) { + var down = e.code === "KeyJ" + var up = e.code === "KeyK" + if (!down && !up) + return + var id = highlightStack[0] ? highlightStack[0][0] : 'R0' + var oldelem = document.getElementById(id) + var number = parseInt(id.substr(1)) + var increment = down ? 1 : -1 + var lastnumber = number + var prefix = id[0] + do { + number = nextChangedNode(prefix, increment, number) + var elem = document.getElementById(prefix + number) + if (up && elem) { + while (elem.parentElement && changed(elem.parentElement)) + elem = elem.parentElement + number = elem.id.substr(1) + } + } while ((down && id !== 'R0' && oldelem.contains(elem))) + if (!number) + number = lastnumber + elem = document.getElementById(prefix + number) + doHighlight(prefix + number) +} +window.onload = scrollToBoth +window.onkeydown = handleKey +</script> +<body> +<div onclick='highlight(event)'> +)"; + +static void printHtml(raw_ostream &OS, char C) { + switch (C) { + case '&': + OS << "&"; + break; + case '<': + OS << "<"; + break; + case '>': + OS << ">"; + break; + case '\'': + OS << "'"; + break; + case '"': + OS << """; + break; + default: + OS << C; + } +} + +static void printHtml(raw_ostream &OS, const StringRef Str) { + for (char C : Str) + printHtml(OS, C); +} + +static std::string getChangeKindAbbr(diff::ChangeKind Kind) { + switch (Kind) { + case diff::None: + return ""; + case diff::Delete: + return "d"; + case diff::Update: + return "u"; + case diff::Insert: + return "i"; + case diff::Move: + return "m"; + case diff::UpdateMove: + return "u m"; + } + llvm_unreachable("Invalid enumeration value."); +} + +static unsigned printHtmlForNode(raw_ostream &OS, const diff::ASTDiff &Diff, + diff::SyntaxTree &Tree, bool IsLeft, + diff::NodeId Id, unsigned Offset) { + const diff::Node &Node = Tree.getNode(Id); + char MyTag, OtherTag; + diff::NodeId LeftId, RightId; + diff::NodeId TargetId = Diff.getMapped(Tree, Id); + if (IsLeft) { + MyTag = 'L'; + OtherTag = 'R'; + LeftId = Id; + RightId = TargetId; + } else { + MyTag = 'R'; + OtherTag = 'L'; + LeftId = TargetId; + RightId = Id; + } + unsigned Begin, End; + std::tie(Begin, End) = Tree.getSourceRangeOffsets(Node); + const SourceManager &SrcMgr = Tree.getASTContext().getSourceManager(); + auto Code = SrcMgr.getBuffer(SrcMgr.getMainFileID())->getBuffer(); + for (; Offset < Begin; ++Offset) + printHtml(OS, Code[Offset]); + OS << "<span id='" << MyTag << Id << "' " + << "tid='" << OtherTag << TargetId << "' "; + OS << "title='"; + printHtml(OS, Node.getTypeLabel()); + OS << "\n" << LeftId << " -> " << RightId; + std::string Value = Tree.getNodeValue(Node); + if (!Value.empty()) { + OS << "\n"; + printHtml(OS, Value); + } + OS << "'"; + if (Node.Change != diff::None) + OS << " class='" << getChangeKindAbbr(Node.Change) << "'"; + OS << ">"; + + for (diff::NodeId Child : Node.Children) + Offset = printHtmlForNode(OS, Diff, Tree, IsLeft, Child, Offset); + + for (; Offset < End; ++Offset) + printHtml(OS, Code[Offset]); + if (Id == Tree.getRootId()) { + End = Code.size(); + for (; Offset < End; ++Offset) + printHtml(OS, Code[Offset]); + } + OS << "</span>"; + return Offset; +} + +static void printJsonString(raw_ostream &OS, const StringRef Str) { + for (signed 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); + 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); + auto Identifier = N.getIdentifier(); + auto QualifiedIdentifier = N.getQualifiedIdentifier(); + if (Identifier) { + OS << R"(,"identifier":")"; + printJsonString(OS, *Identifier); + OS << R"(")"; + if (QualifiedIdentifier && *Identifier != *QualifiedIdentifier) { + OS << R"(,"qualified_identifier":")"; + printJsonString(OS, *QualifiedIdentifier); + OS << R"(")"; + } + } + 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 << "]}"; +} + +static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree, + diff::NodeId Id) { + if (Id.isInvalid()) { + OS << "None"; + return; + } + OS << Tree.getNode(Id).getTypeLabel(); + std::string Value = Tree.getNodeValue(Id); + if (!Value.empty()) + OS << ": " << Value; + OS << "(" << Id << ")"; +} + +static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) { + for (diff::NodeId Id : Tree) { + for (int I = 0; I < Tree.getNode(Id).Depth; ++I) + OS << " "; + printNode(OS, Tree, Id); + OS << "\n"; + } +} + +static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff, + diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree, + diff::NodeId Dst) { + const diff::Node &DstNode = DstTree.getNode(Dst); + diff::NodeId Src = Diff.getMapped(DstTree, Dst); + switch (DstNode.Change) { + case diff::None: + break; + case diff::Delete: + llvm_unreachable("The destination tree can't have deletions."); + case diff::Update: + OS << "Update "; + printNode(OS, SrcTree, Src); + OS << " to " << DstTree.getNodeValue(Dst) << "\n"; + break; + case diff::Insert: + case diff::Move: + case diff::UpdateMove: + if (DstNode.Change == diff::Insert) + OS << "Insert"; + else if (DstNode.Change == diff::Move) + OS << "Move"; + else if (DstNode.Change == diff::UpdateMove) + OS << "Update and Move"; + OS << " "; + printNode(OS, DstTree, Dst); + OS << " into "; + printNode(OS, DstTree, DstNode.Parent); + OS << " at " << DstTree.findPositionInParent(Dst) << "\n"; + break; + } +} + +int main(int argc, const char **argv) { + std::string ErrorMessage; + std::unique_ptr<CompilationDatabase> 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 || ASTDumpJson) { + if (!DestinationPath.empty()) { + llvm::errs() << "Error: Please specify exactly one filename.\n"; + return 1; + } + std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath); + if (!AST) + return 1; + diff::SyntaxTree Tree(AST->getASTContext()); + if (ASTDump) { + printTree(llvm::outs(), Tree); + return 0; + } + 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<ASTUnit> Src = getAST(CommonCompilations, SourcePath); + std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath); + if (!Src || !Dst) + return 1; + + diff::ComparisonOptions Options; + if (MaxSize != -1) + Options.MaxSize = MaxSize; + if (!StopAfter.empty()) { + if (StopAfter == "topdown") + Options.StopAfterTopDown = true; + else if (StopAfter != "bottomup") { + llvm::errs() << "Error: Invalid argument for -stop-after\n"; + return 1; + } + } + diff::SyntaxTree SrcTree(Src->getASTContext()); + diff::SyntaxTree DstTree(Dst->getASTContext()); + diff::ASTDiff Diff(SrcTree, DstTree, Options); + + if (HtmlDiff) { + llvm::outs() << HtmlDiffHeader << "<pre>"; + llvm::outs() << "<div id='L' class='code'>"; + printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0); + llvm::outs() << "</div>"; + llvm::outs() << "<div id='R' class='code'>"; + printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(), + 0); + llvm::outs() << "</div>"; + llvm::outs() << "</pre></div></body></html>\n"; + return 0; + } + + for (diff::NodeId Dst : DstTree) { + diff::NodeId Src = Diff.getMapped(DstTree, Dst); + if (PrintMatches && Src.isValid()) { + llvm::outs() << "Match "; + printNode(llvm::outs(), SrcTree, Src); + llvm::outs() << " to "; + printNode(llvm::outs(), DstTree, Dst); + llvm::outs() << "\n"; + } + printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst); + } + for (diff::NodeId Src : SrcTree) { + if (Diff.getMapped(SrcTree, Src).isInvalid()) { + llvm::outs() << "Delete "; + printNode(llvm::outs(), SrcTree, Src); + llvm::outs() << "\n"; + } + } + + return 0; +} |