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; +}  | 
