diff options
Diffstat (limited to 'contrib/llvm/tools/clang/lib/Tooling')
9 files changed, 1962 insertions, 0 deletions
diff --git a/contrib/llvm/tools/clang/lib/Tooling/ArgumentsAdjusters.cpp b/contrib/llvm/tools/clang/lib/Tooling/ArgumentsAdjusters.cpp new file mode 100644 index 0000000..1722ede --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/ArgumentsAdjusters.cpp @@ -0,0 +1,88 @@ +//===--- ArgumentsAdjusters.cpp - Command line arguments adjuster ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains definitions of classes which implement ArgumentsAdjuster +// interface. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace tooling { + +/// 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; + }; +} + +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; + }; +} + +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 + } + + 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 [First, Second](const CommandLineArguments &Args) { + return Second(First(Args)); + }; +} + +} // end namespace tooling +} // end namespace clang + diff --git a/contrib/llvm/tools/clang/lib/Tooling/CommonOptionsParser.cpp b/contrib/llvm/tools/clang/lib/Tooling/CommonOptionsParser.cpp new file mode 100644 index 0000000..adae178 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/CommonOptionsParser.cpp @@ -0,0 +1,143 @@ +//===--- CommonOptionsParser.cpp - common options for clang tools ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the CommonOptionsParser class used to parse common +// command-line options for clang tools, so that they can be run as separate +// command-line applications with a consistent common interface for handling +// compilation database and input files. +// +// It provides a common subset of command-line options, common algorithm +// for locating a compilation database and source files, and help messages +// for the basic command-line interface. +// +// It creates a CompilationDatabase and reads common command-line options. +// +// This class uses the Clang Tooling infrastructure, see +// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +// for details on setting it up with LLVM source tree. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/CommandLine.h" +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" + +using namespace clang::tooling; +using namespace llvm; + +const char *const CommonOptionsParser::HelpMessage = + "\n" + "-p <build-path> is used to read a compile command database.\n" + "\n" + "\tFor example, it can be a CMake build directory in which a file named\n" + "\tcompile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON\n" + "\tCMake option to get this output). When no build path is specified,\n" + "\ta search for compile_commands.json will be attempted through all\n" + "\tparent paths of the first input file . See:\n" + "\thttp://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an\n" + "\texample of setting up Clang Tooling on a source tree.\n" + "\n" + "<source0> ... specify the paths of source files. These paths are\n" + "\tlooked up in the compile command database. If the path of a file is\n" + "\tabsolute, it needs to point into CMake's source tree. If the path is\n" + "\trelative, the current working directory needs to be in the CMake\n" + "\tsource tree and the file must be in a subdirectory of the current\n" + "\tworking directory. \"./\" prefixes in the relative files will be\n" + "\tautomatically removed, but the rest of a relative path must be a\n" + "\tsuffix of a path in the compile command database.\n" + "\n"; + +namespace { +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; + } +}; +} // namespace + +CommonOptionsParser::CommonOptionsParser(int &argc, const char **argv, + cl::OptionCategory &Category, + const char *Overview) { + static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden); + + static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), + cl::Optional, cl::cat(Category)); + + static cl::list<std::string> SourcePaths( + 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)); + + cl::HideUnrelatedOptions(Category); + + Compilations.reset(FixedCompilationDatabase::loadFromCommandLine(argc, + argv)); + cl::ParseCommandLineOptions(argc, argv, Overview); + SourcePathList = SourcePaths; + if (!Compilations) { + std::string ErrorMessage; + if (!BuildPath.empty()) { + Compilations = + CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage); + } else { + 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/contrib/llvm/tools/clang/lib/Tooling/CompilationDatabase.cpp b/contrib/llvm/tools/clang/lib/Tooling/CompilationDatabase.cpp new file mode 100644 index 0000000..2272be6 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/CompilationDatabase.cpp @@ -0,0 +1,331 @@ +//===--- CompilationDatabase.cpp - ----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains implementations of the CompilationDatabase base class +// and the FixedCompilationDatabase. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Driver/Action.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/Job.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Tooling/CompilationDatabasePluginRegistry.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Option/Arg.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/Path.h" +#include <sstream> +#include <system_error> +using namespace clang; +using namespace tooling; + +CompilationDatabase::~CompilationDatabase() {} + +std::unique_ptr<CompilationDatabase> +CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, + std::string &ErrorMessage) { + std::stringstream ErrorStream; + for (CompilationDatabasePluginRegistry::iterator + It = CompilationDatabasePluginRegistry::begin(), + Ie = CompilationDatabasePluginRegistry::end(); + It != Ie; ++It) { + std::string DatabaseErrorMessage; + std::unique_ptr<CompilationDatabasePlugin> Plugin(It->instantiate()); + if (std::unique_ptr<CompilationDatabase> DB = + Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage)) + return DB; + ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; + } + ErrorMessage = ErrorStream.str(); + return nullptr; +} + +static std::unique_ptr<CompilationDatabase> +findCompilationDatabaseFromDirectory(StringRef Directory, + std::string &ErrorMessage) { + std::stringstream ErrorStream; + bool HasErrorMessage = false; + while (!Directory.empty()) { + std::string LoadErrorMessage; + + if (std::unique_ptr<CompilationDatabase> DB = + CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage)) + return DB; + + if (!HasErrorMessage) { + ErrorStream << "No compilation database found in " << Directory.str() + << " or any parent directory\n" << LoadErrorMessage; + HasErrorMessage = true; + } + + Directory = llvm::sys::path::parent_path(Directory); + } + ErrorMessage = ErrorStream.str(); + return nullptr; +} + +std::unique_ptr<CompilationDatabase> +CompilationDatabase::autoDetectFromSource(StringRef SourceFile, + std::string &ErrorMessage) { + SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile)); + StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); + + std::unique_ptr<CompilationDatabase> DB = + findCompilationDatabaseFromDirectory(Directory, ErrorMessage); + + if (!DB) + ErrorMessage = ("Could not auto-detect compilation database for file \"" + + SourceFile + "\"\n" + ErrorMessage).str(); + return DB; +} + +std::unique_ptr<CompilationDatabase> +CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir, + std::string &ErrorMessage) { + SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir)); + + std::unique_ptr<CompilationDatabase> DB = + findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage); + + if (!DB) + ErrorMessage = ("Could not auto-detect compilation database from directory \"" + + SourceDir + "\"\n" + ErrorMessage).str(); + return DB; +} + +CompilationDatabasePlugin::~CompilationDatabasePlugin() {} + +namespace { +// Helper for recursively searching through a chain of actions and collecting +// all inputs, direct and indirect, of compile jobs. +struct CompileJobAnalyzer { + void run(const driver::Action *A) { + runImpl(A, false); + } + + SmallVector<std::string, 2> Inputs; + +private: + + void runImpl(const driver::Action *A, bool Collect) { + bool CollectChildren = Collect; + switch (A->getKind()) { + case driver::Action::CompileJobClass: + CollectChildren = true; + break; + + case driver::Action::InputClass: { + if (Collect) { + const driver::InputAction *IA = cast<driver::InputAction>(A); + Inputs.push_back(IA->getInputArg().getSpelling()); + } + } break; + + default: + // Don't care about others + ; + } + + for (driver::ActionList::const_iterator I = A->begin(), E = A->end(); + I != E; ++I) + runImpl(*I, CollectChildren); + } +}; + +// Special DiagnosticConsumer that looks for warn_drv_input_file_unused +// diagnostics from the driver and collects the option strings for those unused +// options. +class UnusedInputDiagConsumer : public DiagnosticConsumer { +public: + UnusedInputDiagConsumer() : Other(nullptr) {} + + // Useful for debugging, chain diagnostics to another consumer after + // recording for our own purposes. + UnusedInputDiagConsumer(DiagnosticConsumer *Other) : Other(Other) {} + + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info) override { + if (Info.getID() == clang::diag::warn_drv_input_file_unused) { + // Arg 1 for this diagnostic is the option that didn't get used. + UnusedInputs.push_back(Info.getArgStdStr(0)); + } + if (Other) + Other->HandleDiagnostic(DiagLevel, Info); + } + + DiagnosticConsumer *Other; + SmallVector<std::string, 2> UnusedInputs; +}; + +// Unary functor for asking "Given a StringRef S1, does there exist a string +// S2 in Arr where S1 == S2?" +struct MatchesAny { + MatchesAny(ArrayRef<std::string> Arr) : Arr(Arr) {} + bool operator() (StringRef S) { + for (const std::string *I = Arr.begin(), *E = Arr.end(); I != E; ++I) + if (*I == S) + return true; + return false; + } +private: + ArrayRef<std::string> Arr; +}; +} // namespace + +/// \brief Strips any positional args and possible argv[0] from a command-line +/// provided by the user to construct a FixedCompilationDatabase. +/// +/// FixedCompilationDatabase requires a command line to be in this format as it +/// constructs the command line for each file by appending the name of the file +/// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the +/// start of the command line although its value is not important as it's just +/// ignored by the Driver invoked by the ClangTool using the +/// FixedCompilationDatabase. +/// +/// FIXME: This functionality should probably be made available by +/// clang::driver::Driver although what the interface should look like is not +/// clear. +/// +/// \param[in] Args Args as provided by the user. +/// \return Resulting stripped command line. +/// \li true if successful. +/// \li false if \c Args cannot be used for compilation jobs (e.g. +/// contains an option like -E or -version). +static bool stripPositionalArgs(std::vector<const char *> Args, + std::vector<std::string> &Result) { + IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); + UnusedInputDiagConsumer DiagClient; + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<clang::DiagnosticIDs>(new DiagnosticIDs()), + &*DiagOpts, &DiagClient, false); + + // The clang executable path isn't required since the jobs the driver builds + // will not be executed. + std::unique_ptr<driver::Driver> NewDriver(new driver::Driver( + /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(), + Diagnostics)); + NewDriver->setCheckInputsExist(false); + + // This becomes the new argv[0]. The value is actually not important as it + // isn't used for invoking Tools. + Args.insert(Args.begin(), "clang-tool"); + + // By adding -c, we force the driver to treat compilation as the last phase. + // It will then issue warnings via Diagnostics about un-used options that + // would have been used for linking. If the user provided a compiler name as + // the original argv[0], this will be treated as a linker input thanks to + // insertng a new argv[0] above. All un-used options get collected by + // UnusedInputdiagConsumer and get stripped out later. + Args.push_back("-c"); + + // Put a dummy C++ file on to ensure there's at least one compile job for the + // driver to construct. If the user specified some other argument that + // prevents compilation, e.g. -E or something like -version, we may still end + // up with no jobs but then this is the user's fault. + Args.push_back("placeholder.cpp"); + + // Remove -no-integrated-as; it's not used for syntax checking, + // and it confuses targets which don't support this option. + Args.erase(std::remove_if(Args.begin(), Args.end(), + MatchesAny(std::string("-no-integrated-as"))), + Args.end()); + + const std::unique_ptr<driver::Compilation> Compilation( + NewDriver->BuildCompilation(Args)); + + const driver::JobList &Jobs = Compilation->getJobs(); + + CompileJobAnalyzer CompileAnalyzer; + + for (const auto &Cmd : Jobs) { + // 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 (CompileAnalyzer.Inputs.empty()) { + // No compile jobs found. + // FIXME: Emit a warning of some kind? + return false; + } + + // Remove all compilation input files from the command line. This is + // necessary so that getCompileCommands() can construct a command line for + // each file. + std::vector<const char *>::iterator End = std::remove_if( + Args.begin(), Args.end(), MatchesAny(CompileAnalyzer.Inputs)); + + // Remove all inputs deemed unused for compilation. + End = std::remove_if(Args.begin(), End, MatchesAny(DiagClient.UnusedInputs)); + + // Remove the -c add above as well. It will be at the end right now. + assert(strcmp(*(End - 1), "-c") == 0); + --End; + + Result = std::vector<std::string>(Args.begin() + 1, End); + return true; +} + +FixedCompilationDatabase *FixedCompilationDatabase::loadFromCommandLine( + int &Argc, const char *const *Argv, Twine Directory) { + const char *const *DoubleDash = std::find(Argv, Argv + Argc, StringRef("--")); + if (DoubleDash == Argv + Argc) + return nullptr; + std::vector<const char *> CommandLine(DoubleDash + 1, Argv + Argc); + Argc = DoubleDash - Argv; + + std::vector<std::string> StrippedArgs; + if (!stripPositionalArgs(CommandLine, StrippedArgs)) + return nullptr; + return new FixedCompilationDatabase(Directory, StrippedArgs); +} + +FixedCompilationDatabase:: +FixedCompilationDatabase(Twine Directory, ArrayRef<std::string> CommandLine) { + std::vector<std::string> ToolCommandLine(1, "clang-tool"); + ToolCommandLine.insert(ToolCommandLine.end(), + CommandLine.begin(), CommandLine.end()); + CompileCommands.emplace_back(Directory, std::move(ToolCommandLine)); +} + +std::vector<CompileCommand> +FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const { + std::vector<CompileCommand> Result(CompileCommands); + Result[0].CommandLine.push_back(FilePath); + return Result; +} + +std::vector<std::string> +FixedCompilationDatabase::getAllFiles() const { + return std::vector<std::string>(); +} + +std::vector<CompileCommand> +FixedCompilationDatabase::getAllCompileCommands() const { + return std::vector<CompileCommand>(); +} + +namespace clang { +namespace tooling { + +// This anchor is used to force the linker to link in the generated object file +// and thus register the JSONCompilationDatabasePlugin. +extern volatile int JSONAnchorSource; +static int JSONAnchorDest = JSONAnchorSource; + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Core/Replacement.cpp b/contrib/llvm/tools/clang/lib/Tooling/Core/Replacement.cpp new file mode 100644 index 0000000..6d37a49 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Core/Replacement.cpp @@ -0,0 +1,297 @@ +//===--- 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, + const LangOptions &LangOpts) { + setFromSourceRange(Sources, Range, ReplacementText, LangOpts); +} + +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 Stream.str(); +} + +bool operator<(const Replacement &LHS, const Replacement &RHS) { + if (LHS.getOffset() != RHS.getOffset()) + return LHS.getOffset() < RHS.getOffset(); + + // Apply longer replacements first, specifically so that deletions are + // executed before insertions. It is (hopefully) never the intention to + // delete parts of newly inserted code. + 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, + const LangOptions &LangOpts) { + 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, LangOpts); + return End.second - Start.second; +} + +void Replacement::setFromSourceRange(const SourceManager &Sources, + const CharSourceRange &Range, + StringRef ReplacementText, + const LangOptions &LangOpts) { + setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()), + getRangeSize(Sources, Range, LangOpts), + 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/contrib/llvm/tools/clang/lib/Tooling/FileMatchTrie.cpp b/contrib/llvm/tools/clang/lib/Tooling/FileMatchTrie.cpp new file mode 100644 index 0000000..86ed036 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/FileMatchTrie.cpp @@ -0,0 +1,189 @@ +//===--- FileMatchTrie.cpp - ----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains the implementation of a FileMatchTrie. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/FileMatchTrie.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include <sstream> +using namespace clang; +using namespace tooling; + +namespace { +/// \brief Default \c PathComparator using \c llvm::sys::fs::equivalent(). +struct DefaultPathComparator : public PathComparator { + bool equivalent(StringRef FileA, StringRef FileB) const override { + return FileA == FileB || llvm::sys::fs::equivalent(FileA, FileB); + } +}; +} + +namespace clang { +namespace tooling { +/// \brief A node of the \c FileMatchTrie. +/// +/// Each node has storage for up to one path and a map mapping a path segment to +/// child nodes. The trie starts with an empty root node. +class FileMatchTrieNode { +public: + /// \brief Inserts 'NewPath' into this trie. \c ConsumedLength denotes + /// the number of \c NewPath's trailing characters already consumed during + /// recursion. + /// + /// An insert of a path + /// 'p'starts at the root node and does the following: + /// - If the node is empty, insert 'p' into its storage and abort. + /// - If the node has a path 'p2' but no children, take the last path segment + /// 's' of 'p2', put a new child into the map at 's' an insert the rest of + /// 'p2' there. + /// - Insert a new child for the last segment of 'p' and insert the rest of + /// 'p' there. + /// + /// An insert operation is linear in the number of a path's segments. + void insert(StringRef NewPath, unsigned ConsumedLength = 0) { + // We cannot put relative paths into the FileMatchTrie as then a path can be + // a postfix of another path, violating a core assumption of the trie. + if (llvm::sys::path::is_relative(NewPath)) + return; + if (Path.empty()) { + // This is an empty leaf. Store NewPath and return. + Path = NewPath; + return; + } + if (Children.empty()) { + // This is a leaf, ignore duplicate entry if 'Path' equals 'NewPath'. + if (NewPath == Path) + return; + // Make this a node and create a child-leaf with 'Path'. + StringRef Element(llvm::sys::path::filename( + StringRef(Path).drop_back(ConsumedLength))); + Children[Element].Path = Path; + } + StringRef Element(llvm::sys::path::filename( + StringRef(NewPath).drop_back(ConsumedLength))); + Children[Element].insert(NewPath, ConsumedLength + Element.size() + 1); + } + + /// \brief Tries to find the node under this \c FileMatchTrieNode that best + /// matches 'FileName'. + /// + /// If multiple paths fit 'FileName' equally well, \c IsAmbiguous is set to + /// \c true and an empty string is returned. If no path fits 'FileName', an + /// empty string is returned. \c ConsumedLength denotes the number of + /// \c Filename's trailing characters already consumed during recursion. + /// + /// To find the best matching node for a given path 'p', the + /// \c findEquivalent() function is called recursively for each path segment + /// (back to fron) of 'p' until a node 'n' is reached that does not .. + /// - .. have children. In this case it is checked + /// whether the stored path is equivalent to 'p'. If yes, the best match is + /// found. Otherwise continue with the parent node as if this node did not + /// exist. + /// - .. a child matching the next path segment. In this case, all children of + /// 'n' are an equally good match for 'p'. All children are of 'n' are found + /// recursively and their equivalence to 'p' is determined. If none are + /// equivalent, continue with the parent node as if 'n' didn't exist. If one + /// is equivalent, the best match is found. Otherwise, report and ambigiuity + /// error. + StringRef findEquivalent(const PathComparator& Comparator, + StringRef FileName, + bool &IsAmbiguous, + unsigned ConsumedLength = 0) const { + if (Children.empty()) { + if (Comparator.equivalent(StringRef(Path), FileName)) + return StringRef(Path); + return StringRef(); + } + StringRef Element(llvm::sys::path::filename(FileName.drop_back( + ConsumedLength))); + llvm::StringMap<FileMatchTrieNode>::const_iterator MatchingChild = + Children.find(Element); + if (MatchingChild != Children.end()) { + StringRef Result = MatchingChild->getValue().findEquivalent( + Comparator, FileName, IsAmbiguous, + ConsumedLength + Element.size() + 1); + if (!Result.empty() || IsAmbiguous) + return Result; + } + std::vector<StringRef> AllChildren; + getAll(AllChildren, MatchingChild); + StringRef Result; + for (unsigned i = 0; i < AllChildren.size(); i++) { + if (Comparator.equivalent(AllChildren[i], FileName)) { + if (Result.empty()) { + Result = AllChildren[i]; + } else { + IsAmbiguous = true; + return StringRef(); + } + } + } + return Result; + } + +private: + /// \brief Gets all paths under this FileMatchTrieNode. + void getAll(std::vector<StringRef> &Results, + llvm::StringMap<FileMatchTrieNode>::const_iterator Except) const { + if (Path.empty()) + return; + if (Children.empty()) { + Results.push_back(StringRef(Path)); + return; + } + for (llvm::StringMap<FileMatchTrieNode>::const_iterator + It = Children.begin(), E = Children.end(); + It != E; ++It) { + if (It == Except) + continue; + It->getValue().getAll(Results, Children.end()); + } + } + + // The stored absolute path in this node. Only valid for leaf nodes, i.e. + // nodes where Children.empty(). + std::string Path; + + // The children of this node stored in a map based on the next path segment. + llvm::StringMap<FileMatchTrieNode> Children; +}; +} // end namespace tooling +} // end namespace clang + +FileMatchTrie::FileMatchTrie() + : Root(new FileMatchTrieNode), Comparator(new DefaultPathComparator()) {} + +FileMatchTrie::FileMatchTrie(PathComparator *Comparator) + : Root(new FileMatchTrieNode), Comparator(Comparator) {} + +FileMatchTrie::~FileMatchTrie() { + delete Root; +} + +void FileMatchTrie::insert(StringRef NewPath) { + Root->insert(NewPath); +} + +StringRef FileMatchTrie::findEquivalent(StringRef FileName, + raw_ostream &Error) const { + if (llvm::sys::path::is_relative(FileName)) { + Error << "Cannot resolve relative paths"; + return StringRef(); + } + bool IsAmbiguous = false; + StringRef Result = Root->findEquivalent(*Comparator, FileName, IsAmbiguous); + if (IsAmbiguous) + Error << "Path is ambiguous"; + return Result; +} diff --git a/contrib/llvm/tools/clang/lib/Tooling/JSONCompilationDatabase.cpp b/contrib/llvm/tools/clang/lib/Tooling/JSONCompilationDatabase.cpp new file mode 100644 index 0000000..454a2ff --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/JSONCompilationDatabase.cpp @@ -0,0 +1,322 @@ +//===--- JSONCompilationDatabase.cpp - ------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains the implementation of the JSONCompilationDatabase. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/JSONCompilationDatabase.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/CompilationDatabasePluginRegistry.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Path.h" +#include <system_error> + +namespace clang { +namespace tooling { + +namespace { + +/// \brief A parser for escaped strings of command line arguments. +/// +/// Assumes \-escaping for quoted arguments (see the documentation of +/// unescapeCommandLine(...)). +class CommandLineArgumentParser { + public: + CommandLineArgumentParser(StringRef CommandLine) + : Input(CommandLine), Position(Input.begin()-1) {} + + std::vector<std::string> parse() { + bool HasMoreInput = true; + while (HasMoreInput && nextNonWhitespace()) { + std::string Argument; + HasMoreInput = parseStringInto(Argument); + CommandLine.push_back(Argument); + } + return CommandLine; + } + + private: + // All private methods return true if there is more input available. + + bool parseStringInto(std::string &String) { + do { + if (*Position == '"') { + if (!parseDoubleQuotedStringInto(String)) return false; + } else if (*Position == '\'') { + if (!parseSingleQuotedStringInto(String)) return false; + } else { + if (!parseFreeStringInto(String)) return false; + } + } while (*Position != ' '); + return true; + } + + bool parseDoubleQuotedStringInto(std::string &String) { + if (!next()) return false; + while (*Position != '"') { + if (!skipEscapeCharacter()) return false; + String.push_back(*Position); + if (!next()) return false; + } + return next(); + } + + bool parseSingleQuotedStringInto(std::string &String) { + if (!next()) return false; + while (*Position != '\'') { + String.push_back(*Position); + if (!next()) return false; + } + return next(); + } + + bool parseFreeStringInto(std::string &String) { + do { + if (!skipEscapeCharacter()) return false; + String.push_back(*Position); + if (!next()) return false; + } while (*Position != ' ' && *Position != '"' && *Position != '\''); + return true; + } + + bool skipEscapeCharacter() { + if (*Position == '\\') { + return next(); + } + return true; + } + + bool nextNonWhitespace() { + do { + if (!next()) return false; + } while (*Position == ' '); + return true; + } + + bool next() { + ++Position; + return Position != Input.end(); + } + + const StringRef Input; + StringRef::iterator Position; + std::vector<std::string> CommandLine; +}; + +std::vector<std::string> unescapeCommandLine( + StringRef EscapedCommandLine) { + CommandLineArgumentParser parser(EscapedCommandLine); + return parser.parse(); +} + +class JSONCompilationDatabasePlugin : public CompilationDatabasePlugin { + 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; + } +}; + +} // end namespace + +// Register the JSONCompilationDatabasePlugin with the +// CompilationDatabasePluginRegistry using this statically initialized variable. +static CompilationDatabasePluginRegistry::Add<JSONCompilationDatabasePlugin> +X("json-compilation-database", "Reads JSON formatted compilation databases"); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the JSONCompilationDatabasePlugin. +volatile int JSONAnchorSource = 0; + +std::unique_ptr<JSONCompilationDatabase> +JSONCompilationDatabase::loadFromFile(StringRef FilePath, + std::string &ErrorMessage) { + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> DatabaseBuffer = + llvm::MemoryBuffer::getFile(FilePath); + if (std::error_code Result = DatabaseBuffer.getError()) { + ErrorMessage = "Error while opening JSON database: " + Result.message(); + return nullptr; + } + std::unique_ptr<JSONCompilationDatabase> Database( + new JSONCompilationDatabase(std::move(*DatabaseBuffer))); + if (!Database->parse(ErrorMessage)) + return nullptr; + return Database; +} + +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(std::move(DatabaseBuffer))); + if (!Database->parse(ErrorMessage)) + return nullptr; + return Database; +} + +std::vector<CompileCommand> +JSONCompilationDatabase::getCompileCommands(StringRef FilePath) const { + SmallString<128> NativeFilePath; + llvm::sys::path::native(FilePath, NativeFilePath); + + std::string Error; + llvm::raw_string_ostream ES(Error); + StringRef Match = MatchTrie.findEquivalent(NativeFilePath, ES); + if (Match.empty()) + return std::vector<CompileCommand>(); + llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator + CommandsRefI = IndexByFile.find(Match); + if (CommandsRefI == IndexByFile.end()) + return std::vector<CompileCommand>(); + std::vector<CompileCommand> Commands; + getCommands(CommandsRefI->getValue(), Commands); + return Commands; +} + +std::vector<std::string> +JSONCompilationDatabase::getAllFiles() const { + std::vector<std::string> Result; + + llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator + CommandsRefI = IndexByFile.begin(); + const llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator + CommandsRefEnd = IndexByFile.end(); + for (; CommandsRefI != CommandsRefEnd; ++CommandsRefI) { + Result.push_back(CommandsRefI->first().str()); + } + + return Result; +} + +std::vector<CompileCommand> +JSONCompilationDatabase::getAllCompileCommands() const { + std::vector<CompileCommand> Commands; + for (llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator + CommandsRefI = IndexByFile.begin(), CommandsRefEnd = IndexByFile.end(); + CommandsRefI != CommandsRefEnd; ++CommandsRefI) { + getCommands(CommandsRefI->getValue(), Commands); + } + return Commands; +} + +void JSONCompilationDatabase::getCommands( + ArrayRef<CompileCommandRef> CommandsRef, + std::vector<CompileCommand> &Commands) const { + for (int I = 0, E = CommandsRef.size(); I != E; ++I) { + SmallString<8> DirectoryStorage; + SmallString<1024> CommandStorage; + Commands.emplace_back( + // FIXME: Escape correctly: + CommandsRef[I].first->getValue(DirectoryStorage), + unescapeCommandLine(CommandsRef[I].second->getValue(CommandStorage))); + } +} + +bool JSONCompilationDatabase::parse(std::string &ErrorMessage) { + llvm::yaml::document_iterator I = YAMLStream.begin(); + if (I == YAMLStream.end()) { + ErrorMessage = "Error while parsing YAML."; + return false; + } + llvm::yaml::Node *Root = I->getRoot(); + if (!Root) { + ErrorMessage = "Error while parsing YAML."; + return false; + } + llvm::yaml::SequenceNode *Array = dyn_cast<llvm::yaml::SequenceNode>(Root); + if (!Array) { + ErrorMessage = "Expected array."; + return false; + } + for (llvm::yaml::SequenceNode::iterator AI = Array->begin(), + AE = Array->end(); + AI != AE; ++AI) { + llvm::yaml::MappingNode *Object = dyn_cast<llvm::yaml::MappingNode>(&*AI); + if (!Object) { + ErrorMessage = "Expected object."; + return false; + } + llvm::yaml::ScalarNode *Directory = nullptr; + llvm::yaml::ScalarNode *Command = nullptr; + llvm::yaml::ScalarNode *File = nullptr; + for (llvm::yaml::MappingNode::iterator KVI = Object->begin(), + KVE = Object->end(); + KVI != KVE; ++KVI) { + llvm::yaml::Node *Value = (*KVI).getValue(); + if (!Value) { + ErrorMessage = "Expected value."; + return false; + } + llvm::yaml::ScalarNode *ValueString = + dyn_cast<llvm::yaml::ScalarNode>(Value); + if (!ValueString) { + ErrorMessage = "Expected string as value."; + return false; + } + llvm::yaml::ScalarNode *KeyString = + dyn_cast<llvm::yaml::ScalarNode>((*KVI).getKey()); + if (!KeyString) { + ErrorMessage = "Expected strings as key."; + return false; + } + SmallString<8> KeyStorage; + if (KeyString->getValue(KeyStorage) == "directory") { + Directory = ValueString; + } else if (KeyString->getValue(KeyStorage) == "command") { + Command = ValueString; + } else if (KeyString->getValue(KeyStorage) == "file") { + File = ValueString; + } else { + ErrorMessage = ("Unknown key: \"" + + KeyString->getRawValue() + "\"").str(); + return false; + } + } + if (!File) { + ErrorMessage = "Missing key: \"file\"."; + return false; + } + if (!Command) { + ErrorMessage = "Missing key: \"command\"."; + return false; + } + if (!Directory) { + ErrorMessage = "Missing key: \"directory\"."; + return false; + } + SmallString<8> FileStorage; + StringRef FileName = File->getValue(FileStorage); + SmallString<128> NativeFilePath; + if (llvm::sys::path::is_relative(FileName)) { + SmallString<8> DirectoryStorage; + SmallString<128> AbsolutePath( + Directory->getValue(DirectoryStorage)); + llvm::sys::path::append(AbsolutePath, FileName); + llvm::sys::path::native(AbsolutePath, NativeFilePath); + } else { + llvm::sys::path::native(FileName, NativeFilePath); + } + IndexByFile[NativeFilePath].push_back( + CompileCommandRef(Directory, Command)); + MatchTrie.insert(NativeFilePath); + } + return true; +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring.cpp new file mode 100644 index 0000000..d32452f --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring.cpp @@ -0,0 +1,65 @@ +//===--- Refactoring.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 tools to support refactorings. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/Lexer.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/Tooling/Refactoring.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_os_ostream.h" + +namespace clang { +namespace tooling { + +RefactoringTool::RefactoringTool( + const CompilationDatabase &Compilations, ArrayRef<std::string> SourcePaths, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : ClangTool(Compilations, SourcePaths, PCHContainerOps) {} + +Replacements &RefactoringTool::getReplacements() { return Replace; } + +int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) { + if (int Result = run(ActionFactory)) { + return Result; + } + + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); + TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), + &*DiagOpts, &DiagnosticPrinter, false); + SourceManager Sources(Diagnostics, getFiles()); + Rewriter Rewrite(Sources, DefaultLangOptions); + + if (!applyAllReplacements(Rewrite)) { + llvm::errs() << "Skipped some replacements.\n"; + } + + return saveRewrittenFiles(Rewrite); +} + +bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) { + return tooling::applyAllReplacements(Replace, Rewrite); +} + +int RefactoringTool::saveRewrittenFiles(Rewriter &Rewrite) { + return Rewrite.overwriteChangedFiles() ? 1 : 0; +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/RefactoringCallbacks.cpp b/contrib/llvm/tools/clang/lib/Tooling/RefactoringCallbacks.cpp new file mode 100644 index 0000000..4de125e --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/RefactoringCallbacks.cpp @@ -0,0 +1,81 @@ +//===--- RefactoringCallbacks.cpp - Structural query framework ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// +//===----------------------------------------------------------------------===// +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/RefactoringCallbacks.h" + +namespace clang { +namespace tooling { + +RefactoringCallback::RefactoringCallback() {} +tooling::Replacements &RefactoringCallback::getReplacements() { + return Replace; +} + +static Replacement replaceStmtWithText(SourceManager &Sources, + const Stmt &From, + StringRef Text) { + return tooling::Replacement(Sources, CharSourceRange::getTokenRange( + From.getSourceRange()), Text); +} +static Replacement replaceStmtWithStmt(SourceManager &Sources, + const Stmt &From, + const Stmt &To) { + return replaceStmtWithText(Sources, From, Lexer::getSourceText( + CharSourceRange::getTokenRange(To.getSourceRange()), + Sources, LangOptions())); +} + +ReplaceStmtWithText::ReplaceStmtWithText(StringRef FromId, StringRef ToText) + : FromId(FromId), ToText(ToText) {} + +void ReplaceStmtWithText::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + if (const Stmt *FromMatch = Result.Nodes.getStmtAs<Stmt>(FromId)) { + Replace.insert(tooling::Replacement( + *Result.SourceManager, + CharSourceRange::getTokenRange(FromMatch->getSourceRange()), + ToText)); + } +} + +ReplaceStmtWithStmt::ReplaceStmtWithStmt(StringRef FromId, StringRef ToId) + : FromId(FromId), ToId(ToId) {} + +void ReplaceStmtWithStmt::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + const Stmt *FromMatch = Result.Nodes.getStmtAs<Stmt>(FromId); + const Stmt *ToMatch = Result.Nodes.getStmtAs<Stmt>(ToId); + if (FromMatch && ToMatch) + Replace.insert(replaceStmtWithStmt( + *Result.SourceManager, *FromMatch, *ToMatch)); +} + +ReplaceIfStmtWithItsBody::ReplaceIfStmtWithItsBody(StringRef Id, + bool PickTrueBranch) + : Id(Id), PickTrueBranch(PickTrueBranch) {} + +void ReplaceIfStmtWithItsBody::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + if (const IfStmt *Node = Result.Nodes.getStmtAs<IfStmt>(Id)) { + const Stmt *Body = PickTrueBranch ? Node->getThen() : Node->getElse(); + if (Body) { + Replace.insert(replaceStmtWithStmt(*Result.SourceManager, *Node, *Body)); + } else if (!PickTrueBranch) { + // If we want to use the 'else'-branch, but it doesn't exist, delete + // the whole 'if'. + Replace.insert(replaceStmtWithText(*Result.SourceManager, *Node, "")); + } + } +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Tooling.cpp b/contrib/llvm/tools/clang/lib/Tooling/Tooling.cpp new file mode 100644 index 0000000..f9cb7c6 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Tooling.cpp @@ -0,0 +1,446 @@ +//===--- Tooling.cpp - Running clang standalone tools ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements functions to run clang tools standalone instead +// of running them as a plugin. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Tooling.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/Tool.h" +#include "clang/Frontend/ASTUnit.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/raw_ostream.h" + +// For chdir, see the comment in ClangTool::run for more information. +#ifdef LLVM_ON_WIN32 +# include <direct.h> +#else +# include <unistd.h> +#endif + +#define DEBUG_TYPE "clang-tooling" + +namespace clang { +namespace tooling { + +ToolAction::~ToolAction() {} + +FrontendActionFactory::~FrontendActionFactory() {} + +// FIXME: This file contains structural duplication with other parts of the +// code that sets up a compiler to run tools on it, and we should refactor +// it to be based on the same framework. + +/// \brief Builds a clang driver initialized for running clang tools. +static clang::driver::Driver *newDriver(clang::DiagnosticsEngine *Diagnostics, + const char *BinaryName) { + clang::driver::Driver *CompilerDriver = new clang::driver::Driver( + BinaryName, llvm::sys::getDefaultTargetTriple(), *Diagnostics); + CompilerDriver->setTitle("clang_based_tool"); + return CompilerDriver; +} + +/// \brief Retrieves the clang CC1 specific flags out of the compilation's jobs. +/// +/// Returns NULL on error. +static const llvm::opt::ArgStringList *getCC1Arguments( + clang::DiagnosticsEngine *Diagnostics, + clang::driver::Compilation *Compilation) { + // We expect to get back exactly one Command job, if we didn't something + // failed. Extract that job from the Compilation. + const clang::driver::JobList &Jobs = Compilation->getJobs(); + if (Jobs.size() != 1 || !isa<clang::driver::Command>(*Jobs.begin())) { + SmallString<256> error_msg; + llvm::raw_svector_ostream error_stream(error_msg); + Jobs.Print(error_stream, "; ", true); + Diagnostics->Report(clang::diag::err_fe_expected_compiler_job) + << error_stream.str(); + return nullptr; + } + + // The one job we find should be to invoke clang again. + const clang::driver::Command &Cmd = + cast<clang::driver::Command>(*Jobs.begin()); + if (StringRef(Cmd.getCreator().getName()) != "clang") { + Diagnostics->Report(clang::diag::err_fe_expected_clang_command); + return nullptr; + } + + return &Cmd.getArguments(); +} + +/// \brief Returns a clang build invocation initialized from the CC1 flags. +clang::CompilerInvocation *newInvocation( + clang::DiagnosticsEngine *Diagnostics, + const llvm::opt::ArgStringList &CC1Args) { + assert(!CC1Args.empty() && "Must at least contain the program name!"); + clang::CompilerInvocation *Invocation = new clang::CompilerInvocation; + clang::CompilerInvocation::CreateFromArgs( + *Invocation, CC1Args.data() + 1, CC1Args.data() + CC1Args.size(), + *Diagnostics); + Invocation->getFrontendOpts().DisableFree = false; + Invocation->getCodeGenOpts().DisableFree = false; + Invocation->getDependencyOutputOpts() = DependencyOutputOptions(); + return Invocation; +} + +bool runToolOnCode(clang::FrontendAction *ToolAction, const Twine &Code, + const Twine &FileName, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) { + return runToolOnCodeWithArgs(ToolAction, Code, std::vector<std::string>(), + FileName, PCHContainerOps); +} + +static std::vector<std::string> +getSyntaxOnlyToolArgs(const std::vector<std::string> &ExtraArgs, + StringRef FileName) { + std::vector<std::string> Args; + Args.push_back("clang-tool"); + Args.push_back("-fsyntax-only"); + Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); + Args.push_back(FileName.str()); + return Args; +} + +bool runToolOnCodeWithArgs( + clang::FrontendAction *ToolAction, const Twine &Code, + const std::vector<std::string> &Args, const Twine &FileName, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + 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(), PCHContainerOps); + + SmallString<1024> CodeStorage; + Invocation.mapVirtualFile(FileNameRef, + Code.toNullTerminatedStringRef(CodeStorage)); + + for (auto &FilenameWithContent : VirtualMappedFiles) { + Invocation.mapVirtualFile(FilenameWithContent.first, + FilenameWithContent.second); + } + + return Invocation.run(); +} + +std::string getAbsolutePath(StringRef File) { + StringRef RelativePath(File); + // FIXME: Should '.\\' be accepted on Win32? + if (RelativePath.startswith("./")) { + RelativePath = RelativePath.substr(strlen("./")); + } + + SmallString<1024> AbsolutePath = RelativePath; + std::error_code EC = llvm::sys::fs::make_absolute(AbsolutePath); + assert(!EC); + (void)EC; + llvm::sys::path::native(AbsolutePath); + return AbsolutePath.str(); +} + +namespace { + +class SingleFrontendActionFactory : public FrontendActionFactory { + FrontendAction *Action; + +public: + SingleFrontendActionFactory(FrontendAction *Action) : Action(Action) {} + + FrontendAction *create() override { return Action; } +}; + +} + +ToolInvocation::ToolInvocation( + std::vector<std::string> CommandLine, ToolAction *Action, + FileManager *Files, std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : CommandLine(std::move(CommandLine)), Action(Action), OwnsAction(false), + Files(Files), PCHContainerOps(PCHContainerOps), DiagConsumer(nullptr) {} + +ToolInvocation::ToolInvocation( + std::vector<std::string> CommandLine, FrontendAction *FAction, + FileManager *Files, std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : CommandLine(std::move(CommandLine)), + Action(new SingleFrontendActionFactory(FAction)), OwnsAction(true), + Files(Files), PCHContainerOps(PCHContainerOps), DiagConsumer(nullptr) {} + +ToolInvocation::~ToolInvocation() { + if (OwnsAction) + delete Action; +} + +void ToolInvocation::mapVirtualFile(StringRef FilePath, StringRef Content) { + SmallString<1024> PathStorage; + llvm::sys::path::native(FilePath, PathStorage); + MappedFileContents[PathStorage] = Content; +} + +bool ToolInvocation::run() { + std::vector<const char*> Argv; + for (const std::string &Str : CommandLine) + Argv.push_back(Str.c_str()); + const char *const BinaryName = Argv[0]; + IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); + TextDiagnosticPrinter DiagnosticPrinter( + llvm::errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<clang::DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts, + DiagConsumer ? DiagConsumer : &DiagnosticPrinter, false); + + const std::unique_ptr<clang::driver::Driver> Driver( + newDriver(&Diagnostics, BinaryName)); + // Since the input might only be virtual, don't check whether it exists. + Driver->setCheckInputsExist(false); + const std::unique_ptr<clang::driver::Compilation> Compilation( + Driver->BuildCompilation(llvm::makeArrayRef(Argv))); + const llvm::opt::ArgStringList *const CC1Args = getCC1Arguments( + &Diagnostics, Compilation.get()); + if (!CC1Args) { + return false; + } + std::unique_ptr<clang::CompilerInvocation> Invocation( + newInvocation(&Diagnostics, *CC1Args)); + for (const auto &It : MappedFileContents) { + // Inject the code as the given file name into the preprocessor options. + 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(), + PCHContainerOps); +} + +bool ToolInvocation::runInvocation( + const char *BinaryName, clang::driver::Compilation *Compilation, + clang::CompilerInvocation *Invocation, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) { + // Show the invocation, with -v. + if (Invocation->getHeaderSearchOpts().Verbose) { + llvm::errs() << "clang Invocation:\n"; + Compilation->getJobs().Print(llvm::errs(), "\n", true); + llvm::errs() << "\n"; + } + + return Action->runInvocation(Invocation, Files, PCHContainerOps, + DiagConsumer); +} + +bool FrontendActionFactory::runInvocation( + CompilerInvocation *Invocation, FileManager *Files, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *DiagConsumer) { + // Create a compiler instance to handle the actual work. + clang::CompilerInstance Compiler(PCHContainerOps); + Compiler.setInvocation(Invocation); + Compiler.setFileManager(Files); + + // The FrontendAction can have lifetime requirements for Compiler or its + // members, and we need to ensure it's deleted earlier than Compiler. So we + // pass it to an std::unique_ptr declared after the Compiler variable. + std::unique_ptr<FrontendAction> ScopedToolAction(create()); + + // Create the compiler's actual diagnostics engine. + Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); + if (!Compiler.hasDiagnostics()) + return false; + + Compiler.createSourceManager(*Files); + + const bool Success = Compiler.ExecuteAction(*ScopedToolAction); + + Files->clearStatCaches(); + return Success; +} + +ClangTool::ClangTool(const CompilationDatabase &Compilations, + ArrayRef<std::string> SourcePaths, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : Compilations(Compilations), SourcePaths(SourcePaths), + PCHContainerOps(PCHContainerOps), + Files(new FileManager(FileSystemOptions())), DiagConsumer(nullptr) { + appendArgumentsAdjuster(getClangStripOutputAdjuster()); + appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); +} + +ClangTool::~ClangTool() {} + +void ClangTool::mapVirtualFile(StringRef FilePath, StringRef Content) { + MappedFileContents.push_back(std::make_pair(FilePath, Content)); +} + +void ClangTool::appendArgumentsAdjuster(ArgumentsAdjuster Adjuster) { + if (ArgsAdjuster) + ArgsAdjuster = combineAdjusters(ArgsAdjuster, Adjuster); + else + ArgsAdjuster = Adjuster; +} + +void ClangTool::clearArgumentsAdjusters() { + ArgsAdjuster = nullptr; +} + +int ClangTool::run(ToolAction *Action) { + // Exists solely for the purpose of lookup of the resource path. + // This just needs to be some symbol in the binary. + static int StaticSymbol; + // The driver detects the builtin header path based on the path of the + // executable. + // FIXME: On linux, GetMainExecutable is independent of the value of the + // first argument, thus allowing ClangTool and runToolOnCode to just + // pass in made-up names here. Make sure this works on other platforms. + 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 &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; + } + 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(), + PCHContainerOps); + 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; +} + +namespace { + +class ASTBuilderAction : public ToolAction { + std::vector<std::unique_ptr<ASTUnit>> &ASTs; + +public: + ASTBuilderAction(std::vector<std::unique_ptr<ASTUnit>> &ASTs) : ASTs(ASTs) {} + + bool runInvocation(CompilerInvocation *Invocation, FileManager *Files, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *DiagConsumer) override { + // FIXME: This should use the provided FileManager. + std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation( + Invocation, PCHContainerOps, + CompilerInstance::createDiagnostics(&Invocation->getDiagnosticOpts(), + DiagConsumer, + /*ShouldOwnClient=*/false)); + if (!AST) + return false; + + ASTs.push_back(std::move(AST)); + return true; + } +}; + +} + +int ClangTool::buildASTs(std::vector<std::unique_ptr<ASTUnit>> &ASTs) { + ASTBuilderAction Action(ASTs); + return run(&Action); +} + +std::unique_ptr<ASTUnit> +buildASTFromCode(const Twine &Code, const Twine &FileName, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) { + return buildASTFromCodeWithArgs(Code, std::vector<std::string>(), FileName, + PCHContainerOps); +} + +std::unique_ptr<ASTUnit> buildASTFromCodeWithArgs( + const Twine &Code, const std::vector<std::string> &Args, + const Twine &FileName, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) { + SmallString<16> FileNameStorage; + StringRef FileNameRef = FileName.toNullTerminatedStringRef(FileNameStorage); + + std::vector<std::unique_ptr<ASTUnit>> ASTs; + ASTBuilderAction Action(ASTs); + ToolInvocation Invocation(getSyntaxOnlyToolArgs(Args, FileNameRef), &Action, + nullptr, PCHContainerOps); + + SmallString<1024> CodeStorage; + Invocation.mapVirtualFile(FileNameRef, + Code.toNullTerminatedStringRef(CodeStorage)); + if (!Invocation.run()) + return nullptr; + + assert(ASTs.size() == 1); + return std::move(ASTs[0]); +} + +} // end namespace tooling +} // end namespace clang |