diff options
Diffstat (limited to 'lib/Tooling')
-rw-r--r-- | lib/Tooling/ArgumentsAdjusters.cpp | 83 | ||||
-rw-r--r-- | lib/Tooling/CMakeLists.txt | 3 | ||||
-rw-r--r-- | lib/Tooling/CommonOptionsParser.cpp | 63 | ||||
-rw-r--r-- | lib/Tooling/CompilationDatabase.cpp | 38 | ||||
-rw-r--r-- | lib/Tooling/Core/CMakeLists.txt | 10 | ||||
-rw-r--r-- | lib/Tooling/Core/Makefile | 13 | ||||
-rw-r--r-- | lib/Tooling/Core/Replacement.cpp | 289 | ||||
-rw-r--r-- | lib/Tooling/JSONCompilationDatabase.cpp | 18 | ||||
-rw-r--r-- | lib/Tooling/Makefile | 1 | ||||
-rw-r--r-- | lib/Tooling/Refactoring.cpp | 246 | ||||
-rw-r--r-- | lib/Tooling/Tooling.cpp | 162 |
11 files changed, 543 insertions, 383 deletions
diff --git a/lib/Tooling/ArgumentsAdjusters.cpp b/lib/Tooling/ArgumentsAdjusters.cpp index a69971e..5a67db0 100644 --- a/lib/Tooling/ArgumentsAdjusters.cpp +++ b/lib/Tooling/ArgumentsAdjusters.cpp @@ -19,39 +19,66 @@ namespace clang { namespace tooling { -void ArgumentsAdjuster::anchor() { +/// Add -fsyntax-only option to the commnand line arguments. +ArgumentsAdjuster getClangSyntaxOnlyAdjuster() { + return [](const CommandLineArguments &Args) { + CommandLineArguments AdjustedArgs; + for (size_t i = 0, e = Args.size(); i != e; ++i) { + StringRef Arg = Args[i]; + // FIXME: Remove options that generate output. + if (!Arg.startswith("-fcolor-diagnostics") && + !Arg.startswith("-fdiagnostics-color")) + AdjustedArgs.push_back(Args[i]); + } + AdjustedArgs.push_back("-fsyntax-only"); + return AdjustedArgs; + }; } -/// Add -fsyntax-only option to the commnand line arguments. -CommandLineArguments -ClangSyntaxOnlyAdjuster::Adjust(const CommandLineArguments &Args) { - CommandLineArguments AdjustedArgs; - for (size_t i = 0, e = Args.size(); i != e; ++i) { - StringRef Arg = Args[i]; - // FIXME: Remove options that generate output. - if (!Arg.startswith("-fcolor-diagnostics") && - !Arg.startswith("-fdiagnostics-color")) - AdjustedArgs.push_back(Args[i]); - } - AdjustedArgs.push_back("-fsyntax-only"); - return AdjustedArgs; +ArgumentsAdjuster getClangStripOutputAdjuster() { + return [](const CommandLineArguments &Args) { + CommandLineArguments AdjustedArgs; + for (size_t i = 0, e = Args.size(); i < e; ++i) { + StringRef Arg = Args[i]; + if (!Arg.startswith("-o")) + AdjustedArgs.push_back(Args[i]); + + if (Arg == "-o") { + // Output is specified as -o foo. Skip the next argument also. + ++i; + } + // Else, the output is specified as -ofoo. Just do nothing. + } + return AdjustedArgs; + }; } -CommandLineArguments -ClangStripOutputAdjuster::Adjust(const CommandLineArguments &Args) { - CommandLineArguments AdjustedArgs; - for (size_t i = 0, e = Args.size(); i < e; ++i) { - StringRef Arg = Args[i]; - if(!Arg.startswith("-o")) - AdjustedArgs.push_back(Args[i]); - - if(Arg == "-o") { - // Output is specified as -o foo. Skip the next argument also. - ++i; +ArgumentsAdjuster getInsertArgumentAdjuster(const CommandLineArguments &Extra, + ArgumentInsertPosition Pos) { + return [Extra, Pos](const CommandLineArguments &Args) { + CommandLineArguments Return(Args); + + CommandLineArguments::iterator I; + if (Pos == ArgumentInsertPosition::END) { + I = Return.end(); + } else { + I = Return.begin(); + ++I; // To leave the program name in place } - // Else, the output is specified as -ofoo. Just do nothing. - } - return AdjustedArgs; + + Return.insert(I, Extra.begin(), Extra.end()); + return Return; + }; +} + +ArgumentsAdjuster getInsertArgumentAdjuster(const char *Extra, + ArgumentInsertPosition Pos) { + return getInsertArgumentAdjuster(CommandLineArguments(1, Extra), Pos); +} + +ArgumentsAdjuster combineAdjusters(ArgumentsAdjuster First, + ArgumentsAdjuster Second) { + return std::bind(Second, std::bind(First, std::placeholders::_1)); } } // end namespace tooling diff --git a/lib/Tooling/CMakeLists.txt b/lib/Tooling/CMakeLists.txt index 2bf9652..b5c3d54 100644 --- a/lib/Tooling/CMakeLists.txt +++ b/lib/Tooling/CMakeLists.txt @@ -1,5 +1,7 @@ set(LLVM_LINK_COMPONENTS support) +add_subdirectory(Core) + add_clang_library(clangTooling ArgumentsAdjusters.cpp CommonOptionsParser.cpp @@ -18,4 +20,5 @@ add_clang_library(clangTooling clangFrontend clangLex clangRewrite + clangToolingCore ) diff --git a/lib/Tooling/CommonOptionsParser.cpp b/lib/Tooling/CommonOptionsParser.cpp index e0b844c..91c74a4 100644 --- a/lib/Tooling/CommonOptionsParser.cpp +++ b/lib/Tooling/CommonOptionsParser.cpp @@ -25,6 +25,7 @@ //===----------------------------------------------------------------------===// #include "llvm/Support/CommandLine.h" +#include "clang/Tooling/ArgumentsAdjusters.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Tooling.h" @@ -53,6 +54,42 @@ const char *const CommonOptionsParser::HelpMessage = "\tsuffix of a path in the compile command database.\n" "\n"; +class ArgumentsAdjustingCompilations : public CompilationDatabase { +public: + ArgumentsAdjustingCompilations( + std::unique_ptr<CompilationDatabase> Compilations) + : Compilations(std::move(Compilations)) {} + + void appendArgumentsAdjuster(ArgumentsAdjuster Adjuster) { + Adjusters.push_back(Adjuster); + } + + std::vector<CompileCommand> + getCompileCommands(StringRef FilePath) const override { + return adjustCommands(Compilations->getCompileCommands(FilePath)); + } + + std::vector<std::string> getAllFiles() const override { + return Compilations->getAllFiles(); + } + + std::vector<CompileCommand> getAllCompileCommands() const override { + return adjustCommands(Compilations->getAllCompileCommands()); + } + +private: + std::unique_ptr<CompilationDatabase> Compilations; + std::vector<ArgumentsAdjuster> Adjusters; + + std::vector<CompileCommand> + adjustCommands(std::vector<CompileCommand> Commands) const { + for (CompileCommand &Command : Commands) + for (const auto &Adjuster : Adjusters) + Command.CommandLine = Adjuster(Command.CommandLine); + return Commands; + } +}; + CommonOptionsParser::CommonOptionsParser(int &argc, const char **argv, cl::OptionCategory &Category, const char *Overview) { @@ -65,6 +102,16 @@ CommonOptionsParser::CommonOptionsParser(int &argc, const char **argv, cl::Positional, cl::desc("<source0> [... <sourceN>]"), cl::OneOrMore, cl::cat(Category)); + static cl::list<std::string> ArgsAfter( + "extra-arg", + cl::desc("Additional argument to append to the compiler command line"), + cl::cat(Category)); + + static cl::list<std::string> ArgsBefore( + "extra-arg-before", + cl::desc("Additional argument to prepend to the compiler command line"), + cl::cat(Category)); + // Hide unrelated options. StringMap<cl::Option*> Options; cl::getRegisteredOptions(Options); @@ -82,13 +129,21 @@ CommonOptionsParser::CommonOptionsParser(int &argc, const char **argv, if (!Compilations) { std::string ErrorMessage; if (!BuildPath.empty()) { - Compilations.reset(CompilationDatabase::autoDetectFromDirectory( - BuildPath, ErrorMessage)); + Compilations = + CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage); } else { - Compilations.reset(CompilationDatabase::autoDetectFromSource( - SourcePaths[0], ErrorMessage)); + Compilations = CompilationDatabase::autoDetectFromSource(SourcePaths[0], + ErrorMessage); } if (!Compilations) llvm::report_fatal_error(ErrorMessage); } + auto AdjustingCompilations = + llvm::make_unique<ArgumentsAdjustingCompilations>( + std::move(Compilations)); + AdjustingCompilations->appendArgumentsAdjuster( + getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN)); + AdjustingCompilations->appendArgumentsAdjuster( + getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); + Compilations = std::move(AdjustingCompilations); } diff --git a/lib/Tooling/CompilationDatabase.cpp b/lib/Tooling/CompilationDatabase.cpp index 4b776bf..7613988 100644 --- a/lib/Tooling/CompilationDatabase.cpp +++ b/lib/Tooling/CompilationDatabase.cpp @@ -35,7 +35,7 @@ namespace tooling { CompilationDatabase::~CompilationDatabase() {} -CompilationDatabase * +std::unique_ptr<CompilationDatabase> CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, std::string &ErrorMessage) { std::stringstream ErrorStream; @@ -45,17 +45,16 @@ CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, It != Ie; ++It) { std::string DatabaseErrorMessage; std::unique_ptr<CompilationDatabasePlugin> Plugin(It->instantiate()); - if (CompilationDatabase *DB = - Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage)) + if (std::unique_ptr<CompilationDatabase> DB = + Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage)) return DB; - else - ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; + ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; } ErrorMessage = ErrorStream.str(); return nullptr; } -static CompilationDatabase * +static std::unique_ptr<CompilationDatabase> findCompilationDatabaseFromDirectory(StringRef Directory, std::string &ErrorMessage) { std::stringstream ErrorStream; @@ -63,8 +62,8 @@ findCompilationDatabaseFromDirectory(StringRef Directory, while (!Directory.empty()) { std::string LoadErrorMessage; - if (CompilationDatabase *DB = - CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage)) + if (std::unique_ptr<CompilationDatabase> DB = + CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage)) return DB; if (!HasErrorMessage) { @@ -79,14 +78,14 @@ findCompilationDatabaseFromDirectory(StringRef Directory, return nullptr; } -CompilationDatabase * +std::unique_ptr<CompilationDatabase> CompilationDatabase::autoDetectFromSource(StringRef SourceFile, std::string &ErrorMessage) { SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile)); StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); - CompilationDatabase *DB = findCompilationDatabaseFromDirectory(Directory, - ErrorMessage); + std::unique_ptr<CompilationDatabase> DB = + findCompilationDatabaseFromDirectory(Directory, ErrorMessage); if (!DB) ErrorMessage = ("Could not auto-detect compilation database for file \"" + @@ -94,13 +93,13 @@ CompilationDatabase::autoDetectFromSource(StringRef SourceFile, return DB; } -CompilationDatabase * +std::unique_ptr<CompilationDatabase> CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir, std::string &ErrorMessage) { SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir)); - CompilationDatabase *DB = findCompilationDatabaseFromDirectory(AbsolutePath, - ErrorMessage); + std::unique_ptr<CompilationDatabase> DB = + findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage); if (!DB) ErrorMessage = ("Could not auto-detect compilation database from directory \"" + @@ -250,14 +249,13 @@ static bool stripPositionalArgs(std::vector<const char *> Args, CompileJobAnalyzer CompileAnalyzer; - for (driver::JobList::const_iterator I = Jobs.begin(), E = Jobs.end(); I != E; - ++I) { - if ((*I)->getKind() == driver::Job::CommandClass) { - const driver::Command *Cmd = cast<driver::Command>(*I); + for (const auto &Job : Jobs) { + if (Job.getKind() == driver::Job::CommandClass) { + const driver::Command &Cmd = cast<driver::Command>(Job); // Collect only for Assemble jobs. If we do all jobs we get duplicates // since Link jobs point to Assemble jobs as inputs. - if (Cmd->getSource().getKind() == driver::Action::AssembleJobClass) - CompileAnalyzer.run(&Cmd->getSource()); + if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass) + CompileAnalyzer.run(&Cmd.getSource()); } } diff --git a/lib/Tooling/Core/CMakeLists.txt b/lib/Tooling/Core/CMakeLists.txt new file mode 100644 index 0000000..c8c75f9 --- /dev/null +++ b/lib/Tooling/Core/CMakeLists.txt @@ -0,0 +1,10 @@ +set(LLVM_LINK_COMPONENTS support) + +add_clang_library(clangToolingCore + Replacement.cpp + + LINK_LIBS + clangBasic + clangLex + clangRewrite + ) diff --git a/lib/Tooling/Core/Makefile b/lib/Tooling/Core/Makefile new file mode 100644 index 0000000..366466c --- /dev/null +++ b/lib/Tooling/Core/Makefile @@ -0,0 +1,13 @@ +##===- clang/lib/Tooling/Core/Makefile ---------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../../.. +LIBRARYNAME := clangToolingCore + +include $(CLANG_LEVEL)/Makefile diff --git a/lib/Tooling/Core/Replacement.cpp b/lib/Tooling/Core/Replacement.cpp new file mode 100644 index 0000000..525f7df --- /dev/null +++ b/lib/Tooling/Core/Replacement.cpp @@ -0,0 +1,289 @@ +//===--- Replacement.cpp - Framework for clang refactoring tools ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements classes to support/store refactorings. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_os_ostream.h" + +namespace clang { +namespace tooling { + +static const char * const InvalidLocation = ""; + +Replacement::Replacement() + : FilePath(InvalidLocation) {} + +Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length, + StringRef ReplacementText) + : FilePath(FilePath), ReplacementRange(Offset, Length), + ReplacementText(ReplacementText) {} + +Replacement::Replacement(const SourceManager &Sources, SourceLocation Start, + unsigned Length, StringRef ReplacementText) { + setFromSourceLocation(Sources, Start, Length, ReplacementText); +} + +Replacement::Replacement(const SourceManager &Sources, + const CharSourceRange &Range, + StringRef ReplacementText) { + setFromSourceRange(Sources, Range, ReplacementText); +} + +bool Replacement::isApplicable() const { + return FilePath != InvalidLocation; +} + +bool Replacement::apply(Rewriter &Rewrite) const { + SourceManager &SM = Rewrite.getSourceMgr(); + const FileEntry *Entry = SM.getFileManager().getFile(FilePath); + if (!Entry) + return false; + FileID ID; + // FIXME: Use SM.translateFile directly. + SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1); + ID = Location.isValid() ? + SM.getFileID(Location) : + SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User); + // FIXME: We cannot check whether Offset + Length is in the file, as + // the remapping API is not public in the RewriteBuffer. + const SourceLocation Start = + SM.getLocForStartOfFile(ID). + getLocWithOffset(ReplacementRange.getOffset()); + // ReplaceText returns false on success. + // ReplaceText only fails if the source location is not a file location, in + // which case we already returned false earlier. + bool RewriteSucceeded = !Rewrite.ReplaceText( + Start, ReplacementRange.getLength(), ReplacementText); + assert(RewriteSucceeded); + return RewriteSucceeded; +} + +std::string Replacement::toString() const { + std::string result; + llvm::raw_string_ostream stream(result); + stream << FilePath << ": " << ReplacementRange.getOffset() << ":+" + << ReplacementRange.getLength() << ":\"" << ReplacementText << "\""; + return result; +} + +bool operator<(const Replacement &LHS, const Replacement &RHS) { + if (LHS.getOffset() != RHS.getOffset()) + return LHS.getOffset() < RHS.getOffset(); + if (LHS.getLength() != RHS.getLength()) + return LHS.getLength() < RHS.getLength(); + if (LHS.getFilePath() != RHS.getFilePath()) + return LHS.getFilePath() < RHS.getFilePath(); + return LHS.getReplacementText() < RHS.getReplacementText(); +} + +bool operator==(const Replacement &LHS, const Replacement &RHS) { + return LHS.getOffset() == RHS.getOffset() && + LHS.getLength() == RHS.getLength() && + LHS.getFilePath() == RHS.getFilePath() && + LHS.getReplacementText() == RHS.getReplacementText(); +} + +void Replacement::setFromSourceLocation(const SourceManager &Sources, + SourceLocation Start, unsigned Length, + StringRef ReplacementText) { + const std::pair<FileID, unsigned> DecomposedLocation = + Sources.getDecomposedLoc(Start); + const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first); + if (Entry) { + // Make FilePath absolute so replacements can be applied correctly when + // relative paths for files are used. + llvm::SmallString<256> FilePath(Entry->getName()); + std::error_code EC = llvm::sys::fs::make_absolute(FilePath); + this->FilePath = EC ? FilePath.c_str() : Entry->getName(); + } else { + this->FilePath = InvalidLocation; + } + this->ReplacementRange = Range(DecomposedLocation.second, Length); + this->ReplacementText = ReplacementText; +} + +// FIXME: This should go into the Lexer, but we need to figure out how +// to handle ranges for refactoring in general first - there is no obvious +// good way how to integrate this into the Lexer yet. +static int getRangeSize(const SourceManager &Sources, + const CharSourceRange &Range) { + SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin()); + SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd()); + std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin); + std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd); + if (Start.first != End.first) return -1; + if (Range.isTokenRange()) + End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources, + LangOptions()); + return End.second - Start.second; +} + +void Replacement::setFromSourceRange(const SourceManager &Sources, + const CharSourceRange &Range, + StringRef ReplacementText) { + setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()), + getRangeSize(Sources, Range), ReplacementText); +} + +unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { + unsigned NewPosition = Position; + for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E; + ++I) { + if (I->getOffset() >= Position) + break; + if (I->getOffset() + I->getLength() > Position) + NewPosition += I->getOffset() + I->getLength() - Position; + NewPosition += I->getReplacementText().size() - I->getLength(); + } + return NewPosition; +} + +// FIXME: Remove this function when Replacements is implemented as std::vector +// instead of std::set. +unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces, + unsigned Position) { + unsigned NewPosition = Position; + for (std::vector<Replacement>::const_iterator I = Replaces.begin(), + E = Replaces.end(); + I != E; ++I) { + if (I->getOffset() >= Position) + break; + if (I->getOffset() + I->getLength() > Position) + NewPosition += I->getOffset() + I->getLength() - Position; + NewPosition += I->getReplacementText().size() - I->getLength(); + } + return NewPosition; +} + +void deduplicate(std::vector<Replacement> &Replaces, + std::vector<Range> &Conflicts) { + if (Replaces.empty()) + return; + + auto LessNoPath = [](const Replacement &LHS, const 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 Replacement &LHS, const 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 + Range ConflictRange(Replaces.front().getOffset(), + Replaces.front().getLength()); + unsigned ConflictStart = 0; + unsigned ConflictLength = 1; + for (unsigned i = 1; i < Replaces.size(); ++i) { + Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); + if (ConflictRange.overlapsWith(Current)) { + // Extend conflicted range + ConflictRange = Range(ConflictRange.getOffset(), + std::max(ConflictRange.getLength(), + Current.getOffset() + Current.getLength() - + ConflictRange.getOffset())); + ++ConflictLength; + } else { + if (ConflictLength > 1) + Conflicts.push_back(Range(ConflictStart, ConflictLength)); + ConflictRange = Current; + ConflictStart = i; + ConflictLength = 1; + } + } + + if (ConflictLength > 1) + Conflicts.push_back(Range(ConflictStart, ConflictLength)); +} + +bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) { + bool Result = true; + for (Replacements::const_iterator I = Replaces.begin(), + E = Replaces.end(); + I != E; ++I) { + if (I->isApplicable()) { + Result = I->apply(Rewrite) && Result; + } else { + Result = false; + } + } + return Result; +} + +// FIXME: Remove this function when Replacements is implemented as std::vector +// instead of std::set. +bool applyAllReplacements(const std::vector<Replacement> &Replaces, + Rewriter &Rewrite) { + bool Result = true; + for (std::vector<Replacement>::const_iterator I = Replaces.begin(), + E = Replaces.end(); + I != E; ++I) { + if (I->isApplicable()) { + Result = I->apply(Rewrite) && Result; + } else { + Result = false; + } + } + return Result; +} + +std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) { + FileManager Files((FileSystemOptions())); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), + new DiagnosticOptions); + SourceManager SourceMgr(Diagnostics, Files); + Rewriter Rewrite(SourceMgr, LangOptions()); + std::unique_ptr<llvm::MemoryBuffer> Buf = + llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>"); + const clang::FileEntry *Entry = + Files.getVirtualFile("<stdin>", Buf->getBufferSize(), 0); + SourceMgr.overrideFileContents(Entry, std::move(Buf)); + FileID ID = + SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User); + for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end(); + I != E; ++I) { + Replacement Replace("<stdin>", I->getOffset(), I->getLength(), + I->getReplacementText()); + if (!Replace.apply(Rewrite)) + return ""; + } + std::string Result; + llvm::raw_string_ostream OS(Result); + Rewrite.getEditBuffer(ID).write(OS); + OS.flush(); + return Result; +} + +} // end namespace tooling +} // end namespace clang + diff --git a/lib/Tooling/JSONCompilationDatabase.cpp b/lib/Tooling/JSONCompilationDatabase.cpp index 8b8bd29..3b5f7e2 100644 --- a/lib/Tooling/JSONCompilationDatabase.cpp +++ b/lib/Tooling/JSONCompilationDatabase.cpp @@ -118,15 +118,15 @@ std::vector<std::string> unescapeCommandLine( } class JSONCompilationDatabasePlugin : public CompilationDatabasePlugin { - CompilationDatabase *loadFromDirectory(StringRef Directory, - std::string &ErrorMessage) override { + std::unique_ptr<CompilationDatabase> + loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override { SmallString<1024> JSONDatabasePath(Directory); llvm::sys::path::append(JSONDatabasePath, "compile_commands.json"); std::unique_ptr<CompilationDatabase> Database( JSONCompilationDatabase::loadFromFile(JSONDatabasePath, ErrorMessage)); if (!Database) return nullptr; - return Database.release(); + return Database; } }; @@ -141,7 +141,7 @@ X("json-compilation-database", "Reads JSON formatted compilation databases"); // and thus register the JSONCompilationDatabasePlugin. volatile int JSONAnchorSource = 0; -JSONCompilationDatabase * +std::unique_ptr<JSONCompilationDatabase> JSONCompilationDatabase::loadFromFile(StringRef FilePath, std::string &ErrorMessage) { llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> DatabaseBuffer = @@ -151,22 +151,22 @@ JSONCompilationDatabase::loadFromFile(StringRef FilePath, return nullptr; } std::unique_ptr<JSONCompilationDatabase> Database( - new JSONCompilationDatabase(DatabaseBuffer->release())); + new JSONCompilationDatabase(std::move(*DatabaseBuffer))); if (!Database->parse(ErrorMessage)) return nullptr; - return Database.release(); + return Database; } -JSONCompilationDatabase * +std::unique_ptr<JSONCompilationDatabase> JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString, std::string &ErrorMessage) { std::unique_ptr<llvm::MemoryBuffer> DatabaseBuffer( llvm::MemoryBuffer::getMemBuffer(DatabaseString)); std::unique_ptr<JSONCompilationDatabase> Database( - new JSONCompilationDatabase(DatabaseBuffer.release())); + new JSONCompilationDatabase(std::move(DatabaseBuffer))); if (!Database->parse(ErrorMessage)) return nullptr; - return Database.release(); + return Database; } std::vector<CompileCommand> diff --git a/lib/Tooling/Makefile b/lib/Tooling/Makefile index 0d2e7a2..7ea85a8 100644 --- a/lib/Tooling/Makefile +++ b/lib/Tooling/Makefile @@ -9,5 +9,6 @@ CLANG_LEVEL := ../.. LIBRARYNAME := clangTooling +PARALLEL_DIRS := Core include $(CLANG_LEVEL)/Makefile diff --git a/lib/Tooling/Refactoring.cpp b/lib/Tooling/Refactoring.cpp index c96b8c9..c817306 100644 --- a/lib/Tooling/Refactoring.cpp +++ b/lib/Tooling/Refactoring.cpp @@ -25,252 +25,6 @@ namespace clang { namespace tooling { -static const char * const InvalidLocation = ""; - -Replacement::Replacement() - : FilePath(InvalidLocation) {} - -Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length, - StringRef ReplacementText) - : FilePath(FilePath), ReplacementRange(Offset, Length), - ReplacementText(ReplacementText) {} - -Replacement::Replacement(const SourceManager &Sources, SourceLocation Start, - unsigned Length, StringRef ReplacementText) { - setFromSourceLocation(Sources, Start, Length, ReplacementText); -} - -Replacement::Replacement(const SourceManager &Sources, - const CharSourceRange &Range, - StringRef ReplacementText) { - setFromSourceRange(Sources, Range, ReplacementText); -} - -bool Replacement::isApplicable() const { - return FilePath != InvalidLocation; -} - -bool Replacement::apply(Rewriter &Rewrite) const { - SourceManager &SM = Rewrite.getSourceMgr(); - const FileEntry *Entry = SM.getFileManager().getFile(FilePath); - if (!Entry) - return false; - FileID ID; - // FIXME: Use SM.translateFile directly. - SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1); - ID = Location.isValid() ? - SM.getFileID(Location) : - SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User); - // FIXME: We cannot check whether Offset + Length is in the file, as - // the remapping API is not public in the RewriteBuffer. - const SourceLocation Start = - SM.getLocForStartOfFile(ID). - getLocWithOffset(ReplacementRange.getOffset()); - // ReplaceText returns false on success. - // ReplaceText only fails if the source location is not a file location, in - // which case we already returned false earlier. - bool RewriteSucceeded = !Rewrite.ReplaceText( - Start, ReplacementRange.getLength(), ReplacementText); - assert(RewriteSucceeded); - return RewriteSucceeded; -} - -std::string Replacement::toString() const { - std::string result; - llvm::raw_string_ostream stream(result); - stream << FilePath << ": " << ReplacementRange.getOffset() << ":+" - << ReplacementRange.getLength() << ":\"" << ReplacementText << "\""; - return result; -} - -bool operator<(const Replacement &LHS, const Replacement &RHS) { - if (LHS.getOffset() != RHS.getOffset()) - return LHS.getOffset() < RHS.getOffset(); - if (LHS.getLength() != RHS.getLength()) - return LHS.getLength() < RHS.getLength(); - if (LHS.getFilePath() != RHS.getFilePath()) - return LHS.getFilePath() < RHS.getFilePath(); - return LHS.getReplacementText() < RHS.getReplacementText(); -} - -bool operator==(const Replacement &LHS, const Replacement &RHS) { - return LHS.getOffset() == RHS.getOffset() && - LHS.getLength() == RHS.getLength() && - LHS.getFilePath() == RHS.getFilePath() && - LHS.getReplacementText() == RHS.getReplacementText(); -} - -void Replacement::setFromSourceLocation(const SourceManager &Sources, - SourceLocation Start, unsigned Length, - StringRef ReplacementText) { - const std::pair<FileID, unsigned> DecomposedLocation = - Sources.getDecomposedLoc(Start); - const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first); - if (Entry) { - // Make FilePath absolute so replacements can be applied correctly when - // relative paths for files are used. - llvm::SmallString<256> FilePath(Entry->getName()); - std::error_code EC = llvm::sys::fs::make_absolute(FilePath); - this->FilePath = EC ? FilePath.c_str() : Entry->getName(); - } else { - this->FilePath = InvalidLocation; - } - this->ReplacementRange = Range(DecomposedLocation.second, Length); - this->ReplacementText = ReplacementText; -} - -// FIXME: This should go into the Lexer, but we need to figure out how -// to handle ranges for refactoring in general first - there is no obvious -// good way how to integrate this into the Lexer yet. -static int getRangeSize(const SourceManager &Sources, - const CharSourceRange &Range) { - SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin()); - SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd()); - std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin); - std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd); - if (Start.first != End.first) return -1; - if (Range.isTokenRange()) - End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources, - LangOptions()); - return End.second - Start.second; -} - -void Replacement::setFromSourceRange(const SourceManager &Sources, - const CharSourceRange &Range, - StringRef ReplacementText) { - setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()), - getRangeSize(Sources, Range), ReplacementText); -} - -bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) { - bool Result = true; - for (Replacements::const_iterator I = Replaces.begin(), - E = Replaces.end(); - I != E; ++I) { - if (I->isApplicable()) { - Result = I->apply(Rewrite) && Result; - } else { - Result = false; - } - } - return Result; -} - -// FIXME: Remove this function when Replacements is implemented as std::vector -// instead of std::set. -bool applyAllReplacements(const std::vector<Replacement> &Replaces, - Rewriter &Rewrite) { - bool Result = true; - for (std::vector<Replacement>::const_iterator I = Replaces.begin(), - E = Replaces.end(); - I != E; ++I) { - if (I->isApplicable()) { - Result = I->apply(Rewrite) && Result; - } else { - Result = false; - } - } - return Result; -} - -std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) { - FileManager Files((FileSystemOptions())); - DiagnosticsEngine Diagnostics( - IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), - new DiagnosticOptions); - Diagnostics.setClient(new TextDiagnosticPrinter( - llvm::outs(), &Diagnostics.getDiagnosticOptions())); - SourceManager SourceMgr(Diagnostics, Files); - Rewriter Rewrite(SourceMgr, LangOptions()); - llvm::MemoryBuffer *Buf = llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>"); - const clang::FileEntry *Entry = - Files.getVirtualFile("<stdin>", Buf->getBufferSize(), 0); - SourceMgr.overrideFileContents(Entry, Buf); - FileID ID = - SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User); - for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end(); - I != E; ++I) { - Replacement Replace("<stdin>", I->getOffset(), I->getLength(), - I->getReplacementText()); - if (!Replace.apply(Rewrite)) - return ""; - } - std::string Result; - llvm::raw_string_ostream OS(Result); - Rewrite.getEditBuffer(ID).write(OS); - OS.flush(); - return Result; -} - -unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { - unsigned NewPosition = Position; - for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E; - ++I) { - if (I->getOffset() >= Position) - break; - if (I->getOffset() + I->getLength() > Position) - NewPosition += I->getOffset() + I->getLength() - Position; - NewPosition += I->getReplacementText().size() - I->getLength(); - } - return NewPosition; -} - -// FIXME: Remove this function when Replacements is implemented as std::vector -// instead of std::set. -unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces, - unsigned Position) { - unsigned NewPosition = Position; - for (std::vector<Replacement>::const_iterator I = Replaces.begin(), - E = Replaces.end(); - I != E; ++I) { - if (I->getOffset() >= Position) - break; - if (I->getOffset() + I->getLength() > Position) - NewPosition += I->getOffset() + I->getLength() - Position; - NewPosition += I->getReplacementText().size() - I->getLength(); - } - return NewPosition; -} - -void deduplicate(std::vector<Replacement> &Replaces, - std::vector<Range> &Conflicts) { - if (Replaces.empty()) - return; - - // Deduplicate - std::sort(Replaces.begin(), Replaces.end()); - std::vector<Replacement>::iterator End = - std::unique(Replaces.begin(), Replaces.end()); - Replaces.erase(End, Replaces.end()); - - // Detect conflicts - Range ConflictRange(Replaces.front().getOffset(), - Replaces.front().getLength()); - unsigned ConflictStart = 0; - unsigned ConflictLength = 1; - for (unsigned i = 1; i < Replaces.size(); ++i) { - Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); - if (ConflictRange.overlapsWith(Current)) { - // Extend conflicted range - ConflictRange = Range(ConflictRange.getOffset(), - std::max(ConflictRange.getLength(), - Current.getOffset() + Current.getLength() - - ConflictRange.getOffset())); - ++ConflictLength; - } else { - if (ConflictLength > 1) - Conflicts.push_back(Range(ConflictStart, ConflictLength)); - ConflictRange = Current; - ConflictStart = i; - ConflictLength = 1; - } - } - - if (ConflictLength > 1) - Conflicts.push_back(Range(ConflictStart, ConflictLength)); -} - - RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations, ArrayRef<std::string> SourcePaths) : ClangTool(Compilations, SourcePaths) {} diff --git a/lib/Tooling/Tooling.cpp b/lib/Tooling/Tooling.cpp index 0db38db..60371fb 100644 --- a/lib/Tooling/Tooling.cpp +++ b/lib/Tooling/Tooling.cpp @@ -79,14 +79,14 @@ static const llvm::opt::ArgStringList *getCC1Arguments( } // The one job we find should be to invoke clang again. - const clang::driver::Command *Cmd = + const clang::driver::Command &Cmd = cast<clang::driver::Command>(*Jobs.begin()); - if (StringRef(Cmd->getCreator().getName()) != "clang") { + if (StringRef(Cmd.getCreator().getName()) != "clang") { Diagnostics->Report(clang::diag::err_fe_expected_clang_command); return nullptr; } - return &Cmd->getArguments(); + return &Cmd.getArguments(); } /// \brief Returns a clang build invocation initialized from the CC1 flags. @@ -123,17 +123,25 @@ getSyntaxOnlyToolArgs(const std::vector<std::string> &ExtraArgs, bool runToolOnCodeWithArgs(clang::FrontendAction *ToolAction, const Twine &Code, const std::vector<std::string> &Args, - const Twine &FileName) { + const Twine &FileName, + const FileContentMappings &VirtualMappedFiles) { + SmallString<16> FileNameStorage; StringRef FileNameRef = FileName.toNullTerminatedStringRef(FileNameStorage); llvm::IntrusiveRefCntPtr<FileManager> Files( new FileManager(FileSystemOptions())); - ToolInvocation Invocation(getSyntaxOnlyToolArgs(Args, FileNameRef), ToolAction, - Files.get()); + ToolInvocation Invocation(getSyntaxOnlyToolArgs(Args, FileNameRef), + ToolAction, Files.get()); SmallString<1024> CodeStorage; Invocation.mapVirtualFile(FileNameRef, Code.toNullTerminatedStringRef(CodeStorage)); + + for (auto &FilenameWithContent : VirtualMappedFiles) { + Invocation.mapVirtualFile(FilenameWithContent.first, + FilenameWithContent.second); + } + return Invocation.run(); } @@ -186,10 +194,6 @@ ToolInvocation::~ToolInvocation() { delete Action; } -void ToolInvocation::setDiagnosticConsumer(DiagnosticConsumer *D) { - DiagConsumer = D; -} - void ToolInvocation::mapVirtualFile(StringRef FilePath, StringRef Content) { SmallString<1024> PathStorage; llvm::sys::path::native(FilePath, PathStorage); @@ -223,8 +227,10 @@ bool ToolInvocation::run() { newInvocation(&Diagnostics, *CC1Args)); for (const auto &It : MappedFileContents) { // Inject the code as the given file name into the preprocessor options. - auto *Input = llvm::MemoryBuffer::getMemBuffer(It.getValue()); - Invocation->getPreprocessorOpts().addRemappedFile(It.getKey(), Input); + std::unique_ptr<llvm::MemoryBuffer> Input = + llvm::MemoryBuffer::getMemBuffer(It.getValue()); + Invocation->getPreprocessorOpts().addRemappedFile(It.getKey(), + Input.release()); } return runInvocation(BinaryName, Compilation.get(), Invocation.release()); } @@ -271,51 +277,27 @@ bool FrontendActionFactory::runInvocation(CompilerInvocation *Invocation, ClangTool::ClangTool(const CompilationDatabase &Compilations, ArrayRef<std::string> SourcePaths) - : Files(new FileManager(FileSystemOptions())), DiagConsumer(nullptr) { - ArgsAdjusters.push_back(new ClangStripOutputAdjuster()); - ArgsAdjusters.push_back(new ClangSyntaxOnlyAdjuster()); - for (const auto &SourcePath : SourcePaths) { - std::string File(getAbsolutePath(SourcePath)); - - std::vector<CompileCommand> CompileCommandsForFile = - Compilations.getCompileCommands(File); - if (!CompileCommandsForFile.empty()) { - for (CompileCommand &CompileCommand : CompileCommandsForFile) { - CompileCommands.push_back( - std::make_pair(File, std::move(CompileCommand))); - } - } else { - // FIXME: There are two use cases here: doing a fuzzy - // "find . -name '*.cc' |xargs tool" match, where as a user I don't care - // about the .cc files that were not found, and the use case where I - // specify all files I want to run over explicitly, where this should - // be an error. We'll want to add an option for this. - llvm::errs() << "Skipping " << File << ". Compile command not found.\n"; - } - } + : Compilations(Compilations), SourcePaths(SourcePaths), + Files(new FileManager(FileSystemOptions())), DiagConsumer(nullptr) { + appendArgumentsAdjuster(getClangStripOutputAdjuster()); + appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); } -void ClangTool::setDiagnosticConsumer(DiagnosticConsumer *D) { - DiagConsumer = D; -} +ClangTool::~ClangTool() {} void ClangTool::mapVirtualFile(StringRef FilePath, StringRef Content) { MappedFileContents.push_back(std::make_pair(FilePath, Content)); } -void ClangTool::setArgumentsAdjuster(ArgumentsAdjuster *Adjuster) { - clearArgumentsAdjusters(); - appendArgumentsAdjuster(Adjuster); -} - -void ClangTool::appendArgumentsAdjuster(ArgumentsAdjuster *Adjuster) { - ArgsAdjusters.push_back(Adjuster); +void ClangTool::appendArgumentsAdjuster(ArgumentsAdjuster Adjuster) { + if (ArgsAdjuster) + ArgsAdjuster = combineAdjusters(ArgsAdjuster, Adjuster); + else + ArgsAdjuster = Adjuster; } void ClangTool::clearArgumentsAdjusters() { - for (unsigned I = 0, E = ArgsAdjusters.size(); I != E; ++I) - delete ArgsAdjusters[I]; - ArgsAdjusters.clear(); + ArgsAdjuster = nullptr; } int ClangTool::run(ToolAction *Action) { @@ -330,37 +312,65 @@ int ClangTool::run(ToolAction *Action) { std::string MainExecutable = llvm::sys::fs::getMainExecutable("clang_tool", &StaticSymbol); + llvm::SmallString<128> InitialDirectory; + if (std::error_code EC = llvm::sys::fs::current_path(InitialDirectory)) + llvm::report_fatal_error("Cannot detect current path: " + + Twine(EC.message())); bool ProcessingFailed = false; - for (const auto &Command : CompileCommands) { - // FIXME: chdir is thread hostile; on the other hand, creating the same - // behavior as chdir is complex: chdir resolves the path once, thus - // guaranteeing that all subsequent relative path operations work - // on the same path the original chdir resulted in. This makes a difference - // for example on network filesystems, where symlinks might be switched - // during runtime of the tool. Fixing this depends on having a file system - // abstraction that allows openat() style interactions. - if (chdir(Command.second.Directory.c_str())) - llvm::report_fatal_error("Cannot chdir into \"" + - Twine(Command.second.Directory) + "\n!"); - std::vector<std::string> CommandLine = Command.second.CommandLine; - for (ArgumentsAdjuster *Adjuster : ArgsAdjusters) - CommandLine = Adjuster->Adjust(CommandLine); - assert(!CommandLine.empty()); - CommandLine[0] = MainExecutable; - // FIXME: We need a callback mechanism for the tool writer to output a - // customized message for each file. - DEBUG({ - llvm::dbgs() << "Processing: " << Command.first << ".\n"; - }); - ToolInvocation Invocation(std::move(CommandLine), Action, Files.get()); - Invocation.setDiagnosticConsumer(DiagConsumer); - for (const auto &MappedFile : MappedFileContents) { - Invocation.mapVirtualFile(MappedFile.first, MappedFile.second); + for (const auto &SourcePath : SourcePaths) { + std::string File(getAbsolutePath(SourcePath)); + + // Currently implementations of CompilationDatabase::getCompileCommands can + // change the state of the file system (e.g. prepare generated headers), so + // this method needs to run right before we invoke the tool, as the next + // file may require a different (incompatible) state of the file system. + // + // FIXME: Make the compilation database interface more explicit about the + // requirements to the order of invocation of its members. + std::vector<CompileCommand> CompileCommandsForFile = + Compilations.getCompileCommands(File); + if (CompileCommandsForFile.empty()) { + // FIXME: There are two use cases here: doing a fuzzy + // "find . -name '*.cc' |xargs tool" match, where as a user I don't care + // about the .cc files that were not found, and the use case where I + // specify all files I want to run over explicitly, where this should + // be an error. We'll want to add an option for this. + llvm::errs() << "Skipping " << File << ". Compile command not found.\n"; + continue; } - if (!Invocation.run()) { - // FIXME: Diagnostics should be used instead. - llvm::errs() << "Error while processing " << Command.first << ".\n"; - ProcessingFailed = true; + for (CompileCommand &CompileCommand : CompileCommandsForFile) { + // FIXME: chdir is thread hostile; on the other hand, creating the same + // behavior as chdir is complex: chdir resolves the path once, thus + // guaranteeing that all subsequent relative path operations work + // on the same path the original chdir resulted in. This makes a + // difference for example on network filesystems, where symlinks might be + // switched during runtime of the tool. Fixing this depends on having a + // file system abstraction that allows openat() style interactions. + if (chdir(CompileCommand.Directory.c_str())) + llvm::report_fatal_error("Cannot chdir into \"" + + Twine(CompileCommand.Directory) + "\n!"); + std::vector<std::string> CommandLine = CompileCommand.CommandLine; + if (ArgsAdjuster) + CommandLine = ArgsAdjuster(CommandLine); + assert(!CommandLine.empty()); + CommandLine[0] = MainExecutable; + // FIXME: We need a callback mechanism for the tool writer to output a + // customized message for each file. + DEBUG({ llvm::dbgs() << "Processing: " << File << ".\n"; }); + ToolInvocation Invocation(std::move(CommandLine), Action, Files.get()); + Invocation.setDiagnosticConsumer(DiagConsumer); + for (const auto &MappedFile : MappedFileContents) + Invocation.mapVirtualFile(MappedFile.first, MappedFile.second); + if (!Invocation.run()) { + // FIXME: Diagnostics should be used instead. + llvm::errs() << "Error while processing " << File << ".\n"; + ProcessingFailed = true; + } + // Return to the initial directory to correctly resolve next file by + // relative path. + if (chdir(InitialDirectory.c_str())) + llvm::report_fatal_error("Cannot chdir into \"" + + Twine(InitialDirectory) + "\n!"); } } return ProcessingFailed ? 1 : 0; |