[PATCH] D34329: [clang-diff] Initial implementation.
johannes added a comment. In https://reviews.llvm.org/D34329#1213667, @sylvestre.ledru wrote: > @arphaman @johannes Is that normal that clang-diff isn't installed by cmake? > (like clang-format?) Yes, we did not add that. I don't know if anyone would use it. Repository: rL LLVM https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34329: [GSoC] Clang AST diffing
johannes created this revision. Herald added subscribers: mgorny, klimek. https://reviews.llvm.org/D34329 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt lib/Tooling/CMakeLists.txt tools/CMakeLists.txt tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- /dev/null +++ tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,58 @@ +//===- 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. +// +//===--===// +/// +/// \file +/// \brief +/// +//===--===// + +#include "clang/Tooling/ASTDiff/ASTDiff.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" + +static cl::OptionCategory ClangDiffCategory("clang-diff options"); + +static cl::opt +DumpAST("ast-dump", +cl::desc("Print the internal representation of the AST as JSON."), +cl::init(false), cl::cat(ClangDiffCategory)); + +int main(int argc, const char **argv) { + tooling::CommonOptionsParser Options(argc, argv, ClangDiffCategory); + ArrayRef Files = Options.getSourcePathList(); + + tooling::ClangTool Tool(Options.getCompilations(), Files); + + if (DumpAST) { +if (Files.size() != 1) { + errs() << "Error: please specify exactly one filename.\n"; + return 1; +} +std::vector> ASTs; +Tool.buildASTs(ASTs); +if (ASTs.size() != 1) { + return 1; +} +clang::diff::TreeRoot Tree(*ASTs[0]); +Tree.dumpAsJson(); +return 0; + } + if (Files.size() != 2) { +errs() << "Error: exactly two filenames are required.\n"; +return 1; + } + std::vector> ASTs; + Tool.buildASTs(ASTs); + if (ASTs.size() != 2) { +return 1; + } + clang::diff::runDiff(*ASTs[0], *ASTs[1]); + return 0; +} Index: tools/clang-diff/CMakeLists.txt === --- /dev/null +++ tools/clang-diff/CMakeLists.txt @@ -0,0 +1,16 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-diff + ClangDiff.cpp + ) + +target_link_libraries(clang-diff + clangAST + clangBasic + clangFrontend + clangLex + clangTooling + clangToolingASTDiff + ) Index: tools/CMakeLists.txt === --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) Index: lib/Tooling/CMakeLists.txt === --- lib/Tooling/CMakeLists.txt +++ lib/Tooling/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(Core) add_subdirectory(Refactoring) +add_subdirectory(ASTDiff) add_clang_library(clangTooling ArgumentsAdjusters.cpp Index: lib/Tooling/ASTDiff/CMakeLists.txt === --- /dev/null +++ lib/Tooling/ASTDiff/CMakeLists.txt @@ -0,0 +1,12 @@ +set(LLVM_LINK_COMPONENTS + # Option + Support + ) + +add_clang_library(clangToolingASTDiff + ASTDiff.cpp + LINK_LIBS + clangBasic + clangAST + # clangToolingCore + ) Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- /dev/null +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -0,0 +1,865 @@ +//===- ASTDiff.cpp - AST differencing implementation---*- C++ -*- -===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===--===// +/// +/// \file +/// \brief +/// +//===--===// + +#include "clang/Tooling/ASTDiff/ASTDiff.h" + +#include "clang/AST/RecursiveASTVisitor.h" +#include "llvm/ADT/PriorityQueue.h" +#include "llvm/Support/FormatVariadic.h" + +#include +#include +#include + +using namespace llvm; +using namespace clang; + +namespace clang { +namespace diff { + +static bool isRelevantNode(Decl *D) { return D != nullptr; } +static bool isRelevantNode(Stmt *S) { return S != nullptr; } + +namespace { +// Count the number of non-null nodes +struct NodeCountVisitor : public RecursiveASTVisitor { + int Count = 0; + bool TraverseDecl(Decl *D) { +if (isRelevantNode(D)) { + ++Count; + RecursiveASTVisitor::Trave
[PATCH] D34329: [GSoC] Clang AST diffing
johannes updated this revision to Diff 102986. https://reviews.llvm.org/D34329 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt lib/Tooling/CMakeLists.txt tools/CMakeLists.txt tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- /dev/null +++ tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,94 @@ +//===- 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. +// +//===--===// +/// +/// \file +/// \brief +/// +//===--===// + +#include "clang/Tooling/ASTDiff/ASTDiff.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" + +using namespace tooling; + +static cl::OptionCategory ClangDiffCategory("clang-diff options"); + +static cl::opt +DumpAST("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 std::unique_ptr getAST(const StringRef Filename) { + std::string ErrorMessage; + std::unique_ptr Compilations = + CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); + if (!Compilations) { +errs() << "Error while trying to load a compilation database, running " + "without flags.\n" + << ErrorMessage; +Compilations.reset( +new FixedCompilationDatabase(".", std::vector())); + } + std::array Files = {{Filename}}; + ClangTool Tool(*Compilations, Files); + std::vector> ASTs; + Tool.buildASTs(ASTs); + if (ASTs.size() != Files.size()) { +return nullptr; + } + return std::move(ASTs[0]); +} + +int main(int argc, const char **argv) { + cl::HideUnrelatedOptions(ClangDiffCategory); + if (!cl::ParseCommandLineOptions(argc, argv)) { +cl::PrintOptionValues(); +return 1; + } + + if (DumpAST) { +if (!DestinationPath.empty()) { + errs() << "Error: Please specify exactly one filename.\n"; + return 1; +} +std::unique_ptr AST = getAST(SourcePath); +if (!AST) { + return 1; +} +clang::diff::TreeRoot Tree(*AST); +Tree.dumpAsJson(); +return 0; + } + + if (DestinationPath.empty()) { +errs() << "Error: Exactly two paths are required.\n"; +return 1; + } + + std::unique_ptr Src = getAST(SourcePath); + std::unique_ptr Dst = getAST(DestinationPath); + if (!Src || !Dst) { +return 1; + } + + clang::diff::runDiff(*Src, *Dst); + + return 0; +} Index: tools/clang-diff/CMakeLists.txt === --- /dev/null +++ tools/clang-diff/CMakeLists.txt @@ -0,0 +1,16 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-diff + ClangDiff.cpp + ) + +target_link_libraries(clang-diff + clangAST + clangBasic + clangFrontend + clangLex + clangTooling + clangToolingASTDiff + ) Index: tools/CMakeLists.txt === --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) Index: lib/Tooling/CMakeLists.txt === --- lib/Tooling/CMakeLists.txt +++ lib/Tooling/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(Core) add_subdirectory(Refactoring) +add_subdirectory(ASTDiff) add_clang_library(clangTooling ArgumentsAdjusters.cpp Index: lib/Tooling/ASTDiff/CMakeLists.txt === --- /dev/null +++ lib/Tooling/ASTDiff/CMakeLists.txt @@ -0,0 +1,12 @@ +set(LLVM_LINK_COMPONENTS + # Option + Support + ) + +add_clang_library(clangToolingASTDiff + ASTDiff.cpp + LINK_LIBS + clangBasic + clangAST + # clangToolingCore + ) Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- /dev/null +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -0,0 +1,836 @@ +//===- ASTDiff.cpp - AST di
[PATCH] D34329: [GSoC] Clang AST diffing
johannes updated this revision to Diff 103147. johannes added a comment. - style fixes - do not compare nodes from system headers https://reviews.llvm.org/D34329 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt lib/Tooling/CMakeLists.txt tools/CMakeLists.txt tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- /dev/null +++ tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,94 @@ +//===- 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. +// +//===--===// +/// +/// \file +/// \brief +/// +//===--===// + +#include "clang/Tooling/ASTDiff/ASTDiff.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" + +using namespace tooling; + +static cl::OptionCategory ClangDiffCategory("clang-diff options"); + +static cl::opt +DumpAST("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 std::unique_ptr getAST(const StringRef Filename) { + std::string ErrorMessage; + std::unique_ptr Compilations = + CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); + if (!Compilations) { +errs() << "Error while trying to load a compilation database, running " + "without flags.\n" + << ErrorMessage; +Compilations.reset( +new FixedCompilationDatabase(".", std::vector())); + } + std::array Files = {{Filename}}; + ClangTool Tool(*Compilations, Files); + std::vector> ASTs; + Tool.buildASTs(ASTs); + if (ASTs.size() != Files.size()) { +return nullptr; + } + return std::move(ASTs[0]); +} + +int main(int argc, const char **argv) { + cl::HideUnrelatedOptions(ClangDiffCategory); + if (!cl::ParseCommandLineOptions(argc, argv)) { +cl::PrintOptionValues(); +return 1; + } + + if (DumpAST) { +if (!DestinationPath.empty()) { + errs() << "Error: Please specify exactly one filename.\n"; + return 1; +} +std::unique_ptr AST = getAST(SourcePath); +if (!AST) { + return 1; +} +clang::diff::TreeRoot Tree(*AST); +Tree.dumpAsJson(); +return 0; + } + + if (DestinationPath.empty()) { +errs() << "Error: Exactly two paths are required.\n"; +return 1; + } + + std::unique_ptr Src = getAST(SourcePath); + std::unique_ptr Dst = getAST(DestinationPath); + if (!Src || !Dst) { +return 1; + } + + clang::diff::runDiff(*Src, *Dst); + + return 0; +} Index: tools/clang-diff/CMakeLists.txt === --- /dev/null +++ tools/clang-diff/CMakeLists.txt @@ -0,0 +1,16 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-diff + ClangDiff.cpp + ) + +target_link_libraries(clang-diff + clangAST + clangBasic + clangFrontend + clangLex + clangTooling + clangToolingASTDiff + ) Index: tools/CMakeLists.txt === --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) Index: lib/Tooling/CMakeLists.txt === --- lib/Tooling/CMakeLists.txt +++ lib/Tooling/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(Core) add_subdirectory(Refactoring) +add_subdirectory(ASTDiff) add_clang_library(clangTooling ArgumentsAdjusters.cpp Index: lib/Tooling/ASTDiff/CMakeLists.txt === --- /dev/null +++ lib/Tooling/ASTDiff/CMakeLists.txt @@ -0,0 +1,12 @@ +set(LLVM_LINK_COMPONENTS + # Option + Support + ) + +add_clang_library(clangToolingASTDiff + ASTDiff.cpp + LINK_LIBS + clangBasic + clangAST + # clangToolingCore + ) Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- /dev/nu
[PATCH] D34329: [GSoC] Clang AST diffing
johannes added a comment. In https://reviews.llvm.org/D34329#784090, @arphaman wrote: > Generally we shouldn't have untested code in trunk, so I think that we need > to find a way to test this before committing. We can start off by testing the > output of the diff tool. Since there will be a lot of changes in the future, > you don't have to have everything covered now, so I think that even a couple > of tests be enough. Thanks for your feedback! I think I have adressed all issues, except for the tests. For the tests to run properly, I tried to create a local compile_commands.json, because ClangTool refuses to build an AST when the command for a source not found in the compilation database. However, it seems like relative paths do not work for the "directory" property. So maybe this can be added? Other options are: 1. patching the compilation database before running the test, so that it has the absolute path or 2. adding an option to my tool to not load a compilation database. WDYT? Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:31 + +static bool isRelevantNode(Decl *D) { return D != nullptr; } +static bool isRelevantNode(Stmt *S) { return S != nullptr; } arphaman wrote: > I don't see the point in these functions. Are you going to add some more > logic to them? > > I'd suggest removing them and using early return in the traversal functions > instead, e.g: > > ``` > bool TraverseDecl(Decl *D) { > if (!D) > return true; > ++Count; > return RecursiveASTVisitor::TraverseDecl(D); > } > ``` This is now done in `discardNode`, which also filters nodes from system headers. https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34329: [GSoC] Clang AST diffing
johannes updated this revision to Diff 103151. https://reviews.llvm.org/D34329 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt lib/Tooling/CMakeLists.txt tools/CMakeLists.txt tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- /dev/null +++ tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,92 @@ +//===- 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 tooling; + +static cl::OptionCategory ClangDiffCategory("clang-diff options"); + +static cl::opt +DumpAST("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 std::unique_ptr getAST(const StringRef Filename) { + std::string ErrorMessage; + std::unique_ptr Compilations = + CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); + if (!Compilations) { +llvm::errs() +<< "Error while trying to load a compilation database, running " + "without flags.\n" +<< ErrorMessage; +Compilations.reset( +new FixedCompilationDatabase(".", std::vector())); + } + std::array Files = {{Filename}}; + ClangTool Tool(*Compilations, Files); + std::vector> ASTs; + Tool.buildASTs(ASTs); + if (ASTs.size() != Files.size()) +return nullptr; + return std::move(ASTs[0]); +} + +int main(int argc, const char **argv) { + cl::HideUnrelatedOptions(ClangDiffCategory); + if (!cl::ParseCommandLineOptions(argc, argv)) { +cl::PrintOptionValues(); +return 1; + } + + if (DumpAST) { +if (!DestinationPath.empty()) { + llvm::errs() << "Error: Please specify exactly one filename.\n"; + return 1; +} +std::unique_ptr AST = getAST(SourcePath); +if (!AST) + return 1; +clang::diff::TreeRoot Tree(AST->getASTContext()); +Tree.printAsJson(); +return 0; + } + + if (DestinationPath.empty()) { +llvm::errs() << "Error: Exactly two paths are required.\n"; +return 1; + } + + std::unique_ptr Src = getAST(SourcePath); + std::unique_ptr Dst = getAST(DestinationPath); + if (!Src || !Dst) +return 1; + + clang::diff::runDiff(Src->getASTContext(), Dst->getASTContext()); + + return 0; +} Index: tools/clang-diff/CMakeLists.txt === --- /dev/null +++ tools/clang-diff/CMakeLists.txt @@ -0,0 +1,16 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-diff + ClangDiff.cpp + ) + +target_link_libraries(clang-diff + clangAST + clangBasic + clangFrontend + clangLex + clangTooling + clangToolingASTDiff + ) Index: tools/CMakeLists.txt === --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) Index: lib/Tooling/CMakeLists.txt === --- lib/Tooling/CMakeLists.txt +++ lib/Tooling/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(Core) add_subdirectory(Refactoring) +add_subdirectory(ASTDiff) add_clang_library(clangTooling ArgumentsAdjusters.cpp Index: lib/Tooling/ASTDiff/CMakeLists.txt === --- /dev/null +++ lib/Tooling/ASTDiff/CMakeLists.txt @@ -0,0 +1,12 @@ +set(LLVM_LINK_COMPONENTS + # Option + Support + ) + +add_clang_library(clangToolingASTDiff + ASTDiff.cpp + LINK_LIBS + clangBasic + clangAST + # clangToolingCore + ) Index: lib/Tooling/ASTDiff/ASTDiff.cpp ==
[PATCH] D34329: [GSoC] Clang AST diffing
johannes updated this revision to Diff 103163. johannes added a comment. - Add the option to not use compilation databases. - Add a basic test. https://reviews.llvm.org/D34329 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt lib/Tooling/CMakeLists.txt test/Tooling/clang-diff-basic.cpp tools/CMakeLists.txt tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- /dev/null +++ tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,101 @@ +//===- 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 tooling; + +static cl::OptionCategory ClangDiffCategory("clang-diff options"); + +static cl::opt +DumpAST("ast-dump", +cl::desc("Print the internal representation of the AST as JSON."), +cl::init(false), cl::cat(ClangDiffCategory)); + +static cl::opt NoCompilationDatabase( +"no-compilation-database", +cl::desc( +"Do not attempt to load build settigns from a compilation database"), +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 std::unique_ptr getAST(const StringRef Filename) { + std::string ErrorMessage; + std::unique_ptr Compilations; + if (!NoCompilationDatabase) +Compilations = +CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); + if (!Compilations) { +if (!NoCompilationDatabase) + llvm::errs() + << "Error while trying to load a compilation database, running " + "without flags.\n" + << ErrorMessage; +Compilations.reset( +new FixedCompilationDatabase(".", std::vector())); + } + std::array Files = {{Filename}}; + ClangTool Tool(*Compilations, Files); + std::vector> ASTs; + Tool.buildASTs(ASTs); + if (ASTs.size() != Files.size()) +return nullptr; + return std::move(ASTs[0]); +} + +int main(int argc, const char **argv) { + cl::HideUnrelatedOptions(ClangDiffCategory); + if (!cl::ParseCommandLineOptions(argc, argv)) { +cl::PrintOptionValues(); +return 1; + } + + if (DumpAST) { +if (!DestinationPath.empty()) { + llvm::errs() << "Error: Please specify exactly one filename.\n"; + return 1; +} +std::unique_ptr AST = getAST(SourcePath); +if (!AST) + return 1; +clang::diff::TreeRoot Tree(AST->getASTContext()); +Tree.printAsJson(); +return 0; + } + + if (DestinationPath.empty()) { +llvm::errs() << "Error: Exactly two paths are required.\n"; +return 1; + } + + std::unique_ptr Src = getAST(SourcePath); + std::unique_ptr Dst = getAST(DestinationPath); + if (!Src || !Dst) +return 1; + + clang::diff::runDiff(Src->getASTContext(), Dst->getASTContext()); + + return 0; +} Index: tools/clang-diff/CMakeLists.txt === --- /dev/null +++ tools/clang-diff/CMakeLists.txt @@ -0,0 +1,16 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-diff + ClangDiff.cpp + ) + +target_link_libraries(clang-diff + clangAST + clangBasic + clangFrontend + clangLex + clangTooling + clangToolingASTDiff + ) Index: tools/CMakeLists.txt === --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) Index: test/Tooling/clang-diff-basic.cpp === --- /dev/null +++ test/Tooling/clang-diff-basic.cpp @@ -0,0 +1,65 @@ +// RUN: awk '/^.. src/{f=1;next}/^.. end src/{f=0}f' %s > %T/src.cpp +// RUN: awk '/^.. dst/{f=1;next}/^.. end dst/{f=0}f' %s > %T/dst.cpp +//
[PATCH] D34329: [GSoC] Clang AST diffing
johannes marked 10 inline comments as done. johannes added inline comments. Comment at: include/clang/Tooling/ASTDiff/ASTDiff.h:123 + +void runDiff(ASTContext &AST1, ASTContext &AST2); + klimek wrote: > This is the main exposed interface? > > Generally, if all we want to do is printing, I wouldn't put that into a > library in Tooling, but just implement a tools/ASTDiffer or somesuch. > > If you want to make this a library, it should return the diff in some form > that's nice to use (or print). > I started out by creating a self contained tool, that's why the interface does not really make sense. I will change it to provide the mappings and the edit script in a nice way, this might be quite useful. https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34329: [GSoC] Clang AST diffing
johannes updated this revision to Diff 103208. johannes marked 7 inline comments as done. https://reviews.llvm.org/D34329 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt lib/Tooling/CMakeLists.txt test/Tooling/clang-diff-basic.cpp tools/CMakeLists.txt tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- /dev/null +++ tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,110 @@ +//===- 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 tooling; + +static cl::OptionCategory ClangDiffCategory("clang-diff options"); + +static cl::opt +DumpAST("ast-dump", +cl::desc("Print the internal representation of the AST as JSON."), +cl::init(false), cl::cat(ClangDiffCategory)); + +static cl::opt NoCompilationDatabase( +"no-compilation-database", +cl::desc( +"Do not attempt to load build settigns from a compilation database"), +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 std::unique_ptr getAST(const StringRef Filename) { + std::string ErrorMessage; + std::unique_ptr Compilations; + if (!NoCompilationDatabase) +Compilations = +CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); + if (!Compilations) { +if (!NoCompilationDatabase) + llvm::errs() + << "Error while trying to load a compilation database, running " + "without flags.\n" + << ErrorMessage; +Compilations.reset( +new FixedCompilationDatabase(".", std::vector())); + } + std::array Files = {{Filename}}; + ClangTool Tool(*Compilations, Files); + std::vector> ASTs; + Tool.buildASTs(ASTs); + if (ASTs.size() != Files.size()) +return nullptr; + return std::move(ASTs[0]); +} + +int main(int argc, const char **argv) { + cl::HideUnrelatedOptions(ClangDiffCategory); + if (!cl::ParseCommandLineOptions(argc, argv)) { +cl::PrintOptionValues(); +return 1; + } + + if (DumpAST) { +if (!DestinationPath.empty()) { + llvm::errs() << "Error: Please specify exactly one filename.\n"; + return 1; +} +std::unique_ptr AST = getAST(SourcePath); +if (!AST) + return 1; +clang::diff::TreeRoot Tree(AST->getASTContext()); +Tree.printAsJson(); +return 0; + } + + if (DestinationPath.empty()) { +llvm::errs() << "Error: Exactly two paths are required.\n"; +return 1; + } + + std::unique_ptr Src = getAST(SourcePath); + std::unique_ptr Dst = getAST(DestinationPath); + if (!Src || !Dst) +return 1; + + diff::TreeRoot T1(Src->getASTContext()); + diff::TreeRoot T2(Dst->getASTContext()); + diff::ASTDiff DiffTool(T1, T2); + diff::Mapping M = DiffTool.computeMapping(); + M.printMapping(); + auto Changes = DiffTool.computeChanges(M); + for (const auto &C : Changes) +DiffTool.printChange(C); + + return 0; +} Index: tools/clang-diff/CMakeLists.txt === --- /dev/null +++ tools/clang-diff/CMakeLists.txt @@ -0,0 +1,13 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-diff + ClangDiff.cpp + ) + +target_link_libraries(clang-diff + clangFrontend + clangTooling + clangToolingASTDiff + ) Index: tools/CMakeLists.txt === --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) Index: test/Tooling/clang-diff-basic.cpp === --- /dev/null +++ test/Tooli
[PATCH] D34329: [GSoC] Clang AST diffing
johannes marked an inline comment as done. johannes added a comment. In https://reviews.llvm.org/D34329#785190, @arphaman wrote: > Looking at the output of the tool, I have the following suggestion: > > - We should avoid implicit expressions (We don't need to see things like > `Insert ImplicitCastExpr(21) into BinaryOperator: *(22) at 0`). This can be > done in a follow-up patch though. Ok, I will include that and other features in future patches. Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:474 +for (SNodeId D1 = LMD1 + 1; D1 <= Id1; ++D1) { + double DeletionCost = 1.0; + ForestDist[D1][LMD2] = ForestDist[D1 - 1][LMD2] + DeletionCost; arphaman wrote: > Are the `DeletionCost` and `InsertionCost` constants or are you planning to > modify them in the future? I think they could be modified for minor improvements of the matching, but I am probably not going to do so anytime soon. Maybe it is better to store them at class scope as static const / constexpr? Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:693 + H2 = L2.pop(); + for (NodeId Id1 : H1) +for (NodeId Id2 : H2) arphaman wrote: > Please wrap these loops in braces. Not sure if I got this right. Comment at: test/Tooling/clang-diff-basic.cpp:3 +// RUN: awk '/^.. dst/{f=1;next}/^.. end dst/{f=0}f' %s > %T/dst.cpp +// RUN: clang-diff -no-compilation-database %T/src.cpp %T/dst.cpp +// RUN: clang-diff -no-compilation-database %T/src.cpp %T/dst.cpp | FileCheck %s arphaman wrote: > I'd prefer it if we used something like `clang -E` and preprocessor to > generate the two files. > > E.g.: > > ``` > RUN: %clang_cc1 -E %s > %T/src.cpp > RUN: %clang_cc1 -E %s -D DEST > %T/dst.cpp > #ifndef DEST > namespace src { }; > #else > namespace dst { }; > #endif > ``` Yes, much better! Comment at: test/Tooling/clang-diff-basic.cpp:4 +// RUN: clang-diff -no-compilation-database %T/src.cpp %T/dst.cpp +// RUN: clang-diff -no-compilation-database %T/src.cpp %T/dst.cpp | FileCheck %s + arphaman wrote: > Why do you need two invocations of `clang-diff` with the same arguments? That was unintentional, I removed it. https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34329: [GSoC] Clang AST diffing
johannes added inline comments. Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:303 +/// Identifies a node in this subtree by its postorder offset. +using SNodeId = int; + arphaman wrote: > What's the difference between `SNodeId` and `NodeId`? NodeId is the preorder offset inside the root tree, starting at 0. SNodeId is the postorder offset within a subtree, starting at 1. Not sure if this irregularity can be improved upon easily, but I guess I can mention it in the comments. https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34329: [GSoC] Clang AST diffing
johannes added inline comments. Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:171 + +std::string TreeRoot::label(NodeId Id) const { + const Node &N = getNode(Id); arphaman wrote: > I believe that this method that you call `label` actually represents the > `value` attribute that's described in the paper for the gumtree algorithm. Is > that right? If so, then this method name should be updated to reflect that. Yes, good catch. Strangely, the gumtree implementation uses `label`. I think we should use `type` for node kinds and `value` for their actual value, in order to avoid confusion about what a label is. https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34329: [GSoC] Clang AST diffing
johannes updated this revision to Diff 103320. johannes added a comment. - Fix a bug in getSimilarity() - Change terminology: `label` -> `value` - Define SNodeId: Now it cannot be implicitly constructed from an int, however it can be converted to int. Still feels a bit weird - Fix some issues with SNodeId - Rewrite the computation of leftmost descendants in subtrees. https://reviews.llvm.org/D34329 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt lib/Tooling/CMakeLists.txt test/Tooling/clang-diff-basic.cpp tools/CMakeLists.txt tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- /dev/null +++ tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,110 @@ +//===- 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 tooling; + +static cl::OptionCategory ClangDiffCategory("clang-diff options"); + +static cl::opt +DumpAST("ast-dump", +cl::desc("Print the internal representation of the AST as JSON."), +cl::init(false), cl::cat(ClangDiffCategory)); + +static cl::opt NoCompilationDatabase( +"no-compilation-database", +cl::desc( +"Do not attempt to load build settigns from a compilation database"), +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 std::unique_ptr getAST(const StringRef Filename) { + std::string ErrorMessage; + std::unique_ptr Compilations; + if (!NoCompilationDatabase) +Compilations = +CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); + if (!Compilations) { +if (!NoCompilationDatabase) + llvm::errs() + << "Error while trying to load a compilation database, running " + "without flags.\n" + << ErrorMessage; +Compilations.reset( +new FixedCompilationDatabase(".", std::vector())); + } + std::array Files = {{Filename}}; + ClangTool Tool(*Compilations, Files); + std::vector> ASTs; + Tool.buildASTs(ASTs); + if (ASTs.size() != Files.size()) +return nullptr; + return std::move(ASTs[0]); +} + +int main(int argc, const char **argv) { + cl::HideUnrelatedOptions(ClangDiffCategory); + if (!cl::ParseCommandLineOptions(argc, argv)) { +cl::PrintOptionValues(); +return 1; + } + + if (DumpAST) { +if (!DestinationPath.empty()) { + llvm::errs() << "Error: Please specify exactly one filename.\n"; + return 1; +} +std::unique_ptr AST = getAST(SourcePath); +if (!AST) + return 1; +clang::diff::TreeRoot Tree(AST->getASTContext()); +Tree.printAsJson(); +return 0; + } + + if (DestinationPath.empty()) { +llvm::errs() << "Error: Exactly two paths are required.\n"; +return 1; + } + + std::unique_ptr Src = getAST(SourcePath); + std::unique_ptr Dst = getAST(DestinationPath); + if (!Src || !Dst) +return 1; + + diff::TreeRoot T1(Src->getASTContext()); + diff::TreeRoot T2(Dst->getASTContext()); + diff::ASTDiff DiffTool(T1, T2); + diff::Mapping M = DiffTool.computeMapping(); + M.printMapping(); + auto Changes = DiffTool.computeChanges(M); + for (const auto &C : Changes) +DiffTool.printChange(C); + + return 0; +} Index: tools/clang-diff/CMakeLists.txt === --- /dev/null +++ tools/clang-diff/CMakeLists.txt @@ -0,0 +1,13 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-diff + ClangDiff.cpp + ) + +target_link_libraries(clang-diff + clangFrontend + clangTooling + clangToolingASTDiff + ) Index: tools/CMakeLists.txt === --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_cl
[PATCH] D34329: [GSoC] Clang AST diffing
johannes updated this revision to Diff 103409. johannes marked 4 inline comments as done. johannes added a comment. - move some unnecessary things out of the public header Is this a proper way to declutter the header file? Using inheritance would also be possible. I have to define a destructor for ASTDiff because TreeComparator is forward-declared https://reviews.llvm.org/D34329 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt lib/Tooling/CMakeLists.txt test/Tooling/clang-diff-basic.cpp tools/CMakeLists.txt tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- /dev/null +++ tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,107 @@ +//===- 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 tooling; + +static cl::OptionCategory ClangDiffCategory("clang-diff options"); + +static cl::opt +DumpAST("ast-dump", +cl::desc("Print the internal representation of the AST as JSON."), +cl::init(false), cl::cat(ClangDiffCategory)); + +static cl::opt NoCompilationDatabase( +"no-compilation-database", +cl::desc( +"Do not attempt to load build settigns from a compilation database"), +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 std::unique_ptr getAST(const StringRef Filename) { + std::string ErrorMessage; + std::unique_ptr Compilations; + if (!NoCompilationDatabase) +Compilations = +CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); + if (!Compilations) { +if (!NoCompilationDatabase) + llvm::errs() + << "Error while trying to load a compilation database, running " + "without flags.\n" + << ErrorMessage; +Compilations.reset( +new FixedCompilationDatabase(".", std::vector())); + } + std::array Files = {{Filename}}; + ClangTool Tool(*Compilations, Files); + std::vector> ASTs; + Tool.buildASTs(ASTs); + if (ASTs.size() != Files.size()) +return nullptr; + return std::move(ASTs[0]); +} + +int main(int argc, const char **argv) { + cl::HideUnrelatedOptions(ClangDiffCategory); + if (!cl::ParseCommandLineOptions(argc, argv)) { +cl::PrintOptionValues(); +return 1; + } + + if (DumpAST) { +if (!DestinationPath.empty()) { + llvm::errs() << "Error: Please specify exactly one filename.\n"; + return 1; +} +std::unique_ptr AST = getAST(SourcePath); +if (!AST) + return 1; +clang::diff::TreeRoot Tree(AST->getASTContext()); +Tree.printAsJson(); +return 0; + } + + if (DestinationPath.empty()) { +llvm::errs() << "Error: Exactly two paths are required.\n"; +return 1; + } + + std::unique_ptr Src = getAST(SourcePath); + std::unique_ptr Dst = getAST(DestinationPath); + if (!Src || !Dst) +return 1; + + diff::ASTDiff DiffTool(Src->getASTContext(), Dst->getASTContext()); + for (const auto &Match : DiffTool.getMatches()) +DiffTool.printMatch(Match); + for (const auto &Change : DiffTool.getChanges()) +DiffTool.printChange(Change); + + return 0; +} Index: tools/clang-diff/CMakeLists.txt === --- /dev/null +++ tools/clang-diff/CMakeLists.txt @@ -0,0 +1,13 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-diff + ClangDiff.cpp + ) + +target_link_libraries(clang-diff + clangFrontend + clangTooling + clangToolingASTDiff + ) Index: tools/CMakeLists.txt === --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clan
[PATCH] D34329: [GSoC] Clang AST diffing
johannes added inline comments. Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:157 + int Leaves = 0; + std::function Traverse = [&](NodeId Id) { +const Node &N = getNode(Id); arphaman wrote: > you should be able to use `auto` instead of `std::function` here I think. It does not work because the function is recursive. I'm not sure whether it is good practise to do it like this. Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:209 +if (X->hasQualifier() && X->getQualifier()->getAsIdentifier()) + Value += std::string(X->getQualifier()->getAsIdentifier()->getName()); +Value += X->getDecl()->getNameAsString(); arphaman wrote: > Qualifiers (i.e. `NestedNameSpecifier`s) can contain multiple identifiers > (e.g. `foo::bar::`). You should use the `print` method from > `NestedNameSpecifier` instead. Ok nice, this works properly! This function needs a lot of work. I try to extract relevant information from base classes first. https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34329: [GSoC] Clang AST diffing
johannes updated this revision to Diff 103432. https://reviews.llvm.org/D34329 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt lib/Tooling/CMakeLists.txt test/Tooling/clang-diff-basic.cpp tools/CMakeLists.txt tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- /dev/null +++ tools/clang-diff/ClangDiff.cpp @@ -0,0 +1,107 @@ +//===- 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 tooling; + +static cl::OptionCategory ClangDiffCategory("clang-diff options"); + +static cl::opt +DumpAST("ast-dump", +cl::desc("Print the internal representation of the AST as JSON."), +cl::init(false), cl::cat(ClangDiffCategory)); + +static cl::opt NoCompilationDatabase( +"no-compilation-database", +cl::desc( +"Do not attempt to load build settigns from a compilation database"), +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 std::unique_ptr getAST(const StringRef Filename) { + std::string ErrorMessage; + std::unique_ptr Compilations; + if (!NoCompilationDatabase) +Compilations = +CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); + if (!Compilations) { +if (!NoCompilationDatabase) + llvm::errs() + << "Error while trying to load a compilation database, running " + "without flags.\n" + << ErrorMessage; +Compilations.reset( +new FixedCompilationDatabase(".", std::vector())); + } + std::array Files = {{Filename}}; + ClangTool Tool(*Compilations, Files); + std::vector> ASTs; + Tool.buildASTs(ASTs); + if (ASTs.size() != Files.size()) +return nullptr; + return std::move(ASTs[0]); +} + +int main(int argc, const char **argv) { + cl::HideUnrelatedOptions(ClangDiffCategory); + if (!cl::ParseCommandLineOptions(argc, argv)) { +cl::PrintOptionValues(); +return 1; + } + + if (DumpAST) { +if (!DestinationPath.empty()) { + llvm::errs() << "Error: Please specify exactly one filename.\n"; + return 1; +} +std::unique_ptr AST = getAST(SourcePath); +if (!AST) + return 1; +clang::diff::TreeRoot Tree(AST->getASTContext()); +Tree.printAsJson(); +return 0; + } + + if (DestinationPath.empty()) { +llvm::errs() << "Error: Exactly two paths are required.\n"; +return 1; + } + + std::unique_ptr Src = getAST(SourcePath); + std::unique_ptr Dst = getAST(DestinationPath); + if (!Src || !Dst) +return 1; + + diff::ASTDiff DiffTool(Src->getASTContext(), Dst->getASTContext()); + for (const auto &Match : DiffTool.getMatches()) +DiffTool.printMatch(Match); + for (const auto &Change : DiffTool.getChanges()) +DiffTool.printChange(Change); + + return 0; +} Index: tools/clang-diff/CMakeLists.txt === --- /dev/null +++ tools/clang-diff/CMakeLists.txt @@ -0,0 +1,13 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_executable(clang-diff + ClangDiff.cpp + ) + +target_link_libraries(clang-diff + clangFrontend + clangTooling + clangToolingASTDiff + ) Index: tools/CMakeLists.txt === --- tools/CMakeLists.txt +++ tools/CMakeLists.txt @@ -2,6 +2,7 @@ add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) +add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) add_clang_subdirectory(clang-fuzzer) Index: test/Tooling/clang-diff-basic.cpp === --- /dev/null +++ test/Tooling/clang-diff-basic.cpp @@ -0,0 +1,61 @@ +// RUN: %clang_cc1 -E %s > %T/src.cpp +// RUN: %clang_cc1 -E
[PATCH] D34329: [GSoC] Clang AST diffing
johannes added inline comments. Comment at: include/clang/Tooling/ASTDiff/ASTDiff.h:57 +/// Within a tree, this identifies a node by its preorder offset. +using NodeId = int; + arphaman wrote: > I think that it's better to make make `NodeId` a structure as well and make > `InvalidNodeId` a private member. I suggest the following interface for > `NodeId`: > > ``` > struct NodeId { > private: > static const int InvalidNodeId; > public: > int Id; > > NodeId() : Id(InvalidNodeId) { } > NodeId(int Id) : Id(Id) { } > > bool isValid() const { return Id != InvalidNodeId; } > bool isInvalid() const { return Id == InvalidNodeId; } > }; > ``` > > > This way you'll get rid of a couple of variable initializations that use > `InvalidNodeId`. You also won't need to call the `memset` when creating the > unique pointer array of `NodeId`s. Ok, I did it like this. Can I create a header file inside lib/Tooling/ASTDiff and include it from the public interface? This would help reduce the clutter. Instead of NodeId we could also just use references / pointer types. I don't see any particularly good reason for choosing either one above the other. I guess indices make it more obvious how to compute the number of descendants and such. On the other hand, when using reference types, there is less boilerplate to write for loops. https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34329: [GSoC] Clang AST diffing
johannes added inline comments. Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:730 + +Mapping TreeComparator::matchTopDown() const { + PriorityList L1(T1); arphaman wrote: > Johannes, it seems to me that your implementation of the top-down portion of > the GumTree algorithm doesn't use the `an empty list A of candidate mappings` > that's described in the paper (and that you have in the Python prototype). Is > that correct or am I missing something? Yes, initially I implemented it as it is described in the paper, but then I realized that the list of candidate mappings will always stay empty, because the condition in the top-down algorithm in the paper on line 14 will never be true. Maybe I am mistaken here, but if `t1` and `t2` are isomorphic, then none of the descendants of `t1` will be isomorphic to `t2`. I mean, the height of isomorphic trees must be equal, and the descendant does not have the same height. So to me this looks like an error in the paper, I probably should have communicated this. What I did instead is, I had a look at the reference implementation, and what they do instead of using a list of candidate mappings is to just use a data structure for the mapping that allows multiple matches for each node. After matching collecting all candidates this way, they extract the unambiguous matches and then sort the ambiguous matches by their parents' similarity. [[ https://github.com/GumTreeDiff/gumtree/blob/develop/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractSubtreeMatcher.java | AbstractSubtreeMatcher.java ]] [[ https://github.com/GumTreeDiff/gumtree/blob/develop/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedySubtreeMatcher.java | GreedySubtreeMatcher.java ]] This seems to be a good solution, I plan to implement that in the future. https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34329: [GSoC] Clang AST diffing
johannes added inline comments. Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:730 + +Mapping TreeComparator::matchTopDown() const { + PriorityList L1(T1); johannes wrote: > arphaman wrote: > > Johannes, it seems to me that your implementation of the top-down portion > > of the GumTree algorithm doesn't use the `an empty list A of candidate > > mappings` that's described in the paper (and that you have in the Python > > prototype). Is that correct or am I missing something? > Yes, initially I implemented it as it is described in the paper, but then I > realized that the list of candidate mappings will always stay empty, because > the condition in the top-down algorithm in the paper on line 14 will never be > true. Maybe I am mistaken here, but if `t1` and `t2` are isomorphic, then > none of the descendants of `t1` will be isomorphic to `t2`. I mean, the > height of isomorphic trees must be equal, and the descendant does not have > the same height. So to me this looks like an error in the paper, I probably > should have communicated this. > What I did instead is, I had a look at the reference implementation, and what > they do instead of using a list of candidate mappings is to just use a data > structure for the mapping that allows multiple matches for each node. > After matching collecting all candidates this way, they extract the > unambiguous matches and then sort the ambiguous matches by their parents' > similarity. > [[ > https://github.com/GumTreeDiff/gumtree/blob/develop/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/AbstractSubtreeMatcher.java > | AbstractSubtreeMatcher.java ]] > [[ > https://github.com/GumTreeDiff/gumtree/blob/develop/core/src/main/java/com/github/gumtreediff/matchers/heuristic/gt/GreedySubtreeMatcher.java > | GreedySubtreeMatcher.java ]] > This seems to be a good solution, I plan to implement that in the future. My bad, I misread the algorithm in the paper, of course the entire tree is searched for other isomorphic subtrees. I will still stick to the way it is implemented in gumtree, it should be more efficient. https://reviews.llvm.org/D34329 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37001: [clang-diff] Use data collectors for node comparison
johannes added a comment. In https://reviews.llvm.org/D37001#852442, @arphaman wrote: > Can you remove `getNodeValue` now or do you still need it? It is only used in the client, I think it makes sense to move it there, or to remove it altogether. I think I'd keep it in the client for now, to disambiguate things with different names in the tests. https://reviews.llvm.org/D37001 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37004: [clang-diff] Fix the html output for CXXOperatorCallExpr
johannes added inline comments. Comment at: tools/clang-diff/ClangDiff.cpp:319 + "A Binary operator is supposed to have two arguments."); +for (int I : {1, 0, 2}) + Offset = printHtmlForNode(OS, Diff, Tree, IsLeft, Children[I], Offset); arphaman wrote: > Please add a short comment that describes why this out-of-order traversal is > required Should we do this in LexicallyOrderedRecursiveASTVisitor? There are some other cases with CXXOperatorCallExpr where the order needs to be changed, e.g. postfix operators, operator->, operator() and operator[]. It can be done by sorting by SourceLocation of the first two elements, as the operator is always the first one. https://reviews.llvm.org/D37004 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36998: [AST] Traverse templates in LexicallyOrderedRecursiveASTVisitor
johannes updated this revision to Diff 112707. johannes added a comment. test ordering, class template https://reviews.llvm.org/D36998 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp Index: unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp === --- unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp +++ unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp @@ -21,8 +21,9 @@ : public LexicallyOrderedRecursiveASTVisitor { public: LexicallyOrderedDeclVisitor(DummyMatchVisitor &Matcher, - const SourceManager &SM) - : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher) {} + const SourceManager &SM, bool EmitIndices) + : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher), +EmitIndices(EmitIndices) {} bool TraverseDecl(Decl *D) { TraversalStack.push_back(D); @@ -35,15 +36,20 @@ private: DummyMatchVisitor &Matcher; + bool EmitIndices; + unsigned Index = 0; llvm::SmallVector TraversalStack; }; class DummyMatchVisitor : public ExpectedLocationVisitor { + bool EmitIndices; + public: + DummyMatchVisitor(bool EmitIndices = false) : EmitIndices(EmitIndices) {} bool VisitTranslationUnitDecl(TranslationUnitDecl *TU) { const ASTContext &Context = TU->getASTContext(); const SourceManager &SM = Context.getSourceManager(); -LexicallyOrderedDeclVisitor SubVisitor(*this, SM); +LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitIndices); SubVisitor.TraverseDecl(TU); return false; } @@ -64,9 +70,11 @@ OS << ND->getNameAsString(); else OS << "???"; -if (isa(D)) +if (isa(D) or isa(D)) OS << "/"; } + if (EmitIndices) +OS << "@" << Index++; Matcher.match(OS.str(), D); return true; } @@ -138,4 +146,18 @@ EXPECT_TRUE(Visitor.runOver(Source, DummyMatchVisitor::Lang_OBJC)); } +TEST(LexicallyOrderedRecursiveASTVisitor, VisitTemplateDecl) { + StringRef Source = R"( +template T f(); +template class Class {}; +)"; + DummyMatchVisitor Visitor(/*EmitIndices=*/true); + Visitor.ExpectMatch("/f/T@0", 2, 11); + Visitor.ExpectMatch("/f/f/@1", 2, 20); + Visitor.ExpectMatch("/Class/U@2", 3, 11); + Visitor.ExpectMatch("/Class/@3", 3, 20); + Visitor.ExpectMatch("/Class/Class/@4", 3, 34); + EXPECT_TRUE(Visitor.runOver(Source)); +} + } // end anonymous namespace Index: include/clang/AST/RecursiveASTVisitor.h === --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -499,10 +499,10 @@ bool canIgnoreChildDeclWhileTraversingDeclContext(const Decl *Child); -private: // These are helper methods used by more than one Traverse* method. bool TraverseTemplateParameterListHelper(TemplateParameterList *TPL); +private: // Traverses template parameter lists of either a DeclaratorDecl or TagDecl. template bool TraverseDeclTemplateParameterLists(T *D); Index: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h === --- include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h +++ include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h @@ -111,6 +111,21 @@ return true; } +#define DEF_TRAVERSE_TEMPLATE_DECL(TMPLDECLKIND) \ + bool Traverse##TMPLDECLKIND##TemplateDecl(TMPLDECLKIND##TemplateDecl *TD) { \ +if (!BaseType::TraverseTemplateParameterListHelper(\ +TD->getTemplateParameters())) \ + return false;\ +assert(!BaseType::getDerived().shouldVisitTemplateInstantiations() && \ + "It does not make sense for most clients to visit template "\ + "instantiations here.");\ +return BaseType::getDerived().TraverseDecl(TD->getTemplatedDecl());\ + } + DEF_TRAVERSE_TEMPLATE_DECL(Class) + DEF_TRAVERSE_TEMPLATE_DECL(Var) + DEF_TRAVERSE_TEMPLATE_DECL(Function) +#undef DEF_TRAVERSE_TEMPLATE_DECL + private: bool TraverseAdditionalLexicallyNestedDeclarations() { // FIXME: Ideally the gathered declarations and the declarations in the ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37200: [AST] Traverse CXXOperatorCallExpr in LexicallyOrderedRecursiveASTVisitor
johannes created this revision. Herald added a subscriber: klimek. This affects overloaded operators, which are represented by a CXXOperatorCallExpr whose first child is always a DeclRefExpr referring to the operator. For infix, postfix and call operators we want the first argument to be traversed before the operator. https://reviews.llvm.org/D37200 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp Index: unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp === --- unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp +++ unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp @@ -21,9 +21,10 @@ : public LexicallyOrderedRecursiveASTVisitor { public: LexicallyOrderedDeclVisitor(DummyMatchVisitor &Matcher, - const SourceManager &SM, bool EmitIndices) + const SourceManager &SM, bool EmitDeclIndices, + bool EmitStmtIndices) : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher), -EmitIndices(EmitIndices) {} +EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool TraverseDecl(Decl *D) { TraversalStack.push_back(D); @@ -33,28 +34,33 @@ } bool VisitNamedDecl(const NamedDecl *D); + bool VisitDeclRefExpr(const DeclRefExpr *D); private: DummyMatchVisitor &Matcher; - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; unsigned Index = 0; llvm::SmallVector TraversalStack; }; class DummyMatchVisitor : public ExpectedLocationVisitor { - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; public: - DummyMatchVisitor(bool EmitIndices = false) : EmitIndices(EmitIndices) {} + DummyMatchVisitor(bool EmitDeclIndices = false, bool EmitStmtIndices = false) + : EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool VisitTranslationUnitDecl(TranslationUnitDecl *TU) { const ASTContext &Context = TU->getASTContext(); const SourceManager &SM = Context.getSourceManager(); -LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitIndices); +LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitDeclIndices, + EmitStmtIndices); SubVisitor.TraverseDecl(TU); return false; } - void match(StringRef Path, const Decl *D) { Match(Path, D->getLocStart()); } + template void match(StringRef Path, const T *D) { +Match(Path, D->getLocStart()); + } }; bool LexicallyOrderedDeclVisitor::VisitNamedDecl(const NamedDecl *D) { @@ -73,7 +79,16 @@ if (isa(D) or isa(D)) OS << "/"; } - if (EmitIndices) + if (EmitDeclIndices) +OS << "@" << Index++; + Matcher.match(OS.str(), D); + return true; +} + +bool LexicallyOrderedDeclVisitor::VisitDeclRefExpr(const DeclRefExpr *D) { + std::string Name = D->getFoundDecl()->getNameAsString(); + llvm::raw_string_ostream OS(Name); + if (EmitStmtIndices) OS << "@" << Index++; Matcher.match(OS.str(), D); return true; @@ -151,13 +166,50 @@ template T f(); template class Class {}; )"; - DummyMatchVisitor Visitor(/*EmitIndices=*/true); + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/true); Visitor.ExpectMatch("/f/T@0", 2, 11); Visitor.ExpectMatch("/f/f/@1", 2, 20); Visitor.ExpectMatch("/Class/U@2", 3, 11); Visitor.ExpectMatch("/Class/@3", 3, 20); Visitor.ExpectMatch("/Class/Class/@4", 3, 34); EXPECT_TRUE(Visitor.runOver(Source)); } +TEST(LexicallyOrderedRecursiveASTVisitor, VisitCXXOperatorCallExpr) { + StringRef Source = R"( +struct S { + S &operator+(S&); + S *operator->(); + S &operator++(); + S operator++(int); + void operator()(int, int); + void operator[](int); + void f(); +}; +S a, b, c; + +void test() { + a = b + c; + a->f(); + a(1, 2); + b[0]; + ++a; + b++; +} +)"; + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/false, +/*EmitStmtIndices=*/true); + Visitor.ExpectMatch("a@0", 14, 3); + Visitor.ExpectMatch("operator=@1", 14, 5); + Visitor.ExpectMatch("b@2", 14, 7); + Visitor.ExpectMatch("operator+@3", 14, 9); + Visitor.ExpectMatch("c@4", 14, 11); + Visitor.ExpectMatch("operator->@6", 15, 4); + Visitor.ExpectMatch("operator()@8", 16, 4); + Visitor.ExpectMatch("operator[]@10", 17, 4); + Visitor.ExpectMatch("operator++@11", 18, 3); + Visitor.ExpectMatch("operator++@14", 19, 4); + EXPECT_TRUE(Visitor.runOver(Source)); +} + } // end anonymous namespace Index: include/clang/AST/RecursiveASTVisitor.h === --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -3209,7 +3209,6 @@ #undef DEF_TRAVERSE_STMT #undef TRAVERSE_STMT -#undef TRAVERSE_STMT_BASE #undef TRY_TO Index: include/clang/AST/L
[PATCH] D37201: [clang-diff] Use correct SourceRange for CXXConstructExpr
johannes created this revision. Herald added a subscriber: klimek. This way the variable name of a declaration is not included https://reviews.llvm.org/D37201 Files: test/Tooling/Inputs/clang-diff-basic-src.cpp test/Tooling/clang-diff-basic.cpp test/Tooling/clang-diff-html.test Index: test/Tooling/clang-diff-html.test === --- test/Tooling/clang-diff-html.test +++ test/Tooling/clang-diff-html.test @@ -26,6 +26,20 @@ update + move CHECK: 2' class='u m'>2 +VarDecl of same type but different variable name +ensure that only the latter is marked changed +CHECK: VarDecl: s +CHECK: TypeLoc: class T +CHECK-NEXT: T{{.*}}CXXConstructExpr +CHECK-NEXT: CXXConstructExpr +CHECK: s +CHECK: CXXTemporaryObjectExpr +CHECK-NEXT: CXXTemporaryObjectExpr +CHECK-NEXT: +CHECK-NEXT: TypeLoc: class S +CHECK-NEXT: S + + insertion CHECK: 2 +VarDecl of same type but different variable name +ensure that only the latter is marked changed +CHECK: VarDecl: s +CHECK: TypeLoc: class T +CHECK-NEXT: T{{.*}}CXXConstructExpr +CHECK-NEXT: CXXConstructExpr +CHECK: s +CHECK: CXXTemporaryObjectExpr +CHECK-NEXT: CXXTemporaryObjectExpr +CHECK-NEXT: +CHECK-NEXT: TypeLoc: class S +CHECK-NEXT: S + + insertion CHECK:
[PATCH] D37005: [clang-diff] Initial implementation of patching
johannes updated this revision to Diff 112845. johannes retitled this revision from "Add include/clang/Tooling/ASTDiff/ASTPatch.h" to "[clang-diff] Initial implementation of patching". johannes edited the summary of this revision. johannes added a comment. use rewriter to patch a third AST https://reviews.llvm.org/D37005 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp lib/Tooling/ASTDiff/CMakeLists.txt 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/Tooling.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace tooling; + +static std::string patchResult(std::array Codes) { + diff::SyntaxTree Trees[3]; + std::unique_ptr ASTs[3]; + std::vector 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 @@ -42,6 +42,12 @@ cl::desc("Output a side-by-side diff in HTML."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt +Patch("patch", + cl::desc("Try to apply the edit actions between the two input " + "files to the specified target."), + cl::desc(""), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -563,6 +569,16 @@ } diff::SyntaxTree SrcTree(*Src); diff::Syntax
[PATCH] D37200: [AST] Traverse CXXOperatorCallExpr in LexicallyOrderedRecursiveASTVisitor
johannes added inline comments. Comment at: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h:75 + Derived &getDerived() { return *static_cast(this); } + arphaman wrote: > I don't think you need this since `getDerived` in RecursiveASTVisitor is > already public. `has_same_member_pointer` and `getDerived` are used by the TRAVERSE_STMT macro, which should be the right one to use here. Also the `RecursiveASTVisitor` typedef just above. Comment at: include/clang/AST/RecursiveASTVisitor.h:3212 #undef TRAVERSE_STMT -#undef TRAVERSE_STMT_BASE arphaman wrote: > Getting rid of `undef` is not ideal. You might want to extract these macros > into one .def file that's included by both RecursiveASTVisitor.h and the > LexicallyOrdered one. Ok, I will do that https://reviews.llvm.org/D37200 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37200: [AST] Traverse CXXOperatorCallExpr in LexicallyOrderedRecursiveASTVisitor
johannes updated this revision to Diff 112885. johannes added a comment. use RecursiveASTVisitor::TraverseStmt https://reviews.llvm.org/D37200 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp Index: unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp === --- unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp +++ unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp @@ -21,9 +21,10 @@ : public LexicallyOrderedRecursiveASTVisitor { public: LexicallyOrderedDeclVisitor(DummyMatchVisitor &Matcher, - const SourceManager &SM, bool EmitIndices) + const SourceManager &SM, bool EmitDeclIndices, + bool EmitStmtIndices) : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher), -EmitIndices(EmitIndices) {} +EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool TraverseDecl(Decl *D) { TraversalStack.push_back(D); @@ -33,28 +34,33 @@ } bool VisitNamedDecl(const NamedDecl *D); + bool VisitDeclRefExpr(const DeclRefExpr *D); private: DummyMatchVisitor &Matcher; - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; unsigned Index = 0; llvm::SmallVector TraversalStack; }; class DummyMatchVisitor : public ExpectedLocationVisitor { - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; public: - DummyMatchVisitor(bool EmitIndices = false) : EmitIndices(EmitIndices) {} + DummyMatchVisitor(bool EmitDeclIndices = false, bool EmitStmtIndices = false) + : EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool VisitTranslationUnitDecl(TranslationUnitDecl *TU) { const ASTContext &Context = TU->getASTContext(); const SourceManager &SM = Context.getSourceManager(); -LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitIndices); +LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitDeclIndices, + EmitStmtIndices); SubVisitor.TraverseDecl(TU); return false; } - void match(StringRef Path, const Decl *D) { Match(Path, D->getLocStart()); } + template void match(StringRef Path, const T *D) { +Match(Path, D->getLocStart()); + } }; bool LexicallyOrderedDeclVisitor::VisitNamedDecl(const NamedDecl *D) { @@ -73,7 +79,16 @@ if (isa(D) or isa(D)) OS << "/"; } - if (EmitIndices) + if (EmitDeclIndices) +OS << "@" << Index++; + Matcher.match(OS.str(), D); + return true; +} + +bool LexicallyOrderedDeclVisitor::VisitDeclRefExpr(const DeclRefExpr *D) { + std::string Name = D->getFoundDecl()->getNameAsString(); + llvm::raw_string_ostream OS(Name); + if (EmitStmtIndices) OS << "@" << Index++; Matcher.match(OS.str(), D); return true; @@ -151,13 +166,50 @@ template T f(); template class Class {}; )"; - DummyMatchVisitor Visitor(/*EmitIndices=*/true); + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/true); Visitor.ExpectMatch("/f/T@0", 2, 11); Visitor.ExpectMatch("/f/f/@1", 2, 20); Visitor.ExpectMatch("/Class/U@2", 3, 11); Visitor.ExpectMatch("/Class/@3", 3, 20); Visitor.ExpectMatch("/Class/Class/@4", 3, 34); EXPECT_TRUE(Visitor.runOver(Source)); } +TEST(LexicallyOrderedRecursiveASTVisitor, VisitCXXOperatorCallExpr) { + StringRef Source = R"( +struct S { + S &operator+(S&); + S *operator->(); + S &operator++(); + S operator++(int); + void operator()(int, int); + void operator[](int); + void f(); +}; +S a, b, c; + +void test() { + a = b + c; + a->f(); + a(1, 2); + b[0]; + ++a; + b++; +} +)"; + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/false, +/*EmitStmtIndices=*/true); + Visitor.ExpectMatch("a@0", 14, 3); + Visitor.ExpectMatch("operator=@1", 14, 5); + Visitor.ExpectMatch("b@2", 14, 7); + Visitor.ExpectMatch("operator+@3", 14, 9); + Visitor.ExpectMatch("c@4", 14, 11); + Visitor.ExpectMatch("operator->@6", 15, 4); + Visitor.ExpectMatch("operator()@8", 16, 4); + Visitor.ExpectMatch("operator[]@10", 17, 4); + Visitor.ExpectMatch("operator++@11", 18, 3); + Visitor.ExpectMatch("operator++@14", 19, 4); + EXPECT_TRUE(Visitor.runOver(Source)); +} + } // end anonymous namespace Index: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h === --- include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h +++ include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h @@ -60,6 +60,7 @@ class LexicallyOrderedRecursiveASTVisitor : public RecursiveASTVisitor { using BaseType = RecursiveASTVisitor; + using typename BaseType::DataRecursionQueue; public: LexicallyOrderedRecursiveASTVisitor(const SourceManager &SM) : SM(SM) {} @@ -126,6 +127,36 @@ DEF_TRAVERSE_TEMPLATE_DECL(Function) #undef DEF_T
[PATCH] D37200: [AST] Traverse CXXOperatorCallExpr in LexicallyOrderedRecursiveASTVisitor
johannes updated this revision to Diff 112889. johannes added a comment. do call derived TraverseStmt for children of CXXOperatorCallExpr https://reviews.llvm.org/D37200 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp Index: unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp === --- unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp +++ unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp @@ -21,42 +21,55 @@ : public LexicallyOrderedRecursiveASTVisitor { public: LexicallyOrderedDeclVisitor(DummyMatchVisitor &Matcher, - const SourceManager &SM, bool EmitIndices) + const SourceManager &SM, bool EmitDeclIndices, + bool EmitStmtIndices) : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher), -EmitIndices(EmitIndices) {} +EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool TraverseDecl(Decl *D) { TraversalStack.push_back(D); LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D); TraversalStack.pop_back(); return true; } + bool TraverseStmt(Stmt *S); + bool VisitNamedDecl(const NamedDecl *D); + bool VisitDeclRefExpr(const DeclRefExpr *D); private: DummyMatchVisitor &Matcher; - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; unsigned Index = 0; llvm::SmallVector TraversalStack; }; class DummyMatchVisitor : public ExpectedLocationVisitor { - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; public: - DummyMatchVisitor(bool EmitIndices = false) : EmitIndices(EmitIndices) {} + DummyMatchVisitor(bool EmitDeclIndices = false, bool EmitStmtIndices = false) + : EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool VisitTranslationUnitDecl(TranslationUnitDecl *TU) { const ASTContext &Context = TU->getASTContext(); const SourceManager &SM = Context.getSourceManager(); -LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitIndices); +LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitDeclIndices, + EmitStmtIndices); SubVisitor.TraverseDecl(TU); return false; } - void match(StringRef Path, const Decl *D) { Match(Path, D->getLocStart()); } + template void match(StringRef Path, const T *D) { +Match(Path, D->getLocStart()); + } }; +bool LexicallyOrderedDeclVisitor::TraverseStmt(Stmt *S) { + Matcher.match("overridden TraverseStmt", S); + return LexicallyOrderedRecursiveASTVisitor::TraverseStmt(S); +} + bool LexicallyOrderedDeclVisitor::VisitNamedDecl(const NamedDecl *D) { std::string Path; llvm::raw_string_ostream OS(Path); @@ -73,7 +86,16 @@ if (isa(D) or isa(D)) OS << "/"; } - if (EmitIndices) + if (EmitDeclIndices) +OS << "@" << Index++; + Matcher.match(OS.str(), D); + return true; +} + +bool LexicallyOrderedDeclVisitor::VisitDeclRefExpr(const DeclRefExpr *D) { + std::string Name = D->getFoundDecl()->getNameAsString(); + llvm::raw_string_ostream OS(Name); + if (EmitStmtIndices) OS << "@" << Index++; Matcher.match(OS.str(), D); return true; @@ -151,13 +173,55 @@ template T f(); template class Class {}; )"; - DummyMatchVisitor Visitor(/*EmitIndices=*/true); + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/true); Visitor.ExpectMatch("/f/T@0", 2, 11); Visitor.ExpectMatch("/f/f/@1", 2, 20); Visitor.ExpectMatch("/Class/U@2", 3, 11); Visitor.ExpectMatch("/Class/@3", 3, 20); Visitor.ExpectMatch("/Class/Class/@4", 3, 34); EXPECT_TRUE(Visitor.runOver(Source)); } +TEST(LexicallyOrderedRecursiveASTVisitor, VisitCXXOperatorCallExpr) { + StringRef Source = R"( +struct S { + S &operator+(S&); + S *operator->(); + S &operator++(); + S operator++(int); + void operator()(int, int); + void operator[](int); + void f(); +}; +S a, b, c; + +void test() { + a = b + c; + a->f(); + a(1, 2); + b[0]; + ++a; + b++; +} +)"; + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/false, +/*EmitStmtIndices=*/true); + // There are two overloaded operators that start at this point + // This makes sure they are both traversed using the overridden + // TraverseStmt, as the traversal is implemented by us for + // CXXOperatorCallExpr. + Visitor.ExpectMatch("overridden TraverseStmt", 14, 3, 2); + Visitor.ExpectMatch("a@0", 14, 3); + Visitor.ExpectMatch("operator=@1", 14, 5); + Visitor.ExpectMatch("b@2", 14, 7); + Visitor.ExpectMatch("operator+@3", 14, 9); + Visitor.ExpectMatch("c@4", 14, 11); + Visitor.ExpectMatch("operator->@6", 15, 4); + Visitor.ExpectMatch("operator()@8", 16, 4); + Visitor.ExpectMatch("operator[]@10", 17, 4); + Visitor.ExpectMatch("operator++@11", 18, 3); + Visitor.E
[PATCH] D37200: [AST] Traverse CXXOperatorCallExpr in LexicallyOrderedRecursiveASTVisitor
johannes added a comment. The previous version didn't call TraverseDecl of the derived class, this is fixed now. The public getDerived.TraverseStmt() does not accept a DataRecursionQueue, so this also could not be used (I think) I used the wrapper TraverseStmtBase, which should behave exactly as the method that originally traverses CXXOperatorCallExpr Comment at: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h:63 using BaseType = RecursiveASTVisitor; + using typename BaseType::DataRecursionQueue; arphaman wrote: > Do you still need the using here? removed it now as it's only used once https://reviews.llvm.org/D37200 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37200: [AST] Traverse CXXOperatorCallExpr in LexicallyOrderedRecursiveASTVisitor
johannes updated this revision to Diff 112894. johannes added a comment. detect prefix/postfix from number of arguments https://reviews.llvm.org/D37200 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp Index: unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp === --- unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp +++ unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp @@ -21,42 +21,55 @@ : public LexicallyOrderedRecursiveASTVisitor { public: LexicallyOrderedDeclVisitor(DummyMatchVisitor &Matcher, - const SourceManager &SM, bool EmitIndices) + const SourceManager &SM, bool EmitDeclIndices, + bool EmitStmtIndices) : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher), -EmitIndices(EmitIndices) {} +EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool TraverseDecl(Decl *D) { TraversalStack.push_back(D); LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D); TraversalStack.pop_back(); return true; } + bool TraverseStmt(Stmt *S); + bool VisitNamedDecl(const NamedDecl *D); + bool VisitDeclRefExpr(const DeclRefExpr *D); private: DummyMatchVisitor &Matcher; - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; unsigned Index = 0; llvm::SmallVector TraversalStack; }; class DummyMatchVisitor : public ExpectedLocationVisitor { - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; public: - DummyMatchVisitor(bool EmitIndices = false) : EmitIndices(EmitIndices) {} + DummyMatchVisitor(bool EmitDeclIndices = false, bool EmitStmtIndices = false) + : EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool VisitTranslationUnitDecl(TranslationUnitDecl *TU) { const ASTContext &Context = TU->getASTContext(); const SourceManager &SM = Context.getSourceManager(); -LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitIndices); +LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitDeclIndices, + EmitStmtIndices); SubVisitor.TraverseDecl(TU); return false; } - void match(StringRef Path, const Decl *D) { Match(Path, D->getLocStart()); } + template void match(StringRef Path, const T *D) { +Match(Path, D->getLocStart()); + } }; +bool LexicallyOrderedDeclVisitor::TraverseStmt(Stmt *S) { + Matcher.match("overridden TraverseStmt", S); + return LexicallyOrderedRecursiveASTVisitor::TraverseStmt(S); +} + bool LexicallyOrderedDeclVisitor::VisitNamedDecl(const NamedDecl *D) { std::string Path; llvm::raw_string_ostream OS(Path); @@ -73,7 +86,16 @@ if (isa(D) or isa(D)) OS << "/"; } - if (EmitIndices) + if (EmitDeclIndices) +OS << "@" << Index++; + Matcher.match(OS.str(), D); + return true; +} + +bool LexicallyOrderedDeclVisitor::VisitDeclRefExpr(const DeclRefExpr *D) { + std::string Name = D->getFoundDecl()->getNameAsString(); + llvm::raw_string_ostream OS(Name); + if (EmitStmtIndices) OS << "@" << Index++; Matcher.match(OS.str(), D); return true; @@ -151,13 +173,55 @@ template T f(); template class Class {}; )"; - DummyMatchVisitor Visitor(/*EmitIndices=*/true); + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/true); Visitor.ExpectMatch("/f/T@0", 2, 11); Visitor.ExpectMatch("/f/f/@1", 2, 20); Visitor.ExpectMatch("/Class/U@2", 3, 11); Visitor.ExpectMatch("/Class/@3", 3, 20); Visitor.ExpectMatch("/Class/Class/@4", 3, 34); EXPECT_TRUE(Visitor.runOver(Source)); } +TEST(LexicallyOrderedRecursiveASTVisitor, VisitCXXOperatorCallExpr) { + StringRef Source = R"( +struct S { + S &operator+(S&); + S *operator->(); + S &operator++(); + S operator++(int); + void operator()(int, int); + void operator[](int); + void f(); +}; +S a, b, c; + +void test() { + a = b + c; + a->f(); + a(1, 2); + b[0]; + ++a; + b++; +} +)"; + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/false, +/*EmitStmtIndices=*/true); + // There are two overloaded operators that start at this point + // This makes sure they are both traversed using the overridden + // TraverseStmt, as the traversal is implemented by us for + // CXXOperatorCallExpr. + Visitor.ExpectMatch("overridden TraverseStmt", 14, 3, 2); + Visitor.ExpectMatch("a@0", 14, 3); + Visitor.ExpectMatch("operator=@1", 14, 5); + Visitor.ExpectMatch("b@2", 14, 7); + Visitor.ExpectMatch("operator+@3", 14, 9); + Visitor.ExpectMatch("c@4", 14, 11); + Visitor.ExpectMatch("operator->@6", 15, 4); + Visitor.ExpectMatch("operator()@8", 16, 4); + Visitor.ExpectMatch("operator[]@10", 17, 4); + Visitor.ExpectMatch("operator++@11", 18, 3); + Visitor.ExpectMatch("operat
[PATCH] D37005: [clang-diff] Initial implementation of patching
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 Codes) { + diff::SyntaxTree Trees[3]; + std::unique_ptr ASTs[3]; + std::vector 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 +Patch("patch", + cl::desc("Try to apply the edit actions between the two input " + "files to the specifi
[PATCH] D37200: [AST] Traverse CXXOperatorCallExpr in LexicallyOrderedRecursiveASTVisitor
johannes updated this revision to Diff 112951. johannes added a comment. use a specialized getStmtChildren to fix the order for CXXOperatorCallExpr https://reviews.llvm.org/D37200 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp Index: unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp === --- unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp +++ unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp @@ -21,42 +21,55 @@ : public LexicallyOrderedRecursiveASTVisitor { public: LexicallyOrderedDeclVisitor(DummyMatchVisitor &Matcher, - const SourceManager &SM, bool EmitIndices) + const SourceManager &SM, bool EmitDeclIndices, + bool EmitStmtIndices) : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher), -EmitIndices(EmitIndices) {} +EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool TraverseDecl(Decl *D) { TraversalStack.push_back(D); LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D); TraversalStack.pop_back(); return true; } + bool TraverseStmt(Stmt *S); + bool VisitNamedDecl(const NamedDecl *D); + bool VisitDeclRefExpr(const DeclRefExpr *D); private: DummyMatchVisitor &Matcher; - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; unsigned Index = 0; llvm::SmallVector TraversalStack; }; class DummyMatchVisitor : public ExpectedLocationVisitor { - bool EmitIndices; + bool EmitDeclIndices, EmitStmtIndices; public: - DummyMatchVisitor(bool EmitIndices = false) : EmitIndices(EmitIndices) {} + DummyMatchVisitor(bool EmitDeclIndices = false, bool EmitStmtIndices = false) + : EmitDeclIndices(EmitDeclIndices), EmitStmtIndices(EmitStmtIndices) {} bool VisitTranslationUnitDecl(TranslationUnitDecl *TU) { const ASTContext &Context = TU->getASTContext(); const SourceManager &SM = Context.getSourceManager(); -LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitIndices); +LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitDeclIndices, + EmitStmtIndices); SubVisitor.TraverseDecl(TU); return false; } - void match(StringRef Path, const Decl *D) { Match(Path, D->getLocStart()); } + template void match(StringRef Path, const T *D) { +Match(Path, D->getLocStart()); + } }; +bool LexicallyOrderedDeclVisitor::TraverseStmt(Stmt *S) { + Matcher.match("overridden TraverseStmt", S); + return LexicallyOrderedRecursiveASTVisitor::TraverseStmt(S); +} + bool LexicallyOrderedDeclVisitor::VisitNamedDecl(const NamedDecl *D) { std::string Path; llvm::raw_string_ostream OS(Path); @@ -73,7 +86,16 @@ if (isa(D) or isa(D)) OS << "/"; } - if (EmitIndices) + if (EmitDeclIndices) +OS << "@" << Index++; + Matcher.match(OS.str(), D); + return true; +} + +bool LexicallyOrderedDeclVisitor::VisitDeclRefExpr(const DeclRefExpr *D) { + std::string Name = D->getFoundDecl()->getNameAsString(); + llvm::raw_string_ostream OS(Name); + if (EmitStmtIndices) OS << "@" << Index++; Matcher.match(OS.str(), D); return true; @@ -151,13 +173,55 @@ template T f(); template class Class {}; )"; - DummyMatchVisitor Visitor(/*EmitIndices=*/true); + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/true); Visitor.ExpectMatch("/f/T@0", 2, 11); Visitor.ExpectMatch("/f/f/@1", 2, 20); Visitor.ExpectMatch("/Class/U@2", 3, 11); Visitor.ExpectMatch("/Class/@3", 3, 20); Visitor.ExpectMatch("/Class/Class/@4", 3, 34); EXPECT_TRUE(Visitor.runOver(Source)); } +TEST(LexicallyOrderedRecursiveASTVisitor, VisitCXXOperatorCallExpr) { + StringRef Source = R"( +struct S { + S &operator+(S&); + S *operator->(); + S &operator++(); + S operator++(int); + void operator()(int, int); + void operator[](int); + void f(); +}; +S a, b, c; + +void test() { + a = b + c; + a->f(); + a(1, 2); + b[0]; + ++a; + b++; +} +)"; + DummyMatchVisitor Visitor(/*EmitDeclIndices=*/false, +/*EmitStmtIndices=*/true); + // There are two overloaded operators that start at this point + // This makes sure they are both traversed using the overridden + // TraverseStmt, as the traversal is implemented by us for + // CXXOperatorCallExpr. + Visitor.ExpectMatch("overridden TraverseStmt", 14, 3, 2); + Visitor.ExpectMatch("a@0", 14, 3); + Visitor.ExpectMatch("operator=@1", 14, 5); + Visitor.ExpectMatch("b@2", 14, 7); + Visitor.ExpectMatch("operator+@3", 14, 9); + Visitor.ExpectMatch("c@4", 14, 11); + Visitor.ExpectMatch("operator->@6", 15, 4); + Visitor.ExpectMatch("operator()@8", 16, 4); + Visitor.ExpectMatch("operator[]@10", 17, 4); + Visitor.ExpectMatch("operator++@11", 18, 3); +
[PATCH] D37200: [AST] Traverse CXXOperatorCallExpr in LexicallyOrderedRecursiveASTVisitor
johannes added a comment. Yeah, this seems to be the best solution for this. I think I ran into the same issue when working on the StmtDataCollector - basically there can only be two Traverse*, one in the Derived class and the other one needs to do all the magic with walking the specialisation hierarchy. https://reviews.llvm.org/D37200 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36998: [AST] Traverse templates in LexicallyOrderedRecursiveASTVisitor
johannes added a comment. This is currently broken, if a user provides a TraverseClassTemplateDecl, then the same method in this class will not be called, I think I will add a flag (probably not user visible) in RecursiveASTVisitor.h to switch the order for templates https://reviews.llvm.org/D36998 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37005: [clang-diff] Initial implementation of patching
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 Codes) { + llvm::Optional Trees[3]; + std::unique_ptr 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 +Patch("patch", + cl::desc("Try to apply the edit actions between the two input " + "files to the specified target."), + cl::desc(""), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::P
[PATCH] D37005: [clang-diff] Initial implementation of patching
johannes added inline comments. Comment at: include/clang/Tooling/ASTDiff/ASTDiff.h:73 public: + /// Empty (invalid) SyntaxTree. + SyntaxTree(); arphaman wrote: > Why do you need to create an empty tree? What about using llvm::Optional > instead? ok, i use optional now instead in the unittest https://reviews.llvm.org/D37005 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37001: [clang-diff] Use data collectors for node comparison
johannes added inline comments. Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:537 + +#include "../../AST/DeclDataCollectors.inc" + arphaman wrote: > I didn't realize that you're including files from within `lib`. That's not > ideal. You should add a pre-commit that moves the *.inc files over to > include/AST (and marks them as textual in Clang's `module.modulemap`). Then > you should include them using the clang/AST path. I tried moving StmtDataCollectors.inc to and adding `+ textual header "AST/StmtDataCollectors.inc" ` in the modulemap, then I get some CMake errors, not sure why. It might make sense to tablegen it like the other .inc files. https://reviews.llvm.org/D37001 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36998: [AST] Traverse templates in LexicallyOrderedRecursiveASTVisitor
johannes updated this revision to Diff 113546. johannes added a comment. fix by adding an option in RecursiveASTVisitor https://reviews.llvm.org/D36998 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp Index: unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp === --- unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp +++ unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp @@ -21,8 +21,9 @@ : public LexicallyOrderedRecursiveASTVisitor { public: LexicallyOrderedDeclVisitor(DummyMatchVisitor &Matcher, - const SourceManager &SM) - : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher) {} + const SourceManager &SM, bool EmitIndices) + : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher), +EmitIndices(EmitIndices) {} bool TraverseDecl(Decl *D) { TraversalStack.push_back(D); @@ -35,15 +36,20 @@ private: DummyMatchVisitor &Matcher; + bool EmitIndices; + unsigned Index = 0; llvm::SmallVector TraversalStack; }; class DummyMatchVisitor : public ExpectedLocationVisitor { + bool EmitIndices; + public: + DummyMatchVisitor(bool EmitIndices = false) : EmitIndices(EmitIndices) {} bool VisitTranslationUnitDecl(TranslationUnitDecl *TU) { const ASTContext &Context = TU->getASTContext(); const SourceManager &SM = Context.getSourceManager(); -LexicallyOrderedDeclVisitor SubVisitor(*this, SM); +LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitIndices); SubVisitor.TraverseDecl(TU); return false; } @@ -64,9 +70,11 @@ OS << ND->getNameAsString(); else OS << "???"; -if (isa(D)) +if (isa(D) or isa(D)) OS << "/"; } + if (EmitIndices) +OS << "@" << Index++; Matcher.match(OS.str(), D); return true; } @@ -138,4 +146,18 @@ EXPECT_TRUE(Visitor.runOver(Source, DummyMatchVisitor::Lang_OBJC)); } +TEST(LexicallyOrderedRecursiveASTVisitor, VisitTemplateDecl) { + StringRef Source = R"( +template T f(); +template class Class {}; +)"; + DummyMatchVisitor Visitor(/*EmitIndices=*/true); + Visitor.ExpectMatch("/f/T@1", 2, 11); + Visitor.ExpectMatch("/f/f/@2", 2, 20); + Visitor.ExpectMatch("/Class/U@4", 3, 11); + Visitor.ExpectMatch("/Class/@5", 3, 20); + Visitor.ExpectMatch("/Class/Class/@6", 3, 34); + EXPECT_TRUE(Visitor.runOver(Source)); +} + } // end anonymous namespace Index: include/clang/AST/RecursiveASTVisitor.h === --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -499,10 +499,10 @@ bool canIgnoreChildDeclWhileTraversingDeclContext(const Decl *Child); -private: // These are helper methods used by more than one Traverse* method. bool TraverseTemplateParameterListHelper(TemplateParameterList *TPL); +private: // Traverses template parameter lists of either a DeclaratorDecl or TagDecl. template bool TraverseDeclTemplateParameterLists(T *D); @@ -535,6 +535,7 @@ bool dataTraverseNode(Stmt *S, DataRecursionQueue *Queue); bool PostVisitStmt(Stmt *S); + bool shouldTraverseTemplateArgumentsBeforeDecl() const { return false; } }; template @@ -1688,8 +1689,13 @@ // template declarations. #define DEF_TRAVERSE_TMPL_DECL(TMPLDECLKIND) \ DEF_TRAVERSE_DECL(TMPLDECLKIND##TemplateDecl, { \ -TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ -TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ +if (getDerived().shouldTraverseTemplateArgumentsBeforeDecl()) {\ + TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ + TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ +} else { \ + TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ + TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ +} \ \ /* By default, we do not traverse the instantiations of\ class templates since they do not appear in the user code. The \ Index: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h === --- include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h +++ include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h @@ -111,6 +111,8 @@ return true; } + bool shouldTraverseTemplateArgumentsBeforeDecl() const { return
[PATCH] D37383: [AST] Add TableGen for StmtDataCollectors
johannes created this revision. Herald added a subscriber: mgorny. Herald added 1 blocking reviewer(s): teemperor. This adds an option "-gen-clang-data-collectors" to the Clang TableGen that is used to generate StmtDataCollectors.inc. https://reviews.llvm.org/D37383 Files: include/clang/AST/CMakeLists.txt include/clang/AST/StmtDataCollectors.td lib/AST/StmtDataCollectors.inc lib/Analysis/CloneDetection.cpp unittests/AST/DataCollectionTest.cpp utils/TableGen/CMakeLists.txt utils/TableGen/ClangDataCollectorsEmitter.cpp utils/TableGen/TableGen.cpp utils/TableGen/TableGenBackends.h Index: utils/TableGen/TableGenBackends.h === --- utils/TableGen/TableGenBackends.h +++ utils/TableGen/TableGenBackends.h @@ -75,6 +75,8 @@ void EmitClangDiagDocs(RecordKeeper &Records, raw_ostream &OS); void EmitClangOptDocs(RecordKeeper &Records, raw_ostream &OS); +void EmitClangDataCollectors(RecordKeeper &Records, raw_ostream &OS); + void EmitTestPragmaAttributeSupportedAttributes(RecordKeeper &Records, raw_ostream &OS); Index: utils/TableGen/TableGen.cpp === --- utils/TableGen/TableGen.cpp +++ utils/TableGen/TableGen.cpp @@ -57,6 +57,7 @@ GenAttrDocs, GenDiagDocs, GenOptDocs, + GenDataCollectors, GenTestPragmaAttributeSupportedAttributes }; @@ -147,6 +148,7 @@ clEnumValN(GenDiagDocs, "gen-diag-docs", "Generate diagnostic documentation"), clEnumValN(GenOptDocs, "gen-opt-docs", "Generate option documentation"), +clEnumValN(GenDataCollectors, "gen-clang-data-collectors", "Generate data collectors for AST nodes"), clEnumValN(GenTestPragmaAttributeSupportedAttributes, "gen-clang-test-pragma-attribute-supported-attributes", "Generate a list of attributes supported by #pragma clang " @@ -262,6 +264,9 @@ case GenOptDocs: EmitClangOptDocs(Records, OS); break; + case GenDataCollectors: +EmitClangDataCollectors(Records, OS); +break; case GenTestPragmaAttributeSupportedAttributes: EmitTestPragmaAttributeSupportedAttributes(Records, OS); break; Index: utils/TableGen/ClangDataCollectorsEmitter.cpp === --- /dev/null +++ utils/TableGen/ClangDataCollectorsEmitter.cpp @@ -0,0 +1,18 @@ +#include "llvm/TableGen/Record.h" +#include "llvm/TableGen/TableGenBackend.h" + +using namespace llvm; + +namespace clang { +void EmitClangDataCollectors(RecordKeeper &RK, raw_ostream &OS) { + const auto &Defs = RK.getClasses(); + for (const auto &Entry : Defs) { +Record &R = *Entry.second; +OS << "DEF_ADD_DATA(" << R.getName() << ", {"; +auto Code = R.getValue("Code")->getValue(); +OS << Code->getAsUnquotedString() << "}\n)"; +OS << "\n"; + } + OS << "#undef DEF_ADD_DATA\n"; +} +} // end namespace clang Index: utils/TableGen/CMakeLists.txt === --- utils/TableGen/CMakeLists.txt +++ utils/TableGen/CMakeLists.txt @@ -6,6 +6,7 @@ ClangCommentCommandInfoEmitter.cpp ClangCommentHTMLNamedCharacterReferenceEmitter.cpp ClangCommentHTMLTagsEmitter.cpp + ClangDataCollectorsEmitter.cpp ClangDiagnosticsEmitter.cpp ClangOptionDocEmitter.cpp ClangSACheckersEmitter.cpp Index: unittests/AST/DataCollectionTest.cpp === --- unittests/AST/DataCollectionTest.cpp +++ unittests/AST/DataCollectionTest.cpp @@ -46,7 +46,7 @@ ConstStmtVisitor::Visit##CLASS(S); \ } -#include "../../lib/AST/StmtDataCollectors.inc" +#include "clang/AST/StmtDataCollectors.inc" }; } // end anonymous namespace Index: lib/Analysis/CloneDetection.cpp === --- lib/Analysis/CloneDetection.cpp +++ lib/Analysis/CloneDetection.cpp @@ -205,7 +205,7 @@ ConstStmtVisitor>::Visit##CLASS(S);\ } -#include "../AST/StmtDataCollectors.inc" +#include "clang/AST/StmtDataCollectors.inc" // Type II clones ignore variable names and literals, so let's skip them. #define SKIP(CLASS)\ Index: lib/AST/StmtDataCollectors.inc === --- lib/AST/StmtDataCollectors.inc +++ /dev/null @@ -1,141 +0,0 @@ -// The functions below collect the class specific data of each Stmt subclass. - -DEF_ADD_DATA(Stmt, { - addData(S->getStmtClass()); - // This ensures that non-macro-generated code isn't identical to - // macro-generated code. - addData(data_collection::getMacroStack(S->getLocStart(), Context)); - addData(data_collection::getMacroStack(S->getLocEnd(), Context)); -}) -DEF_ADD_DATA(Expr, { addData(S->getType()); }) - -//--- Builtin
[PATCH] D37383: [AST] Add TableGen for StmtDataCollectors
johannes added a comment. In https://reviews.llvm.org/D37383#858905, @teemperor wrote: > @arphaman I suggested tablegen in https://reviews.llvm.org/D36664 because I > remembered we had some CMake sanity check about not having an *.inc in our > include dir: > https://github.com/llvm-mirror/clang/blob/master/CMakeLists.txt#L266 Not sure > if it actually fires for our case, but it looks like it does. Yes, that was the issue why it didn't work under include/. I think the sanity check could be removed, since in-source builds are forbidden anyway. Or maybe it is still useful by telling us that .inc is probably an generated file (most, but not all of them are generated, it seems), so another solution would be to just change the file extension. Actually, `.def` might be the right extension. Shall we move to using TableGen only when we need it, and rename it to `.def` for now? A preprocessor-only solution for the two modes could look like this: user: #define DERIVED MyStmtVisitor #define SINGLE_TU #include "clang/AST/StmtDataCollectors.def" StmtDataCollectors.def: #define DEF_VISIT(CLASS, CODE)\ template void Visit##CLASS(const CLASS *S) {\ CODE;\ ConstStmtVisitor<##DERIVED>::Visit##CLASS(S)\ } #ifdef SINGLE_TU #define DEF_ADD_DATA(CLASS, COMMON, SINGLE, CROSS) DEF_VISIT(CLASS, COMMON; SINGLE) #else #define DEF_ADD_DATA(CLASS, COMMON, SINGLE, CROSS) DEF_VISIT(CLASS, COMMON, CROSS) #endif DEF_ADD_DATA(Stmt, .., .., ..) ... However, I think TableGen has an advantage when we can extract some structure instead of just storing the code. Basically, a list of primitive and one of pointer members for each class could simplify things. https://reviews.llvm.org/D37383 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36998: [AST] Traverse templates in LexicallyOrderedRecursiveASTVisitor
johannes updated this revision to Diff 113581. johannes added a comment. undo visibility change https://reviews.llvm.org/D36998 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp Index: unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp === --- unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp +++ unittests/Tooling/LexicallyOrderedRecursiveASTVisitorTest.cpp @@ -21,8 +21,9 @@ : public LexicallyOrderedRecursiveASTVisitor { public: LexicallyOrderedDeclVisitor(DummyMatchVisitor &Matcher, - const SourceManager &SM) - : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher) {} + const SourceManager &SM, bool EmitIndices) + : LexicallyOrderedRecursiveASTVisitor(SM), Matcher(Matcher), +EmitIndices(EmitIndices) {} bool TraverseDecl(Decl *D) { TraversalStack.push_back(D); @@ -35,15 +36,20 @@ private: DummyMatchVisitor &Matcher; + bool EmitIndices; + unsigned Index = 0; llvm::SmallVector TraversalStack; }; class DummyMatchVisitor : public ExpectedLocationVisitor { + bool EmitIndices; + public: + DummyMatchVisitor(bool EmitIndices = false) : EmitIndices(EmitIndices) {} bool VisitTranslationUnitDecl(TranslationUnitDecl *TU) { const ASTContext &Context = TU->getASTContext(); const SourceManager &SM = Context.getSourceManager(); -LexicallyOrderedDeclVisitor SubVisitor(*this, SM); +LexicallyOrderedDeclVisitor SubVisitor(*this, SM, EmitIndices); SubVisitor.TraverseDecl(TU); return false; } @@ -64,9 +70,11 @@ OS << ND->getNameAsString(); else OS << "???"; -if (isa(D)) +if (isa(D) or isa(D)) OS << "/"; } + if (EmitIndices) +OS << "@" << Index++; Matcher.match(OS.str(), D); return true; } @@ -138,4 +146,18 @@ EXPECT_TRUE(Visitor.runOver(Source, DummyMatchVisitor::Lang_OBJC)); } +TEST(LexicallyOrderedRecursiveASTVisitor, VisitTemplateDecl) { + StringRef Source = R"( +template T f(); +template class Class {}; +)"; + DummyMatchVisitor Visitor(/*EmitIndices=*/true); + Visitor.ExpectMatch("/f/T@1", 2, 11); + Visitor.ExpectMatch("/f/f/@2", 2, 20); + Visitor.ExpectMatch("/Class/U@4", 3, 11); + Visitor.ExpectMatch("/Class/@5", 3, 20); + Visitor.ExpectMatch("/Class/Class/@6", 3, 34); + EXPECT_TRUE(Visitor.runOver(Source)); +} + } // end anonymous namespace Index: include/clang/AST/RecursiveASTVisitor.h === --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -535,6 +535,7 @@ bool dataTraverseNode(Stmt *S, DataRecursionQueue *Queue); bool PostVisitStmt(Stmt *S); + bool shouldTraverseTemplateArgumentsBeforeDecl() const { return false; } }; template @@ -1688,8 +1689,13 @@ // template declarations. #define DEF_TRAVERSE_TMPL_DECL(TMPLDECLKIND) \ DEF_TRAVERSE_DECL(TMPLDECLKIND##TemplateDecl, { \ -TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ -TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ +if (getDerived().shouldTraverseTemplateArgumentsBeforeDecl()) {\ + TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ + TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ +} else { \ + TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ + TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ +} \ \ /* By default, we do not traverse the instantiations of\ class templates since they do not appear in the user code. The \ Index: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h === --- include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h +++ include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h @@ -111,6 +111,8 @@ return true; } + bool shouldTraverseTemplateArgumentsBeforeDecl() const { return true; } + private: bool TraverseAdditionalLexicallyNestedDeclarations() { // FIXME: Ideally the gathered declarations and the declarations in the ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37383: [AST] Add TableGen for StmtDataCollectors
johannes added a comment. @teemperor ok for you? did phabricator make you a blocking reviewer because of the affected code, or did I do that somehow? https://reviews.llvm.org/D37383 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37662: [AST] Make RecursiveASTVisitor visit TemplateDecls in source order
johannes created this revision. This causes template arguments to be traversed before the templated declaration, which is useful for clients that expect the nodes in the same order as they are in the source code. Additionally, there seems to be no good reason not to do so. This was moved here from LexicallyOrderedRecursiveASTVisitor. The tests still reside in LexicallyOrderedRecursiveASTVisitorTest.cpp under VisitTemplateDecls. https://reviews.llvm.org/D37662 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h Index: include/clang/AST/RecursiveASTVisitor.h === --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -537,7 +537,6 @@ bool dataTraverseNode(Stmt *S, DataRecursionQueue *Queue); bool PostVisitStmt(Stmt *S); - bool shouldTraverseTemplateArgumentsBeforeDecl() const { return false; } }; template @@ -1691,13 +1690,8 @@ // template declarations. #define DEF_TRAVERSE_TMPL_DECL(TMPLDECLKIND) \ DEF_TRAVERSE_DECL(TMPLDECLKIND##TemplateDecl, { \ -if (getDerived().shouldTraverseTemplateArgumentsBeforeDecl()) { \ - TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ - TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ -} else { \ - TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ - TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ -} \ +TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ +TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ \ /* By default, we do not traverse the instantiations of \ class templates since they do not appear in the user code. The \ Index: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h === --- include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h +++ include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h @@ -111,8 +111,6 @@ return true; } - bool shouldTraverseTemplateArgumentsBeforeDecl() const { return true; } - Stmt::child_range getStmtChildren(Stmt *S) { return S->children(); } SmallVector getStmtChildren(CXXOperatorCallExpr *CE) { Index: include/clang/AST/RecursiveASTVisitor.h === --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -537,7 +537,6 @@ bool dataTraverseNode(Stmt *S, DataRecursionQueue *Queue); bool PostVisitStmt(Stmt *S); - bool shouldTraverseTemplateArgumentsBeforeDecl() const { return false; } }; template @@ -1691,13 +1690,8 @@ // template declarations. #define DEF_TRAVERSE_TMPL_DECL(TMPLDECLKIND) \ DEF_TRAVERSE_DECL(TMPLDECLKIND##TemplateDecl, { \ -if (getDerived().shouldTraverseTemplateArgumentsBeforeDecl()) {\ - TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ - TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ -} else { \ - TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ - TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ -} \ +TRY_TO(TraverseTemplateParameterListHelper(D->getTemplateParameters())); \ +TRY_TO(TraverseDecl(D->getTemplatedDecl())); \ \ /* By default, we do not traverse the instantiations of\ class templates since they do not appear in the user code. The \ Index: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h === --- include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h +++ include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h @@ -111,8 +111,6 @@ return true; } - bool shouldTraverseTemplateArgumentsBeforeDecl() const { return true; } - Stmt::child_range getStmtChildren(Stmt *S) { return S->children(); } SmallVector getStmtChildren(CXXOperatorCallExpr *CE) { ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37663: [AST] Make RecursiveASTVisitor visit CXXOperatorCallExpr in source order
johannes created this revision. This adds a special case for traversing CXXOperatorCallExpr. Its children are traversed in the order in which they appear in the source code, so infix and postfix operators are visited after their first argument. This behavior was previously only in LexicallyOrderedRecursiveASTVisitor, moving it here could avoid bugs in the future. It is still tested in LexicallyOrderedRecursiveASTVisitorTest.cpp in VisitTemplateDecl. Clients that somehow rely on the original traversal order will have to be updated though. https://reviews.llvm.org/D37663 Files: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h Index: include/clang/AST/RecursiveASTVisitor.h === --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -315,15 +315,40 @@ // Methods on Stmts - Stmt::child_range getStmtChildren(Stmt *S) { return S->children(); } - private: template struct has_same_member_pointer_type : std::false_type {}; template struct has_same_member_pointer_type : std::true_type {}; + Stmt::child_range getStmtChildren(Stmt *S) { return S->children(); } + + SmallVector getStmtChildren(CXXOperatorCallExpr *CE) { +SmallVector Children(CE->children()); +bool Swap; +// Swap the operator and the first operand for all infix and postfix +// operations. +switch (CE->getOperator()) { +case OO_Arrow: +case OO_Call: +case OO_Subscript: + Swap = true; + break; +case OO_PlusPlus: +case OO_MinusMinus: + // These are postfix unless there is exactly one argument. + Swap = Children.size() != 2; + break; +default: + Swap = CE->isInfixBinaryOp(); + break; +} +if (Swap && Children.size() > 1) + std::swap(Children[0], Children[1]); +return Children; + } + // Traverse the given statement. If the most-derived traverse function takes a // data recursion queue, pass it on; otherwise, discard it. Note that the // first branch of this conditional must compile whether or not the derived Index: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h === --- include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h +++ include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h @@ -111,33 +111,6 @@ return true; } - Stmt::child_range getStmtChildren(Stmt *S) { return S->children(); } - - SmallVector getStmtChildren(CXXOperatorCallExpr *CE) { -SmallVector Children(CE->children()); -bool Swap; -// Switch the operator and the first operand for all infix and postfix -// operations. -switch (CE->getOperator()) { -case OO_Arrow: -case OO_Call: -case OO_Subscript: - Swap = true; - break; -case OO_PlusPlus: -case OO_MinusMinus: - // These are postfix unless there is exactly one argument. - Swap = Children.size() != 2; - break; -default: - Swap = CE->isInfixBinaryOp(); - break; -} -if (Swap && Children.size() > 1) - std::swap(Children[0], Children[1]); -return Children; - } - private: bool TraverseAdditionalLexicallyNestedDeclarations() { // FIXME: Ideally the gathered declarations and the declarations in the Index: include/clang/AST/RecursiveASTVisitor.h === --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -315,15 +315,40 @@ // Methods on Stmts - Stmt::child_range getStmtChildren(Stmt *S) { return S->children(); } - private: template struct has_same_member_pointer_type : std::false_type {}; template struct has_same_member_pointer_type : std::true_type {}; + Stmt::child_range getStmtChildren(Stmt *S) { return S->children(); } + + SmallVector getStmtChildren(CXXOperatorCallExpr *CE) { +SmallVector Children(CE->children()); +bool Swap; +// Swap the operator and the first operand for all infix and postfix +// operations. +switch (CE->getOperator()) { +case OO_Arrow: +case OO_Call: +case OO_Subscript: + Swap = true; + break; +case OO_PlusPlus: +case OO_MinusMinus: + // These are postfix unless there is exactly one argument. + Swap = Children.size() != 2; + break; +default: + Swap = CE->isInfixBinaryOp(); + break; +} +if (Swap && Children.size() > 1) + std::swap(Children[0], Children[1]); +return Children; + } + // Traverse the given statement. If the most-derived traverse function takes a // data recursion queue, pass it on; otherwise, discard it. Note that the // first branch of this conditional must compile whether or not the derived Index: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h ==
[PATCH] D37663: [AST] Make RecursiveASTVisitor visit CXXOperatorCallExpr in source order
johannes added inline comments. Comment at: include/clang/AST/RecursiveASTVisitor.h:334 +case OO_Arrow: +case OO_Call: +case OO_Subscript: klimek wrote: > Why do we need to swap for calls? The idea is that the opening parenthesis/bracket comes after the caller, for example in this call with two arguments. ``` `-CXXOperatorCallExpr |-ImplicitCastExpr | `-DeclRefExpr operator() |-DeclRefExpr caller |-IntegerLiteral `-IntegerLiteral ``` Of course we fail to capture the fact that there is also a closing parenthesis or bracket. So this is no perfect solution. https://reviews.llvm.org/D37663 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D37663: [AST] Make RecursiveASTVisitor visit CXXOperatorCallExpr in source order
johannes updated this revision to Diff 115018. johannes added a comment. use CXXOperatorCallExpr::isPrefixOp() to determine whether it's infix or postfix directly traverse statement children instead of copying https://reviews.llvm.org/D37663 Files: include/clang/AST/ExprCXX.h include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h include/clang/AST/RecursiveASTVisitor.h lib/AST/ExprCXX.cpp Index: lib/AST/ExprCXX.cpp === --- lib/AST/ExprCXX.cpp +++ lib/AST/ExprCXX.cpp @@ -41,6 +41,12 @@ } } +bool CXXOperatorCallExpr::isPrefixOp() const { + return getNumArgs() == 1 && getOperator() != OO_Call && + getOperator() != OO_Arrow; +} + + bool CXXTypeidExpr::isPotentiallyEvaluated() const { if (isTypeOperand()) return false; Index: include/clang/AST/RecursiveASTVisitor.h === --- include/clang/AST/RecursiveASTVisitor.h +++ include/clang/AST/RecursiveASTVisitor.h @@ -315,8 +315,6 @@ // Methods on Stmts - Stmt::child_range getStmtChildren(Stmt *S) { return S->children(); } - private: template struct has_same_member_pointer_type : std::false_type {}; @@ -2080,15 +2078,26 @@ TRY_TO(WalkUpFrom##STMT(S)); \ { CODE; } \ if (ShouldVisitChildren) { \ - for (Stmt * SubStmt : getDerived().getStmtChildren(S)) { \ -TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(SubStmt); \ + auto ChildIterator = S->child_begin(); \ + /* Make the operator come second for infix and postfix overloaded\ + * operators. */ \ + if (auto *CE = dyn_cast(S)) { \ +if (CE->isInfixBinaryOp() || !CE->isPrefixOp()) { \ + Stmt *Operator = *ChildIterator; \ + TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(*++ChildIterator); \ + TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(Operator); \ + ++ChildIterator; \ +} \ + }\ + for (auto ChildEnd = S->child_end(); ChildIterator != ChildEnd; \ + ++ChildIterator) { \ +TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(*ChildIterator); \ }\ } \ if (!Queue && ReturnValue && getDerived().shouldTraversePostOrder()) \ TRY_TO(WalkUpFrom##STMT(S)); \ return ReturnValue;\ } - DEF_TRAVERSE_STMT(GCCAsmStmt, { TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getAsmString()); for (unsigned I = 0, E = S->getNumInputs(); I < E; ++I) { Index: include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h === --- include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h +++ include/clang/AST/LexicallyOrderedRecursiveASTVisitor.h @@ -111,33 +111,6 @@ return true; } - Stmt::child_range getStmtChildren(Stmt *S) { return S->children(); } - - SmallVector getStmtChildren(CXXOperatorCallExpr *CE) { -SmallVector Children(CE->children()); -bool Swap; -// Switch the operator and the first operand for all infix and postfix -// operations. -switch (CE->getOperator()) { -case OO_Arrow: -case OO_Call: -case OO_Subscript: - Swap = true; - break; -case OO_PlusPlus: -case OO_MinusMinus: - // These are postfix unless there is exactly one argument. - Swap = Children.size() != 2; - break; -default: - Swap = CE->isInfixBinaryOp(); - break; -} -if (Swap && Children.size() > 1) - std::swap(Children[0], Children[1]); -return Children; - } - private: bool TraverseAdditionalLexicallyNestedDeclarations() { // FIXME: Ideally the gathered declarations and the declarations in the Index: include/clang/AST/ExprCXX.h === --- include/clang/AST/ExprCXX.h +++ include/clang/AST/ExprCXX.h @@ -87,6 +87,8 @@ /// \brief Is this written as an infix binary operator? bool isInfixBinaryOp() const; + bool isPrefixOp() const; + /// \brief Returns the location of the operator symbol in the expression. /// /// When \c getOperator()==O
[PATCH] D37663: [AST] Make RecursiveASTVisitor visit CXXOperatorCallExpr in source order
johannes added inline comments. Comment at: include/clang/AST/RecursiveASTVisitor.h:327 + + SmallVector getStmtChildren(CXXOperatorCallExpr *CE) { +SmallVector Children(CE->children()); rsmith wrote: > The copy here is more expensive than I'd like. Instead of returning a list of > children, please make this function *traverse* the children (which is what > its one and only caller is going to do anyway) to avoid the copy. I wasn't sure whether there is a way to do this in a function without writing a custom iterator that checks whether it should be swapped; I used a different approach. Comment at: include/clang/AST/RecursiveASTVisitor.h:334 +case OO_Arrow: +case OO_Call: +case OO_Subscript: rsmith wrote: > johannes wrote: > > klimek wrote: > > > Why do we need to swap for calls? > > The idea is that the opening parenthesis/bracket comes after the caller, > > for example in this call with two arguments. > > > > ``` > > `-CXXOperatorCallExpr > > |-ImplicitCastExpr > > | `-DeclRefExpr operator() > > |-DeclRefExpr caller > > |-IntegerLiteral > > `-IntegerLiteral > > ``` > > > > Of course we fail to capture the fact that there is also a closing > > parenthesis or bracket. So this is no perfect solution. > > Of course we fail to capture the fact that there is also a closing > > parenthesis or bracket. > > One way we could handle this would be to effectively order by the start > location for preorder traversal, and by the end location for postorder > traversal. So for a case like your `caller(1,2)`, we would visit > `CXXOperatorCallExpr`, `caller`, `operator()`, `1`, then `2` when doing > preorder visitation, and `caller`, `1`, `2`, `operator()`, then > `CXXOperatorCallExpr` when doing postorder visitation (because the notional > source location for the `operator()` invocation extends across both `1` and > `2` subexpressions). > > But I think it's probably not worthwhile to go to this level of detail, and > treating the `(` as the location of the `operator()` call and ignoring the > `)` seems like a reasonable approach to me. Yeah, we can do that if someone needs it. Another way would be to model the operator as parent of the operands, although I assume that this would not compose well. I don't expect it to be feasible but I imagine CXXOperatorCallExpr could also inherit from DeclRefExpr. https://reviews.llvm.org/D37663 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D40781: [DataCollection] Allow choosing between collection categories
johannes created this revision. Herald added 1 blocking reviewer(s): teemperor. Herald added a subscriber: ilya-biryukov. This adds a list of collection categories to ClangDataCollectorsEmitter.cpp, in an aim to eventually unify the various AST hashing mechanisms (each one will get its own category). In the various *DataCollectors.td files we can specify for each AST node a way to collect data specific to a collection category. Clients can then define COLLECT_FROM_* to collect data as defined in this category. https://reviews.llvm.org/D40781 Files: include/clang/AST/StmtDataCollectors.td lib/Analysis/CloneDetection.cpp unittests/AST/DataCollectionTest.cpp utils/TableGen/ClangDataCollectorsEmitter.cpp Index: utils/TableGen/ClangDataCollectorsEmitter.cpp === --- utils/TableGen/ClangDataCollectorsEmitter.cpp +++ utils/TableGen/ClangDataCollectorsEmitter.cpp @@ -4,15 +4,31 @@ using namespace llvm; namespace clang { + +static struct { + const char *Name; + const char *MacroName; +} CollectionCategories[] = { +{"TypeIIClone", "COLLECT_FOR_TYPEIICLONE"}, +}; + void EmitClangDataCollectors(RecordKeeper &RK, raw_ostream &OS) { - const auto &Defs = RK.getClasses(); + for (const auto &Category : CollectionCategories) { +OS << "#ifndef " << Category.MacroName << "\n"; +OS << "#define " << Category.MacroName << "(A, B)\n"; +OS << "#endif\n"; + } + const auto &Defs = RK.getDefs(); for (const auto &Entry : Defs) { Record &R = *Entry.second; -OS << "DEF_ADD_DATA(" << R.getName() << ", {\n"; -auto Code = R.getValue("Code")->getValue(); -OS << Code->getAsUnquotedString() << "}\n)"; -OS << "\n"; +for (auto Category : CollectionCategories) { + OS << Category.MacroName << "(" << R.getName() << ", {\n"; + auto Code = R.getValue(Category.Name)->getValue(); + OS << Code->getAsUnquotedString() << "}\n)"; + OS << "\n"; +} } - OS << "#undef DEF_ADD_DATA\n"; + for (const auto &Category : CollectionCategories) +OS << "#undef " << Category.MacroName << "\n"; } } // end namespace clang Index: unittests/AST/DataCollectionTest.cpp === --- unittests/AST/DataCollectionTest.cpp +++ unittests/AST/DataCollectionTest.cpp @@ -40,7 +40,7 @@ this->Visit(S); } -#define DEF_ADD_DATA(CLASS, CODE) \ +#define COLLECT_FOR_TYPEIICLONE(CLASS, CODE) \ template Dummy Visit##CLASS(const CLASS *S) { \ CODE; \ ConstStmtVisitor::Visit##CLASS(S); \ Index: lib/Analysis/CloneDetection.cpp === --- lib/Analysis/CloneDetection.cpp +++ lib/Analysis/CloneDetection.cpp @@ -199,7 +199,7 @@ // Define a visit method for each class to collect data and subsequently visit // all parent classes. This uses a template so that custom visit methods by us // take precedence. -#define DEF_ADD_DATA(CLASS, CODE) \ +#define COLLECT_FOR_TYPEIICLONE(CLASS, CODE) \ template void Visit##CLASS(const CLASS *S) { \ CODE; \ ConstStmtVisitor>::Visit##CLASS(S);\ Index: include/clang/AST/StmtDataCollectors.td === --- include/clang/AST/StmtDataCollectors.td +++ include/clang/AST/StmtDataCollectors.td @@ -1,46 +1,50 @@ -class Stmt { - code Code = [{ +class Collector { + code TypeIIClone = [{}]; +} + +def Stmt : Collector { + let TypeIIClone = [{ addData(S->getStmtClass()); // This ensures that non-macro-generated code isn't identical to // macro-generated code. addData(data_collection::getMacroStack(S->getLocStart(), Context)); addData(data_collection::getMacroStack(S->getLocEnd(), Context)); }]; } -class Expr { - code Code = [{ +def Expr : Collector { + let TypeIIClone = [{ addData(S->getType()); }]; } //--- Builtin functionality --// -class ArrayTypeTraitExpr { - code Code = [{ +def ArrayTypeTraitExpr : Collector { + let TypeIIClone = [{ addData(S->getTrait()); }]; } -class ExpressionTraitExpr { - code Code = [{ +def ExpressionTraitExpr : Collector { + let TypeIIClone = [{ addData(S->getTrait()); }]; } -class PredefinedExpr { - code Code = [{ +def PredefinedExpr : Collector { + let TypeIIClone = [{ addData(S->getIdentType()); }]; } -class TypeTraitExpr { - code Code = [{ +def TypeTraitExpr : Collector { + let TypeIIClone = [{ addData(S->getTrait()); for (unsigned i = 0; i < S->getNumArgs(); ++i) addData(S->getArg(i)-
[PATCH] D40731: Integrate CHash into CLang
johannes added a comment. In https://reviews.llvm.org/D40731#943469, @stettberger wrote: > @Eugene.Zelenko Thank you for pointing me out on these issues. I ran > clang-tidy and clang-format on CHashVisitor.h > > @rsmith You're right, there is already more than one implemenation of > {partial,unstable} AST hashes within LLVM, as we already discussed on cfe-dev > in August[1]. Therefore, I rewrote our original CHash implementation to > extend the already existing StmtDataCollectors approach. However, you are > right, efforts should be coordinated to get a AST hashing implementation that > can be adapted to various use-case scenarios. > > [1] http://lists.llvm.org/pipermail/cfe-dev/2017-August/054911.html I think it would be quite nice if we manage to consolidate the implementation of hashing mechanisms. Modifications to StmtDataCollectors.td will alter the behaviour of clone detection. Currently, this file assumes that we want to find type 2 clones. So we should maintain that, and add things only where needed. I submitted this patch so we can have different categories of data collection https://reviews.llvm.org/D40781 Repository: rC Clang https://reviews.llvm.org/D40731 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D40731: Integrate CHash into CLang
johannes added a comment. So you can define a category for cHash and add `let cHash = [{ .. }]` or `let cHash = TypeIIClone;` if you just want to reuse it. With this patch you can also use !codeconcat to append some code: https://reviews.llvm.org/D40782 Repository: rC Clang https://reviews.llvm.org/D40731 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D40731: Integrate CHash into CLang
johannes added a comment. In https://reviews.llvm.org/D40731#943564, @stettberger wrote: > For my changes to StmtDataCollectors: I tried to leave the already existing > entries in StmtDataCollector.td untouched and the test-cases still work OK. > If there is something somebody could break by changing these files it would > be good to have a test case for it. Yeah, the tests are not comprehensive unfortunately > However, some of the collectors are not node local, which seems a little bit > inconsequent to me. For example, DeclStmt includes information about the its > Decl Children. In my opinion every entry in the *DataCollector.td should > include only information that can be directly retrieved from this particular > AST node. Cross-referencing between nodes should be done on the user side of > the collectors (as it is done in CHashVisitor.h, which follows pointers in a > cross-tree manner). With this separation of concerns it was quite easy for me > to base CHash of the TableGen'ed headers. Furthermore, a purely syntactical > hash (only the children, no cross-tree dependencies) could be derived easily. Good point, I think we can move the code for DeclStmt to CloneDetection.cpp > @johannes For the different types of information that should be included for > one AST node (e.g., Type II clones): We could use a hierachy of "What should > be considered here". But we should clearly define what is the definition of > equivalency for that level. For example, if you wake me up at night I could > not define you the difference between a type I and a type II clone. Yeah, it's difficult to clearly define that though. The thing I noticed is that the function name is included for PredefinedExpr, but Type II clones do not care about values, but only structure. I think most of your additions should be fine , @teemperor could confirm them. Repository: rC Clang https://reviews.llvm.org/D40731 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D35921: [clang-diff] HTML side-by-side diff
johannes abandoned this revision. johannes added a comment. moved to https://reviews.llvm.org/D36182, sorry for the noise https://reviews.llvm.org/D35921 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D34748: [clang-diff] improve mapping accuracy
johannes abandoned this revision. johannes added a comment. split up in several commits starting from https://reviews.llvm.org/D36176 Comment at: lib/Tooling/ASTDiff/ASTDiff.cpp:170 + // Ignore everything from other files. + if (!SrcMgr.isInMainFile(SLoc)) +return true; arphaman wrote: > How will you deal with changes in headers if you limit analysis to main files > only? I will change this to support headers in another patch https://reviews.llvm.org/D34748 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36176: [clang-diff] Fix some errors and inconsistencies
johannes added inline comments. Comment at: include/clang/Tooling/ASTDiff/ASTDiff.h:96 : TreeImpl(llvm::make_unique(this, Node, AST)) {} + SyntaxTree(const SyntaxTree &Tree) = delete; ~SyntaxTree(); arphaman wrote: > It might be better to add a move constructor which would disable copy > constructors. ASTDiff::getMapped internally uses the address of the SyntaxTree that is passed as parameter. So the tree must be exactly the same as the one that is passed to the constructor of ASTDiff. This is quite bad. I will change ASTDiff::getMapped to accept a boolean or split it into two methods Should I still add a move constructor? https://reviews.llvm.org/D36176 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36176: [clang-diff] Fix some errors and inconsistencies
johannes updated this revision to Diff 109414. johannes added a comment. move most functional changes to other commits move constructor for Syntaxtree https://reviews.llvm.org/D36176 Files: include/clang/Tooling/ASTDiff/ASTDiff.h include/clang/Tooling/ASTDiff/ASTDiffInternal.h lib/Tooling/ASTDiff/ASTDiff.cpp Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -27,6 +27,7 @@ namespace clang { namespace diff { +namespace { /// Maps nodes of the left tree to ones on the right, and vice versa. class Mapping { public: @@ -76,6 +77,7 @@ private: std::unique_ptr[]> SrcToDst, DstToSrc; }; +} // end anonymous namespace class ASTDiff::Impl { public: @@ -110,10 +112,6 @@ // Returns false if the nodes must not be mached. bool isMatchingPossible(NodeId Id1, NodeId Id2) const; - // Adds all corresponding subtrees of the two nodes to the mapping. - // The two nodes must be identical. - void addIsomorphicSubTrees(Mapping &M, NodeId Id1, NodeId Id2) const; - // Uses an optimal albeit slow algorithm to compute a mapping between two // subtrees, but only if both have fewer nodes than MaxSize. void addOptimalMapping(Mapping &M, NodeId Id1, NodeId Id2) const; @@ -254,7 +252,10 @@ Parent = PreviousParent; --Depth; Node &N = Tree.getMutableNode(MyId); -N.RightMostDescendant = Id; +N.RightMostDescendant = Id - 1; +assert(N.RightMostDescendant >= 0 && + N.RightMostDescendant < Tree.getSize() && + "Rightmost descendant must be a valid tree node."); if (N.isLeaf()) Tree.Leaves.push_back(MyId); N.Height = 1; @@ -358,9 +359,7 @@ } bool SyntaxTree::Impl::isInSubtree(NodeId Id, NodeId SubtreeRoot) const { - NodeId Lower = SubtreeRoot; - NodeId Upper = getNode(SubtreeRoot).RightMostDescendant; - return Id >= Lower && Id <= Upper; + return Id >= SubtreeRoot && Id <= getNode(SubtreeRoot).RightMostDescendant; } std::string SyntaxTree::Impl::getNodeValue(NodeId Id) const { @@ -625,12 +624,11 @@ } private: - /// Simple cost model for edit actions. + /// We use a simple cost model for edit actions, which seems good enough. + /// Simple cost model for edit actions. This seems to make the matching + /// algorithm perform reasonably well. /// The values range between 0 and 1, or infinity if this edit action should /// always be avoided. - - /// These costs could be modified to better model the estimated cost of / - /// inserting / deleting the current node. static constexpr double DeletionCost = 1; static constexpr double InsertionCost = 1; @@ -687,6 +685,7 @@ }; } // end anonymous namespace +namespace { // Priority queue for nodes, sorted descendingly by their height. class PriorityList { const SyntaxTree::Impl &Tree; @@ -723,6 +722,7 @@ push(Child); } }; +} // end anonymous namespace bool ASTDiff::Impl::identical(NodeId Id1, NodeId Id2) const { const Node &N1 = T1.getNode(Id1); @@ -759,16 +759,6 @@ T2.getNode(Id2).ASTNode); } -void ASTDiff::Impl::addIsomorphicSubTrees(Mapping &M, NodeId Id1, - NodeId Id2) const { - assert(identical(Id1, Id2) && "Can only be called on identical subtrees."); - M.link(Id1, Id2); - const Node &N1 = T1.getNode(Id1); - const Node &N2 = T2.getNode(Id2); - for (size_t Id = 0, E = N1.Children.size(); Id < E; ++Id) -addIsomorphicSubTrees(M, N1.Children[Id], N2.Children[Id]); -} - void ASTDiff::Impl::addOptimalMapping(Mapping &M, NodeId Id1, NodeId Id2) const { if (std::max(T1.getNumberOfDescendants(Id1), @@ -805,7 +795,7 @@ if (M.hasDst(Id2)) continue; double Similarity = getSimilarity(M, Id1, Id2); -if (Similarity > HighestSimilarity) { +if (Similarity >= Options.MinSimilarity && Similarity > HighestSimilarity) { HighestSimilarity = Similarity; Candidate = Id2; } @@ -816,7 +806,8 @@ void ASTDiff::Impl::matchBottomUp(Mapping &M) const { std::vector Postorder = getSubtreePostorder(T1, T1.getRootId()); for (NodeId Id1 : Postorder) { -if (Id1 == T1.getRootId()) { +if (Id1 == T1.getRootId() && !M.hasSrc(T1.getRootId()) && +!M.hasDst(T2.getRootId())) { if (isMatchingPossible(T1.getRootId(), T2.getRootId())) { M.link(T1.getRootId(), T2.getRootId()); addOptimalMapping(M, T1.getRootId(), T2.getRootId()); @@ -831,11 +822,10 @@ if (Matched || !MatchedChildren) continue; NodeId Id2 = findCandidate(M, Id1); -if (Id2.isInvalid() || !canBeAddedToMapping(M, Id1, Id2) || -getSimilarity(M, Id1, Id2) < Options.MinSimilarity) - continue; -M.link(Id1, Id2); -addOptimalMapping(M, Id1, Id2); +if (Id2.isValid() && canBeAddedToMapping(M, Id1, Id2)) { + M.link(Id1, Id2); +
[PATCH] D36177: [clang-diff] Add commandline arguments.
johannes updated this revision to Diff 109415. johannes added a comment. add some test, replace -no-compilation-database with -- https://reviews.llvm.org/D36177 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-args.sh test/Tooling/clang-diff-basic.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -28,12 +28,6 @@ cl::desc("Print the internal representation of the AST as JSON."), cl::init(false), cl::cat(ClangDiffCategory)); -static cl::opt NoCompilationDatabase( -"no-compilation-database", -cl::desc( -"Do not attempt to load build settings from a compilation database"), -cl::init(false), cl::cat(ClangDiffCategory)); - static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -43,43 +37,88 @@ cl::Optional, cl::cat(ClangDiffCategory)); -static std::unique_ptr getAST(const StringRef Filename) { +static cl::opt StopAfter("stop-after", + cl::desc(""), + cl::Optional, cl::init(""), + 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 (!NoCompilationDatabase) -Compilations = -CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); - if (!Compilations) { -if (!NoCompilationDatabase) + 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()); + Compilations = + llvm::make_unique( + ".", std::vector()); +} } + addExtraArgs(Compilations); std::array Files = {{Filename}}; - ClangTool Tool(*Compilations, Files); + ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files); std::vector> ASTs; Tool.buildASTs(ASTs); if (ASTs.size() != Files.size()) return nullptr; return std::move(ASTs[0]); } 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(SourcePath); +std::unique_ptr AST = getAST(CommonCompilations, SourcePath); if (!AST) return 1; diff::SyntaxTree Tree(AST->getASTContext()); @@ -92,12 +131,22 @@ return 1; } - std::unique_ptr Src = getAST(SourcePath); - std::unique_ptr Dst = getAST(DestinationPath); + std::unique_ptr Src = getAST(CommonCompilations, SourcePath); + std::unique_ptr Dst = getAST(CommonCompilations, DestinationPath); if (!Src || !Dst) return 1; diff::ComparisonOptions Options; +
[PATCH] D36178: [clang-diff] Move the JSON export function to clang-diff
johannes updated this revision to Diff 109416. johannes added a comment. getSourceRangeOffsets, test https://reviews.llvm.org/D36178 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-json.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -99,6 +99,65 @@ 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 = @@ -122,7 +181,11 @@ if (!AST) return 1; diff::SyntaxTree Tree(AST->getASTContext()); -Tree.printAsJson(llvm::outs()); +llvm::outs() << R"({"filename":")"; +printJsonString(llvm::outs(), SourcePath); +llvm::outs() << R"(","root":)"; +printNodeAsJson(llvm::outs(), Tree, Tree.getRootId()); +llvm::outs() << "}\n"; return 0; } Index: test/Tooling/clang-diff-json.cpp === --- /dev/null +++ test/Tooling/clang-diff-json.cpp @@ -0,0 +1,21 @@ +// RUN: clang-diff -ast-dump %s -- | python -m json.tool | FileCheck %s + +// CHECK: "type": "CXXRecordDecl", +// CHECK: "begin": 185, +// CHECK: "end": 205, +// CHECK: "type": "FieldDecl", +class A { + int x; +}; + +// CHECK: "type": "VarDecl", +// CHECK: "children": [ +// CHECK: "type": "CharacterLiteral", +// CHECK: "children": [] +// CHECK-NOT: "children": [ +// CHECK: ] +char nl = '\n'; + +// CHECK: "value": "abc \n\t\u\u001f\udcc4\udca3 \udceb\udc8d\udcb0\udceb\udcb0\udc95" +char s[] = "abc \n\t\0\x1f\u0123 데박"; + Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -176,9 +176,6 @@ void printTree(NodeId Root) const; void printTree(raw_ostream &OS, NodeId Root) const; - void printAsJsonImpl(raw_ostream &OS) const; - void printNodeAsJson(raw_ostream &OS, NodeId Id) const; - private: /// Nodes in preorder. std::vector Nodes; @@ -438,28 +435,6 @@ OS << "(" << PostorderIds[Id] << ")"; } -void SyntaxTree::Impl::printNodeAsJson(raw_ostream &OS, NodeId Id) const { - auto N = getNode(Id); - OS << R"({"type":")" << N.getTypeLabel() << R"(")"; - if (getNodeValue(Id) != "") -OS << R"(,"value":")" << getNodeValue(Id) << R"(")"; - OS << R"(,"children":[)"; - if (N.Children.size() > 0) { -printNodeAsJson(OS, N.Children[0]); -for (size_t I = 1, E = N.Children.size(); I < E; ++I) { - OS << ","; - printNodeAsJson(OS, N.Children[I]); -} - } - OS << "]}"; -} - -void SyntaxTree::Impl::printAsJsonImpl(raw_ostream &OS) const { - OS << R"({"root":)"; - printNodeAsJson(OS, getRootId()); - OS << "}\n"; -} - /// Identifies a node in a subtree by its postorder offset, starting at 1. struct SNodeId { int Id = 0; @@ -674,6 +649,12 @@ } }; +ast_type_traits::ASTNodeKind Node::getType() const { + return ASTNode.getNodeKind(); +} + +StringRef Node::getTypeLabel() const { return getType().asStringRef(); } + namespace { // Compares nodes by their depth. struct HeightLess { @@ -1001,7 +982,28 @@ SyntaxTree::~SyntaxTree() = default; -void SyntaxTree::printAsJson(raw_ostream &OS) { TreeImpl->p
[PATCH] D36179: [clang-diff] Move printing of matches and changes to clang-diff
johannes updated this revision to Diff 109417. johannes added a comment. remove unused SubtreeIterator https://reviews.llvm.org/D36179 Files: include/clang/Tooling/ASTDiff/ASTDiff.h include/clang/Tooling/ASTDiff/ASTDiffInternal.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-basic.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -134,7 +134,7 @@ auto Offsets = Tree.getSourceRangeOffsets(N); OS << R"(,"begin":)" << Offsets.first; OS << R"(,"end":)" << Offsets.second; - std::string Value = Tree.getNodeValue(N.ASTNode); + std::string Value = Tree.getNodeValue(N); if (!Value.empty()) { OS << R"(,"value":")"; printJsonString(OS, Value); @@ -158,6 +158,52 @@ 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 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.ChangeKind) { + 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.ChangeKind == diff::Insert) + OS << "Insert"; +else if (DstNode.ChangeKind == diff::Move) + OS << "Move"; +else if (DstNode.ChangeKind == 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 CommonCompilations = @@ -212,11 +258,26 @@ } 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); + diff::ASTDiff Diff(SrcTree, DstTree, Options); + + for (diff::NodeId Dst : DstTree) { +diff::NodeId Src = Diff.getMapped(DstTree, Dst); +if (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; } Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -54,8 +54,8 @@ typedef unsigned nat; // CHECK: Match VarDecl: p(int){{.*}} to VarDecl: prod(double) -// CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * // CHECK: Update VarDecl: p(int){{.*}} to prod(double) +// CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * double prod = 1 * 2 * 10; // CHECK: Update DeclRefExpr int squared = prod * prod; Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -82,26 +82,23 @@ class ASTDiff::Impl { public: SyntaxTree::Impl &T1, &T2; - bool IsMappingDone = false; Mapping TheMapping; Impl(SyntaxTree::Impl &T1, SyntaxTree::Impl &T2, - const ComparisonOptions &Options) - : T1(T1), T2(T2), Options(Options) {} + const ComparisonOptions &Options); /// Matches nodes one-by-one based on their similarity. void computeMapping(); - std::vector getMatches(Mapping &M); + // Compute ChangeKind for each node based on similarity. + void computeChangeKinds(Mapping &M); - /// Finds an edit script that converts T1 to T2. - std::vector computeChanges(Mapping &M); - - void printChangeImpl(raw_ostream &OS, const Change &Chg) const; - void printMatchImpl(raw_ostream &OS, const Match &M) const; - -
[PATCH] D36181: [clang-diff] Make printing of matches optional
johannes updated this revision to Diff 109420. johannes added a comment. rename to -dump-matches, add a test https://reviews.llvm.org/D36181 Files: test/Tooling/clang-diff-args.sh test/Tooling/clang-diff-basic.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -33,6 +33,10 @@ cl::desc("Print the internal representation of the AST as JSON."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt +PrintMatches("dump-matches", cl::desc("Print the matched nodes."), + cl::init(false), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -280,7 +284,7 @@ for (diff::NodeId Dst : DstTree) { diff::NodeId Src = Diff.getMapped(DstTree, Dst); -if (Src.isValid()) { +if (PrintMatches && Src.isValid()) { llvm::outs() << "Match "; printNode(llvm::outs(), SrcTree, Src); llvm::outs() << " to "; Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -E %s > %T/src.cpp // RUN: %clang_cc1 -E %s > %T/dst.cpp -DDEST -// RUN: clang-diff %T/src.cpp %T/dst.cpp -- | FileCheck %s +// RUN: clang-diff -dump-matches %T/src.cpp %T/dst.cpp -- | FileCheck %s #ifndef DEST namespace src { Index: test/Tooling/clang-diff-args.sh === --- test/Tooling/clang-diff-args.sh +++ test/Tooling/clang-diff-args.sh @@ -6,3 +6,7 @@ RUN: clang-diff -ast-dump -extra-arg=-Da=X%t.cpp -- 2>&1 | FileCheck %t1 RUN: clang-diff -ast-dump -extra-arg-before=-Da=X %t.cpp -- 2>&1 | FileCheck %t1 RUN: clang-diff -ast-dump %t.cpp -- 2>&1 -Da=X | FileCheck %t1 + +RUN: echo "// CHECK-NOT: {{.}}" > %t-no-output +RUN: clang-diff %S/clang-diff-ast.cpp %S/clang-diff-ast.cpp -- 2>&1 -std=c++11 \ +RUN: | FileCheck -allow-empty %t-no-output Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -33,6 +33,10 @@ cl::desc("Print the internal representation of the AST as JSON."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt +PrintMatches("dump-matches", cl::desc("Print the matched nodes."), + cl::init(false), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -280,7 +284,7 @@ for (diff::NodeId Dst : DstTree) { diff::NodeId Src = Diff.getMapped(DstTree, Dst); -if (Src.isValid()) { +if (PrintMatches && Src.isValid()) { llvm::outs() << "Match "; printNode(llvm::outs(), SrcTree, Src); llvm::outs() << " to "; Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -E %s > %T/src.cpp // RUN: %clang_cc1 -E %s > %T/dst.cpp -DDEST -// RUN: clang-diff %T/src.cpp %T/dst.cpp -- | FileCheck %s +// RUN: clang-diff -dump-matches %T/src.cpp %T/dst.cpp -- | FileCheck %s #ifndef DEST namespace src { Index: test/Tooling/clang-diff-args.sh === --- test/Tooling/clang-diff-args.sh +++ test/Tooling/clang-diff-args.sh @@ -6,3 +6,7 @@ RUN: clang-diff -ast-dump -extra-arg=-Da=X%t.cpp -- 2>&1 | FileCheck %t1 RUN: clang-diff -ast-dump -extra-arg-before=-Da=X %t.cpp -- 2>&1 | FileCheck %t1 RUN: clang-diff -ast-dump %t.cpp -- 2>&1 -Da=X | FileCheck %t1 + +RUN: echo "// CHECK-NOT: {{.}}" > %t-no-output +RUN: clang-diff %S/clang-diff-ast.cpp %S/clang-diff-ast.cpp -- 2>&1 -std=c++11 \ +RUN: | FileCheck -allow-empty %t-no-output ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36180: [clang-diff] Add option to dump the AST, one node per line
johannes updated this revision to Diff 109419. johannes added a comment. Herald added a subscriber: klimek. add a test https://reviews.llvm.org/D36180 Files: test/Tooling/clang-diff-ast.cpp test/Tooling/clang-diff-json.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -25,9 +25,14 @@ static cl::opt ASTDump("ast-dump", -cl::desc("Print the internal representation of the AST as JSON."), +cl::desc("Print the internal representation of the AST."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt ASTDumpJson( +"ast-dump-json", +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)); @@ -171,6 +176,15 @@ 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) { @@ -218,15 +232,19 @@ addExtraArgs(CommonCompilations); - if (ASTDump) { + if (ASTDump || ASTDumpJson) { 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()); +if (ASTDump) { + printTree(llvm::outs(), Tree); + return 0; +} llvm::outs() << R"({"filename":")"; printJsonString(llvm::outs(), SourcePath); llvm::outs() << R"(","root":)"; Index: test/Tooling/clang-diff-json.cpp === --- test/Tooling/clang-diff-json.cpp +++ test/Tooling/clang-diff-json.cpp @@ -1,8 +1,8 @@ -// RUN: clang-diff -ast-dump %s -- | python -m json.tool | FileCheck %s +// RUN: clang-diff -ast-dump-json %s -- | python -m json.tool | FileCheck %s // CHECK: "type": "CXXRecordDecl", -// CHECK: "begin": 185, -// CHECK: "end": 205, +// CHECK: "begin": 190, +// CHECK: "end": 210, // CHECK: "type": "FieldDecl", class A { int x; Index: test/Tooling/clang-diff-ast.cpp === --- /dev/null +++ test/Tooling/clang-diff-ast.cpp @@ -0,0 +1,50 @@ +// RUN: clang-diff -ast-dump %s -- -std=c++11 | FileCheck %s + + +// CHECK: {{^}}TranslationUnitDecl(0) +// CHECK: {{^}} NamespaceDecl: test;( +namespace test { + +// CHECK: {{^}} FunctionDecl: f( +// CHECK: CompoundStmt( +void f() { + // CHECK: VarDecl: i(int)( + // CHECK: IntegerLiteral: 1 + auto i = 1; + // CHECK: CallExpr( + // CHECK: DeclRefExpr: f( + f(); + // CHECK: BinaryOperator: =( + i = i; +} + +} // end namespace test + +// CHECK: TypedefDecl: nat;unsigned int;( +typedef unsigned nat; +// CHECK: TypeAliasDecl: real;double;( +using real = double; + +class Base { +}; + +// CHECK: CXXRecordDecl: X;class X;( +class X : Base { + int m; + // CHECK: CXXMethodDecl: foo(const char *(int))( + // CHECK: ParmVarDecl: i(int)( + const char *foo(int i) { +if (i == 0) + // CHECK: StringLiteral: foo( + return "foo"; +return 0; + } + + // CHECK: AccessSpecDecl: public( +public: + // CHECK: CXXConstructorDecl: X(void (char, int))( + X(char, int) : Base(), m(0) { +// CHECK: MemberExpr( +int x = m; + } +}; ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36182: [clang-diff] Add HTML side-by-side diff output
johannes updated this revision to Diff 109421. johannes added a comment. - https://reviews.llvm.org/D36182 Files: test/Tooling/Inputs/clang-diff-basic-src.cpp test/Tooling/clang-diff-basic.cpp test/Tooling/clang-diff-html.py tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -37,6 +37,10 @@ PrintMatches("dump-matches", cl::desc("Print the matched nodes."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt HtmlDiff("html", + cl::desc("Output a side-by-side diff in HTML."), + cl::init(false), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -110,6 +114,161 @@ static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); } +static const char HtmlDiffHeader[] = R"( + + + + +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; +} + + + +highlightStack = [] +function clearHighlight() { + while (highlightStack.length) { +let [l, r] = highlightStack.pop() +document.getElementById(l).style.backgroundColor = 'white' +document.getElementById(r).style.backgroundColor = 'white' + } +} +function highlight(event) { + id = event.target['id'] + doHighlight(id) +} +function doHighlight(id) { + clearHighlight() + source = document.getElementById(id) + if (!source.attributes['tid']) +return + tid = source.attributes['tid'].value + target = document.getElementById(tid) + if (!target || source.parentElement && source.parentElement.classList.contains('code')) +return + source.style.backgroundColor = target.style.backgroundColor = 'lightgrey' + highlightStack.push([id, tid]) + source.scrollIntoView() + target.scrollIntoView() + location.hash = '#' + id +} +function scrollToBoth() { + doHighlight(location.hash.substr(1)) +} +window.onload = scrollToBoth + + + +)"; + +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"; + } +} + +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 << ""; + + 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 << ""; + return Offset; +} + static void printJsonString(raw_ostream &OS, const StringRef Str) { for (char C : Str) { switch (C) { @@ -282,6 +441,19 @@ diff::SyntaxTree DstTree(Dst->getASTContext()); diff::ASTDiff Diff(SrcTree, DstTree, Options); + if (HtmlDiff) { +llvm::outs() << HtmlDiffHeader << ""; +llvm::outs() << ""; +printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0); +llvm::outs() << ""; +llvm::outs() << ""; +printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(), +
[PATCH] D36183: [clang-diff] Simplify mapping
johannes updated this revision to Diff 109422. johannes edited the summary of this revision. johannes added a comment. - https://reviews.llvm.org/D36183 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/Inputs/clang-diff-basic-src.cpp test/Tooling/clang-diff-basic.cpp Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -38,8 +38,15 @@ return "foo"; return 0; } - // CHECK: Delete AccessSpecDecl: public X(){}; - // CHECK: Delete CXXMethodDecl }; } + +namespace { +// match with parents of different type +// CHECK: Match FunctionDecl: f1{{.*}} to FunctionDecl: f1 +void f1() {{ (void) __func__;;; }} +} + +// CHECK: Delete AccessSpecDecl: public +// CHECK: Delete CXXMethodDecl Index: test/Tooling/Inputs/clang-diff-basic-src.cpp === --- test/Tooling/Inputs/clang-diff-basic-src.cpp +++ test/Tooling/Inputs/clang-diff-basic-src.cpp @@ -26,3 +26,5 @@ int id(int i) { return i; } }; } + +void f1() {{ (void) __func__;;; }} Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -34,48 +34,23 @@ Mapping() = default; Mapping(Mapping &&Other) = default; Mapping &operator=(Mapping &&Other) = default; - Mapping(int Size1, int Size2) { -// Maximum possible size after patching one tree. -int Size = Size1 + Size2; -SrcToDst = llvm::make_unique[]>(Size); -DstToSrc = llvm::make_unique[]>(Size); + + Mapping(size_t Size) { +SrcToDst = llvm::make_unique(Size); +DstToSrc = llvm::make_unique(Size); } void link(NodeId Src, NodeId Dst) { -SrcToDst[Src].push_back(Dst); -DstToSrc[Dst].push_back(Src); - } - - NodeId getDst(NodeId Src) const { -if (hasSrc(Src)) - return SrcToDst[Src][0]; -return NodeId(); - } - NodeId getSrc(NodeId Dst) const { -if (hasDst(Dst)) - return DstToSrc[Dst][0]; -return NodeId(); - } - const SmallVector &getAllDsts(NodeId Src) const { -return SrcToDst[Src]; - } - const SmallVector &getAllSrcs(NodeId Dst) const { -return DstToSrc[Dst]; - } - bool hasSrc(NodeId Src) const { return !SrcToDst[Src].empty(); } - bool hasDst(NodeId Dst) const { return !DstToSrc[Dst].empty(); } - bool hasSrcDst(NodeId Src, NodeId Dst) const { -for (NodeId DstId : SrcToDst[Src]) - if (DstId == Dst) -return true; -for (NodeId SrcId : DstToSrc[Dst]) - if (SrcId == Src) -return true; -return false; +SrcToDst[Src] = Dst, DstToSrc[Dst] = Src; } + NodeId getDst(NodeId Src) const { return SrcToDst[Src]; } + NodeId getSrc(NodeId Dst) const { return DstToSrc[Dst]; } + bool hasSrc(NodeId Src) const { return getDst(Src).isValid(); } + bool hasDst(NodeId Dst) const { return getSrc(Dst).isValid(); } + private: - std::unique_ptr[]> SrcToDst, DstToSrc; + std::unique_ptr SrcToDst, DstToSrc; }; } // end anonymous namespace @@ -104,8 +79,6 @@ // Returns true if the two subtrees are identical. bool identical(NodeId Id1, NodeId Id2) const; - bool canBeAddedToMapping(const Mapping &M, NodeId Id1, NodeId Id2) const; - // Returns false if the nodes must not be mached. bool isMatchingPossible(NodeId Id1, NodeId Id2) const; @@ -711,23 +684,6 @@ return true; } -bool ASTDiff::Impl::canBeAddedToMapping(const Mapping &M, NodeId Id1, -NodeId Id2) const { - assert(isMatchingPossible(Id1, Id2) && - "Matching must be possible in the first place."); - if (M.hasSrcDst(Id1, Id2)) -return false; - if (Options.EnableMatchingWithUnmatchableParents) -return true; - const Node &N1 = T1.getNode(Id1); - const Node &N2 = T2.getNode(Id2); - NodeId P1 = N1.Parent; - NodeId P2 = N2.Parent; - // Only allow matching if parents can be matched. - return (P1.isInvalid() && P2.isInvalid()) || - (P1.isValid() && P2.isValid() && isMatchingPossible(P1, P2)); -} - bool ASTDiff::Impl::isMatchingPossible(NodeId Id1, NodeId Id2) const { return Options.isMatchingAllowed(T1.getNode(Id1), T2.getNode(Id2)); } @@ -750,7 +706,7 @@ for (const auto Tuple : R) { NodeId Src = Tuple.first; NodeId Dst = Tuple.second; -if (canBeAddedToMapping(M, Src, Dst)) +if (!M.hasSrc(Src) && !M.hasDst(Dst)) M.link(Src, Dst); } } @@ -803,7 +759,7 @@ if (Matched || !MatchedChildren) continue; NodeId Id2 = findCandidate(M, Id1); -if (Id2.isValid() && canBeAddedToMapping(M, Id1, Id2)) { +if (Id2.isValid()) { M.link(Id1, Id2); addOptimalMapping(M, Id1, Id2); } @@ -814,7 +770,7 @@ PriorityList L1(T1); PriorityList L2(T2); - Mapping M(T1.getSize(), T2.getSize()); + Mapping M(T1.getSize() + T2.ge
[PATCH] D36184: [clang-diff] Filter AST nodes
johannes updated this revision to Diff 109423. johannes added a comment. tests https://reviews.llvm.org/D36184 Files: lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-ast.cpp Index: test/Tooling/clang-diff-ast.cpp === --- test/Tooling/clang-diff-ast.cpp +++ test/Tooling/clang-diff-ast.cpp @@ -12,7 +12,8 @@ // CHECK: IntegerLiteral: 1 auto i = 1; // CHECK: CallExpr( - // CHECK: DeclRefExpr: f( + // CHECK-NOT: ImplicitCastExpr + // CHECK-NEXT: DeclRefExpr: f( f(); // CHECK: BinaryOperator: =( i = i; @@ -37,6 +38,7 @@ if (i == 0) // CHECK: StringLiteral: foo( return "foo"; +// CHECK-NOT: ImplicitCastExpr return 0; } @@ -48,3 +50,22 @@ int x = m; } }; + +#define M (void)1 +#define MA(a, b) (void)a, b +// CHECK: FunctionDecl +// CHECK-NEXT: CompoundStmt +void macros() { + M; + MA(1, 2); +} +// CHECK-NEXT: NamespaceDecl + +#ifndef GUARD +#define GUARD +namespace world { +// nodes from other files are excluded +// CHECK-NOT {{.}} +#include "clang-diff-ast.cpp" +} +#endif Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -162,24 +162,37 @@ if (!N) return true; SourceLocation SLoc = N->getLocStart(); - return SLoc.isValid() && SrcMgr.isInSystemHeader(SLoc); + if (!SLoc.isValid()) +return false; + // Ignore everything from other files. + if (!SrcMgr.isInMainFile(SLoc)) +return true; + // Ignore macros. + if (N->getLocStart() != SrcMgr.getSpellingLoc(N->getLocStart())) +return true; + return false; } +static bool isDeclExcluded(const Decl *D) { return D->isImplicit(); } +static bool isStmtExcluded(const Stmt *S) { return false; } + namespace { /// Counts the number of nodes that will be compared. struct NodeCountVisitor : public RecursiveASTVisitor { int Count = 0; const SyntaxTree::Impl &Tree; NodeCountVisitor(const SyntaxTree::Impl &Tree) : Tree(Tree) {} bool TraverseDecl(Decl *D) { -if (isNodeExcluded(Tree.AST.getSourceManager(), D)) +if (isNodeExcluded(Tree.AST.getSourceManager(), D) || isDeclExcluded(D)) return true; ++Count; RecursiveASTVisitor::TraverseDecl(D); return true; } bool TraverseStmt(Stmt *S) { -if (isNodeExcluded(Tree.AST.getSourceManager(), S)) +if (S) + S = S->IgnoreImplicit(); +if (isNodeExcluded(Tree.AST.getSourceManager(), S) || isStmtExcluded(S)) return true; ++Count; RecursiveASTVisitor::TraverseStmt(S); @@ -233,15 +246,17 @@ N.Height = std::max(N.Height, 1 + Tree.getNode(Child).Height); } bool TraverseDecl(Decl *D) { -if (isNodeExcluded(Tree.AST.getSourceManager(), D)) +if (isNodeExcluded(Tree.AST.getSourceManager(), D) || isDeclExcluded(D)) return true; auto SavedState = PreTraverse(D); RecursiveASTVisitor::TraverseDecl(D); PostTraverse(SavedState); return true; } bool TraverseStmt(Stmt *S) { -if (isNodeExcluded(Tree.AST.getSourceManager(), S)) +if (S) + S = S->IgnoreImplicit(); +if (isNodeExcluded(Tree.AST.getSourceManager(), S) || isStmtExcluded(S)) return true; auto SavedState = PreTraverse(S); RecursiveASTVisitor::TraverseStmt(S); ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36185: [clang-diff] Fix similarity computation
johannes updated this revision to Diff 109424. johannes added a comment. add test for Options.MaxSize https://reviews.llvm.org/D36185 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-bottomup.cpp test/Tooling/clang-diff-opt.cpp test/Tooling/clang-diff-topdown.cpp Index: test/Tooling/clang-diff-topdown.cpp === --- /dev/null +++ test/Tooling/clang-diff-topdown.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -stop-after=topdown %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the top-down matching of identical subtrees only. + +#ifndef DEST + +void f1() +{ + // Match some subtree of height greater than 2. + // CHECK: Match CompoundStmt(3) to CompoundStmt(3) + // CHECK: Match CompoundStmt(4) to CompoundStmt(4) + // CHECK: Match NullStmt(5) to NullStmt(5) + {{;}} + + // Don't match subtrees that are smaller. + // CHECK-NOT: Match CompoundStmt(6) + // CHECK-NOT: Match NullStmt(7) + {;} + + // Greedy approach - use the first matching subtree when there are multiple + // identical subtrees. + // CHECK: Match CompoundStmt(8) to CompoundStmt(8) + // CHECK: Match CompoundStmt(9) to CompoundStmt(9) + // CHECK: Match NullStmt(10) to NullStmt(10) + {{;;}} +} + +#else + +void f1() { + + {{;}} + + {;} + + {{;;}} + // CHECK-NOT: Match {{.*}} to CompoundStmt(11) + // CHECK-NOT: Match {{.*}} to CompoundStmt(12) + // CHECK-NOT: Match {{.*}} to NullStmt(13) + {{;;}} + + // CHECK-NOT: Match {{.*}} to NullStmt(14) + ; +} + +#endif Index: test/Tooling/clang-diff-opt.cpp === --- /dev/null +++ test/Tooling/clang-diff-opt.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -s=10 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the behaviour of the matching according to the optimal tree edit +// distance, implemented with Zhang and Shasha's algorithm. +// Just for testing we use a tiny value of 10 for maxsize. Subtrees bigger than +// this size will not be processed by the optimal algorithm. + +#ifndef DEST + +void f1() { {;} {{;}} } + +void f2() { {;} {{;}} } + +void f3() { {;} {{;;}} } + +#else + +void f1() { +// Jaccard similarity = 3 / (5 + 4 - 3) = 3 / 6 >= 0.5 +// The optimal matching algorithm should move the ; into the outer block +// CHECK: Match CompoundStmt(2) to CompoundStmt(2) +// CHECK-NOT: Match CompoundStmt(3) +// CHECK-NEXT: Match NullStmt(4) to NullStmt(3) + ; {{;}} +} + +void f2() { + // Jaccard similarity = 7 / (10 + 10 - 7) >= 0.5 + // As none of the subtrees is bigger than 10 nodes, the optimal algorithm + // will be run. + // CHECK: Match NullStmt(11) to NullStmt(9) + ;; {{;}} +} + +void f3() { + // Jaccard similarity = 8 / (11 + 11 - 8) >= 0.5 + // As the subtrees are bigger than 10 nodes, the optimal algorithm will not + // be run. + // CHECK: Delete NullStmt(22) + ;; {{;;}} +} +#endif Index: test/Tooling/clang-diff-bottomup.cpp === --- /dev/null +++ test/Tooling/clang-diff-bottomup.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -s=0 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the bottom-up matching, with maxsize set to 0, so that the optimal matching will never be applied. + +#ifndef DEST + +void f1() { ; {{;}} } +void f2() { ;; {{;}} } + +#else + +// Jaccard similarity threshold is 0.5. + +void f1() { +// CompoundStmt: 3 matched descendants, subtree sizes 4 and 5 +// Jaccard similarity = 3 / (4 + 5 - 3) = 3 / 6 >= 0.5 +// CHECK: Match FunctionDecl: f1(void (void))(1) to FunctionDecl: f1(void (void))(1) +// CHECK: Match CompoundStmt(2) to CompoundStmt(2) +// CHECK: Match CompoundStmt(4) to CompoundStmt(3) +// CHECK: Match CompoundStmt(5) to CompoundStmt(4) +// CHECK: Match NullStmt(6) to NullStmt(5) + {{;}} ;; +} + +void f2() { +// CompoundStmt: 3 matched descendants, subtree sizes 4 and 5 +// Jaccard similarity = 3 / (5 + 6 - 3) = 3 / 8 < 0.5 +// CHECK-NOT: Match FunctionDecl(9) +// CHECK-NOT: Match CompoundStmt(10) +// CHECK: Match CompoundStmt(11) to CompoundStmt(10) +// CHECK: Match CompoundStmt(12) to CompoundStmt(11) +// CHECK: Match NullStmt(13) to NullStmt(12) +// CHECK-NOT: Match NullStmt(13) + {{;}} ;;; +} + +#endif Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -68,7 +68,8 @@ // Compute ChangeKind for each node based on similarity. void computeChangeKinds(Mapping &M); - NodeId getMapped(const std::unique_ptr &Tree, NodeId Id) const { + NodeId getMapped(const st
[PATCH] D36186: [clang-diff] Improve and test getNodeValue
johannes updated this revision to Diff 109425. johannes added a comment. NFC renames https://reviews.llvm.org/D36186 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-ast.cpp test/Tooling/clang-diff-basic.cpp test/Tooling/clang-diff-bottomup.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -315,6 +315,18 @@ 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]); Index: test/Tooling/clang-diff-bottomup.cpp === --- test/Tooling/clang-diff-bottomup.cpp +++ test/Tooling/clang-diff-bottomup.cpp @@ -16,7 +16,7 @@ void f1() { // CompoundStmt: 3 matched descendants, subtree sizes 4 and 5 // Jaccard similarity = 3 / (4 + 5 - 3) = 3 / 6 >= 0.5 -// CHECK: Match FunctionDecl: f1(void (void))(1) to FunctionDecl: f1(void (void))(1) +// CHECK: Match FunctionDecl: f1(void ())(1) to FunctionDecl: f1(void ())(1) // CHECK: Match CompoundStmt(2) to CompoundStmt(2) // CHECK: Match CompoundStmt(4) to CompoundStmt(3) // CHECK: Match CompoundStmt(5) to CompoundStmt(4) Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -11,18 +11,18 @@ } } -// CHECK: Match DeclRefExpr: foo{{.*}} to DeclRefExpr: inner::foo +// CHECK: Match DeclRefExpr: src::foo{{.*}} to DeclRefExpr: dst::inner::foo void main() { inner::foo(); } // CHECK: Match StringLiteral: foo{{.*}} to StringLiteral: foo const char *b = "f" "o" "o"; // unsigned is canonicalized to unsigned int -// CHECK: Match TypedefDecl: nat;unsigned int;{{.*}} to TypedefDecl: nat;unsigned int; +// CHECK: Match TypedefDecl: src::nat;unsigned int;{{.*}} to TypedefDecl: dst::nat;unsigned int; typedef unsigned nat; -// CHECK: Match VarDecl: p(int){{.*}} to VarDecl: prod(double) -// CHECK: Update VarDecl: p(int){{.*}} to prod(double) +// CHECK: Match VarDecl: src::p(int){{.*}} to VarDecl: dst::prod(double) +// CHECK: Update VarDecl: src::p(int){{.*}} to dst::prod(double) // CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * double prod = 1 * 2 * 10; // CHECK: Update DeclRefExpr @@ -44,7 +44,7 @@ namespace { // match with parents of different type -// CHECK: Match FunctionDecl: f1{{.*}} to FunctionDecl: f1 +// CHECK: Match FunctionDecl: f1{{.*}} to FunctionDecl: (anonymous namespace)::f1 void f1() {{ (void) __func__;;; }} } Index: test/Tooling/clang-diff-ast.cpp === --- test/Tooling/clang-diff-ast.cpp +++ test/Tooling/clang-diff-ast.cpp @@ -5,34 +5,43 @@ // CHECK: {{^}} NamespaceDecl: test;( namespace test { -// CHECK: {{^}} FunctionDecl: f( +// CHECK: {{^}} FunctionDecl: test::f( // CHECK: CompoundStmt( void f() { // CHECK: VarDecl: i(int)( // CHECK: IntegerLiteral: 1 auto i = 1; + // CHECK: FloatingLiteral: 1.5( + auto r = 1.5; + // CHECK: CXXBoolLiteralExpr: true( + auto b = true; // CHECK: CallExpr( // CHECK-NOT: ImplicitCastExpr - // CHECK-NEXT: DeclRefExpr: f( + // CHECK: DeclRefExpr: test::f( f(); + // CHECK: UnaryOperator: ++( + ++i; // CHECK: BinaryOperator: =( i = i; } } // end namespace test +// CHECK: UsingDirectiveDecl: test( +using namespace test; + // CHECK: TypedefDecl: nat;unsigned int;( typedef unsigned nat; // CHECK: TypeAliasDecl: real;double;( using real = double; class Base { }; -// CHECK: CXXRecordDecl: X;class X;( +// CHECK: CXXRecordDecl: X;X;( class X : Base { int m; - // CHECK: CXXMethodDecl: foo(const char *(int))( + // CHECK: CXXMethodDecl: X::foo(const char *(int))( // CHECK: ParmVarDecl: i(int)( const char *foo(int i) { if (i == 0) @@ -44,9 +53,9 @@ // CHECK: AccessSpecDecl: public( public: - // CHECK: CXXConstructorDecl: X(void (char, int))( + // CHECK: CXXConstructorDecl: X::X(void (char, int))Base,X::m,( X(char, int) : Base(), m(0) { -// CHECK: MemberExpr( +// CHECK: MemberExpr: X::m( int x = m; } }; Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -149,6 +149,8 @
[PATCH] D36186: [clang-diff] Improve and test getNodeValue
johannes updated this revision to Diff 109426. johannes added a comment. renamse, NFC https://reviews.llvm.org/D36186 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-ast.cpp test/Tooling/clang-diff-basic.cpp test/Tooling/clang-diff-bottomup.cpp test/Tooling/clang-diff-opt.cpp test/Tooling/clang-diff-topdown.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -315,6 +315,18 @@ 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]); Index: test/Tooling/clang-diff-topdown.cpp === --- test/Tooling/clang-diff-topdown.cpp +++ test/Tooling/clang-diff-topdown.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -E %s > %t.src.cpp // RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST -// RUN: clang-diff -dump-matches -stop-after=topdown %t.src.cpp %t.dst.cpp -- | FileCheck %s +// RUN: clang-diff -dump-matches -stop-after=topdown %t.src.cpp %t.dst.cpp -- -std=c++11 | FileCheck %s // // Test the top-down matching of identical subtrees only. @@ -27,8 +27,19 @@ {{;;}} } +int x; + +namespace src { + int x; + int x1 = x + 1; + int x2 = ::x + 1; +} + +class A { int x = 1 + 1; void f() { int x1 = x; } }; + #else + void f1() { {{;}} @@ -45,4 +56,28 @@ ; } +int x; + +namespace dst { + int x; + // CHECK: Match DeclRefExpr: :x(17) to DeclRefExpr: :x(22) + int x1 = x + 1; + // CHECK: Match DeclRefExpr: x(21) to DeclRefExpr: x(26) + int x2 = ::x + 1; +} + +class B { + // Only the class name changed; it is not included in the field value, + // therefore there is no update. + // CHECK: Match FieldDecl: :x(int)(24) to FieldDecl: :x(int)(29) + // CHECK-NOT: Update FieldDecl: :x(int)(24) + int x = 1+1; + void f() { +// CHECK: Match MemberExpr: :x(32) to MemberExpr: :x(37) +// CHECK-NOT: Update MemberExpr: :x(32) +int x1 = B::x; + } + +}; + #endif Index: test/Tooling/clang-diff-opt.cpp === --- test/Tooling/clang-diff-opt.cpp +++ test/Tooling/clang-diff-opt.cpp @@ -25,7 +25,7 @@ // CHECK-NEXT: Match NullStmt(4) to NullStmt(3) ; {{;}} } - + void f2() { // Jaccard similarity = 7 / (10 + 10 - 7) >= 0.5 // As none of the subtrees is bigger than 10 nodes, the optimal algorithm @@ -41,4 +41,5 @@ // CHECK: Delete NullStmt(22) ;; {{;;}} } + #endif Index: test/Tooling/clang-diff-bottomup.cpp === --- test/Tooling/clang-diff-bottomup.cpp +++ test/Tooling/clang-diff-bottomup.cpp @@ -16,7 +16,7 @@ void f1() { // CompoundStmt: 3 matched descendants, subtree sizes 4 and 5 // Jaccard similarity = 3 / (4 + 5 - 3) = 3 / 6 >= 0.5 -// CHECK: Match FunctionDecl: f1(void (void))(1) to FunctionDecl: f1(void (void))(1) +// CHECK: Match FunctionDecl: f1(void ())(1) to FunctionDecl: f1(void ())(1) // CHECK: Match CompoundStmt(2) to CompoundStmt(2) // CHECK: Match CompoundStmt(4) to CompoundStmt(3) // CHECK: Match CompoundStmt(5) to CompoundStmt(4) Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -11,18 +11,18 @@ } } -// CHECK: Match DeclRefExpr: foo{{.*}} to DeclRefExpr: inner::foo +// CHECK: Match DeclRefExpr: :foo{{.*}} to DeclRefExpr: :inner::foo void main() { inner::foo(); } // CHECK: Match StringLiteral: foo{{.*}} to StringLiteral: foo const char *b = "f" "o" "o"; // unsigned is canonicalized to unsigned int -// CHECK: Match TypedefDecl: nat;unsigned int;{{.*}} to TypedefDecl: nat;unsigned int; +// CHECK: Match TypedefDecl: :nat;unsigned int;{{.*}} to TypedefDecl: :nat;unsigned int; typedef unsigned nat; -// CHECK: Match VarDecl: p(int){{.*}} to VarDecl: prod(double) -// CHECK: Update VarDecl: p(int){{.*}} to prod(double) +// CHECK: Match VarDecl: :p(int){{.*}} to VarDecl: :prod(double) +// CHECK: Update VarDecl: :p(int){{.*}} to :prod(double) // CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * double prod = 1 * 2 * 10; // CHECK: Update DeclRefExpr @@ -44,7 +44,7 @@ namespace { // match with parents of different type -// CHECK: Match FunctionDecl: f1{{.*}} to
[PATCH] D36176: [clang-diff] Fix some errors and inconsistencies
johannes added inline comments. Comment at: include/clang/Tooling/ASTDiff/ASTDiff.h:96 : TreeImpl(llvm::make_unique(this, Node, AST)) {} + SyntaxTree(const SyntaxTree &Tree) = delete; ~SyntaxTree(); arphaman wrote: > johannes wrote: > > arphaman wrote: > > > It might be better to add a move constructor which would disable copy > > > constructors. > > ASTDiff::getMapped internally uses the address of the SyntaxTree that is > > passed as parameter. > > So the tree must be exactly the same as the one that is passed to the > > constructor of ASTDiff. > > > > This is quite bad. I will change ASTDiff::getMapped to accept a boolean or > > split it into two methods > > Should I still add a move constructor? > Can't you just use the `TreeImpl` pointer value? That should be the same even > after you've moved `SyntaxTree` with the move constructor. ok perfect https://reviews.llvm.org/D36176 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36177: [clang-diff] Add commandline arguments.
johannes added a comment. In https://reviews.llvm.org/D36177#828909, @arphaman wrote: > There should be a test that ensures that `stop-after` works and `extra-arg`s > are correctly passed to the compiler. `stop-after` is not tested yet. It is also broken in this patch, I will move it to a later patch, then it is easier to test. https://reviews.llvm.org/D36177 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36184: [clang-diff] Filter AST nodes
johannes added a comment. In https://reviews.llvm.org/D36184#828866, @klimek wrote: > Why? Also, missing tests. implicit nodes are noisy / they generally don't add information; I guess one could also keep them. I excluded nodes outside of the main file are because the visualisation only works with single files for now. That will change, same as with macros. https://reviews.llvm.org/D36184 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36185: [clang-diff] Fix similarity computation
johannes added inline comments. Comment at: test/Tooling/clang-diff-bottomup.cpp:3 +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -m -no-compilation-database -s=0 %t.src.cpp %t.dst.cpp | FileCheck %s +// klimek wrote: > Instead of using -no-compilation-database most tools support adding -- (args) > for using a fixed compilation database. Any specific reason to deviate from > that for clang-diff? ah I actually wasn't aware of that, done https://reviews.llvm.org/D36185 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36186: [clang-diff] Improve and test getNodeValue
johannes added inline comments. Comment at: test/Tooling/clang-diff-ast.cpp:32 + +// CHECK: TypedefDecl: nat;unsigned int;( +typedef unsigned nat; klimek wrote: > For my curiosity: why are there semicolons here? Is the format documented > somewhere? To avoid collisions (the value should be unique per TypedefDecl in this example), the second one is unnecessary I guess. It is not documented, If it works out I want to move to StmtDataCollector for node comparison and keep these just for visualisation / debugging. https://reviews.llvm.org/D36186 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36178: [clang-diff] Move the JSON export function to clang-diff
johannes added inline comments. Comment at: test/Tooling/clang-diff-json.cpp:1 +// RUN: clang-diff -ast-dump %s -- | python -m json.tool | FileCheck %s + arphaman wrote: > I think you have to use `%python` instead of `python`, like LLVM tests do. ok So I have to use python 2.7? (for the test in https://reviews.llvm.org/D36182) https://reviews.llvm.org/D36178 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36177: [clang-diff] Add commandline arguments.
johannes updated this revision to Diff 109502. johannes added a comment. fix tests https://reviews.llvm.org/D36177 Files: test/Tooling/clang-diff-args.test test/Tooling/clang-diff-basic.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -28,12 +28,6 @@ cl::desc("Print the internal representation of the AST as JSON."), cl::init(false), cl::cat(ClangDiffCategory)); -static cl::opt NoCompilationDatabase( -"no-compilation-database", -cl::desc( -"Do not attempt to load build settings from a compilation database"), -cl::init(false), cl::cat(ClangDiffCategory)); - static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -43,43 +37,83 @@ cl::Optional, cl::cat(ClangDiffCategory)); -static std::unique_ptr getAST(const StringRef Filename) { +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 (!NoCompilationDatabase) -Compilations = -CompilationDatabase::autoDetectFromSource(Filename, ErrorMessage); - if (!Compilations) { -if (!NoCompilationDatabase) + 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()); + Compilations = + llvm::make_unique( + ".", std::vector()); +} } + addExtraArgs(Compilations); std::array Files = {{Filename}}; - ClangTool Tool(*Compilations, Files); + ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files); std::vector> ASTs; Tool.buildASTs(ASTs); if (ASTs.size() != Files.size()) return nullptr; return std::move(ASTs[0]); } 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(SourcePath); +std::unique_ptr AST = getAST(CommonCompilations, SourcePath); if (!AST) return 1; diff::SyntaxTree Tree(AST->getASTContext()); @@ -92,12 +126,14 @@ return 1; } - std::unique_ptr Src = getAST(SourcePath); - std::unique_ptr Dst = getAST(DestinationPath); + 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); Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic
[PATCH] D36181: [clang-diff] Make printing of matches optional
johannes updated this revision to Diff 109503. johannes added a comment. fix tests https://reviews.llvm.org/D36181 Files: test/Tooling/clang-diff-args.test test/Tooling/clang-diff-basic.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -33,6 +33,10 @@ cl::desc("Print the internal representation of the AST as JSON."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt +PrintMatches("dump-matches", cl::desc("Print the matched nodes."), + cl::init(false), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -267,7 +271,7 @@ for (diff::NodeId Dst : DstTree) { diff::NodeId Src = Diff.getMapped(DstTree, Dst); -if (Src.isValid()) { +if (PrintMatches && Src.isValid()) { llvm::outs() << "Match "; printNode(llvm::outs(), SrcTree, Src); llvm::outs() << " to "; Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -E %s > %T/src.cpp // RUN: %clang_cc1 -E %s > %T/dst.cpp -DDEST -// RUN: clang-diff %T/src.cpp %T/dst.cpp -- | FileCheck %s +// RUN: clang-diff -dump-matches %T/src.cpp %T/dst.cpp -- | FileCheck %s #ifndef DEST namespace src { Index: test/Tooling/clang-diff-args.test === --- test/Tooling/clang-diff-args.test +++ test/Tooling/clang-diff-args.test @@ -6,3 +6,7 @@ RUN: clang-diff -ast-dump -extra-arg=-Da=X%t.cpp -- 2>&1 | FileCheck %s RUN: clang-diff -ast-dump -extra-arg-before=-Da=X %t.cpp -- 2>&1 | FileCheck %s RUN: clang-diff -ast-dump %t.cpp -- 2>&1 -Da=X | FileCheck %s + +NOMATCH-CHECK-NOT: {{.}} +RUN: clang-diff %S/clang-diff-ast.cpp %S/clang-diff-ast.cpp -- 2>&1 -std=c++11 \ +RUN: | FileCheck -check-prefix=NOMATCH-CHECK -allow-empty %s Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -33,6 +33,10 @@ cl::desc("Print the internal representation of the AST as JSON."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt +PrintMatches("dump-matches", cl::desc("Print the matched nodes."), + cl::init(false), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -267,7 +271,7 @@ for (diff::NodeId Dst : DstTree) { diff::NodeId Src = Diff.getMapped(DstTree, Dst); -if (Src.isValid()) { +if (PrintMatches && Src.isValid()) { llvm::outs() << "Match "; printNode(llvm::outs(), SrcTree, Src); llvm::outs() << " to "; Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -E %s > %T/src.cpp // RUN: %clang_cc1 -E %s > %T/dst.cpp -DDEST -// RUN: clang-diff %T/src.cpp %T/dst.cpp -- | FileCheck %s +// RUN: clang-diff -dump-matches %T/src.cpp %T/dst.cpp -- | FileCheck %s #ifndef DEST namespace src { Index: test/Tooling/clang-diff-args.test === --- test/Tooling/clang-diff-args.test +++ test/Tooling/clang-diff-args.test @@ -6,3 +6,7 @@ RUN: clang-diff -ast-dump -extra-arg=-Da=X%t.cpp -- 2>&1 | FileCheck %s RUN: clang-diff -ast-dump -extra-arg-before=-Da=X %t.cpp -- 2>&1 | FileCheck %s RUN: clang-diff -ast-dump %t.cpp -- 2>&1 -Da=X | FileCheck %s + +NOMATCH-CHECK-NOT: {{.}} +RUN: clang-diff %S/clang-diff-ast.cpp %S/clang-diff-ast.cpp -- 2>&1 -std=c++11 \ +RUN: | FileCheck -check-prefix=NOMATCH-CHECK -allow-empty %s ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36183: [clang-diff] Simplify mapping
johannes updated this revision to Diff 109504. johannes added a comment. merge parent changes https://reviews.llvm.org/D36183 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/Inputs/clang-diff-basic-src.cpp test/Tooling/clang-diff-basic.cpp Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -38,8 +38,15 @@ return "foo"; return 0; } - // CHECK: Delete AccessSpecDecl: public X(){}; - // CHECK: Delete CXXMethodDecl }; } + +namespace { +// match with parents of different type +// CHECK: Match FunctionDecl: f1{{.*}} to FunctionDecl: f1 +void f1() {{ (void) __func__;;; }} +} + +// CHECK: Delete AccessSpecDecl: public +// CHECK: Delete CXXMethodDecl Index: test/Tooling/Inputs/clang-diff-basic-src.cpp === --- test/Tooling/Inputs/clang-diff-basic-src.cpp +++ test/Tooling/Inputs/clang-diff-basic-src.cpp @@ -26,3 +26,5 @@ int id(int i) { return i; } }; } + +void f1() {{ (void) __func__;;; }} Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -34,48 +34,23 @@ Mapping() = default; Mapping(Mapping &&Other) = default; Mapping &operator=(Mapping &&Other) = default; - Mapping(int Size1, int Size2) { -// Maximum possible size after patching one tree. -int Size = Size1 + Size2; -SrcToDst = llvm::make_unique[]>(Size); -DstToSrc = llvm::make_unique[]>(Size); + + Mapping(size_t Size) { +SrcToDst = llvm::make_unique(Size); +DstToSrc = llvm::make_unique(Size); } void link(NodeId Src, NodeId Dst) { -SrcToDst[Src].push_back(Dst); -DstToSrc[Dst].push_back(Src); - } - - NodeId getDst(NodeId Src) const { -if (hasSrc(Src)) - return SrcToDst[Src][0]; -return NodeId(); - } - NodeId getSrc(NodeId Dst) const { -if (hasDst(Dst)) - return DstToSrc[Dst][0]; -return NodeId(); - } - const SmallVector &getAllDsts(NodeId Src) const { -return SrcToDst[Src]; - } - const SmallVector &getAllSrcs(NodeId Dst) const { -return DstToSrc[Dst]; - } - bool hasSrc(NodeId Src) const { return !SrcToDst[Src].empty(); } - bool hasDst(NodeId Dst) const { return !DstToSrc[Dst].empty(); } - bool hasSrcDst(NodeId Src, NodeId Dst) const { -for (NodeId DstId : SrcToDst[Src]) - if (DstId == Dst) -return true; -for (NodeId SrcId : DstToSrc[Dst]) - if (SrcId == Src) -return true; -return false; +SrcToDst[Src] = Dst, DstToSrc[Dst] = Src; } + NodeId getDst(NodeId Src) const { return SrcToDst[Src]; } + NodeId getSrc(NodeId Dst) const { return DstToSrc[Dst]; } + bool hasSrc(NodeId Src) const { return getDst(Src).isValid(); } + bool hasDst(NodeId Dst) const { return getSrc(Dst).isValid(); } + private: - std::unique_ptr[]> SrcToDst, DstToSrc; + std::unique_ptr SrcToDst, DstToSrc; }; } // end anonymous namespace @@ -104,8 +79,6 @@ // Returns true if the two subtrees are identical. bool identical(NodeId Id1, NodeId Id2) const; - bool canBeAddedToMapping(const Mapping &M, NodeId Id1, NodeId Id2) const; - // Returns false if the nodes must not be mached. bool isMatchingPossible(NodeId Id1, NodeId Id2) const; @@ -711,23 +684,6 @@ return true; } -bool ASTDiff::Impl::canBeAddedToMapping(const Mapping &M, NodeId Id1, -NodeId Id2) const { - assert(isMatchingPossible(Id1, Id2) && - "Matching must be possible in the first place."); - if (M.hasSrcDst(Id1, Id2)) -return false; - if (Options.EnableMatchingWithUnmatchableParents) -return true; - const Node &N1 = T1.getNode(Id1); - const Node &N2 = T2.getNode(Id2); - NodeId P1 = N1.Parent; - NodeId P2 = N2.Parent; - // Only allow matching if parents can be matched. - return (P1.isInvalid() && P2.isInvalid()) || - (P1.isValid() && P2.isValid() && isMatchingPossible(P1, P2)); -} - bool ASTDiff::Impl::isMatchingPossible(NodeId Id1, NodeId Id2) const { return Options.isMatchingAllowed(T1.getNode(Id1), T2.getNode(Id2)); } @@ -750,7 +706,7 @@ for (const auto Tuple : R) { NodeId Src = Tuple.first; NodeId Dst = Tuple.second; -if (canBeAddedToMapping(M, Src, Dst)) +if (!M.hasSrc(Src) && !M.hasDst(Dst)) M.link(Src, Dst); } } @@ -803,7 +759,7 @@ if (Matched || !MatchedChildren) continue; NodeId Id2 = findCandidate(M, Id1); -if (Id2.isValid() && canBeAddedToMapping(M, Id1, Id2)) { +if (Id2.isValid()) { M.link(Id1, Id2); addOptimalMapping(M, Id1, Id2); } @@ -814,7 +770,7 @@ PriorityList L1(T1); PriorityList L2(T2); - Mapping M(T1.getSize(), T2.getSize()); + Mapping M(T1.getSize() + T2.getSize()); L1.push(T1.g
[PATCH] D36185: [clang-diff] Fix similarity computation
johannes updated this revision to Diff 109505. johannes added a comment. merge parent changes https://reviews.llvm.org/D36185 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-bottomup.cpp test/Tooling/clang-diff-opt.cpp test/Tooling/clang-diff-topdown.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -50,6 +50,11 @@ cl::Optional, cl::cat(ClangDiffCategory)); +static cl::opt StopAfter("stop-after", + cl::desc(""), + cl::Optional, cl::init(""), + cl::cat(ClangDiffCategory)); + static cl::opt MaxSize("s", cl::desc(""), cl::Optional, cl::init(-1), cl::cat(ClangDiffCategory)); @@ -424,6 +429,14 @@ 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"; + return 1; +} + } diff::SyntaxTree SrcTree(Src->getASTContext()); diff::SyntaxTree DstTree(Dst->getASTContext()); diff::ASTDiff Diff(SrcTree, DstTree, Options); Index: test/Tooling/clang-diff-topdown.cpp === --- /dev/null +++ test/Tooling/clang-diff-topdown.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -stop-after=topdown %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the top-down matching of identical subtrees only. + +#ifndef DEST + +void f1() +{ + // Match some subtree of height greater than 2. + // CHECK: Match CompoundStmt(3) to CompoundStmt(3) + // CHECK: Match CompoundStmt(4) to CompoundStmt(4) + // CHECK: Match NullStmt(5) to NullStmt(5) + {{;}} + + // Don't match subtrees that are smaller. + // CHECK-NOT: Match CompoundStmt(6) + // CHECK-NOT: Match NullStmt(7) + {;} + + // Greedy approach - use the first matching subtree when there are multiple + // identical subtrees. + // CHECK: Match CompoundStmt(8) to CompoundStmt(8) + // CHECK: Match CompoundStmt(9) to CompoundStmt(9) + // CHECK: Match NullStmt(10) to NullStmt(10) + {{;;}} +} + +#else + +void f1() { + + {{;}} + + {;} + + {{;;}} + // CHECK-NOT: Match {{.*}} to CompoundStmt(11) + // CHECK-NOT: Match {{.*}} to CompoundStmt(12) + // CHECK-NOT: Match {{.*}} to NullStmt(13) + {{;;}} + + // CHECK-NOT: Match {{.*}} to NullStmt(14) + ; +} + +#endif Index: test/Tooling/clang-diff-opt.cpp === --- /dev/null +++ test/Tooling/clang-diff-opt.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -s=10 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the behaviour of the matching according to the optimal tree edit +// distance, implemented with Zhang and Shasha's algorithm. +// Just for testing we use a tiny value of 10 for maxsize. Subtrees bigger than +// this size will not be processed by the optimal algorithm. + +#ifndef DEST + +void f1() { {;} {{;}} } + +void f2() { {;} {{;}} } + +void f3() { {;} {{;;}} } + +#else + +void f1() { +// Jaccard similarity = 3 / (5 + 4 - 3) = 3 / 6 >= 0.5 +// The optimal matching algorithm should move the ; into the outer block +// CHECK: Match CompoundStmt(2) to CompoundStmt(2) +// CHECK-NOT: Match CompoundStmt(3) +// CHECK-NEXT: Match NullStmt(4) to NullStmt(3) + ; {{;}} +} + +void f2() { + // Jaccard similarity = 7 / (10 + 10 - 7) >= 0.5 + // As none of the subtrees is bigger than 10 nodes, the optimal algorithm + // will be run. + // CHECK: Match NullStmt(11) to NullStmt(9) + ;; {{;}} +} + +void f3() { + // Jaccard similarity = 8 / (11 + 11 - 8) >= 0.5 + // As the subtrees are bigger than 10 nodes, the optimal algorithm will not + // be run. + // CHECK: Delete NullStmt(22) + ;; {{;;}} +} +#endif Index: test/Tooling/clang-diff-bottomup.cpp === --- /dev/null +++ test/Tooling/clang-diff-bottomup.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -s=0 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the bottom-up matching, with maxsize set to 0, so that the optimal matching will never be applied. + +#ifndef DEST + +void f1() { ; {{;}} } +void f2() { ;; {{;}} } + +#else + +// Jaccard similarity threshold is 0.5. + +void f1() { +// CompoundStmt: 3 m
[PATCH] D36178: [clang-diff] Move the JSON export function to clang-diff
johannes added inline comments. Comment at: test/Tooling/clang-diff-json.cpp:1 +// RUN: clang-diff -ast-dump %s -- | python -m json.tool | FileCheck %s + johannes wrote: > arphaman wrote: > > I think you have to use `%python` instead of `python`, like LLVM tests do. > ok > So I have to use python 2.7? (for the test in https://reviews.llvm.org/D36182) It looks like %python is expanded in the wrong way. I get the same as when using %Sython, the directory of the test case is substituted https://reviews.llvm.org/D36178 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36176: [clang-diff] Fix some errors and inconsistencies
johannes added a comment. In https://reviews.llvm.org/D36176#830341, @arphaman wrote: > LGTM. Although I'm not 100% sure it's fully NFC, so if you can't come up with > a test please justify why. There are changes that affect the output. Firstly the computation of the rightmost descendant; the added assertion fails every time with the old version. Secondly, the change around line 811 prevents root nodes from being mapped if they are already mapped. When comparing translation units this happens only if the two trees are identical (in this case the output would not change). Otherwise, it would require some change in the client to demonstrate how the behaviour changed. One needs to compare AST Nodes other than entire translation units. At some point there will be something that uses this feature so we cover it too (or remove the feature). So I'd keep it like this https://reviews.llvm.org/D36176 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36178: [clang-diff] Move the JSON export function to clang-diff
johannes updated this revision to Diff 109537. johannes added a comment. use %python in tests https://reviews.llvm.org/D36178 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-json.cpp test/lit.cfg test/lit.site.cfg.in tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -94,6 +94,65 @@ 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 = @@ -117,7 +176,11 @@ if (!AST) return 1; diff::SyntaxTree Tree(AST->getASTContext()); -Tree.printAsJson(llvm::outs()); +llvm::outs() << R"({"filename":")"; +printJsonString(llvm::outs(), SourcePath); +llvm::outs() << R"(","root":)"; +printNodeAsJson(llvm::outs(), Tree, Tree.getRootId()); +llvm::outs() << "}\n"; return 0; } Index: test/lit.site.cfg.in === --- test/lit.site.cfg.in +++ test/lit.site.cfg.in @@ -25,6 +25,7 @@ config.enable_backtrace = @ENABLE_BACKTRACES@ config.host_arch = "@HOST_ARCH@" config.enable_abi_breaking_checks = "@LLVM_ENABLE_ABI_BREAKING_CHECKS@" +config.python_executable = "@PYTHON_EXECUTABLE@" # Support substitution of the tools and libs dirs with user parameters. This is # used when we can't determine the tool dir at configuration time. Index: test/lit.cfg === --- test/lit.cfg +++ test/lit.cfg @@ -276,6 +276,7 @@ config.substitutions.append( ('%itanium_abi_triple', makeItaniumABITriple(config.target_triple)) ) config.substitutions.append( ('%ms_abi_triple', makeMSABITriple(config.target_triple)) ) config.substitutions.append( ('%resource_dir', getClangBuiltinIncludeDir(config.clang)) ) +config.substitutions.append( ('%python', config.python_executable) ) # The host triple might not be set, at least if we're compiling clang from # an already installed llvm. Index: test/Tooling/clang-diff-json.cpp === --- /dev/null +++ test/Tooling/clang-diff-json.cpp @@ -0,0 +1,27 @@ +// RUN: clang-diff -ast-dump %s -- \ +// RUN: | %python -c 'import json, sys; json.dump(json.loads(sys.stdin.read()), sys.stdout, sort_keys=True, indent=2)' \ +// RUN: | FileCheck %s + +// CHECK: "begin": 294, +// CHECK: "type": "CXXRecordDecl", +// CHECK: "type": "FieldDecl", +// CHECK: "end": 314, +class A { + int x; +}; + +// CHECK: "children": [ +// CHECK-NEXT: { +// CHECK-NEXT: "begin": +// CHECK-NEXT: "children": [] +// CHECK-NEXT: "end": +// CHECK-NEXT: "id": +// CHECK-NEXT: "type": "CharacterLiteral" +// CHECK-NEXT: } +// CHECK: ] +// CHECK: "type": "VarDecl", +char nl = '\n'; + +// CHECK: "value": "abc \n\t\u\u001f\u0123 \ub370\ubc15" +char s[] = "abc \n\t\0\x1f\u0123 데박"; + Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -176,9 +176,6 @@ void printTree(NodeId Root) const; void printTree(raw_ostream &OS, NodeId Root) const; - void printAsJsonImpl(raw_ostream &OS) const; - void printNodeA
[PATCH] D36180: [clang-diff] Add option to dump the AST, one node per line
johannes updated this revision to Diff 109538. johannes added a comment. merge update https://reviews.llvm.org/D36180 Files: test/Tooling/clang-diff-ast.cpp test/Tooling/clang-diff-json.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -25,9 +25,14 @@ static cl::opt ASTDump("ast-dump", -cl::desc("Print the internal representation of the AST as JSON."), +cl::desc("Print the internal representation of the AST."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt ASTDumpJson( +"ast-dump-json", +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)); @@ -166,6 +171,15 @@ 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) { @@ -213,15 +227,19 @@ addExtraArgs(CommonCompilations); - if (ASTDump) { + if (ASTDump || ASTDumpJson) { 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()); +if (ASTDump) { + printTree(llvm::outs(), Tree); + return 0; +} llvm::outs() << R"({"filename":")"; printJsonString(llvm::outs(), SourcePath); llvm::outs() << R"(","root":)"; Index: test/Tooling/clang-diff-json.cpp === --- test/Tooling/clang-diff-json.cpp +++ test/Tooling/clang-diff-json.cpp @@ -1,11 +1,11 @@ -// RUN: clang-diff -ast-dump %s -- \ +// RUN: clang-diff -ast-dump-json %s -- \ // RUN: | %python -c 'import json, sys; json.dump(json.loads(sys.stdin.read()), sys.stdout, sort_keys=True, indent=2)' \ // RUN: | FileCheck %s -// CHECK: "begin": 294, +// CHECK: "begin": 299, // CHECK: "type": "CXXRecordDecl", // CHECK: "type": "FieldDecl", -// CHECK: "end": 314, +// CHECK: "end": 319, class A { int x; }; Index: test/Tooling/clang-diff-ast.cpp === --- /dev/null +++ test/Tooling/clang-diff-ast.cpp @@ -0,0 +1,50 @@ +// RUN: clang-diff -ast-dump %s -- -std=c++11 | FileCheck %s + + +// CHECK: {{^}}TranslationUnitDecl(0) +// CHECK: {{^}} NamespaceDecl: test;( +namespace test { + +// CHECK: {{^}} FunctionDecl: f( +// CHECK: CompoundStmt( +void f() { + // CHECK: VarDecl: i(int)( + // CHECK: IntegerLiteral: 1 + auto i = 1; + // CHECK: CallExpr( + // CHECK: DeclRefExpr: f( + f(); + // CHECK: BinaryOperator: =( + i = i; +} + +} // end namespace test + +// CHECK: TypedefDecl: nat;unsigned int;( +typedef unsigned nat; +// CHECK: TypeAliasDecl: real;double;( +using real = double; + +class Base { +}; + +// CHECK: CXXRecordDecl: X;class X;( +class X : Base { + int m; + // CHECK: CXXMethodDecl: foo(const char *(int))( + // CHECK: ParmVarDecl: i(int)( + const char *foo(int i) { +if (i == 0) + // CHECK: StringLiteral: foo( + return "foo"; +return 0; + } + + // CHECK: AccessSpecDecl: public( +public: + // CHECK: CXXConstructorDecl: X(void (char, int))( + X(char, int) : Base(), m(0) { +// CHECK: MemberExpr( +int x = m; + } +}; ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36182: [clang-diff] Add HTML side-by-side diff output
johannes updated this revision to Diff 109539. johannes added a comment. make test work with python 2 https://reviews.llvm.org/D36182 Files: test/Tooling/Inputs/clang-diff-basic-src.cpp test/Tooling/clang-diff-basic.cpp test/Tooling/clang-diff-html.py tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -37,6 +37,10 @@ PrintMatches("dump-matches", cl::desc("Print the matched nodes."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt HtmlDiff("html", + cl::desc("Output a side-by-side diff in HTML."), + cl::init(false), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -105,6 +109,161 @@ static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); } +static const char HtmlDiffHeader[] = R"( + + + + +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; +} + + + +highlightStack = [] +function clearHighlight() { + while (highlightStack.length) { +let [l, r] = highlightStack.pop() +document.getElementById(l).style.backgroundColor = 'white' +document.getElementById(r).style.backgroundColor = 'white' + } +} +function highlight(event) { + id = event.target['id'] + doHighlight(id) +} +function doHighlight(id) { + clearHighlight() + source = document.getElementById(id) + if (!source.attributes['tid']) +return + tid = source.attributes['tid'].value + target = document.getElementById(tid) + if (!target || source.parentElement && source.parentElement.classList.contains('code')) +return + source.style.backgroundColor = target.style.backgroundColor = 'lightgrey' + highlightStack.push([id, tid]) + source.scrollIntoView() + target.scrollIntoView() + location.hash = '#' + id +} +function scrollToBoth() { + doHighlight(location.hash.substr(1)) +} +window.onload = scrollToBoth + + + +)"; + +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"; + } +} + +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 << ""; + + 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 << ""; + return Offset; +} + static void printJsonString(raw_ostream &OS, const StringRef Str) { for (char C : Str) { switch (C) { @@ -269,6 +428,19 @@ diff::SyntaxTree DstTree(Dst->getASTContext()); diff::ASTDiff Diff(SrcTree, DstTree, Options); + if (HtmlDiff) { +llvm::outs() << HtmlDiffHeader << ""; +llvm::outs() << ""; +printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0); +llvm::outs() << ""; +llvm::outs() << ""; +printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRo
[PATCH] D36176: [clang-diff] Fix some errors and inconsistencies
johannes updated this revision to Diff 109536. johannes edited the summary of this revision. johannes added a comment. update commit message https://reviews.llvm.org/D36176 Files: include/clang/Tooling/ASTDiff/ASTDiff.h include/clang/Tooling/ASTDiff/ASTDiffInternal.h lib/Tooling/ASTDiff/ASTDiff.cpp Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -27,6 +27,7 @@ namespace clang { namespace diff { +namespace { /// Maps nodes of the left tree to ones on the right, and vice versa. class Mapping { public: @@ -76,6 +77,7 @@ private: std::unique_ptr[]> SrcToDst, DstToSrc; }; +} // end anonymous namespace class ASTDiff::Impl { public: @@ -110,10 +112,6 @@ // Returns false if the nodes must not be mached. bool isMatchingPossible(NodeId Id1, NodeId Id2) const; - // Adds all corresponding subtrees of the two nodes to the mapping. - // The two nodes must be identical. - void addIsomorphicSubTrees(Mapping &M, NodeId Id1, NodeId Id2) const; - // Uses an optimal albeit slow algorithm to compute a mapping between two // subtrees, but only if both have fewer nodes than MaxSize. void addOptimalMapping(Mapping &M, NodeId Id1, NodeId Id2) const; @@ -254,7 +252,10 @@ Parent = PreviousParent; --Depth; Node &N = Tree.getMutableNode(MyId); -N.RightMostDescendant = Id; +N.RightMostDescendant = Id - 1; +assert(N.RightMostDescendant >= 0 && + N.RightMostDescendant < Tree.getSize() && + "Rightmost descendant must be a valid tree node."); if (N.isLeaf()) Tree.Leaves.push_back(MyId); N.Height = 1; @@ -358,9 +359,7 @@ } bool SyntaxTree::Impl::isInSubtree(NodeId Id, NodeId SubtreeRoot) const { - NodeId Lower = SubtreeRoot; - NodeId Upper = getNode(SubtreeRoot).RightMostDescendant; - return Id >= Lower && Id <= Upper; + return Id >= SubtreeRoot && Id <= getNode(SubtreeRoot).RightMostDescendant; } std::string SyntaxTree::Impl::getNodeValue(NodeId Id) const { @@ -625,12 +624,11 @@ } private: - /// Simple cost model for edit actions. + /// We use a simple cost model for edit actions, which seems good enough. + /// Simple cost model for edit actions. This seems to make the matching + /// algorithm perform reasonably well. /// The values range between 0 and 1, or infinity if this edit action should /// always be avoided. - - /// These costs could be modified to better model the estimated cost of / - /// inserting / deleting the current node. static constexpr double DeletionCost = 1; static constexpr double InsertionCost = 1; @@ -687,6 +685,7 @@ }; } // end anonymous namespace +namespace { // Priority queue for nodes, sorted descendingly by their height. class PriorityList { const SyntaxTree::Impl &Tree; @@ -723,6 +722,7 @@ push(Child); } }; +} // end anonymous namespace bool ASTDiff::Impl::identical(NodeId Id1, NodeId Id2) const { const Node &N1 = T1.getNode(Id1); @@ -759,16 +759,6 @@ T2.getNode(Id2).ASTNode); } -void ASTDiff::Impl::addIsomorphicSubTrees(Mapping &M, NodeId Id1, - NodeId Id2) const { - assert(identical(Id1, Id2) && "Can only be called on identical subtrees."); - M.link(Id1, Id2); - const Node &N1 = T1.getNode(Id1); - const Node &N2 = T2.getNode(Id2); - for (size_t Id = 0, E = N1.Children.size(); Id < E; ++Id) -addIsomorphicSubTrees(M, N1.Children[Id], N2.Children[Id]); -} - void ASTDiff::Impl::addOptimalMapping(Mapping &M, NodeId Id1, NodeId Id2) const { if (std::max(T1.getNumberOfDescendants(Id1), @@ -805,7 +795,7 @@ if (M.hasDst(Id2)) continue; double Similarity = getSimilarity(M, Id1, Id2); -if (Similarity > HighestSimilarity) { +if (Similarity >= Options.MinSimilarity && Similarity > HighestSimilarity) { HighestSimilarity = Similarity; Candidate = Id2; } @@ -816,7 +806,8 @@ void ASTDiff::Impl::matchBottomUp(Mapping &M) const { std::vector Postorder = getSubtreePostorder(T1, T1.getRootId()); for (NodeId Id1 : Postorder) { -if (Id1 == T1.getRootId()) { +if (Id1 == T1.getRootId() && !M.hasSrc(T1.getRootId()) && +!M.hasDst(T2.getRootId())) { if (isMatchingPossible(T1.getRootId(), T2.getRootId())) { M.link(T1.getRootId(), T2.getRootId()); addOptimalMapping(M, T1.getRootId(), T2.getRootId()); @@ -831,11 +822,10 @@ if (Matched || !MatchedChildren) continue; NodeId Id2 = findCandidate(M, Id1); -if (Id2.isInvalid() || !canBeAddedToMapping(M, Id1, Id2) || -getSimilarity(M, Id1, Id2) < Options.MinSimilarity) - continue; -M.link(Id1, Id2); -addOptimalMapping(M, Id1, Id2); +if (Id2.isValid() && canBeAddedToMapping(M, Id1, Id2)) { + M.link(Id1, Id2); + addOptim
[PATCH] D36179: [clang-diff] Move printing of matches and changes to clang-diff
johannes updated this revision to Diff 110552. johannes added a comment. add test for 'Move' and 'Update and Move' in output https://reviews.llvm.org/D36179 Files: include/clang/Tooling/ASTDiff/ASTDiff.h include/clang/Tooling/ASTDiff/ASTDiffInternal.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-basic.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -129,7 +129,7 @@ auto Offsets = Tree.getSourceRangeOffsets(N); OS << R"(,"begin":)" << Offsets.first; OS << R"(,"end":)" << Offsets.second; - std::string Value = Tree.getNodeValue(N.ASTNode); + std::string Value = Tree.getNodeValue(N); if (!Value.empty()) { OS << R"(,"value":")"; printJsonString(OS, Value); @@ -153,6 +153,52 @@ 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 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.ChangeKind) { + 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.ChangeKind == diff::Insert) + OS << "Insert"; +else if (DstNode.ChangeKind == diff::Move) + OS << "Move"; +else if (DstNode.ChangeKind == 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 CommonCompilations = @@ -199,11 +245,26 @@ 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); + diff::ASTDiff Diff(SrcTree, DstTree, Options); + + for (diff::NodeId Dst : DstTree) { +diff::NodeId Src = Diff.getMapped(DstTree, Dst); +if (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; } Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -31,6 +31,10 @@ int id(int i) { return i; } }; } + +void m() { int x = 0 + 0 + 0; } +int um = 1 + 2 + 3; + #else // CHECK: Match TranslationUnitDecl{{.*}} to TranslationUnitDecl // CHECK: Match NamespaceDecl: src{{.*}} to NamespaceDecl: dst @@ -54,8 +58,8 @@ typedef unsigned nat; // CHECK: Match VarDecl: p(int){{.*}} to VarDecl: prod(double) -// CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * // CHECK: Update VarDecl: p(int){{.*}} to prod(double) +// CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * double prod = 1 * 2 * 10; // CHECK: Update DeclRefExpr int squared = prod * prod; @@ -70,9 +74,15 @@ return "foo"; return 0; } - // CHECK: Delete AccessSpecDecl: public - X(){}; - // CHECK: Delete CXXMethodDecl + X(){} }; } + +// CHECK: Move DeclStmt{{.*}} into CompoundStmt +void m() { { int x = 0 + 0 + 0; } } +// CHECK: Update and Move IntegerLiteral: 7{{.*}} into BinaryOperator: +({{.*}}) at 1 +int um = 1 + 7; #endif + +// CHECK: Delete AccessSpecDecl: public +// CHECK: Delete CXXMethodDecl Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -82,26 +82,23 @@ class ASTDiff::Impl {
[PATCH] D36182: [clang-diff] Add HTML side-by-side diff output
johannes updated this revision to Diff 110553. johannes edited the summary of this revision. johannes added a comment. change tests https://reviews.llvm.org/D36182 Files: test/Tooling/Inputs/clang-diff-basic-src.cpp test/Tooling/clang-diff-basic.cpp test/Tooling/clang-diff-html.test tools/clang-diff/CMakeLists.txt tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -37,6 +37,10 @@ PrintMatches("dump-matches", cl::desc("Print the matched nodes."), cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt HtmlDiff("html", + cl::desc("Output a side-by-side diff in HTML."), + cl::init(false), cl::cat(ClangDiffCategory)); + static cl::opt SourcePath(cl::Positional, cl::desc(""), cl::Required, cl::cat(ClangDiffCategory)); @@ -105,6 +109,161 @@ static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); } +static const char HtmlDiffHeader[] = R"( + + + + +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; +} + + + +highlightStack = [] +function clearHighlight() { + while (highlightStack.length) { +let [l, r] = highlightStack.pop() +document.getElementById(l).style.backgroundColor = 'white' +document.getElementById(r).style.backgroundColor = 'white' + } +} +function highlight(event) { + id = event.target['id'] + doHighlight(id) +} +function doHighlight(id) { + clearHighlight() + source = document.getElementById(id) + if (!source.attributes['tid']) +return + tid = source.attributes['tid'].value + target = document.getElementById(tid) + if (!target || source.parentElement && source.parentElement.classList.contains('code')) +return + source.style.backgroundColor = target.style.backgroundColor = 'lightgrey' + highlightStack.push([id, tid]) + source.scrollIntoView() + target.scrollIntoView() + location.hash = '#' + id +} +function scrollToBoth() { + doHighlight(location.hash.substr(1)) +} +window.onload = scrollToBoth + + + +)"; + +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"; + } +} + +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 << ""; + + 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 << ""; + return Offset; +} + static void printJsonString(raw_ostream &OS, const StringRef Str) { for (char C : Str) { switch (C) { @@ -269,6 +428,19 @@ diff::SyntaxTree DstTree(Dst->getASTContext()); diff::ASTDiff Diff(SrcTree, DstTree, Options); + if (HtmlDiff) { +llvm::outs() << HtmlDiffHeader << ""; +llvm::outs() << ""; +printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0); +llvm::outs() << ""; +llvm::outs() << ""; +printHtmlForNode(llvm::outs(), Dif
[PATCH] D36183: [clang-diff] Simplify mapping
johannes updated this revision to Diff 110554. johannes added a comment. remove unused https://reviews.llvm.org/D36183 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/Inputs/clang-diff-basic-src.cpp test/Tooling/clang-diff-basic.cpp Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -42,10 +42,16 @@ }; } -// CHECK: Move DeclStmt{{.*}} into CompoundStmt +// CHECK: Move CompoundStmt{{.*}} into CompoundStmt void m() { { int x = 0 + 0 + 0; } } // CHECK: Update and Move IntegerLiteral: 7{{.*}} into BinaryOperator: +({{.*}}) at 1 int um = 1 + 7; +namespace { +// match with parents of different type +// CHECK: Match FunctionDecl: f1{{.*}} to FunctionDecl: f1 +void f1() {{ (void) __func__;;; }} +} + // CHECK: Delete AccessSpecDecl: public // CHECK: Delete CXXMethodDecl Index: test/Tooling/Inputs/clang-diff-basic-src.cpp === --- test/Tooling/Inputs/clang-diff-basic-src.cpp +++ test/Tooling/Inputs/clang-diff-basic-src.cpp @@ -28,4 +28,6 @@ } void m() { int x = 0 + 0 + 0; } -int um = 1 + 2 + 3; +int um = 1 * 2 + 3; + +void f1() {{ (void) __func__;;; }} Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -34,48 +34,23 @@ Mapping() = default; Mapping(Mapping &&Other) = default; Mapping &operator=(Mapping &&Other) = default; - Mapping(int Size1, int Size2) { -// Maximum possible size after patching one tree. -int Size = Size1 + Size2; -SrcToDst = llvm::make_unique[]>(Size); -DstToSrc = llvm::make_unique[]>(Size); + + Mapping(size_t Size) { +SrcToDst = llvm::make_unique(Size); +DstToSrc = llvm::make_unique(Size); } void link(NodeId Src, NodeId Dst) { -SrcToDst[Src].push_back(Dst); -DstToSrc[Dst].push_back(Src); - } - - NodeId getDst(NodeId Src) const { -if (hasSrc(Src)) - return SrcToDst[Src][0]; -return NodeId(); - } - NodeId getSrc(NodeId Dst) const { -if (hasDst(Dst)) - return DstToSrc[Dst][0]; -return NodeId(); - } - const SmallVector &getAllDsts(NodeId Src) const { -return SrcToDst[Src]; - } - const SmallVector &getAllSrcs(NodeId Dst) const { -return DstToSrc[Dst]; - } - bool hasSrc(NodeId Src) const { return !SrcToDst[Src].empty(); } - bool hasDst(NodeId Dst) const { return !DstToSrc[Dst].empty(); } - bool hasSrcDst(NodeId Src, NodeId Dst) const { -for (NodeId DstId : SrcToDst[Src]) - if (DstId == Dst) -return true; -for (NodeId SrcId : DstToSrc[Dst]) - if (SrcId == Src) -return true; -return false; +SrcToDst[Src] = Dst, DstToSrc[Dst] = Src; } + NodeId getDst(NodeId Src) const { return SrcToDst[Src]; } + NodeId getSrc(NodeId Dst) const { return DstToSrc[Dst]; } + bool hasSrc(NodeId Src) const { return getDst(Src).isValid(); } + bool hasDst(NodeId Dst) const { return getSrc(Dst).isValid(); } + private: - std::unique_ptr[]> SrcToDst, DstToSrc; + std::unique_ptr SrcToDst, DstToSrc; }; } // end anonymous namespace @@ -104,8 +79,6 @@ // Returns true if the two subtrees are identical. bool identical(NodeId Id1, NodeId Id2) const; - bool canBeAddedToMapping(const Mapping &M, NodeId Id1, NodeId Id2) const; - // Returns false if the nodes must not be mached. bool isMatchingPossible(NodeId Id1, NodeId Id2) const; @@ -711,23 +684,6 @@ return true; } -bool ASTDiff::Impl::canBeAddedToMapping(const Mapping &M, NodeId Id1, -NodeId Id2) const { - assert(isMatchingPossible(Id1, Id2) && - "Matching must be possible in the first place."); - if (M.hasSrcDst(Id1, Id2)) -return false; - if (Options.EnableMatchingWithUnmatchableParents) -return true; - const Node &N1 = T1.getNode(Id1); - const Node &N2 = T2.getNode(Id2); - NodeId P1 = N1.Parent; - NodeId P2 = N2.Parent; - // Only allow matching if parents can be matched. - return (P1.isInvalid() && P2.isInvalid()) || - (P1.isValid() && P2.isValid() && isMatchingPossible(P1, P2)); -} - bool ASTDiff::Impl::isMatchingPossible(NodeId Id1, NodeId Id2) const { return Options.isMatchingAllowed(T1.getNode(Id1), T2.getNode(Id2)); } @@ -750,7 +706,7 @@ for (const auto Tuple : R) { NodeId Src = Tuple.first; NodeId Dst = Tuple.second; -if (canBeAddedToMapping(M, Src, Dst)) +if (!M.hasSrc(Src) && !M.hasDst(Dst)) M.link(Src, Dst); } } @@ -803,7 +759,7 @@ if (Matched || !MatchedChildren) continue; NodeId Id2 = findCandidate(M, Id1); -if (Id2.isValid() && canBeAddedToMapping(M, Id1, Id2)) { +if (Id2.isValid()) { M.link(Id1, Id2); addOptimalMapping(M, Id1, Id2); } @@ -814,7 +770,7 @@
[PATCH] D36184: [clang-diff] Filter AST nodes
johannes updated this revision to Diff 110555. johannes added a comment. refactor isNodeExcluded https://reviews.llvm.org/D36184 Files: lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-ast.cpp test/Tooling/clang-diff-json.cpp Index: test/Tooling/clang-diff-json.cpp === --- test/Tooling/clang-diff-json.cpp +++ test/Tooling/clang-diff-json.cpp @@ -3,9 +3,9 @@ // RUN: | FileCheck %s // CHECK: "begin": 299, -// CHECK: "type": "CXXRecordDecl", // CHECK: "type": "FieldDecl", // CHECK: "end": 319, +// CHECK: "type": "CXXRecordDecl", class A { int x; }; Index: test/Tooling/clang-diff-ast.cpp === --- test/Tooling/clang-diff-ast.cpp +++ test/Tooling/clang-diff-ast.cpp @@ -12,7 +12,8 @@ // CHECK: IntegerLiteral: 1 auto i = 1; // CHECK: CallExpr( - // CHECK: DeclRefExpr: f( + // CHECK-NOT: ImplicitCastExpr + // CHECK-NEXT: DeclRefExpr: f( f(); // CHECK: BinaryOperator: =( i = i; @@ -37,6 +38,7 @@ if (i == 0) // CHECK: StringLiteral: foo( return "foo"; +// CHECK-NOT: ImplicitCastExpr return 0; } @@ -48,3 +50,22 @@ int x = m; } }; + +#define M (void)1 +#define MA(a, b) (void)a, b +// CHECK: FunctionDecl +// CHECK-NEXT: CompoundStmt +void macros() { + M; + MA(1, 2); +} +// CHECK-NEXT: NamespaceDecl + +#ifndef GUARD +#define GUARD +namespace world { +// nodes from other files are excluded +// CHECK-NOT {{.}} +#include "clang-diff-ast.cpp" +} +#endif Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -68,7 +68,8 @@ // Compute ChangeKind for each node based on similarity. void computeChangeKinds(Mapping &M); - NodeId getMapped(const std::unique_ptr &Tree, NodeId Id) const { + NodeId getMapped(const std::unique_ptr &Tree, + NodeId Id) const { if (&*Tree == &T1) return TheMapping.getDst(Id); assert(&*Tree == &T2 && "Invalid tree."); @@ -157,12 +158,23 @@ void setLeftMostDescendants(); }; +static bool isSpecializedNodeExcluded(const Decl *D) { return D->isImplicit(); } +static bool isSpecializedNodeExcluded(const Stmt *S) { return false; } + template static bool isNodeExcluded(const SourceManager &SrcMgr, T *N) { if (!N) return true; SourceLocation SLoc = N->getLocStart(); - return SLoc.isValid() && SrcMgr.isInSystemHeader(SLoc); + if (SLoc.isValid()) { +// Ignore everything from other files. +if (!SrcMgr.isInMainFile(SLoc)) + return true; +// Ignore macros. +if (SLoc != SrcMgr.getSpellingLoc(SLoc)) + return true; + } + return isSpecializedNodeExcluded(N); } namespace { @@ -179,6 +191,8 @@ return true; } bool TraverseStmt(Stmt *S) { +if (S) + S = S->IgnoreImplicit(); if (isNodeExcluded(Tree.AST.getSourceManager(), S)) return true; ++Count; @@ -241,6 +255,8 @@ return true; } bool TraverseStmt(Stmt *S) { +if (S) + S = S->IgnoreImplicit(); if (isNodeExcluded(Tree.AST.getSourceManager(), S)) return true; auto SavedState = PreTraverse(S); @@ -900,7 +916,8 @@ return TreeImpl->findPositionInParent(Id); } -std::pair SyntaxTree::getSourceRangeOffsets(const Node &N) const { +std::pair +SyntaxTree::getSourceRangeOffsets(const Node &N) const { const SourceManager &SrcMgr = TreeImpl->AST.getSourceManager(); SourceRange Range = N.ASTNode.getSourceRange(); SourceLocation BeginLoc = Range.getBegin(); ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36187: [clang-diff] Use the relative name for NamedDecls
johannes updated this revision to Diff 110557. johannes added a comment. substr https://reviews.llvm.org/D36187 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-ast.cpp test/Tooling/clang-diff-basic.cpp test/Tooling/clang-diff-html.test test/Tooling/clang-diff-topdown.cpp Index: test/Tooling/clang-diff-topdown.cpp === --- test/Tooling/clang-diff-topdown.cpp +++ test/Tooling/clang-diff-topdown.cpp @@ -27,8 +27,19 @@ {{;;}} } +int x; + +namespace src { + int x; + int x1 = x + 1; + int x2 = ::x + 1; +} + +class A { int x = 1 + 1; void f() { int x1 = x; } }; + #else + void f1() { {{;}} @@ -45,4 +56,28 @@ ; } +int x; + +namespace dst { + int x; + // CHECK: Match DeclRefExpr: :x(17) to DeclRefExpr: :x(22) + int x1 = x + 1; + // CHECK: Match DeclRefExpr: x(21) to DeclRefExpr: x(26) + int x2 = ::x + 1; +} + +class B { + // Only the class name changed; it is not included in the field value, + // therefore there is no update. + // CHECK: Match FieldDecl: :x(int)(24) to FieldDecl: :x(int)(29) + // CHECK-NOT: Update FieldDecl: :x(int)(24) + int x = 1+1; + void f() { +// CHECK: Match MemberExpr: :x(32) to MemberExpr: :x(37) +// CHECK-NOT: Update MemberExpr: :x(32) +int x1 = B::x; + } + +}; + #endif Index: test/Tooling/clang-diff-html.test === --- test/Tooling/clang-diff-html.test +++ test/Tooling/clang-diff-html.test @@ -11,12 +11,12 @@ // match, move // CHECK: void foo() +// CHECK-NEXT: :foo(void ())' class='m'>void foo() // match // CHECK: void main() +// CHECK-NEXT: :main(void ())'>void main() // deletion // CHECK: (Context)) +ContextPrefix = Namespace->getQualifiedNameAsString(); + else if (auto *Tag = dyn_cast(Context)) +ContextPrefix = Tag->getQualifiedNameAsString(); + std::string Val = ND->getQualifiedNameAsString(); + // Strip the qualifier, if Val refers to somthing in the current scope. + // But leave one leading ':' in place, so that we know that this is a + // relative path. + if (!ContextPrefix.empty() && + StringRef(Val).startswith(ContextPrefix)) +Val = Val.substr(ContextPrefix.size() + 1); + return Val; +} + +static std::string getRelativeName(const NamedDecl *ND) { + return getRelativeName(ND, ND->getDeclContext()); +} + +static const DeclContext *getEnclosingDeclContext(ASTContext &AST, + const Stmt *S) { + while (S) { +const auto &Parents = AST.getParents(*S); +if (Parents.empty()) + return nullptr; +const auto &P = Parents[0]; +if (const auto *D = P.get()) + return D->getDeclContext(); +S = P.get(); + } + llvm_unreachable("Could not find Decl ancestor."); +} + std::string SyntaxTree::Impl::getNodeValue(NodeId Id) const { return getNodeValue(getNode(Id)); } @@ -384,8 +419,7 @@ TypePP.AnonymousTagLocations = false; if (auto *V = dyn_cast(D)) { -Value += V->getQualifiedNameAsString() + "(" + - V->getType().getAsString(TypePP) + ")"; +Value += getRelativeName(V) + "(" + V->getType().getAsString(TypePP) + ")"; if (auto *C = dyn_cast(D)) { for (auto *Init : C->inits()) { if (!Init->isWritten()) @@ -398,14 +432,14 @@ Value += C->getNameAsString() + ","; } else { assert(Init->isAnyMemberInitializer()); - Value += Init->getMember()->getQualifiedNameAsString() + ","; + Value += getRelativeName(Init->getMember()) + ","; } } } return Value; } if (auto *N = dyn_cast(D)) -Value += N->getQualifiedNameAsString() + ";"; +Value += getRelativeName(N) + ";"; if (auto *T = dyn_cast(D)) return Value + T->getUnderlyingType().getAsString(TypePP) + ";"; if (auto *T = dyn_cast(D)) @@ -429,7 +463,7 @@ if (auto *B = dyn_cast(S)) return B->getOpcodeStr(); if (auto *M = dyn_cast(S)) -return M->getMemberDecl()->getQualifiedNameAsString(); +return getRelativeName(M->getMemberDecl()); if (auto *I = dyn_cast(S)) { SmallString<256> Str; I->getValue().toString(Str, /*Radix=*/10, /*Signed=*/false); @@ -441,7 +475,7 @@ return Str.str(); } if (auto *D = dyn_cast(S)) -return D->getDecl()->getQualifiedNameAsString(); +return getRelativeName(D->getDecl(), getEnclosingDeclContext(AST, S)); if (auto *String = dyn_cast(S)) return String->getString(); if (auto *B = dyn_cast(S)) @@ -946,7 +980,7 @@ return DiffImpl->getMapped(SourceTree.TreeImpl, Id); } -SyntaxTree::SyntaxTree(const ASTContext &AST) +SyntaxTree::SyntaxTree(ASTContext &AST) : TreeImpl(llvm::make_unique( this, AST.getTranslationUnitDecl(), AST)) {} Index: include/clang/Tooling/ASTDiff/ASTDiff.h === --- include/clang/Tooling/
[PATCH] D36179: [clang-diff] Move printing of matches and changes to clang-diff
johannes updated this revision to Diff 110608. johannes added a comment. initialize Node::Shift https://reviews.llvm.org/D36179 Files: include/clang/Tooling/ASTDiff/ASTDiff.h include/clang/Tooling/ASTDiff/ASTDiffInternal.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-basic.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -129,7 +129,7 @@ auto Offsets = Tree.getSourceRangeOffsets(N); OS << R"(,"begin":)" << Offsets.first; OS << R"(,"end":)" << Offsets.second; - std::string Value = Tree.getNodeValue(N.ASTNode); + std::string Value = Tree.getNodeValue(N); if (!Value.empty()) { OS << R"(,"value":")"; printJsonString(OS, Value); @@ -153,6 +153,52 @@ 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 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.ChangeKind) { + 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.ChangeKind == diff::Insert) + OS << "Insert"; +else if (DstNode.ChangeKind == diff::Move) + OS << "Move"; +else if (DstNode.ChangeKind == 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 CommonCompilations = @@ -199,11 +245,26 @@ 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); + diff::ASTDiff Diff(SrcTree, DstTree, Options); + + for (diff::NodeId Dst : DstTree) { +diff::NodeId Src = Diff.getMapped(DstTree, Dst); +if (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; } Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -31,6 +31,10 @@ int id(int i) { return i; } }; } + +void m() { int x = 0 + 0 + 0; } +int um = 1 + 2 + 3; + #else // CHECK: Match TranslationUnitDecl{{.*}} to TranslationUnitDecl // CHECK: Match NamespaceDecl: src{{.*}} to NamespaceDecl: dst @@ -54,8 +58,8 @@ typedef unsigned nat; // CHECK: Match VarDecl: p(int){{.*}} to VarDecl: prod(double) -// CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * // CHECK: Update VarDecl: p(int){{.*}} to prod(double) +// CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * double prod = 1 * 2 * 10; // CHECK: Update DeclRefExpr int squared = prod * prod; @@ -70,9 +74,15 @@ return "foo"; return 0; } - // CHECK: Delete AccessSpecDecl: public - X(){}; - // CHECK: Delete CXXMethodDecl + X(){} }; } + +// CHECK: Move DeclStmt{{.*}} into CompoundStmt +void m() { { int x = 0 + 0 + 0; } } +// CHECK: Update and Move IntegerLiteral: 7{{.*}} into BinaryOperator: +({{.*}}) at 1 +int um = 1 + 7; #endif + +// CHECK: Delete AccessSpecDecl: public +// CHECK: Delete CXXMethodDecl Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -82,26 +82,23 @@ class ASTDiff::Impl { public: SyntaxTree::Impl
[PATCH] D36184: [clang-diff] Filter AST nodes
johannes added inline comments. Comment at: test/Tooling/clang-diff-ast.cpp:68 +// nodes from other files are excluded +// CHECK-NOT {{.}} +#include "clang-diff-ast.cpp" arphaman wrote: > 1) Missing ':' > 2) What exactly does this regex accomplish? Right now it will match any > character which doesn't look correct I want to assert that there is no output here, because other files are excluded, there may be a better way.. https://reviews.llvm.org/D36184 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36184: [clang-diff] Filter AST nodes
johannes updated this revision to Diff 110687. johannes added a comment. clarify test/Tooling/clang-diff-ast.cpp https://reviews.llvm.org/D36184 Files: lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-ast.cpp test/Tooling/clang-diff-json.cpp Index: test/Tooling/clang-diff-json.cpp === --- test/Tooling/clang-diff-json.cpp +++ test/Tooling/clang-diff-json.cpp @@ -3,9 +3,9 @@ // RUN: | FileCheck %s // CHECK: "begin": 299, -// CHECK: "type": "CXXRecordDecl", // CHECK: "type": "FieldDecl", // CHECK: "end": 319, +// CHECK: "type": "CXXRecordDecl", class A { int x; }; Index: test/Tooling/clang-diff-ast.cpp === --- test/Tooling/clang-diff-ast.cpp +++ test/Tooling/clang-diff-ast.cpp @@ -12,7 +12,8 @@ // CHECK: IntegerLiteral: 1 auto i = 1; // CHECK: CallExpr( - // CHECK: DeclRefExpr: f( + // CHECK-NOT: ImplicitCastExpr + // CHECK-NEXT: DeclRefExpr: f( f(); // CHECK: BinaryOperator: =( i = i; @@ -37,6 +38,7 @@ if (i == 0) // CHECK: StringLiteral: foo( return "foo"; +// CHECK-NOT: ImplicitCastExpr return 0; } @@ -48,3 +50,23 @@ int x = m; } }; + +#define M (void)1 +#define MA(a, b) (void)a, b +// CHECK: FunctionDecl +// CHECK-NEXT: CompoundStmt +void macros() { + M; + MA(1, 2); +} +// CHECK-NEXT: NamespaceDecl + +#ifndef GUARD +#define GUARD +namespace world { +// nodes from other files are excluded, there should be no output from this +// point on +// CHECK-NOT: {{.}} +#include "clang-diff-ast.cpp" +} +#endif Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -68,7 +68,8 @@ // Compute ChangeKind for each node based on similarity. void computeChangeKinds(Mapping &M); - NodeId getMapped(const std::unique_ptr &Tree, NodeId Id) const { + NodeId getMapped(const std::unique_ptr &Tree, + NodeId Id) const { if (&*Tree == &T1) return TheMapping.getDst(Id); assert(&*Tree == &T2 && "Invalid tree."); @@ -157,12 +158,23 @@ void setLeftMostDescendants(); }; +static bool isSpecializedNodeExcluded(const Decl *D) { return D->isImplicit(); } +static bool isSpecializedNodeExcluded(const Stmt *S) { return false; } + template static bool isNodeExcluded(const SourceManager &SrcMgr, T *N) { if (!N) return true; SourceLocation SLoc = N->getLocStart(); - return SLoc.isValid() && SrcMgr.isInSystemHeader(SLoc); + if (SLoc.isValid()) { +// Ignore everything from other files. +if (!SrcMgr.isInMainFile(SLoc)) + return true; +// Ignore macros. +if (SLoc != SrcMgr.getSpellingLoc(SLoc)) + return true; + } + return isSpecializedNodeExcluded(N); } namespace { @@ -179,6 +191,8 @@ return true; } bool TraverseStmt(Stmt *S) { +if (S) + S = S->IgnoreImplicit(); if (isNodeExcluded(Tree.AST.getSourceManager(), S)) return true; ++Count; @@ -241,6 +255,8 @@ return true; } bool TraverseStmt(Stmt *S) { +if (S) + S = S->IgnoreImplicit(); if (isNodeExcluded(Tree.AST.getSourceManager(), S)) return true; auto SavedState = PreTraverse(S); @@ -900,7 +916,8 @@ return TreeImpl->findPositionInParent(Id); } -std::pair SyntaxTree::getSourceRangeOffsets(const Node &N) const { +std::pair +SyntaxTree::getSourceRangeOffsets(const Node &N) const { const SourceManager &SrcMgr = TreeImpl->AST.getSourceManager(); SourceRange Range = N.ASTNode.getSourceRange(); SourceLocation BeginLoc = Range.getBegin(); ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36185: [clang-diff] Fix similarity computation
johannes updated this revision to Diff 110688. johannes added a comment. newline in error message https://reviews.llvm.org/D36185 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-bottomup.cpp test/Tooling/clang-diff-opt.cpp test/Tooling/clang-diff-topdown.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -50,6 +50,11 @@ cl::Optional, cl::cat(ClangDiffCategory)); +static cl::opt StopAfter("stop-after", + cl::desc(""), + cl::Optional, cl::init(""), + cl::cat(ClangDiffCategory)); + static cl::opt MaxSize("s", cl::desc(""), cl::Optional, cl::init(-1), cl::cat(ClangDiffCategory)); @@ -424,6 +429,14 @@ 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); Index: test/Tooling/clang-diff-topdown.cpp === --- /dev/null +++ test/Tooling/clang-diff-topdown.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -stop-after=topdown %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the top-down matching of identical subtrees only. + +#ifndef DEST + +void f1() +{ + // Match some subtree of height greater than 2. + // CHECK: Match CompoundStmt(3) to CompoundStmt(3) + // CHECK: Match CompoundStmt(4) to CompoundStmt(4) + // CHECK: Match NullStmt(5) to NullStmt(5) + {{;}} + + // Don't match subtrees that are smaller. + // CHECK-NOT: Match CompoundStmt(6) + // CHECK-NOT: Match NullStmt(7) + {;} + + // Greedy approach - use the first matching subtree when there are multiple + // identical subtrees. + // CHECK: Match CompoundStmt(8) to CompoundStmt(8) + // CHECK: Match CompoundStmt(9) to CompoundStmt(9) + // CHECK: Match NullStmt(10) to NullStmt(10) + {{;;}} +} + +#else + +void f1() { + + {{;}} + + {;} + + {{;;}} + // CHECK-NOT: Match {{.*}} to CompoundStmt(11) + // CHECK-NOT: Match {{.*}} to CompoundStmt(12) + // CHECK-NOT: Match {{.*}} to NullStmt(13) + {{;;}} + + // CHECK-NOT: Match {{.*}} to NullStmt(14) + ; +} + +#endif Index: test/Tooling/clang-diff-opt.cpp === --- /dev/null +++ test/Tooling/clang-diff-opt.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -s=10 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the behaviour of the matching according to the optimal tree edit +// distance, implemented with Zhang and Shasha's algorithm. +// Just for testing we use a tiny value of 10 for maxsize. Subtrees bigger than +// this size will not be processed by the optimal algorithm. + +#ifndef DEST + +void f1() { {;} {{;}} } + +void f2() { {;} {{;}} } + +void f3() { {;} {{;;}} } + +#else + +void f1() { +// Jaccard similarity = 3 / (5 + 4 - 3) = 3 / 6 >= 0.5 +// The optimal matching algorithm should move the ; into the outer block +// CHECK: Match CompoundStmt(2) to CompoundStmt(2) +// CHECK-NOT: Match CompoundStmt(3) +// CHECK-NEXT: Match NullStmt(4) to NullStmt(3) + ; {{;}} +} + +void f2() { + // Jaccard similarity = 7 / (10 + 10 - 7) >= 0.5 + // As none of the subtrees is bigger than 10 nodes, the optimal algorithm + // will be run. + // CHECK: Match NullStmt(11) to NullStmt(9) + ;; {{;}} +} + +void f3() { + // Jaccard similarity = 8 / (11 + 11 - 8) >= 0.5 + // As the subtrees are bigger than 10 nodes, the optimal algorithm will not + // be run. + // CHECK: Delete NullStmt(22) + ;; {{;;}} +} +#endif Index: test/Tooling/clang-diff-bottomup.cpp === --- /dev/null +++ test/Tooling/clang-diff-bottomup.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -s=0 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the bottom-up matching, with maxsize set to 0, so that the optimal matching will never be applied. + +#ifndef DEST + +void f1() { ; {{;}} } +void f2() { ;; {{;}} } + +#else + +// Jaccard similarity threshold is 0.5. + +void f1() { +// CompoundStm
[PATCH] D36664: [analyzer] Make StmtDataCollector customizable
johannes created this revision. Herald added subscribers: xazax.hun, mgorny. Herald added 1 blocking reviewer(s): teemperor. This moves the data collection macro calls for Stmt nodes to lib/AST/StmtDataCollectors.inc Users can subclass ConstStmtVisitor and include StmtDataCollectors.inc to define visitor methods for each Stmt subclass. This makes it also possible to customize the visit methods as exemplified in CloneDetection.cpp. Move helper methods for data collection to a new module, AST/DataCollection. Add data collection for DeclRefExpr, MemberExpr and some literals https://reviews.llvm.org/D36664 Files: include/clang/AST/DataCollection.h include/clang/Analysis/CloneDetection.h lib/AST/CMakeLists.txt lib/AST/DataCollection.cpp lib/AST/StmtDataCollectors.inc lib/Analysis/CloneDetection.cpp Index: lib/Analysis/CloneDetection.cpp === --- lib/Analysis/CloneDetection.cpp +++ lib/Analysis/CloneDetection.cpp @@ -13,16 +13,12 @@ #include "clang/Analysis/CloneDetection.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/RecursiveASTVisitor.h" -#include "clang/AST/Stmt.h" -#include "clang/Lex/Lexer.h" +#include "clang/AST/DataCollection.h" +#include "clang/AST/DeclTemplate.h" #include "llvm/Support/MD5.h" -#include "llvm/Support/raw_ostream.h" #include "llvm/Support/Path.h" using namespace clang; -using namespace clang::clone_detection; StmtSequence::StmtSequence(const CompoundStmt *Stmt, const Decl *D, unsigned StartIndex, unsigned EndIndex) @@ -91,34 +87,6 @@ return SourceRange(getStartLoc(), getEndLoc()); } -/// Prints the macro name that contains the given SourceLocation into the given -/// raw_string_ostream. -static void printMacroName(llvm::raw_string_ostream &MacroStack, - ASTContext &Context, SourceLocation Loc) { - MacroStack << Lexer::getImmediateMacroName(Loc, Context.getSourceManager(), - Context.getLangOpts()); - - // Add an empty space at the end as a padding to prevent - // that macro names concatenate to the names of other macros. - MacroStack << " "; -} - -std::string clone_detection::getMacroStack(SourceLocation Loc, - ASTContext &Context) { - std::string MacroStack; - llvm::raw_string_ostream MacroStackStream(MacroStack); - SourceManager &SM = Context.getSourceManager(); - - // Iterate over all macros that expanded into the given SourceLocation. - while (Loc.isMacroID()) { -// Add the macro name to the stream. -printMacroName(MacroStackStream, Context, Loc); -Loc = SM.getImmediateMacroCallerLoc(Loc); - } - MacroStackStream.flush(); - return MacroStack; -} - void CloneDetector::analyzeCodeBody(const Decl *D) { assert(D); assert(D->hasBody()); @@ -201,6 +169,59 @@ return false; } +/// Collects the data of a single Stmt. +/// +/// This class defines what a code clone is: If it collects for two statements +/// the same data, then those two statements are considered to be clones of each +/// other. +/// +/// All collected data is forwarded to the given data consumer of the type T. +/// The data consumer class needs to provide a member method with the signature: +/// update(StringRef Str) +template +class CloneTypeIIStmtDataCollector +: public ConstStmtVisitor> { + ASTContext &Context; + /// The data sink to which all data is forwarded. + T &DataConsumer; + + template void addData(Ty Data) { +addDataToConsumer(DataConsumer, Data); + } + +public: + CloneTypeIIStmtDataCollector(const Stmt *S, ASTContext &Context, + T &DataConsumer) + : Context(Context), DataConsumer(DataConsumer) { +this->Visit(S); + } + +// Define a visit method for each class to collect data and subsequently visit +// all parent classes. This uses a template so that custom visit methods by us +// take precedence. +#define DEF_ADD_DATA(CLASS, CODE) \ + template Dummy Visit##CLASS(const CLASS *S) { \ +CODE; \ +ConstStmtVisitor>::Visit##CLASS(S);\ + } + +#include "../AST/StmtDataCollectors.inc" + +// Override the definition for some visitors. +#define SKIP(CLASS)\ + void Visit##CLASS(const CLASS *S) { \ +ConstStmtVisitor>::Visit##CLASS(S);\ + } + SKIP(DeclRefExpr) + SKIP(MemberExpr) + SKIP(IntegerLiteral) + SKIP(FloatingLiteral) + SKIP(StringLiteral) + SKIP(CXXBoolLiteralExpr) + SKIP(CharacterLiteral) +#undef SKIP +}; + static size_t createHash(llvm::MD5 &Hash) { size_t HashCode; @@ -222,7 +243,7 @@ llvm::MD5 Hash; ASTContext &Context = D->getASTContext(); - StmtDataCollector(S, Context, Hash); + CloneTypeIIStmtDataC
[PATCH] D36179: [clang-diff] Move printing of matches and changes to clang-diff
johannes updated this revision to Diff 110945. johannes added a comment. format https://reviews.llvm.org/D36179 Files: include/clang/Tooling/ASTDiff/ASTDiff.h include/clang/Tooling/ASTDiff/ASTDiffInternal.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-basic.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -129,7 +129,7 @@ auto Offsets = Tree.getSourceRangeOffsets(N); OS << R"(,"begin":)" << Offsets.first; OS << R"(,"end":)" << Offsets.second; - std::string Value = Tree.getNodeValue(N.ASTNode); + std::string Value = Tree.getNodeValue(N); if (!Value.empty()) { OS << R"(,"value":")"; printJsonString(OS, Value); @@ -153,6 +153,52 @@ 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 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.ChangeKind) { + 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.ChangeKind == diff::Insert) + OS << "Insert"; +else if (DstNode.ChangeKind == diff::Move) + OS << "Move"; +else if (DstNode.ChangeKind == 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 CommonCompilations = @@ -199,11 +245,26 @@ 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); + diff::ASTDiff Diff(SrcTree, DstTree, Options); + + for (diff::NodeId Dst : DstTree) { +diff::NodeId Src = Diff.getMapped(DstTree, Dst); +if (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; } Index: test/Tooling/clang-diff-basic.cpp === --- test/Tooling/clang-diff-basic.cpp +++ test/Tooling/clang-diff-basic.cpp @@ -31,6 +31,10 @@ int id(int i) { return i; } }; } + +void m() { int x = 0 + 0 + 0; } +int um = 1 + 2 + 3; + #else // CHECK: Match TranslationUnitDecl{{.*}} to TranslationUnitDecl // CHECK: Match NamespaceDecl: src{{.*}} to NamespaceDecl: dst @@ -54,8 +58,8 @@ typedef unsigned nat; // CHECK: Match VarDecl: p(int){{.*}} to VarDecl: prod(double) -// CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * // CHECK: Update VarDecl: p(int){{.*}} to prod(double) +// CHECK: Match BinaryOperator: *{{.*}} to BinaryOperator: * double prod = 1 * 2 * 10; // CHECK: Update DeclRefExpr int squared = prod * prod; @@ -70,9 +74,15 @@ return "foo"; return 0; } - // CHECK: Delete AccessSpecDecl: public - X(){}; - // CHECK: Delete CXXMethodDecl + X(){} }; } + +// CHECK: Move DeclStmt{{.*}} into CompoundStmt +void m() { { int x = 0 + 0 + 0; } } +// CHECK: Update and Move IntegerLiteral: 7{{.*}} into BinaryOperator: +({{.*}}) at 1 +int um = 1 + 7; #endif + +// CHECK: Delete AccessSpecDecl: public +// CHECK: Delete CXXMethodDecl Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -82,26 +82,24 @@ class ASTDiff::Impl { public: SyntaxTree::Impl &T1, &T2; - boo
[PATCH] D36185: [clang-diff] Fix similarity computation
johannes updated this revision to Diff 110951. johannes added a comment. comment getJaccardSimilarity https://reviews.llvm.org/D36185 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-bottomup.cpp test/Tooling/clang-diff-opt.cpp test/Tooling/clang-diff-topdown.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -50,6 +50,11 @@ cl::Optional, cl::cat(ClangDiffCategory)); +static cl::opt StopAfter("stop-after", + cl::desc(""), + cl::Optional, cl::init(""), + cl::cat(ClangDiffCategory)); + static cl::opt MaxSize("s", cl::desc(""), cl::Optional, cl::init(-1), cl::cat(ClangDiffCategory)); @@ -424,6 +429,14 @@ 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); Index: test/Tooling/clang-diff-topdown.cpp === --- /dev/null +++ test/Tooling/clang-diff-topdown.cpp @@ -0,0 +1,48 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -stop-after=topdown %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the top-down matching of identical subtrees only. + +#ifndef DEST + +void f1() +{ + // Match some subtree of height greater than 2. + // CHECK: Match CompoundStmt(3) to CompoundStmt(3) + // CHECK: Match CompoundStmt(4) to CompoundStmt(4) + // CHECK: Match NullStmt(5) to NullStmt(5) + {{;}} + + // Don't match subtrees that are smaller. + // CHECK-NOT: Match CompoundStmt(6) + // CHECK-NOT: Match NullStmt(7) + {;} + + // Greedy approach - use the first matching subtree when there are multiple + // identical subtrees. + // CHECK: Match CompoundStmt(8) to CompoundStmt(8) + // CHECK: Match CompoundStmt(9) to CompoundStmt(9) + // CHECK: Match NullStmt(10) to NullStmt(10) + {{;;}} +} + +#else + +void f1() { + + {{;}} + + {;} + + {{;;}} + // CHECK-NOT: Match {{.*}} to CompoundStmt(11) + // CHECK-NOT: Match {{.*}} to CompoundStmt(12) + // CHECK-NOT: Match {{.*}} to NullStmt(13) + {{;;}} + + // CHECK-NOT: Match {{.*}} to NullStmt(14) + ; +} + +#endif Index: test/Tooling/clang-diff-opt.cpp === --- /dev/null +++ test/Tooling/clang-diff-opt.cpp @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -s=10 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the behaviour of the matching according to the optimal tree edit +// distance, implemented with Zhang and Shasha's algorithm. +// Just for testing we use a tiny value of 10 for maxsize. Subtrees bigger than +// this size will not be processed by the optimal algorithm. + +#ifndef DEST + +void f1() { {;} {{;}} } + +void f2() { {;} {{;}} } + +void f3() { {;} {{;;}} } + +#else + +void f1() { +// Jaccard similarity = 3 / (5 + 4 - 3) = 3 / 6 >= 0.5 +// The optimal matching algorithm should move the ; into the outer block +// CHECK: Match CompoundStmt(2) to CompoundStmt(2) +// CHECK-NOT: Match CompoundStmt(3) +// CHECK-NEXT: Match NullStmt(4) to NullStmt(3) + ; {{;}} +} + +void f2() { + // Jaccard similarity = 7 / (10 + 10 - 7) >= 0.5 + // As none of the subtrees is bigger than 10 nodes, the optimal algorithm + // will be run. + // CHECK: Match NullStmt(11) to NullStmt(9) + ;; {{;}} +} + +void f3() { + // Jaccard similarity = 8 / (11 + 11 - 8) >= 0.5 + // As the subtrees are bigger than 10 nodes, the optimal algorithm will not + // be run. + // CHECK: Delete NullStmt(22) + ;; {{;;}} +} +#endif Index: test/Tooling/clang-diff-bottomup.cpp === --- /dev/null +++ test/Tooling/clang-diff-bottomup.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -s=0 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the bottom-up matching, with maxsize set to 0, so that the optimal matching will never be applied. + +#ifndef DEST + +void f1() { ; {{;}} } +void f2() { ;; {{;}} } + +#else + +// Jaccard similarity threshold is 0.5. + +void f1() { +// Compoun
[PATCH] D36186: [clang-diff] Improve and test getNodeValue
johannes updated this revision to Diff 110952. johannes added a comment. add test for delegating initializer and unwritten initializer https://reviews.llvm.org/D36186 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-ast.cpp test/Tooling/clang-diff-basic.cpp test/Tooling/clang-diff-bottomup.cpp test/Tooling/clang-diff-html.test test/Tooling/clang-diff-opt.cpp test/Tooling/clang-diff-topdown.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -315,6 +315,18 @@ 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]); Index: test/Tooling/clang-diff-topdown.cpp === --- test/Tooling/clang-diff-topdown.cpp +++ test/Tooling/clang-diff-topdown.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -E %s > %t.src.cpp // RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST -// RUN: clang-diff -dump-matches -stop-after=topdown %t.src.cpp %t.dst.cpp -- | FileCheck %s +// RUN: clang-diff -dump-matches -stop-after=topdown %t.src.cpp %t.dst.cpp -- -std=c++11 | FileCheck %s // // Test the top-down matching of identical subtrees only. Index: test/Tooling/clang-diff-opt.cpp === --- test/Tooling/clang-diff-opt.cpp +++ test/Tooling/clang-diff-opt.cpp @@ -25,7 +25,7 @@ // CHECK-NEXT: Match NullStmt(4) to NullStmt(3) ; {{;}} } - + void f2() { // Jaccard similarity = 7 / (10 + 10 - 7) >= 0.5 // As none of the subtrees is bigger than 10 nodes, the optimal algorithm @@ -41,4 +41,5 @@ // CHECK: Delete NullStmt(22) ;; {{;;}} } + #endif Index: test/Tooling/clang-diff-html.test === --- test/Tooling/clang-diff-html.test +++ test/Tooling/clang-diff-html.test @@ -11,12 +11,12 @@ // match, move // CHECK: void foo() +// CHECK-NEXT: src::foo(void ())' class='u m'>void foo() // match // CHECK: void main() +// CHECK-NEXT: src::main(void ())' class='u'>void main() // deletion // CHECK:
[PATCH] D36187: [clang-diff] Use the relative name for NamedDecls
johannes updated this revision to Diff 110962. johannes added a comment. rebase https://reviews.llvm.org/D36187 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-ast.cpp test/Tooling/clang-diff-basic.cpp test/Tooling/clang-diff-html.test test/Tooling/clang-diff-topdown.cpp Index: test/Tooling/clang-diff-topdown.cpp === --- test/Tooling/clang-diff-topdown.cpp +++ test/Tooling/clang-diff-topdown.cpp @@ -27,8 +27,19 @@ {{;;}} } +int x; + +namespace src { + int x; + int x1 = x + 1; + int x2 = ::x + 1; +} + +class A { int x = 1 + 1; void f() { int x1 = x; } }; + #else + void f1() { {{;}} @@ -45,4 +56,28 @@ ; } +int x; + +namespace dst { + int x; + // CHECK: Match DeclRefExpr: :x(17) to DeclRefExpr: :x(22) + int x1 = x + 1; + // CHECK: Match DeclRefExpr: x(21) to DeclRefExpr: x(26) + int x2 = ::x + 1; +} + +class B { + // Only the class name changed; it is not included in the field value, + // therefore there is no update. + // CHECK: Match FieldDecl: :x(int)(24) to FieldDecl: :x(int)(29) + // CHECK-NOT: Update FieldDecl: :x(int)(24) + int x = 1+1; + void f() { +// CHECK: Match MemberExpr: :x(32) to MemberExpr: :x(37) +// CHECK-NOT: Update MemberExpr: :x(32) +int x1 = B::x; + } + +}; + #endif Index: test/Tooling/clang-diff-html.test === --- test/Tooling/clang-diff-html.test +++ test/Tooling/clang-diff-html.test @@ -11,12 +11,12 @@ // match, move // CHECK: void foo() +// CHECK-NEXT: :foo(void ())' class='m'>void foo() // match // CHECK: void main() +// CHECK-NEXT: :main(void ())'>void main() // deletion // CHECK: (Context)) +ContextPrefix = Namespace->getQualifiedNameAsString(); + else if (auto *Tag = dyn_cast(Context)) +ContextPrefix = Tag->getQualifiedNameAsString(); + std::string Val = ND->getQualifiedNameAsString(); + // Strip the qualifier, if Val refers to somthing in the current scope. + // But leave one leading ':' in place, so that we know that this is a + // relative path. + if (!ContextPrefix.empty() && + StringRef(Val).startswith(ContextPrefix)) +Val = Val.substr(ContextPrefix.size() + 1); + return Val; +} + +static std::string getRelativeName(const NamedDecl *ND) { + return getRelativeName(ND, ND->getDeclContext()); +} + +static const DeclContext *getEnclosingDeclContext(ASTContext &AST, + const Stmt *S) { + while (S) { +const auto &Parents = AST.getParents(*S); +if (Parents.empty()) + return nullptr; +const auto &P = Parents[0]; +if (const auto *D = P.get()) + return D->getDeclContext(); +S = P.get(); + } + llvm_unreachable("Could not find Decl ancestor."); +} + std::string SyntaxTree::Impl::getNodeValue(NodeId Id) const { return getNodeValue(getNode(Id)); } @@ -384,8 +419,7 @@ TypePP.AnonymousTagLocations = false; if (auto *V = dyn_cast(D)) { -Value += V->getQualifiedNameAsString() + "(" + - V->getType().getAsString(TypePP) + ")"; +Value += getRelativeName(V) + "(" + V->getType().getAsString(TypePP) + ")"; if (auto *C = dyn_cast(D)) { for (auto *Init : C->inits()) { if (!Init->isWritten()) @@ -397,15 +431,15 @@ Value += C->getNameAsString(); } else { assert(Init->isAnyMemberInitializer()); - Value += Init->getMember()->getQualifiedNameAsString(); + Value += getRelativeName(Init->getMember()); } Value += ","; } } return Value; } if (auto *N = dyn_cast(D)) -Value += N->getQualifiedNameAsString() + ";"; +Value += getRelativeName(N) + ";"; if (auto *T = dyn_cast(D)) return Value + T->getUnderlyingType().getAsString(TypePP) + ";"; if (auto *T = dyn_cast(D)) @@ -429,7 +463,7 @@ if (auto *B = dyn_cast(S)) return B->getOpcodeStr(); if (auto *M = dyn_cast(S)) -return M->getMemberDecl()->getQualifiedNameAsString(); +return getRelativeName(M->getMemberDecl()); if (auto *I = dyn_cast(S)) { SmallString<256> Str; I->getValue().toString(Str, /*Radix=*/10, /*Signed=*/false); @@ -441,7 +475,7 @@ return Str.str(); } if (auto *D = dyn_cast(S)) -return D->getDecl()->getQualifiedNameAsString(); +return getRelativeName(D->getDecl(), getEnclosingDeclContext(AST, S)); if (auto *String = dyn_cast(S)) return String->getString(); if (auto *B = dyn_cast(S)) @@ -950,7 +984,7 @@ return DiffImpl->getMapped(SourceTree.TreeImpl, Id); } -SyntaxTree::SyntaxTree(const ASTContext &AST) +SyntaxTree::SyntaxTree(ASTContext &AST) : TreeImpl(llvm::make_unique( this, AST.getTranslationUnitDecl(), AST)) {} Index: include/clang/Tooling/ASTDiff/ASTDiff.h === --- include/clang/Too
[PATCH] D36685: [clang-diff] HTML diff navigation
johannes created this revision. This adds shortcuts j and k to jump between changes. It is especially useful in diffs with few changes. https://reviews.llvm.org/D36685 Files: tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -145,34 +145,79 @@ highlightStack = [] function clearHighlight() { while (highlightStack.length) { -let [l, r] = highlightStack.pop() +var [l, r] = highlightStack.pop() document.getElementById(l).style.backgroundColor = 'white' -document.getElementById(r).style.backgroundColor = 'white' +if (r[1] != '-') + document.getElementById(r).style.backgroundColor = 'white' } } function highlight(event) { - id = event.target['id'] + var id = event.target['id'] doHighlight(id) } function doHighlight(id) { clearHighlight() source = document.getElementById(id) if (!source.attributes['tid']) return - tid = source.attributes['tid'].value - target = document.getElementById(tid) - if (!target || source.parentElement && source.parentElement.classList.contains('code')) + 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 = target.style.backgroundColor = 'lightgrey' - highlightStack.push([id, tid]) + source.style.backgroundColor = 'lightgrey' source.scrollIntoView() - target.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 nextWithoutClass(prefix, increment, number) { + do { +number += increment +var elem = document.getElementById(prefix + number) + } while(elem && elem.classList.length == 0) + 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 = nextWithoutClass(prefix, increment, number) +var elem = document.getElementById(prefix + number) +if (up && elem) { + while (elem.parentElement && elem.parentElement.classList.length != 0) { +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 Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -145,34 +145,79 @@ highlightStack = [] function clearHighlight() { while (highlightStack.length) { -let [l, r] = highlightStack.pop() +var [l, r] = highlightStack.pop() document.getElementById(l).style.backgroundColor = 'white' -document.getElementById(r).style.backgroundColor = 'white' +if (r[1] != '-') + document.getElementById(r).style.backgroundColor = 'white' } } function highlight(event) { - id = event.target['id'] + var id = event.target['id'] doHighlight(id) } function doHighlight(id) { clearHighlight() source = document.getElementById(id) if (!source.attributes['tid']) return - tid = source.attributes['tid'].value - target = document.getElementById(tid) - if (!target || source.parentElement && source.parentElement.classList.contains('code')) + 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 = target.style.backgroundColor = 'lightgrey' - highlightStack.push([id, tid]) + source.style.backgroundColor = 'lightgrey' source.scrollIntoView() - target.scrollIntoView() + if (target) { +if (mapped === source) + target.style.backgroundColor = 'lightgrey' +target.scrollIntoView() + } + highlightStack.push([id, tid]) location.hash = '#'
[PATCH] D36686: [clang-diff] Add option to compare files across git revisions
johannes created this revision. This adds a command line option "-git-rev=". When it is used, only one filename is accepted. The file in its version in the specified revision is compared against the current version. Note that this calls `git checkout` in the current directory. https://reviews.llvm.org/D36686 Files: tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -16,6 +16,7 @@ #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/Program.h" using namespace llvm; using namespace clang; @@ -33,9 +34,9 @@ cl::desc("Print the internal representation of the AST as JSON."), cl::init(false), cl::cat(ClangDiffCategory)); -static cl::opt -PrintMatches("dump-matches", cl::desc("Print the matched nodes."), - cl::init(false), cl::cat(ClangDiffCategory)); +static cl::opt PrintMatches("dump-matches", + cl::desc("Print the matched nodes."), + cl::init(false), cl::cat(ClangDiffCategory)); static cl::opt HtmlDiff("html", cl::desc("Output a side-by-side diff in HTML."), @@ -55,6 +56,11 @@ cl::Optional, cl::init(""), cl::cat(ClangDiffCategory)); +static cl::opt +GitRevision("git-rev", cl::desc("Compare the file from a checkout of this " +"revision with the current version."), +cl::Optional, cl::init(""), cl::cat(ClangDiffCategory)); + static cl::opt MaxSize("s", cl::desc(""), cl::Optional, cl::init(-1), cl::cat(ClangDiffCategory)); @@ -438,6 +444,19 @@ } } +std::string Exec(const char *Command) { + char Buffer[128]; + std::string Result; + std::shared_ptr Pipe(popen(Command, "r"), pclose); + if (!Pipe) +return Result; + while (!feof(Pipe.get())) { +if (fgets(Buffer, 128, Pipe.get()) != nullptr) + Result += Buffer; + } + return Result; +} + int main(int argc, const char **argv) { std::string ErrorMessage; std::unique_ptr CommonCompilations = @@ -473,13 +492,40 @@ return 0; } - if (DestinationPath.empty()) { + if (DestinationPath.empty() && GitRevision.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); + std::unique_ptr Src, Dst; + + if (!GitRevision.empty()) { +std::string CurrentRevision, Git; +auto ErrorOrGit = llvm::sys::findProgramByName("git"); +if (!ErrorOrGit) { + llvm::errs() << "Error: Could not find git executable.\n"; + return 1; +} +Git = ErrorOrGit.get(); +CurrentRevision = Exec("git rev-parse HEAD"); +*std::find(CurrentRevision.begin(), CurrentRevision.end(), '\n') = '\0'; +const char *Checkout[] = {"git", "checkout", GitRevision.data(), nullptr}; +if (llvm::sys::ExecuteAndWait(Git, Checkout)) { + llvm::errs() << "Error: Failed to checkout " << GitRevision << "\n"; + return 1; +} +Src = getAST(CommonCompilations, SourcePath); +Checkout[2] = CurrentRevision.data(); +if (llvm::sys::ExecuteAndWait(Git, Checkout)) { + llvm::errs() << "Error: Failed to checkout " << CurrentRevision << "\n"; + return 1; +} +Dst = getAST(CommonCompilations, SourcePath); + } else { +Src = getAST(CommonCompilations, SourcePath); +Dst = getAST(CommonCompilations, DestinationPath); + } + if (!Src || !Dst) return 1; ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D36687: [clang-diff] Match nodes with the same parent and same value
johannes created this revision. This adds another pass to the matching algorithm that tries to match nodes with common parents, if they have similar values. https://reviews.llvm.org/D36687 Files: include/clang/Tooling/ASTDiff/ASTDiff.h lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-bottomup.cpp test/Tooling/clang-diff-heuristics.cpp test/Tooling/clang-diff-opt.cpp tools/clang-diff/ClangDiff.cpp Index: tools/clang-diff/ClangDiff.cpp === --- tools/clang-diff/ClangDiff.cpp +++ tools/clang-diff/ClangDiff.cpp @@ -535,7 +535,9 @@ if (!StopAfter.empty()) { if (StopAfter == "topdown") Options.StopAfterTopDown = true; -else if (StopAfter != "bottomup") { +else if (StopAfter == "bottomup") + Options.StopAfterBottomUp = true; +else { llvm::errs() << "Error: Invalid argument for -stop-after\n"; return 1; } Index: test/Tooling/clang-diff-opt.cpp === --- test/Tooling/clang-diff-opt.cpp +++ test/Tooling/clang-diff-opt.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -E %s > %t.src.cpp // RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST -// RUN: clang-diff -dump-matches -s=10 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// RUN: clang-diff -dump-matches -s=10 -stop-after=bottomup %t.src.cpp %t.dst.cpp -- | FileCheck %s // // Test the behaviour of the matching according to the optimal tree edit // distance, implemented with Zhang and Shasha's algorithm. @@ -41,5 +41,5 @@ // CHECK: Delete NullStmt(22) ;; {{;;}} } - + #endif Index: test/Tooling/clang-diff-heuristics.cpp === --- /dev/null +++ test/Tooling/clang-diff-heuristics.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -E %s > %t.src.cpp +// RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST +// RUN: clang-diff -dump-matches -s=0 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// +// Test the heuristics, with maxsize set to 0, so that the optimal matching will never be applied. + +#ifndef DEST + +void f1() {;} + +void f2(int) {;} + +#else + +// same parents, same value +// CHECK: Match FunctionDecl: f1(void ())(1) to FunctionDecl: f1(void ())(1) +// CHECK: Match CompoundStmt +void f1() {} + +// same parents, same identifier +// CHECK: Match FunctionDecl: f2(void (int))(4) to FunctionDecl: f2(void ())(3) +// CHECK: Match CompoundStmt +void f2() {} + +#endif Index: test/Tooling/clang-diff-bottomup.cpp === --- test/Tooling/clang-diff-bottomup.cpp +++ test/Tooling/clang-diff-bottomup.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -E %s > %t.src.cpp // RUN: %clang_cc1 -E %s > %t.dst.cpp -DDEST -// RUN: clang-diff -dump-matches -s=0 %t.src.cpp %t.dst.cpp -- | FileCheck %s +// RUN: clang-diff -dump-matches -s=0 -stop-after=bottomup %t.src.cpp %t.dst.cpp -- | FileCheck %s // // Test the bottom-up matching, with maxsize set to 0, so that the optimal matching will never be applied. Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -94,15 +94,23 @@ // Descendants are only considered to be equal when they are mapped in M. double getJaccardSimilarity(const Mapping &M, NodeId Id1, NodeId Id2) const; + double getNodeSimilarity(const Mapping &M, NodeId Id1, NodeId Id2) const; + // Returns the node that has the highest degree of similarity. NodeId findCandidate(const Mapping &M, NodeId Id1) const; + NodeId findCandidateFromChildren(const Mapping &M, NodeId Id1, + NodeId P2) const; + // Returns a mapping of identical subtrees. Mapping matchTopDown() const; // Tries to match any yet unmapped nodes, in a bottom-up fashion. void matchBottomUp(Mapping &M) const; + // Matches nodes, whose parents are matched. + void matchChildren(Mapping &M); + const ComparisonOptions &Options; friend class ZhangShashaMatcher; @@ -828,6 +836,23 @@ return CommonDescendants / Denominator; } +double ASTDiff::Impl::getNodeSimilarity(const Mapping &M, NodeId Id1, +NodeId Id2) const { + const Node &N1 = T1.getNode(Id1); + const Node &N2 = T2.getNode(Id2); + auto Ident1 = N1.getIdentifier(), Ident2 = N2.getIdentifier(); + + bool SameValue = T1.getNodeValue(Id1) == T2.getNodeValue(Id2); + auto SameIdent = Ident1 && Ident2 && *Ident1 == *Ident2; + + double NodeSimilarity = 0; + NodeSimilarity += SameValue; + NodeSimilarity += SameIdent; + + assert(haveSameParents(M, Id1, Id2)); + return NodeSimilarity * Options.MinSimilarity; +} + NodeId ASTDiff::Impl::findCandidate(const Mapping &M, NodeId Id1) const { NodeId Candidate; double HighestSimilarity = 0.0; @@ -845,6 +870,25 @@ return Candidate; } +NodeId ASTDiff::Impl::findCandidateFromChildren(const
[PATCH] D36688: [clang-diff] Fix matching for unnamed NamedDecs
johannes created this revision. Herald added a subscriber: klimek. This makes Node::getIdentifier return a valid value for special NamedDecl nodes that do not have a name, such as ctors, dtors and unnamed classes / namespaces. https://reviews.llvm.org/D36688 Files: lib/Tooling/ASTDiff/ASTDiff.cpp test/Tooling/clang-diff-heuristics.cpp Index: test/Tooling/clang-diff-heuristics.cpp === --- test/Tooling/clang-diff-heuristics.cpp +++ test/Tooling/clang-diff-heuristics.cpp @@ -10,6 +10,8 @@ void f2(int) {;} +class C3 { C3(); }; + #else // same parents, same value @@ -22,4 +24,8 @@ // CHECK: Match CompoundStmt void f2() {} +// same parents, same identifier +// CHECK: Match CXXConstructorDecl: :C3(void ())(9) to CXXConstructorDecl: :C3(void (int))(6) +class C3 { C3(int); }; + #endif Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -384,8 +384,7 @@ // Strip the qualifier, if Val refers to somthing in the current scope. // But leave one leading ':' in place, so that we know that this is a // relative path. - if (!ContextPrefix.empty() && - StringRef(Val).startswith(ContextPrefix)) + if (!ContextPrefix.empty() && StringRef(Val).startswith(ContextPrefix)) Val = Val.substr(ContextPrefix.size() + 1); return Val; } @@ -715,14 +714,18 @@ if (auto *ND = ASTNode.get()) { if (ND->getDeclName().isIdentifier()) return ND->getQualifiedNameAsString(); +else + return std::string(); } return llvm::None; } llvm::Optional Node::getIdentifier() const { if (auto *ND = ASTNode.get()) { if (ND->getDeclName().isIdentifier()) return ND->getName(); +else + return StringRef(); } return llvm::None; } Index: test/Tooling/clang-diff-heuristics.cpp === --- test/Tooling/clang-diff-heuristics.cpp +++ test/Tooling/clang-diff-heuristics.cpp @@ -10,6 +10,8 @@ void f2(int) {;} +class C3 { C3(); }; + #else // same parents, same value @@ -22,4 +24,8 @@ // CHECK: Match CompoundStmt void f2() {} +// same parents, same identifier +// CHECK: Match CXXConstructorDecl: :C3(void ())(9) to CXXConstructorDecl: :C3(void (int))(6) +class C3 { C3(int); }; + #endif Index: lib/Tooling/ASTDiff/ASTDiff.cpp === --- lib/Tooling/ASTDiff/ASTDiff.cpp +++ lib/Tooling/ASTDiff/ASTDiff.cpp @@ -384,8 +384,7 @@ // Strip the qualifier, if Val refers to somthing in the current scope. // But leave one leading ':' in place, so that we know that this is a // relative path. - if (!ContextPrefix.empty() && - StringRef(Val).startswith(ContextPrefix)) + if (!ContextPrefix.empty() && StringRef(Val).startswith(ContextPrefix)) Val = Val.substr(ContextPrefix.size() + 1); return Val; } @@ -715,14 +714,18 @@ if (auto *ND = ASTNode.get()) { if (ND->getDeclName().isIdentifier()) return ND->getQualifiedNameAsString(); +else + return std::string(); } return llvm::None; } llvm::Optional Node::getIdentifier() const { if (auto *ND = ASTNode.get()) { if (ND->getDeclName().isIdentifier()) return ND->getName(); +else + return StringRef(); } return llvm::None; } ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits