johannes updated this revision to Diff 113078.
johannes added a comment.
fixes
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,85 @@
+//===- 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) {
+ llvm::Optional<diff::SyntaxTree> Trees[3];
+ std::unique_ptr<ASTUnit> ASTs[3];
+ 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].emplace(*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,16 @@
return HashResult;
}
+SourceRange SyntaxTree::Impl::getSourceRange(const Node &N) const {
+ if (N.ASTNode.get<TemplateArgument>())
+ return TemplateArgumentLocations.at(&N - &Nodes[0]);
+ SourceRange 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;
@@ -1214,10 +1236,21 @@
: TreeImpl(llvm::make_unique<SyntaxTree::Impl>(
this, AST.getASTContext().getTranslationUnitDecl(), AST)) {}
+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 +1270,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 @@
+//===- ASTPatch.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 {
@@ -76,10 +77,14 @@
SyntaxTree(T *Node, ASTUnit &AST)
: TreeImpl(llvm::make_unique<Impl>(this, Node, AST)) {}
SyntaxTree(SyntaxTree &&Other) = default;
+ SyntaxTree &operator=(SyntaxTree &&Other) = default;
+ 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 +98,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
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits