Imported Upstream version 6.10.0.49

Former-commit-id: 1d6753294b2993e1fbf92de9366bb9544db4189b
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2020-01-16 16:38:04 +00:00
parent d94e79959b
commit 468663ddbb
48518 changed files with 2789335 additions and 61176 deletions

View File

@ -0,0 +1,4 @@
{
"repository.callsign" : "CTE",
"conduit_uri" : "https://reviews.llvm.org/"
}

View File

@ -0,0 +1,32 @@
#==============================================================================#
# This file specifies intentionally untracked files that git should ignore.
# See: http://www.kernel.org/pub/software/scm/git/docs/gitignore.html
#
# This file is intentionally different from the output of `git svn show-ignore`,
# as most of those are useless.
#==============================================================================#
#==============================================================================#
# File extensions to be ignored anywhere in the tree.
#==============================================================================#
# Temp files created by most text editors.
*~
# Merge files created by git.
*.orig
# Byte compiled python modules.
*.pyc
# vim swap files
.*.swp
.sw?
#==============================================================================#
# Explicit files to ignore (only matches one).
#==============================================================================#
cscope.files
cscope.out
.clang_complete
#==============================================================================#
# Directories to ignore (do not add trailing '/'s, they skip symlinks).
#==============================================================================#
docs/_build

View File

@ -0,0 +1,28 @@
add_subdirectory(clang-apply-replacements)
add_subdirectory(clang-reorder-fields)
add_subdirectory(modularize)
if(CLANG_ENABLE_STATIC_ANALYZER)
add_subdirectory(clang-tidy)
add_subdirectory(clang-tidy-vs)
endif()
add_subdirectory(change-namespace)
add_subdirectory(clang-query)
add_subdirectory(clang-move)
add_subdirectory(clangd)
add_subdirectory(include-fixer)
add_subdirectory(pp-trace)
add_subdirectory(tool-template)
# Add the common testsuite after all the tools.
if(CLANG_INCLUDE_TESTS)
add_subdirectory(test)
add_subdirectory(unittests)
endif()
option(CLANG_TOOLS_EXTRA_INCLUDE_DOCS "Generate build targets for the Clang Extra Tools docs."
${LLVM_INCLUDE_DOCS})
if( CLANG_TOOLS_EXTRA_INCLUDE_DOCS )
add_subdirectory(docs)
endif()

View File

@ -0,0 +1,21 @@
This file is a list of the people responsible for ensuring that patches for a
particular tool are reviewed, either by themself or by someone else. They are
also the gatekeepers for their part of Clang, with the final word on what goes
in or not.
The list is sorted by surname and formatted to allow easy grepping and
beautification by scripts. The fields are: name (N), email (E), web-address
(W), PGP key ID and fingerprint (P), description (D), and snail-mail address
(S).
N: Peter Collingbourne
E: peter@pcc.me.uk
D: clang-query
N: Manuel Klimek
E: klimek@google.com
D: clang-rename, all parts of clang-tools-extra not covered by someone else
N: Alexander Kornienko
E: alexfh@google.com
D: clang-tidy

View File

@ -0,0 +1,63 @@
==============================================================================
LLVM Release License
==============================================================================
University of Illinois/NCSA
Open Source License
Copyright (c) 2007-2016 University of Illinois at Urbana-Champaign.
All rights reserved.
Developed by:
LLVM Team
University of Illinois at Urbana-Champaign
http://llvm.org
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal with
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimers.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimers in the
documentation and/or other materials provided with the distribution.
* Neither the names of the LLVM Team, University of Illinois at
Urbana-Champaign, nor the names of its contributors may be used to
endorse or promote products derived from this Software without specific
prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.
==============================================================================
The LLVM software contains code written by third parties. Such software will
have its own individual LICENSE.TXT file in the directory in which it appears.
This file will describe the copyrights, license, and restrictions which apply
to that code.
The disclaimer of warranty in the University of Illinois Open Source License
applies to all code in the LLVM Distribution, and nothing in any of the
other licenses gives permission to use the names of the LLVM Team or the
University of Illinois to endorse or promote products derived from this
Software.
The following pieces of software have additional or alternate copyrights,
licenses, and/or restrictions:
Program Directory
------- ---------
clang-tidy clang-tidy/cert
clang-tidy clang-tidy/hicpp

View File

@ -0,0 +1,22 @@
//===----------------------------------------------------------------------===//
// Clang Tools repository
//===----------------------------------------------------------------------===//
Welcome to the repository of extra Clang Tools. This repository holds tools
that are developed as part of the LLVM compiler infrastructure project and the
Clang frontend. These tools are kept in a separate "extra" repository to
allow lighter weight checkouts of the core Clang codebase.
This repository is only intended to be checked out inside of a full LLVM+Clang
tree, and in the 'tools/extra' subdirectory of the Clang checkout.
All discussion regarding Clang, Clang-based tools, and code in this repository
should be held using the standard Clang mailing lists:
http://lists.llvm.org/mailman/listinfo/cfe-dev
Code review for this tree should take place on the standard Clang patch and
commit lists:
http://lists.llvm.org/mailman/listinfo/cfe-commits
If you find a bug in these tools, please file it in the LLVM bug tracker:
http://llvm.org/bugs/

View File

@ -0,0 +1,19 @@
set(LLVM_LINK_COMPONENTS
support
)
add_clang_library(clangChangeNamespace
ChangeNamespace.cpp
LINK_LIBS
clangAST
clangASTMatchers
clangBasic
clangFormat
clangFrontend
clangLex
clangTooling
clangToolingCore
)
add_subdirectory(tool)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,176 @@
//===-- ChangeNamespace.h -- Change namespace ------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H
#define LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Format/Format.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/Support/Regex.h"
#include <string>
namespace clang {
namespace change_namespace {
// This tool can be used to change the surrounding namespaces of class/function
// definitions. Classes/functions in the moved namespace will have new
// namespaces while references to symbols (e.g. types, functions) which are not
// defined in the changed namespace will be correctly qualified by prepending
// namespace specifiers before them.
// This will try to add shortest namespace specifiers possible. When a symbol
// reference needs to be fully-qualified, this adds a "::" prefix to the
// namespace specifiers unless the new namespace is the global namespace.
// For classes, only classes that are declared/defined in the given namespace in
// speficifed files will be moved: forward declarations will remain in the old
// namespace.
// For example, changing "a" to "x":
// Old code:
// namespace a {
// class FWD;
// class A { FWD *fwd; }
// } // a
// New code:
// namespace a {
// class FWD;
// } // a
// namespace x {
// class A { ::a::FWD *fwd; }
// } // x
// FIXME: support moving typedef, enums across namespaces.
class ChangeNamespaceTool : public ast_matchers::MatchFinder::MatchCallback {
public:
// Moves code in the old namespace `OldNs` to the new namespace `NewNs` in
// files matching `FilePattern`.
ChangeNamespaceTool(
llvm::StringRef OldNs, llvm::StringRef NewNs, llvm::StringRef FilePattern,
llvm::ArrayRef<std::string> WhiteListedSymbolPatterns,
std::map<std::string, tooling::Replacements> *FileToReplacements,
llvm::StringRef FallbackStyle = "LLVM");
void registerMatchers(ast_matchers::MatchFinder *Finder);
void run(const ast_matchers::MatchFinder::MatchResult &Result) override;
// Moves the changed code in old namespaces but leaves class forward
// declarations behind.
void onEndOfTranslationUnit() override;
private:
void moveOldNamespace(const ast_matchers::MatchFinder::MatchResult &Result,
const NamespaceDecl *NsDecl);
void moveClassForwardDeclaration(
const ast_matchers::MatchFinder::MatchResult &Result,
const NamedDecl *FwdDecl);
void replaceQualifiedSymbolInDeclContext(
const ast_matchers::MatchFinder::MatchResult &Result,
const DeclContext *DeclContext, SourceLocation Start, SourceLocation End,
const NamedDecl *FromDecl);
void fixTypeLoc(const ast_matchers::MatchFinder::MatchResult &Result,
SourceLocation Start, SourceLocation End, TypeLoc Type);
void fixUsingShadowDecl(const ast_matchers::MatchFinder::MatchResult &Result,
const UsingDecl *UsingDeclaration);
void fixDeclRefExpr(const ast_matchers::MatchFinder::MatchResult &Result,
const DeclContext *UseContext, const NamedDecl *From,
const DeclRefExpr *Ref);
// Information about moving an old namespace.
struct MoveNamespace {
// The start offset of the namespace block being moved in the original
// code.
unsigned Offset;
// The length of the namespace block in the original code.
unsigned Length;
// The offset at which the new namespace block will be inserted in the
// original code.
unsigned InsertionOffset;
// The file in which the namespace is declared.
FileID FID;
SourceManager *SourceMgr;
};
// Information about inserting a class forward declaration.
struct InsertForwardDeclaration {
// The offset at while the forward declaration will be inserted in the
// original code.
unsigned InsertionOffset;
// The code to be inserted.
std::string ForwardDeclText;
};
std::string FallbackStyle;
// In match callbacks, this contains replacements for replacing `typeLoc`s in
// and deleting forward declarations in the moved namespace blocks.
// In `onEndOfTranslationUnit` callback, the previous added replacements are
// applied (on the moved namespace blocks), and then changed code in old
// namespaces re moved to new namespaces, and previously deleted forward
// declarations are inserted back to old namespaces, from which they are
// deleted.
std::map<std::string, tooling::Replacements> &FileToReplacements;
// A fully qualified name of the old namespace without "::" prefix, e.g.
// "a::b::c".
std::string OldNamespace;
// A fully qualified name of the new namespace without "::" prefix, e.g.
// "x::y::z".
std::string NewNamespace;
// The longest suffix in the old namespace that does not overlap the new
// namespace.
// For example, if `OldNamespace` is "a::b::c" and `NewNamespace` is
// "a::x::y", then `DiffOldNamespace` will be "b::c".
std::string DiffOldNamespace;
// The longest suffix in the new namespace that does not overlap the old
// namespace.
// For example, if `OldNamespace` is "a::b::c" and `NewNamespace` is
// "a::x::y", then `DiffNewNamespace` will be "x::y".
std::string DiffNewNamespace;
// A regex pattern that matches files to be processed.
std::string FilePattern;
llvm::Regex FilePatternRE;
// Information about moved namespaces grouped by file.
// Since we are modifying code in old namespaces (e.g. add namespace
// spedifiers) as well as moving them, we store information about namespaces
// to be moved and only move them after all modifications are finished (i.e.
// in `onEndOfTranslationUnit`).
std::map<std::string, std::vector<MoveNamespace>> MoveNamespaces;
// Information about forward declaration insertions grouped by files.
// A class forward declaration is not moved, so it will be deleted from the
// moved code block and inserted back into the old namespace. The insertion
// will be done after removing the code from the old namespace and before
// inserting it to the new namespace.
std::map<std::string, std::vector<InsertForwardDeclaration>> InsertFwdDecls;
// Records all using declarations, which can be used to shorten namespace
// specifiers.
llvm::SmallPtrSet<const UsingDecl *, 8> UsingDecls;
// Records all using namespace declarations, which can be used to shorten
// namespace specifiers.
llvm::SmallPtrSet<const UsingDirectiveDecl *, 8> UsingNamespaceDecls;
// Records all namespace alias declarations, which can be used to shorten
// namespace specifiers.
llvm::SmallPtrSet<const NamespaceAliasDecl *, 8> NamespaceAliasDecls;
// TypeLocs of CXXCtorInitializer. Types of CXXCtorInitializers do not need to
// be fixed.
llvm::SmallVector<TypeLoc, 8> BaseCtorInitializerTypeLocs;
// Since a DeclRefExpr for a function call can be matched twice (one as
// CallExpr and one as DeclRefExpr), we record all DeclRefExpr's that have
// been processed so that we don't handle them twice.
llvm::SmallPtrSet<const clang::DeclRefExpr*, 16> ProcessedFuncRefs;
// Patterns of symbol names whose references are not expected to be updated
// when changing namespaces around them.
std::vector<llvm::Regex> WhiteListedSymbolRegexes;
};
} // namespace change_namespace
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CHANGE_NAMESPACE_CHANGENAMESPACE_H

View File

@ -0,0 +1,24 @@
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
set(LLVM_LINK_COMPONENTS
Support
)
add_clang_executable(clang-change-namespace
ClangChangeNamespace.cpp
)
target_link_libraries(clang-change-namespace
PRIVATE
clangAST
clangASTMatchers
clangBasic
clangChangeNamespace
clangFormat
clangFrontend
clangRewrite
clangTooling
clangToolingCore
)
install(TARGETS clang-change-namespace
RUNTIME DESTINATION bin)

View File

@ -0,0 +1,178 @@
//===-- ClangIncludeFixer.cpp - Standalone change namespace ---------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// This tool can be used to change the surrounding namespaces of class/function
// definitions.
//
// Example: test.cc
// namespace na {
// class X {};
// namespace nb {
// class Y { X x; };
// } // namespace nb
// } // namespace na
// To move the definition of class Y from namespace "na::nb" to "x::y", run:
// clang-change-namespace --old_namespace "na::nb" \
// --new_namespace "x::y" --file_pattern "test.cc" test.cc --
// Output:
// namespace na {
// class X {};
// } // namespace na
// namespace x {
// namespace y {
// class Y { na::X x; };
// } // namespace y
// } // namespace x
#include "ChangeNamespace.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/YAMLTraits.h"
using namespace clang;
using namespace llvm;
namespace {
cl::OptionCategory ChangeNamespaceCategory("Change namespace.");
cl::opt<std::string> OldNamespace("old_namespace", cl::Required,
cl::desc("Old namespace."),
cl::cat(ChangeNamespaceCategory));
cl::opt<std::string> NewNamespace("new_namespace", cl::Required,
cl::desc("New namespace."),
cl::cat(ChangeNamespaceCategory));
cl::opt<std::string> FilePattern(
"file_pattern", cl::Required,
cl::desc("Only rename namespaces in files that match the given pattern."),
cl::cat(ChangeNamespaceCategory));
cl::opt<bool> Inplace("i", cl::desc("Inplace edit <file>s, if specified."),
cl::cat(ChangeNamespaceCategory));
cl::opt<bool>
DumpYAML("dump_result",
cl::desc("Dump new file contents in YAML, if specified."),
cl::cat(ChangeNamespaceCategory));
cl::opt<std::string> Style("style",
cl::desc("The style name used for reformatting."),
cl::init("LLVM"), cl::cat(ChangeNamespaceCategory));
cl::opt<std::string> WhiteListFile(
"whitelist_file",
cl::desc("A file containing regexes of symbol names that are not expected "
"to be updated when changing namespaces around them."),
cl::init(""), cl::cat(ChangeNamespaceCategory));
llvm::ErrorOr<std::vector<std::string>> GetWhiteListedSymbolPatterns() {
std::vector<std::string> Patterns;
if (WhiteListFile.empty())
return Patterns;
llvm::SmallVector<StringRef, 8> Lines;
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =
llvm::MemoryBuffer::getFile(WhiteListFile);
if (!File)
return File.getError();
llvm::StringRef Content = File.get()->getBuffer();
Content.split(Lines, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false);
for (auto Line : Lines)
Patterns.push_back(Line.trim());
return Patterns;
}
} // anonymous namespace
int main(int argc, const char **argv) {
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
tooling::CommonOptionsParser OptionsParser(argc, argv,
ChangeNamespaceCategory);
const auto &Files = OptionsParser.getSourcePathList();
tooling::RefactoringTool Tool(OptionsParser.getCompilations(), Files);
llvm::ErrorOr<std::vector<std::string>> WhiteListPatterns =
GetWhiteListedSymbolPatterns();
if (!WhiteListPatterns) {
llvm::errs() << "Failed to open whitelist file " << WhiteListFile << ". "
<< WhiteListPatterns.getError().message() << "\n";
return 1;
}
change_namespace::ChangeNamespaceTool NamespaceTool(
OldNamespace, NewNamespace, FilePattern, *WhiteListPatterns,
&Tool.getReplacements(), Style);
ast_matchers::MatchFinder Finder;
NamespaceTool.registerMatchers(&Finder);
std::unique_ptr<tooling::FrontendActionFactory> Factory =
tooling::newFrontendActionFactory(&Finder);
if (int Result = Tool.run(Factory.get()))
return Result;
LangOptions DefaultLangOptions;
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
clang::TextDiagnosticPrinter DiagnosticPrinter(errs(), &*DiagOpts);
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts,
&DiagnosticPrinter, false);
auto &FileMgr = Tool.getFiles();
SourceManager Sources(Diagnostics, FileMgr);
Rewriter Rewrite(Sources, DefaultLangOptions);
if (!formatAndApplyAllReplacements(Tool.getReplacements(), Rewrite, Style)) {
llvm::errs() << "Failed applying all replacements.\n";
return 1;
}
if (Inplace)
return Rewrite.overwriteChangedFiles();
std::set<llvm::StringRef> ChangedFiles;
for (const auto &it : Tool.getReplacements())
ChangedFiles.insert(it.first);
if (DumpYAML) {
auto WriteToYAML = [&](llvm::raw_ostream &OS) {
OS << "[\n";
for (auto I = ChangedFiles.begin(), E = ChangedFiles.end(); I != E; ++I) {
OS << " {\n";
OS << " \"FilePath\": \"" << *I << "\",\n";
const auto *Entry = FileMgr.getFile(*I);
auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User);
std::string Content;
llvm::raw_string_ostream ContentStream(Content);
Rewrite.getEditBuffer(ID).write(ContentStream);
OS << " \"SourceText\": \""
<< llvm::yaml::escape(ContentStream.str()) << "\"\n";
OS << " }";
if (I != std::prev(E))
OS << ",\n";
}
OS << "\n]\n";
};
WriteToYAML(llvm::outs());
return 0;
}
for (const auto &File : ChangedFiles) {
const auto *Entry = FileMgr.getFile(File);
auto ID = Sources.getOrCreateFileID(Entry, SrcMgr::C_User);
outs() << "============== " << File << " ==============\n";
Rewrite.getEditBuffer(ID).write(llvm::outs());
outs() << "\n============================================\n";
}
return 0;
}

View File

@ -0,0 +1,19 @@
set(LLVM_LINK_COMPONENTS
Support
)
add_clang_library(clangApplyReplacements
lib/Tooling/ApplyReplacements.cpp
LINK_LIBS
clangAST
clangBasic
clangRewrite
clangToolingCore
)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
include
)
add_subdirectory(tool)

View File

@ -0,0 +1,155 @@
//===-- ApplyReplacements.h - Deduplicate and apply replacements -- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file provides the interface for deduplicating, detecting
/// conflicts in, and applying collections of Replacements.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_APPLYREPLACEMENTS_H
#define LLVM_CLANG_APPLYREPLACEMENTS_H
#include "clang/Tooling/Core/Diagnostic.h"
#include "clang/Tooling/Refactoring.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include <string>
#include <system_error>
#include <vector>
namespace clang {
class DiagnosticsEngine;
class Rewriter;
namespace format {
struct FormatStyle;
} // end namespace format
namespace replace {
/// \brief Collection of source ranges.
typedef std::vector<clang::tooling::Range> RangeVector;
/// \brief Collection of TranslationUnitReplacements.
typedef std::vector<clang::tooling::TranslationUnitReplacements> TUReplacements;
/// \brief Collection of TranslationUnitReplacement files.
typedef std::vector<std::string> TUReplacementFiles;
/// \brief Collection of TranslationUniDiagnostics.
typedef std::vector<clang::tooling::TranslationUnitDiagnostics> TUDiagnostics;
/// \brief Map mapping file name to Replacements targeting that file.
typedef llvm::DenseMap<const clang::FileEntry *,
std::vector<clang::tooling::Replacement>>
FileToReplacementsMap;
/// \brief Recursively descends through a directory structure rooted at \p
/// Directory and attempts to deserialize *.yaml files as
/// TranslationUnitReplacements. All docs that successfully deserialize are
/// added to \p TUs.
///
/// Directories starting with '.' are ignored during traversal.
///
/// \param[in] Directory Directory to begin search for serialized
/// TranslationUnitReplacements.
/// \param[out] TUs Collection of all found and deserialized
/// TranslationUnitReplacements or TranslationUnitDiagnostics.
/// \param[out] TUFiles Collection of all TranslationUnitReplacement files
/// found in \c Directory.
/// \param[in] Diagnostics DiagnosticsEngine used for error output.
///
/// \returns An error_code indicating success or failure in navigating the
/// directory structure.
std::error_code collectReplacementsFromDirectory(
const llvm::StringRef Directory, TUReplacements &TUs,
TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics);
std::error_code collectReplacementsFromDirectory(
const llvm::StringRef Directory, TUDiagnostics &TUs,
TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics);
/// \brief Deduplicate, check for conflicts, and apply all Replacements stored
/// in \c TUs. If conflicts occur, no Replacements are applied.
///
/// \post For all (key,value) in GroupedReplacements, value[i].getOffset() <=
/// value[i+1].getOffset().
///
/// \param[in] TUs Collection of TranslationUnitReplacements or
/// TranslationUnitDiagnostics to merge,
/// deduplicate, and test for conflicts.
/// \param[out] GroupedReplacements Container grouping all Replacements by the
/// file they target.
/// \param[in] SM SourceManager required for conflict reporting.
///
/// \returns \parblock
/// \li true If all changes were applied successfully.
/// \li false If there were conflicts.
bool mergeAndDeduplicate(const TUReplacements &TUs,
FileToReplacementsMap &GroupedReplacements,
clang::SourceManager &SM);
bool mergeAndDeduplicate(const TUDiagnostics &TUs,
FileToReplacementsMap &GroupedReplacements,
clang::SourceManager &SM);
// FIXME: Remove this function after changing clang-apply-replacements to use
// Replacements class.
bool applyAllReplacements(const std::vector<tooling::Replacement> &Replaces,
Rewriter &Rewrite);
/// \brief Apply all replacements in \c GroupedReplacements.
///
/// \param[in] GroupedReplacements Deduplicated and conflict free Replacements
/// to apply.
/// \param[out] Rewrites The results of applying replacements will be applied
/// to this Rewriter.
///
/// \returns \parblock
/// \li true If all changes were applied successfully.
/// \li false If a replacement failed to apply.
bool applyReplacements(const FileToReplacementsMap &GroupedReplacements,
clang::Rewriter &Rewrites);
/// \brief Given a collection of Replacements for a single file, produces a list
/// of source ranges that enclose those Replacements.
///
/// \pre Replacements[i].getOffset() <= Replacements[i+1].getOffset().
///
/// \param[in] Replacements Replacements from a single file.
///
/// \returns Collection of source ranges that enclose all given Replacements.
/// One range is created for each replacement.
RangeVector calculateChangedRanges(
const std::vector<clang::tooling::Replacement> &Replacements);
/// \brief Write the contents of \c FileContents to disk. Keys of the map are
/// filenames and values are the new contents for those files.
///
/// \param[in] Rewrites Rewriter containing written files to write to disk.
bool writeFiles(const clang::Rewriter &Rewrites);
/// \brief Delete the replacement files.
///
/// \param[in] Files Replacement files to delete.
/// \param[in] Diagnostics DiagnosticsEngine used for error output.
///
/// \returns \parblock
/// \li true If all files have been deleted successfully.
/// \li false If at least one or more failures occur when deleting
/// files.
bool deleteReplacementFiles(const TUReplacementFiles &Files,
clang::DiagnosticsEngine &Diagnostics);
} // end namespace replace
} // end namespace clang
#endif // LLVM_CLANG_APPLYREPLACEMENTS_H

View File

@ -0,0 +1,400 @@
//===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file provides the implementation for deduplicating, detecting
/// conflicts in, and applying collections of Replacements.
///
/// FIXME: Use Diagnostics for output instead of llvm::errs().
///
//===----------------------------------------------------------------------===//
#include "clang-apply-replacements/Tooling/ApplyReplacements.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "clang/Lex/Lexer.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Tooling/DiagnosticsYaml.h"
#include "clang/Tooling/ReplacementsYaml.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
using namespace clang;
static void eatDiagnostics(const SMDiagnostic &, void *) {}
namespace clang {
namespace replace {
std::error_code collectReplacementsFromDirectory(
const llvm::StringRef Directory, TUReplacements &TUs,
TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
using namespace llvm::sys::fs;
using namespace llvm::sys::path;
std::error_code ErrorCode;
for (recursive_directory_iterator I(Directory, ErrorCode), E;
I != E && !ErrorCode; I.increment(ErrorCode)) {
if (filename(I->path())[0] == '.') {
// Indicate not to descend into directories beginning with '.'
I.no_push();
continue;
}
if (extension(I->path()) != ".yaml")
continue;
TUFiles.push_back(I->path());
ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
MemoryBuffer::getFile(I->path());
if (std::error_code BufferError = Out.getError()) {
errs() << "Error reading " << I->path() << ": " << BufferError.message()
<< "\n";
continue;
}
yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
tooling::TranslationUnitReplacements TU;
YIn >> TU;
if (YIn.error()) {
// File doesn't appear to be a header change description. Ignore it.
continue;
}
// Only keep files that properly parse.
TUs.push_back(TU);
}
return ErrorCode;
}
std::error_code
collectReplacementsFromDirectory(const llvm::StringRef Directory,
TUDiagnostics &TUs, TUReplacementFiles &TUFiles,
clang::DiagnosticsEngine &Diagnostics) {
using namespace llvm::sys::fs;
using namespace llvm::sys::path;
std::error_code ErrorCode;
for (recursive_directory_iterator I(Directory, ErrorCode), E;
I != E && !ErrorCode; I.increment(ErrorCode)) {
if (filename(I->path())[0] == '.') {
// Indicate not to descend into directories beginning with '.'
I.no_push();
continue;
}
if (extension(I->path()) != ".yaml")
continue;
TUFiles.push_back(I->path());
ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
MemoryBuffer::getFile(I->path());
if (std::error_code BufferError = Out.getError()) {
errs() << "Error reading " << I->path() << ": " << BufferError.message()
<< "\n";
continue;
}
yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
tooling::TranslationUnitDiagnostics TU;
YIn >> TU;
if (YIn.error()) {
// File doesn't appear to be a header change description. Ignore it.
continue;
}
// Only keep files that properly parse.
TUs.push_back(TU);
}
return ErrorCode;
}
/// \brief Dumps information for a sequence of conflicting Replacements.
///
/// \param[in] File FileEntry for the file the conflicting Replacements are
/// for.
/// \param[in] ConflictingReplacements List of conflicting Replacements.
/// \param[in] SM SourceManager used for reporting.
static void reportConflict(
const FileEntry *File,
const llvm::ArrayRef<clang::tooling::Replacement> ConflictingReplacements,
SourceManager &SM) {
FileID FID = SM.translateFile(File);
if (FID.isInvalid())
FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User);
// FIXME: Output something a little more user-friendly (e.g. unified diff?)
errs() << "The following changes conflict:\n";
for (const tooling::Replacement &R : ConflictingReplacements) {
if (R.getLength() == 0) {
errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":"
<< SM.getColumnNumber(FID, R.getOffset()) << " "
<< R.getReplacementText() << "\n";
} else {
if (R.getReplacementText().empty())
errs() << " Remove ";
else
errs() << " Replace ";
errs() << SM.getLineNumber(FID, R.getOffset()) << ":"
<< SM.getColumnNumber(FID, R.getOffset()) << "-"
<< SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":"
<< SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1);
if (R.getReplacementText().empty())
errs() << "\n";
else
errs() << " with \"" << R.getReplacementText() << "\"\n";
}
}
}
// FIXME: Remove this function after changing clang-apply-replacements to use
// Replacements class.
bool applyAllReplacements(const std::vector<tooling::Replacement> &Replaces,
Rewriter &Rewrite) {
bool Result = true;
for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) {
if (I->isApplicable()) {
Result = I->apply(Rewrite) && Result;
} else {
Result = false;
}
}
return Result;
}
// FIXME: moved from libToolingCore. remove this when std::vector<Replacement>
// is replaced with tooling::Replacements class.
static void deduplicate(std::vector<tooling::Replacement> &Replaces,
std::vector<tooling::Range> &Conflicts) {
if (Replaces.empty())
return;
auto LessNoPath = [](const tooling::Replacement &LHS,
const tooling::Replacement &RHS) {
if (LHS.getOffset() != RHS.getOffset())
return LHS.getOffset() < RHS.getOffset();
if (LHS.getLength() != RHS.getLength())
return LHS.getLength() < RHS.getLength();
return LHS.getReplacementText() < RHS.getReplacementText();
};
auto EqualNoPath = [](const tooling::Replacement &LHS,
const tooling::Replacement &RHS) {
return LHS.getOffset() == RHS.getOffset() &&
LHS.getLength() == RHS.getLength() &&
LHS.getReplacementText() == RHS.getReplacementText();
};
// Deduplicate. We don't want to deduplicate based on the path as we assume
// that all replacements refer to the same file (or are symlinks).
std::sort(Replaces.begin(), Replaces.end(), LessNoPath);
Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath),
Replaces.end());
// Detect conflicts
tooling::Range ConflictRange(Replaces.front().getOffset(),
Replaces.front().getLength());
unsigned ConflictStart = 0;
unsigned ConflictLength = 1;
for (unsigned i = 1; i < Replaces.size(); ++i) {
tooling::Range Current(Replaces[i].getOffset(), Replaces[i].getLength());
if (ConflictRange.overlapsWith(Current)) {
// Extend conflicted range
ConflictRange =
tooling::Range(ConflictRange.getOffset(),
std::max(ConflictRange.getLength(),
Current.getOffset() + Current.getLength() -
ConflictRange.getOffset()));
++ConflictLength;
} else {
if (ConflictLength > 1)
Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength));
ConflictRange = Current;
ConflictStart = i;
ConflictLength = 1;
}
}
if (ConflictLength > 1)
Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength));
}
/// \brief Deduplicates and tests for conflicts among the replacements for each
/// file in \c Replacements. Any conflicts found are reported.
///
/// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset().
///
/// \param[in,out] Replacements Container of all replacements grouped by file
/// to be deduplicated and checked for conflicts.
/// \param[in] SM SourceManager required for conflict reporting.
///
/// \returns \parblock
/// \li true if conflicts were detected
/// \li false if no conflicts were detected
static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements,
SourceManager &SM) {
bool conflictsFound = false;
for (auto &FileAndReplacements : Replacements) {
const FileEntry *Entry = FileAndReplacements.first;
auto &Replacements = FileAndReplacements.second;
assert(Entry != nullptr && "No file entry!");
std::vector<tooling::Range> Conflicts;
deduplicate(FileAndReplacements.second, Conflicts);
if (Conflicts.empty())
continue;
conflictsFound = true;
errs() << "There are conflicting changes to " << Entry->getName() << ":\n";
for (const tooling::Range &Conflict : Conflicts) {
auto ConflictingReplacements = llvm::makeArrayRef(
&Replacements[Conflict.getOffset()], Conflict.getLength());
reportConflict(Entry, ConflictingReplacements, SM);
}
}
return conflictsFound;
}
bool mergeAndDeduplicate(const TUReplacements &TUs,
FileToReplacementsMap &GroupedReplacements,
clang::SourceManager &SM) {
// Group all replacements by target file.
std::set<StringRef> Warned;
for (const auto &TU : TUs) {
for (const tooling::Replacement &R : TU.Replacements) {
// Use the file manager to deduplicate paths. FileEntries are
// automatically canonicalized.
if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) {
GroupedReplacements[Entry].push_back(R);
} else if (Warned.insert(R.getFilePath()).second) {
errs() << "Described file '" << R.getFilePath()
<< "' doesn't exist. Ignoring...\n";
}
}
}
// Ask clang to deduplicate and report conflicts.
return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
}
bool mergeAndDeduplicate(const TUDiagnostics &TUs,
FileToReplacementsMap &GroupedReplacements,
clang::SourceManager &SM) {
// Group all replacements by target file.
std::set<StringRef> Warned;
for (const auto &TU : TUs) {
for (const auto &D : TU.Diagnostics) {
for (const auto &Fix : D.Fix) {
for (const tooling::Replacement &R : Fix.second) {
// Use the file manager to deduplicate paths. FileEntries are
// automatically canonicalized.
if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) {
GroupedReplacements[Entry].push_back(R);
} else if (Warned.insert(R.getFilePath()).second) {
errs() << "Described file '" << R.getFilePath()
<< "' doesn't exist. Ignoring...\n";
}
}
}
}
}
// Ask clang to deduplicate and report conflicts.
return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
}
bool applyReplacements(const FileToReplacementsMap &GroupedReplacements,
clang::Rewriter &Rewrites) {
// Apply all changes
//
// FIXME: No longer certain GroupedReplacements is really the best kind of
// data structure for applying replacements. Rewriter certainly doesn't care.
// However, until we nail down the design of ReplacementGroups, might as well
// leave this as is.
for (const auto &FileAndReplacements : GroupedReplacements) {
if (!applyAllReplacements(FileAndReplacements.second, Rewrites))
return false;
}
return true;
}
RangeVector calculateChangedRanges(
const std::vector<clang::tooling::Replacement> &Replaces) {
RangeVector ChangedRanges;
// Generate the new ranges from the replacements.
int Shift = 0;
for (const tooling::Replacement &R : Replaces) {
unsigned Offset = R.getOffset() + Shift;
unsigned Length = R.getReplacementText().size();
Shift += Length - R.getLength();
ChangedRanges.push_back(tooling::Range(Offset, Length));
}
return ChangedRanges;
}
bool writeFiles(const clang::Rewriter &Rewrites) {
for (auto BufferI = Rewrites.buffer_begin(), BufferE = Rewrites.buffer_end();
BufferI != BufferE; ++BufferI) {
StringRef FileName =
Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName();
std::error_code EC;
llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text);
if (EC) {
errs() << "Warning: Could not write to " << EC.message() << "\n";
continue;
}
BufferI->second.write(FileStream);
}
return true;
}
bool deleteReplacementFiles(const TUReplacementFiles &Files,
clang::DiagnosticsEngine &Diagnostics) {
bool Success = true;
for (const auto &Filename : Files) {
std::error_code Error = llvm::sys::fs::remove(Filename);
if (Error) {
Success = false;
// FIXME: Use Diagnostics for outputting errors.
errs() << "Error deleting file: " << Filename << "\n";
errs() << Error.message() << "\n";
errs() << "Please delete the file manually\n";
}
}
return Success;
}
} // end namespace replace
} // end namespace clang

View File

@ -0,0 +1,18 @@
set(LLVM_LINK_COMPONENTS
Support
)
add_clang_executable(clang-apply-replacements
ClangApplyReplacementsMain.cpp
)
target_link_libraries(clang-apply-replacements
PRIVATE
clangApplyReplacements
clangBasic
clangFormat
clangRewrite
clangToolingCore
)
install(TARGETS clang-apply-replacements
RUNTIME DESTINATION bin)

View File

@ -0,0 +1,290 @@
//===-- ClangApplyReplacementsMain.cpp - Main file for the tool -----------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file provides the main function for the
/// clang-apply-replacements tool.
///
//===----------------------------------------------------------------------===//
#include "clang-apply-replacements/Tooling/ApplyReplacements.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Version.h"
#include "clang/Format/Format.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/CommandLine.h"
using namespace llvm;
using namespace clang;
using namespace clang::replace;
static cl::opt<std::string> Directory(cl::Positional, cl::Required,
cl::desc("<Search Root Directory>"));
static cl::OptionCategory ReplacementCategory("Replacement Options");
static cl::OptionCategory FormattingCategory("Formatting Options");
const cl::OptionCategory *VisibleCategories[] = {&ReplacementCategory,
&FormattingCategory};
static cl::opt<bool> RemoveTUReplacementFiles(
"remove-change-desc-files",
cl::desc("Remove the change description files regardless of successful\n"
"merging/replacing."),
cl::init(false), cl::cat(ReplacementCategory));
static cl::opt<bool> DoFormat(
"format",
cl::desc("Enable formatting of code changed by applying replacements.\n"
"Use -style to choose formatting style.\n"),
cl::cat(FormattingCategory));
// FIXME: Consider making the default behaviour for finding a style
// configuration file to start the search anew for every file being changed to
// handle situations where the style is different for different parts of a
// project.
static cl::opt<std::string> FormatStyleConfig(
"style-config",
cl::desc("Path to a directory containing a .clang-format file\n"
"describing a formatting style to use for formatting\n"
"code when -style=file.\n"),
cl::init(""), cl::cat(FormattingCategory));
static cl::opt<std::string>
FormatStyleOpt("style", cl::desc(format::StyleOptionHelpDescription),
cl::init("LLVM"), cl::cat(FormattingCategory));
namespace {
// Helper object to remove the TUReplacement and TUDiagnostic (triggered by
// "remove-change-desc-files" command line option) when exiting current scope.
class ScopedFileRemover {
public:
ScopedFileRemover(const TUReplacementFiles &Files,
clang::DiagnosticsEngine &Diagnostics)
: TURFiles(Files), Diag(Diagnostics) {}
~ScopedFileRemover() { deleteReplacementFiles(TURFiles, Diag); }
private:
const TUReplacementFiles &TURFiles;
clang::DiagnosticsEngine &Diag;
};
} // namespace
static void printVersion(raw_ostream &OS) {
OS << "clang-apply-replacements version " CLANG_VERSION_STRING << "\n";
}
/// \brief Convenience function to get rewritten content for \c Filename from
/// \c Rewrites.
///
/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath().
/// \post Replacements.empty() -> Result.empty()
///
/// \param[in] Replacements Replacements to apply
/// \param[in] Rewrites Rewriter to use to apply replacements.
/// \param[out] Result Contents of the file after applying replacements if
/// replacements were provided.
///
/// \returns \parblock
/// \li true if all replacements were applied successfully.
/// \li false if at least one replacement failed to apply.
static bool
getRewrittenData(const std::vector<tooling::Replacement> &Replacements,
Rewriter &Rewrites, std::string &Result) {
if (Replacements.empty())
return true;
if (!applyAllReplacements(Replacements, Rewrites))
return false;
SourceManager &SM = Rewrites.getSourceMgr();
FileManager &Files = SM.getFileManager();
StringRef FileName = Replacements.begin()->getFilePath();
const clang::FileEntry *Entry = Files.getFile(FileName);
assert(Entry && "Expected an existing file");
FileID ID = SM.translateFile(Entry);
assert(ID.isValid() && "Expected a valid FileID");
const RewriteBuffer *Buffer = Rewrites.getRewriteBufferFor(ID);
Result = std::string(Buffer->begin(), Buffer->end());
return true;
}
/// \brief Apply \c Replacements and return the new file contents.
///
/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath().
/// \post Replacements.empty() -> Result.empty()
///
/// \param[in] Replacements Replacements to apply.
/// \param[out] Result Contents of the file after applying replacements if
/// replacements were provided.
/// \param[in] Diagnostics For diagnostic output.
///
/// \returns \parblock
/// \li true if all replacements applied successfully.
/// \li false if at least one replacement failed to apply.
static bool
applyReplacements(const std::vector<tooling::Replacement> &Replacements,
std::string &Result, DiagnosticsEngine &Diagnostics) {
FileManager Files((FileSystemOptions()));
SourceManager SM(Diagnostics, Files);
Rewriter Rewrites(SM, LangOptions());
return getRewrittenData(Replacements, Rewrites, Result);
}
/// \brief Apply code formatting to all places where replacements were made.
///
/// \pre !Replacements.empty().
/// \pre Replacements[i].getFilePath() == Replacements[i+1].getFilePath().
/// \pre Replacements[i].getOffset() <= Replacements[i+1].getOffset().
///
/// \param[in] Replacements Replacements that were made to the file. Provided
/// to indicate where changes were made.
/// \param[in] FileData The contents of the file \b after \c Replacements have
/// been applied.
/// \param[out] FormattedFileData The contents of the file after reformatting.
/// \param[in] FormatStyle Style to apply.
/// \param[in] Diagnostics For diagnostic output.
///
/// \returns \parblock
/// \li true if reformatting replacements were all successfully
/// applied.
/// \li false if at least one reformatting replacement failed to apply.
static bool
applyFormatting(const std::vector<tooling::Replacement> &Replacements,
const StringRef FileData, std::string &FormattedFileData,
const format::FormatStyle &FormatStyle,
DiagnosticsEngine &Diagnostics) {
assert(!Replacements.empty() && "Need at least one replacement");
RangeVector Ranges = calculateChangedRanges(Replacements);
StringRef FileName = Replacements.begin()->getFilePath();
tooling::Replacements R =
format::reformat(FormatStyle, FileData, Ranges, FileName);
// FIXME: Remove this copy when tooling::Replacements is implemented as a
// vector instead of a set.
std::vector<tooling::Replacement> FormattingReplacements;
std::copy(R.begin(), R.end(), back_inserter(FormattingReplacements));
if (FormattingReplacements.empty()) {
FormattedFileData = FileData;
return true;
}
FileManager Files((FileSystemOptions()));
SourceManager SM(Diagnostics, Files);
SM.overrideFileContents(Files.getFile(FileName),
llvm::MemoryBuffer::getMemBufferCopy(FileData));
Rewriter Rewrites(SM, LangOptions());
return getRewrittenData(FormattingReplacements, Rewrites, FormattedFileData);
}
int main(int argc, char **argv) {
cl::HideUnrelatedOptions(makeArrayRef(VisibleCategories));
cl::SetVersionPrinter(printVersion);
cl::ParseCommandLineOptions(argc, argv);
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
DiagnosticsEngine Diagnostics(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), DiagOpts.get());
// Determine a formatting style from options.
format::FormatStyle FormatStyle;
if (DoFormat) {
auto FormatStyleOrError =
format::getStyle(FormatStyleOpt, FormatStyleConfig, "LLVM");
if (!FormatStyleOrError) {
llvm::errs() << llvm::toString(FormatStyleOrError.takeError()) << "\n";
return 1;
}
FormatStyle = *FormatStyleOrError;
}
TUReplacements TURs;
TUReplacementFiles TUFiles;
std::error_code ErrorCode =
collectReplacementsFromDirectory(Directory, TURs, TUFiles, Diagnostics);
TUDiagnostics TUDs;
TUFiles.clear();
ErrorCode =
collectReplacementsFromDirectory(Directory, TUDs, TUFiles, Diagnostics);
if (ErrorCode) {
errs() << "Trouble iterating over directory '" << Directory
<< "': " << ErrorCode.message() << "\n";
return 1;
}
// Remove the TUReplacementFiles (triggered by "remove-change-desc-files"
// command line option) when exiting main().
std::unique_ptr<ScopedFileRemover> Remover;
if (RemoveTUReplacementFiles)
Remover.reset(new ScopedFileRemover(TUFiles, Diagnostics));
FileManager Files((FileSystemOptions()));
SourceManager SM(Diagnostics, Files);
FileToReplacementsMap GroupedReplacements;
if (!mergeAndDeduplicate(TURs, GroupedReplacements, SM))
return 1;
if (!mergeAndDeduplicate(TUDs, GroupedReplacements, SM))
return 1;
Rewriter ReplacementsRewriter(SM, LangOptions());
for (const auto &FileAndReplacements : GroupedReplacements) {
// This shouldn't happen but if a file somehow has no replacements skip to
// next file.
if (FileAndReplacements.second.empty())
continue;
std::string NewFileData;
StringRef FileName = FileAndReplacements.first->getName();
if (!applyReplacements(FileAndReplacements.second, NewFileData,
Diagnostics)) {
errs() << "Failed to apply replacements to " << FileName << "\n";
continue;
}
// Apply formatting if requested.
if (DoFormat &&
!applyFormatting(FileAndReplacements.second, NewFileData, NewFileData,
FormatStyle, Diagnostics)) {
errs() << "Failed to apply reformatting replacements for " << FileName
<< "\n";
continue;
}
// Write new file to disk
std::error_code EC;
llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_None);
if (EC) {
llvm::errs() << "Could not open " << FileName << " for writing\n";
continue;
}
FileStream << NewFileData;
}
return 0;
}

View File

@ -0,0 +1,21 @@
set(LLVM_LINK_COMPONENTS
support
)
add_clang_library(clangMove
ClangMove.cpp
HelperDeclRefGraph.cpp
LINK_LIBS
clangAnalysis
clangAST
clangASTMatchers
clangBasic
clangFormat
clangFrontend
clangLex
clangTooling
clangToolingCore
)
add_subdirectory(tool)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,229 @@
//===-- ClangMove.h - Clang move -----------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H
#include "HelperDeclRefGraph.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/StringMap.h"
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace clang {
namespace move {
// A reporter which collects and reports declarations in old header.
class DeclarationReporter {
public:
DeclarationReporter() = default;
~DeclarationReporter() = default;
void reportDeclaration(llvm::StringRef DeclarationName,
llvm::StringRef Type) {
DeclarationList.emplace_back(DeclarationName, Type);
};
// A <DeclarationName, DeclarationKind> pair.
// The DeclarationName is a fully qualified name for the declaration, like
// A::B::Foo. The DeclarationKind is a string represents the kind of the
// declaration, currently only "Function" and "Class" are supported.
typedef std::pair<std::string, std::string> DeclarationPair;
const std::vector<DeclarationPair> getDeclarationList() const {
return DeclarationList;
}
private:
std::vector<DeclarationPair> DeclarationList;
};
// Specify declarations being moved. It contains all information of the moved
// declarations.
struct MoveDefinitionSpec {
// The list of fully qualified names, e.g. Foo, a::Foo, b::Foo.
SmallVector<std::string, 4> Names;
// The file path of old header, can be relative path and absolute path.
std::string OldHeader;
// The file path of old cc, can be relative path and absolute path.
std::string OldCC;
// The file path of new header, can be relative path and absolute path.
std::string NewHeader;
// The file path of new cc, can be relative path and absolute path.
std::string NewCC;
// Whether old.h depends on new.h. If true, #include "new.h" will be added
// in old.h.
bool OldDependOnNew = false;
// Whether new.h depends on old.h. If true, #include "old.h" will be added
// in new.h.
bool NewDependOnOld = false;
};
// A Context which contains extra options which are used in ClangMoveTool.
struct ClangMoveContext {
MoveDefinitionSpec Spec;
// The Key is file path, value is the replacements being applied to the file.
std::map<std::string, tooling::Replacements> &FileToReplacements;
// The original working directory where the local clang-move binary runs.
//
// clang-move will change its current working directory to the build
// directory when analyzing the source file. We save the original working
// directory in order to get the absolute file path for the fields in Spec.
std::string OriginalRunningDirectory;
// The name of a predefined code style.
std::string FallbackStyle;
// Whether dump all declarations in old header.
bool DumpDeclarations;
};
// This tool is used to move class/function definitions from the given source
// files (old.h/cc) to new files (new.h/cc).
// The goal of this tool is to make the new/old files as compilable as possible.
//
// When moving a symbol,all used helper declarations (e.g. static
// functions/variables definitions in global/named namespace,
// functions/variables/classes definitions in anonymous namespace) used by the
// moved symbol in old.cc are moved to the new.cc. In addition, all
// using-declarations in old.cc are also moved to new.cc; forward class
// declarations in old.h are also moved to new.h.
//
// The remaining helper declarations which are unused by non-moved symbols in
// old.cc will be removed.
//
// Note: When all declarations in old header are being moved, all code in
// old.h/cc will be moved, which means old.h/cc are empty. This ignores symbols
// that are not supported (e.g. typedef and enum) so that we always move old
// files to new files when all symbols produced from dump_decls are moved.
class ClangMoveTool : public ast_matchers::MatchFinder::MatchCallback {
public:
ClangMoveTool(ClangMoveContext *const Context,
DeclarationReporter *const Reporter);
void registerMatchers(ast_matchers::MatchFinder *Finder);
void run(const ast_matchers::MatchFinder::MatchResult &Result) override;
void onEndOfTranslationUnit() override;
/// Add #includes from old.h/cc files.
///
/// \param IncludeHeader The name of the file being included, as written in
/// the source code.
/// \param IsAngled Whether the file name was enclosed in angle brackets.
/// \param SearchPath The search path which was used to find the IncludeHeader
/// in the file system. It can be a relative path or an absolute path.
/// \param FileName The name of file where the IncludeHeader comes from.
/// \param IncludeFilenameRange The source range for the written file name in
/// #include (i.e. "old.h" for #include "old.h") in old.cc.
/// \param SM The SourceManager.
void addIncludes(llvm::StringRef IncludeHeader, bool IsAngled,
llvm::StringRef SearchPath, llvm::StringRef FileName,
clang::CharSourceRange IncludeFilenameRange,
const SourceManager &SM);
std::vector<const NamedDecl *> &getMovedDecls() { return MovedDecls; }
/// Add declarations being removed from old.h/cc. For each declarations, the
/// method also records the mapping relationship between the corresponding
/// FilePath and its FileID.
void addRemovedDecl(const NamedDecl *Decl);
llvm::SmallPtrSet<const NamedDecl *, 8> &getUnremovedDeclsInOldHeader() {
return UnremovedDeclsInOldHeader;
}
private:
// Make the Path absolute using the OrignalRunningDirectory if the Path is not
// an absolute path. An empty Path will result in an empty string.
std::string makeAbsolutePath(StringRef Path);
void removeDeclsInOldFiles();
void moveDeclsToNewFiles();
void moveAll(SourceManager& SM, StringRef OldFile, StringRef NewFile);
// Stores all MatchCallbacks created by this tool.
std::vector<std::unique_ptr<ast_matchers::MatchFinder::MatchCallback>>
MatchCallbacks;
// Store all potential declarations (decls being moved, forward decls) that
// might need to move to new.h/cc. It includes all helper declarations
// (include unused ones) by default. The unused ones will be filtered out in
// the last stage. Saving in an AST-visited order.
std::vector<const NamedDecl *> MovedDecls;
// The declarations that needs to be removed in old.cc/h.
std::vector<const NamedDecl *> RemovedDecls;
// The #includes in old_header.h.
std::vector<std::string> HeaderIncludes;
// The #includes in old_cc.cc.
std::vector<std::string> CCIncludes;
// Records all helper declarations (function/variable/class definitions in
// anonymous namespaces, static function/variable definitions in global/named
// namespaces) in old.cc. saving in an AST-visited order.
std::vector<const NamedDecl *> HelperDeclarations;
// The unmoved named declarations in old header.
llvm::SmallPtrSet<const NamedDecl*, 8> UnremovedDeclsInOldHeader;
/// The source range for the written file name in #include (i.e. "old.h" for
/// #include "old.h") in old.cc, including the enclosing quotes or angle
/// brackets.
clang::CharSourceRange OldHeaderIncludeRange;
/// Mapping from FilePath to FileID, which can be used in post processes like
/// cleanup around replacements.
llvm::StringMap<FileID> FilePathToFileID;
/// A context contains all running options. It is not owned.
ClangMoveContext *const Context;
/// A reporter to report all declarations from old header. It is not owned.
DeclarationReporter *const Reporter;
/// Builder for helper declarations reference graph.
HelperDeclRGBuilder RGBuilder;
};
class ClangMoveAction : public clang::ASTFrontendAction {
public:
ClangMoveAction(ClangMoveContext *const Context,
DeclarationReporter *const Reporter)
: MoveTool(Context, Reporter) {
MoveTool.registerMatchers(&MatchFinder);
}
~ClangMoveAction() override = default;
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance &Compiler,
llvm::StringRef InFile) override;
private:
ast_matchers::MatchFinder MatchFinder;
ClangMoveTool MoveTool;
};
class ClangMoveActionFactory : public tooling::FrontendActionFactory {
public:
ClangMoveActionFactory(ClangMoveContext *const Context,
DeclarationReporter *const Reporter = nullptr)
: Context(Context), Reporter(Reporter) {}
clang::FrontendAction *create() override {
return new ClangMoveAction(Context, Reporter);
}
private:
// Not owned.
ClangMoveContext *const Context;
DeclarationReporter *const Reporter;
};
} // namespace move
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_MOVE_CLANGMOVE_H

View File

@ -0,0 +1,137 @@
//===-- UsedHelperDeclFinder.cpp - AST-based call graph for helper decls --===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "HelperDeclRefGraph.h"
#include "ClangMove.h"
#include "clang/AST/Decl.h"
#include "llvm/Support/Debug.h"
#include <vector>
#define DEBUG_TYPE "clang-move"
namespace clang {
namespace move {
void HelperDeclRefGraph::print(raw_ostream &OS) const {
OS << " --- Call graph Dump --- \n";
for (auto I = DeclMap.begin(); I != DeclMap.end(); ++I) {
const CallGraphNode *N = (I->second).get();
OS << " Declarations: ";
N->print(OS);
OS << " (" << N << ") ";
OS << " calls: ";
for (auto CI = N->begin(), CE = N->end(); CI != CE; ++CI) {
(*CI)->print(OS);
OS << " (" << CI << ") ";
}
OS << '\n';
}
OS.flush();
}
void HelperDeclRefGraph::addEdge(const Decl *Caller, const Decl *Callee) {
assert(Caller);
assert(Callee);
// Ignore the case where Caller equals Callee. This happens in the static
// class member definitions in global namespace like "int CLASS::static_var =
// 1;", its DC is a VarDel whose outmost enclosing declaration is the "CLASS"
// CXXRecordDecl.
if (Caller == Callee) return;
// Allocate a new node, mark it as root, and process it's calls.
CallGraphNode *CallerNode = getOrInsertNode(const_cast<Decl *>(Caller));
CallGraphNode *CalleeNode = getOrInsertNode(const_cast<Decl *>(Callee));
CallerNode->addCallee(CalleeNode);
}
void HelperDeclRefGraph::dump() const { print(llvm::errs()); }
CallGraphNode *HelperDeclRefGraph::getOrInsertNode(Decl *F) {
F = F->getCanonicalDecl();
std::unique_ptr<CallGraphNode> &Node = DeclMap[F];
if (Node)
return Node.get();
Node = llvm::make_unique<CallGraphNode>(F);
return Node.get();
}
CallGraphNode *HelperDeclRefGraph::getNode(const Decl *D) const {
auto I = DeclMap.find(D->getCanonicalDecl());
return I == DeclMap.end() ? nullptr : I->second.get();
}
llvm::DenseSet<const CallGraphNode *>
HelperDeclRefGraph::getReachableNodes(const Decl *Root) const {
const auto *RootNode = getNode(Root);
if (!RootNode)
return {};
llvm::DenseSet<const CallGraphNode *> ConnectedNodes;
std::function<void(const CallGraphNode *)> VisitNode =
[&](const CallGraphNode *Node) {
if (ConnectedNodes.count(Node))
return;
ConnectedNodes.insert(Node);
for (auto It = Node->begin(), End = Node->end(); It != End; ++It)
VisitNode(*It);
};
VisitNode(RootNode);
return ConnectedNodes;
}
const Decl *HelperDeclRGBuilder::getOutmostClassOrFunDecl(const Decl *D) {
const auto *DC = D->getDeclContext();
const auto *Result = D;
while (DC) {
if (const auto *RD = dyn_cast<CXXRecordDecl>(DC))
Result = RD;
else if (const auto *FD = dyn_cast<FunctionDecl>(DC))
Result = FD;
DC = DC->getParent();
}
return Result;
}
void HelperDeclRGBuilder::run(
const ast_matchers::MatchFinder::MatchResult &Result) {
// Construct the graph by adding a directed edge from caller to callee.
//
// "dc" is the closest ancestor declaration of "func_ref" or "used_class", it
// might be not the targetted Caller Decl, we always use the outmost enclosing
// FunctionDecl/CXXRecordDecl of "dc". For example,
//
// int MoveClass::F() { int a = helper(); return a; }
//
// The matched "dc" of "helper" DeclRefExpr is a VarDecl, we traverse up AST
// to find the outmost "MoveClass" CXXRecordDecl and use it as Caller.
if (const auto *FuncRef = Result.Nodes.getNodeAs<DeclRefExpr>("func_ref")) {
const auto *DC = Result.Nodes.getNodeAs<Decl>("dc");
assert(DC);
DEBUG(llvm::dbgs() << "Find helper function usage: "
<< FuncRef->getDecl()->getNameAsString() << " ("
<< FuncRef->getDecl() << ")\n");
RG->addEdge(
getOutmostClassOrFunDecl(DC->getCanonicalDecl()),
getOutmostClassOrFunDecl(FuncRef->getDecl()->getCanonicalDecl()));
} else if (const auto *UsedClass =
Result.Nodes.getNodeAs<CXXRecordDecl>("used_class")) {
const auto *DC = Result.Nodes.getNodeAs<Decl>("dc");
assert(DC);
DEBUG(llvm::dbgs() << "Find helper class usage: "
<< UsedClass->getNameAsString() << " (" << UsedClass
<< ")\n");
RG->addEdge(getOutmostClassOrFunDecl(DC->getCanonicalDecl()), UsedClass);
}
}
} // namespace move
} // namespace clang

Some files were not shown because too many files have changed in this diff Show More