johannes updated this revision to Diff 112926.
johannes edited the summary of this revision.
johannes added a comment.

split to ASTDiff/ASTPatch


https://reviews.llvm.org/D37005

Files:
  include/clang/Tooling/ASTDiff/ASTDiff.h
  include/clang/Tooling/ASTDiff/ASTPatch.h
  lib/Tooling/ASTDiff/ASTDiff.cpp
  lib/Tooling/ASTDiff/ASTPatch.cpp
  lib/Tooling/ASTDiff/CMakeLists.txt
  test/Tooling/clang-diff-patch.test
  tools/clang-diff/CMakeLists.txt
  tools/clang-diff/ClangDiff.cpp
  unittests/Tooling/ASTDiffTest.cpp
  unittests/Tooling/CMakeLists.txt

Index: unittests/Tooling/CMakeLists.txt
===================================================================
--- unittests/Tooling/CMakeLists.txt
+++ unittests/Tooling/CMakeLists.txt
@@ -11,6 +11,7 @@
 endif()
 
 add_clang_unittest(ToolingTests
+  ASTDiffTest.cpp
   ASTSelectionTest.cpp
   CastExprTest.cpp
   CommentHandlerTest.cpp
@@ -43,4 +44,5 @@
   clangTooling
   clangToolingCore
   clangToolingRefactor
+  clangToolingASTDiff
   )
Index: unittests/Tooling/ASTDiffTest.cpp
===================================================================
--- /dev/null
+++ unittests/Tooling/ASTDiffTest.cpp
@@ -0,0 +1,86 @@
+//===- unittest/Tooling/ASTDiffTest.cpp -----------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/ASTDiff/ASTDiff.h"
+#include "clang/Tooling/ASTDiff/ASTPatch.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+using namespace tooling;
+
+static std::string patchResult(std::array<std::string, 3> Codes) {
+  diff::SyntaxTree Trees[3];
+  std::unique_ptr<ASTUnit> ASTs[3];
+  std::vector<std::string> Args = {};
+  for (int I = 0; I < 3; I++) {
+    ASTs[I] = buildASTFromCode(Codes[I]);
+    if (!ASTs[I]) {
+      llvm::errs() << "Failed to build AST from code:\n" << Codes[I] << "\n";
+      return "";
+    }
+    Trees[I] = diff::SyntaxTree(*ASTs[I]);
+  }
+
+  diff::ComparisonOptions Options;
+  std::string TargetDstCode;
+  llvm::raw_string_ostream OS(TargetDstCode);
+  if (!diff::patch(/*ModelSrc=*/Trees[0], /*ModelDst=*/Trees[1],
+                   /*TargetSrc=*/Trees[2], Options, OS))
+    return "";
+  return OS.str();
+}
+
+// abstract the EXPECT_EQ call so that the code snippets align properly
+// use macros for this to make test failures have proper line numbers
+#define PATCH(Preamble, ModelSrc, ModelDst, Target, Expected)                  \
+  EXPECT_EQ(patchResult({{std::string(Preamble) + ModelSrc,                    \
+                          std::string(Preamble) + ModelDst,                    \
+                          std::string(Preamble) + Target}}),                   \
+            std::string(Preamble) + Expected)
+
+TEST(ASTDiff, TestDeleteArguments) {
+  PATCH(R"(void printf(const char *, ...);)",
+        R"(void foo(int x) { printf("%d", x, x); })",
+        R"(void foo(int x) { printf("%d", x); })",
+        R"(void foo(int x) { printf("different string %d", x, x); })",
+        R"(void foo(int x) { printf("different string %d", x); })");
+
+  PATCH(R"(void foo(...);)",
+        R"(void test1() { foo ( 1 + 1); })",
+        R"(void test1() { foo ( ); })",
+        R"(void test2() { foo ( 1 + 1 ); })",
+        R"(void test2() { foo (  ); })");
+
+  PATCH(R"(void foo(...);)",
+        R"(void test1() { foo (1, 2 + 2); })",
+        R"(void test1() { foo (2 + 2); })",
+        R"(void test2() { foo (/*L*/ 0 /*R*/ , 2 + 2); })",
+        R"(void test2() { foo (/*L*/  2 + 2); })");
+
+  PATCH(R"(void foo(...);)",
+        R"(void test1() { foo (1, 2); })",
+        R"(void test1() { foo (1); })",
+        R"(void test2() { foo (0, /*L*/ 0 /*R*/); })",
+        R"(void test2() { foo (0 /*R*/); })");
+}
+
+TEST(ASTDiff, TestDeleteDecls) {
+  PATCH(R"()",
+        R"()",
+        R"()",
+        R"()",
+        R"()");
+
+  PATCH(R"()",
+        R"(void foo(){})",
+        R"()",
+        R"(int x; void foo() {;;} int y;)",
+        R"(int x;  int y;)");
+}
Index: tools/clang-diff/ClangDiff.cpp
===================================================================
--- tools/clang-diff/ClangDiff.cpp
+++ tools/clang-diff/ClangDiff.cpp
@@ -13,6 +13,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/Tooling/ASTDiff/ASTDiff.h"
+#include "clang/Tooling/ASTDiff/ASTPatch.h"
 #include "clang/Tooling/CommonOptionsParser.h"
 #include "clang/Tooling/Tooling.h"
 #include "llvm/Support/CommandLine.h"
@@ -42,6 +43,12 @@
                               cl::desc("Output a side-by-side diff in HTML."),
                               cl::init(false), cl::cat(ClangDiffCategory));
 
+static cl::opt<std::string>
+    Patch("patch",
+          cl::desc("Try to apply the edit actions between the two input "
+                   "files to the specified target."),
+          cl::desc("<target>"), cl::cat(ClangDiffCategory));
+
 static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"),
                                        cl::Required,
                                        cl::cat(ClangDiffCategory));
@@ -563,6 +570,16 @@
   }
   diff::SyntaxTree SrcTree(*Src);
   diff::SyntaxTree DstTree(*Dst);
+
+  if (!Patch.empty()) {
+    auto Target = getAST(CommonCompilations, Patch);
+    if (!Target)
+      return 1;
+    diff::SyntaxTree TargetTree(*Target);
+    diff::patch(SrcTree, DstTree, TargetTree, Options, llvm::outs());
+    return 0;
+  }
+
   diff::ASTDiff Diff(SrcTree, DstTree, Options);
 
   if (HtmlDiff) {
Index: tools/clang-diff/CMakeLists.txt
===================================================================
--- tools/clang-diff/CMakeLists.txt
+++ tools/clang-diff/CMakeLists.txt
@@ -9,6 +9,7 @@
 target_link_libraries(clang-diff
   clangBasic
   clangFrontend
+  clangRewrite
   clangTooling
   clangToolingASTDiff
   )
Index: test/Tooling/clang-diff-patch.test
===================================================================
--- /dev/null
+++ test/Tooling/clang-diff-patch.test
@@ -0,0 +1,7 @@
+// compare the file with an empty file, patch it to remove all code
+RUN: echo > %t.dst.cpp
+RUN: clang-diff %S/clang-diff-ast.cpp %t.dst.cpp -patch %S/clang-diff-ast.cpp \
+RUN: -- -std=c++11 > %t.result.cpp
+// the resulting file should not contain anything other than comments and
+// whitespace
+RUN: cat %t.result.cpp | grep -v '^#' | grep -v '^\s*//' | not grep -v '^\s*$'
Index: lib/Tooling/ASTDiff/CMakeLists.txt
===================================================================
--- lib/Tooling/ASTDiff/CMakeLists.txt
+++ lib/Tooling/ASTDiff/CMakeLists.txt
@@ -4,8 +4,11 @@
 
 add_clang_library(clangToolingASTDiff
   ASTDiff.cpp
+  ASTPatch.cpp
   LINK_LIBS
   clangBasic
   clangAST
   clangLex
+  clangRewrite
+  clangToolingCore
   )
Index: lib/Tooling/ASTDiff/ASTPatch.cpp
===================================================================
--- /dev/null
+++ lib/Tooling/ASTDiff/ASTPatch.cpp
@@ -0,0 +1,120 @@
+//===- ASTPatch.cpp - Structural patching based on ASTDiff ----*- C++ -*- -===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/ASTDiff/ASTPatch.h"
+
+#include "clang/AST/DeclTemplate.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "clang/Tooling/Core/Replacement.h"
+
+using namespace llvm;
+using namespace clang;
+using namespace tooling;
+
+namespace clang {
+namespace diff {
+
+namespace {
+struct Patcher {
+  SyntaxTree &ModelSrc, &ModelDst, &Target;
+  const ComparisonOptions &Options;
+  raw_ostream &OS;
+  SourceManager &SrcMgr;
+  const LangOptions &LangOpts;
+  Replacements Replaces;
+  SyntaxTree ModelSrcCopy;
+  ASTDiff ModelDiff, ModelTargetDiff;
+
+  Patcher(SyntaxTree &ModelSrc, SyntaxTree &ModelDst, SyntaxTree &Target,
+          const ComparisonOptions &Options, raw_ostream &OS)
+      : ModelSrc(ModelSrc), ModelDst(ModelDst), Target(Target),
+        Options(Options), OS(OS), SrcMgr(Target.getSourceManager()),
+        LangOpts(Target.getLangOpts()), ModelSrcCopy(ModelSrc),
+        ModelDiff(ModelSrc, ModelDst, Options),
+        ModelTargetDiff(ModelSrcCopy, Target, Options) {}
+
+  bool apply() {
+    addDeletions();
+    Rewriter Rewrite(SrcMgr, LangOpts);
+    if (!applyAllReplacements(Replaces, Rewrite)) {
+      llvm::errs() << "Error: Failed to apply replacements.\n";
+      return false;
+    }
+    Rewrite.getEditBuffer(SrcMgr.getMainFileID()).write(OS);
+    return true;
+  }
+
+private:
+  void addDeletions() {
+    for (NodeId Id = ModelSrc.getRootId(), E = ModelSrc.getSize(); Id < E;
+         ++Id) {
+      const Node &ModelNode = ModelSrc.getNode(Id);
+      if (ModelNode.Change != Delete)
+        continue;
+      NodeId TargetId = ModelTargetDiff.getMapped(ModelSrcCopy, Id);
+      if (TargetId.isInvalid())
+        continue;
+      Replacement R(SrcMgr, findRangeForDeletion(TargetId), "", LangOpts);
+      if (Replaces.add(R))
+        llvm::errs() << "Info: Failed to add replacement.\n";
+      Id = ModelNode.RightMostDescendant;
+    }
+  }
+
+  CharSourceRange findRangeForDeletion(NodeId Id) {
+    const Node &N = Target.getNode(Id);
+    SourceRange Range = Target.getSourceRange(N);
+    if (N.Parent.isInvalid())
+      return {Range, false};
+    const Node &Parent = Target.getNode(N.Parent);
+    auto &DTN = N.ASTNode;
+    auto &ParentDTN = Parent.ASTNode;
+    size_t SiblingIndex = Target.findPositionInParent(Id);
+    const auto &Siblings = Parent.Children;
+    // Remove the comma if the location is within a comma-separated list of at
+    // least size 2 (minus the callee for CallExpr).
+    if (ParentDTN.get<CallExpr>() && Siblings.size() > 2) {
+      bool LastSibling = SiblingIndex == Siblings.size() - 1;
+      SourceLocation CommaLoc = Range.getEnd();
+      if (LastSibling)
+        CommaLoc =
+            Target.getSourceRange(Target.getNode(Siblings[SiblingIndex - 1]))
+                .getEnd();
+      CommaLoc =
+          Lexer::findLocationAfterToken(CommaLoc, tok::comma, SrcMgr, LangOpts,
+                                        /*SkipTrailingWhitespaceAndNewLine=*/
+                                        false);
+      assert(CommaLoc.isValid() && "Adjacent token is not a comma.");
+      if (LastSibling)
+        Range.setBegin(
+            CommaLoc.getLocWithOffset(-static_cast<int>(strlen(","))));
+      else
+        Range.setEnd(CommaLoc);
+    } else if (DTN.get<VarDecl>() or
+               (DTN.get<FunctionDecl>() and
+                not DTN.get<FunctionDecl>()->isThisDeclarationADefinition()) or
+               DTN.get<TypeDecl>() or DTN.get<UsingDirectiveDecl>() or
+               DTN.get<ClassTemplateDecl>()) {
+      SourceLocation SemicolonLoc = Lexer::findLocationAfterToken(
+          Range.getEnd(), tok::semi, SrcMgr, LangOpts,
+          /*SkipTrailingWhitespaceAndNewLine=*/false);
+      Range.setEnd(SemicolonLoc);
+    }
+    return CharSourceRange::getTokenRange(Range);
+  }
+};
+} // end anonymous namespace
+
+bool patch(SyntaxTree &ModelSrc, SyntaxTree &ModelDst, SyntaxTree &Target,
+           const ComparisonOptions &Options, raw_ostream &OS) {
+  return Patcher(ModelSrc, ModelDst, Target, Options, OS).apply();
+}
+
+} // end namespace diff
+} // end namespace clang
Index: lib/Tooling/ASTDiff/ASTDiff.cpp
===================================================================
--- lib/Tooling/ASTDiff/ASTDiff.cpp
+++ lib/Tooling/ASTDiff/ASTDiff.cpp
@@ -139,6 +139,7 @@
        typename std::enable_if<std::is_base_of<Decl, T>::value, T>::type *Node,
        ASTUnit &AST)
       : Impl(Parent, dyn_cast<Decl>(Node), AST) {}
+  explicit Impl(SyntaxTree *Parent, const Impl &Other);
 
   SyntaxTree *Parent;
   ASTUnit &AST;
@@ -175,6 +176,8 @@
 
   HashType hashNode(const Node &N) const;
 
+  SourceRange getSourceRange(const Node &N) const;
+
 private:
   void initTree();
   void setLeftMostDescendants();
@@ -337,6 +340,15 @@
   initTree();
 }
 
+SyntaxTree::Impl::Impl(SyntaxTree *Parent, const Impl &Other)
+    : Impl(Parent, Other.AST) {
+  Nodes = Other.Nodes;
+  Leaves = Other.Leaves;
+  PostorderIds = Other.PostorderIds;
+  NodesBfs = Other.NodesBfs;
+  TemplateArgumentLocations = TemplateArgumentLocations;
+}
+
 static std::vector<NodeId> getSubtreePostorder(const SyntaxTree::Impl &Tree,
                                                NodeId Root) {
   std::vector<NodeId> Postorder;
@@ -638,6 +650,19 @@
   return HashResult;
 }
 
+SourceRange SyntaxTree::Impl::getSourceRange(const Node &N) const {
+  SourceRange Range;
+  if (N.ASTNode.get<TemplateArgument>())
+    Range = TemplateArgumentLocations.at(&N - &Nodes[0]);
+  else {
+    Range = N.ASTNode.getSourceRange();
+    if (auto *ThisExpr = N.ASTNode.get<CXXThisExpr>())
+      if (ThisExpr->isImplicit())
+        Range.setEnd(Range.getBegin());
+  }
+  return Range;
+}
+
 /// Identifies a node in a subtree by its postorder offset, starting at 1.
 struct SNodeId {
   int Id = 0;
@@ -1210,14 +1235,31 @@
   return DiffImpl->getMapped(SourceTree.TreeImpl, Id);
 }
 
+SyntaxTree::SyntaxTree() : TreeImpl(nullptr) {}
+
 SyntaxTree::SyntaxTree(ASTUnit &AST)
     : TreeImpl(llvm::make_unique<SyntaxTree::Impl>(
           this, AST.getASTContext().getTranslationUnitDecl(), AST)) {}
 
+SyntaxTree::SyntaxTree(SyntaxTree &&Other) = default;
+
+SyntaxTree &SyntaxTree::operator=(SyntaxTree &&Other) = default;
+
+SyntaxTree::SyntaxTree(const SyntaxTree &Other)
+    : TreeImpl(llvm::make_unique<SyntaxTree::Impl>(this, *Other.TreeImpl)) {}
+
 SyntaxTree::~SyntaxTree() = default;
 
 ASTUnit &SyntaxTree::getASTUnit() const { return TreeImpl->AST; }
 
+SourceManager &SyntaxTree::getSourceManager() const {
+  return TreeImpl->AST.getSourceManager();
+}
+
+const LangOptions &SyntaxTree::getLangOpts() const {
+  return TreeImpl->AST.getLangOpts();
+}
+
 const ASTContext &SyntaxTree::getASTContext() const {
   return TreeImpl->AST.getASTContext();
 }
@@ -1237,19 +1279,15 @@
   return TreeImpl->findPositionInParent(Id);
 }
 
+SourceRange SyntaxTree::getSourceRange(const Node &N) const {
+  return TreeImpl->getSourceRange(N);
+}
+
 std::pair<unsigned, unsigned>
 SyntaxTree::getSourceRangeOffsets(const Node &N) const {
   const SourceManager &SrcMgr = TreeImpl->AST.getSourceManager();
-  SourceRange Range;
-  if (auto *Arg = N.ASTNode.get<TemplateArgument>())
-    Range = TreeImpl->TemplateArgumentLocations.at(&N - &TreeImpl->Nodes[0]);
-  else {
-    Range = N.ASTNode.getSourceRange();
-    if (auto *ThisExpr = N.ASTNode.get<CXXThisExpr>())
-      if (ThisExpr->isImplicit())
-        Range.setEnd(Range.getBegin());
-  }
-  Range = getSourceExtent(TreeImpl->AST, Range);
+  SourceRange Range =
+      getSourceExtent(TreeImpl->AST, TreeImpl->getSourceRange(N));
   unsigned Begin = SrcMgr.getFileOffset(Range.getBegin());
   unsigned End = SrcMgr.getFileOffset(Range.getEnd());
   return {Begin, End};
Index: include/clang/Tooling/ASTDiff/ASTPatch.h
===================================================================
--- /dev/null
+++ include/clang/Tooling/ASTDiff/ASTPatch.h
@@ -0,0 +1,25 @@
+//===- ASTDiff.h - Structural patching based on ASTDiff -------*- C++ -*- -===//
+//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLING_ASTDIFF_ASTPATCH_H
+#define LLVM_CLANG_TOOLING_ASTDIFF_ASTPATCH_H
+
+#include "clang/Tooling/ASTDiff/ASTDiff.h"
+
+namespace clang {
+namespace diff {
+
+bool patch(SyntaxTree &ModelSrc, SyntaxTree &ModelDst, SyntaxTree &TargetSrc,
+           const ComparisonOptions &Options, raw_ostream &OS);
+
+} // end namespace diff
+} // end namespace clang
+
+#endif // LLVM_CLANG_TOOLING_ASTDIFF_ASTPATCH_H
Index: include/clang/Tooling/ASTDiff/ASTDiff.h
===================================================================
--- include/clang/Tooling/ASTDiff/ASTDiff.h
+++ include/clang/Tooling/ASTDiff/ASTDiff.h
@@ -21,6 +21,7 @@
 #define LLVM_CLANG_TOOLING_ASTDIFF_ASTDIFF_H
 
 #include "clang/Frontend/ASTUnit.h"
+#include "clang/Rewrite/Core/Rewriter.h"
 #include "clang/Tooling/ASTDiff/ASTDiffInternal.h"
 
 namespace clang {
@@ -69,17 +70,23 @@
 /// They can be constructed from any Decl or Stmt.
 class SyntaxTree {
 public:
+  /// Empty (invalid) SyntaxTree.
+  SyntaxTree();
   /// Constructs a tree from a translation unit.
   SyntaxTree(ASTUnit &AST);
   /// Constructs a tree from any AST node.
   template <class T>
   SyntaxTree(T *Node, ASTUnit &AST)
       : TreeImpl(llvm::make_unique<Impl>(this, Node, AST)) {}
-  SyntaxTree(SyntaxTree &&Other) = default;
+  SyntaxTree(SyntaxTree &&Other);
+  SyntaxTree &operator=(SyntaxTree &&Other);
+  explicit SyntaxTree(const SyntaxTree &Other);
   ~SyntaxTree();
 
   ASTUnit &getASTUnit() const;
   const ASTContext &getASTContext() const;
+  SourceManager &getSourceManager() const;
+  const LangOptions &getLangOpts() const;
   StringRef getFilename() const;
 
   int getSize() const;
@@ -93,7 +100,7 @@
 
   /// Returns the range that contains the text that is associated with this
   /// node.
-  /* SourceRange getSourceRange(const Node &N) const; */
+  SourceRange getSourceRange(const Node &N) const;
   /// Returns the offsets for the range returned by getSourceRange.
   std::pair<unsigned, unsigned> getSourceRangeOffsets(const Node &N) const;
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to