diff options
Diffstat (limited to 'contrib/llvm/tools/clang/lib/Tooling')
10 files changed, 2287 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..2f3d829 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/ArgumentsAdjusters.cpp @@ -0,0 +1,86 @@ +//===--- 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" + +namespace clang { +namespace tooling { + +/// Add -fsyntax-only option to the commnand line arguments. +ArgumentsAdjuster getClangSyntaxOnlyAdjuster() { + return [](const CommandLineArguments &Args, StringRef /*unused*/) { + 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, StringRef /*unused*/) { + 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, StringRef /*unused*/) { + 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, StringRef File) { + return Second(First(Args, File), File); + }; +} + +} // 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..82f5601 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/CommonOptionsParser.cpp @@ -0,0 +1,149 @@ +//===--- 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, Command.Filename); + return Commands; + } +}; +} // namespace + +CommonOptionsParser::CommonOptionsParser( + int &argc, const char **argv, cl::OptionCategory &Category, + llvm::cl::NumOccurrencesFlag OccurrencesFlag, 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>]"), OccurrencesFlag, + 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 ((OccurrencesFlag == cl::ZeroOrMore || OccurrencesFlag == cl::Optional) && + SourcePathList.empty()) + return; + if (!Compilations) { + std::string ErrorMessage; + if (!BuildPath.empty()) { + Compilations = + CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage); + } else { + Compilations = CompilationDatabase::autoDetectFromSource(SourcePaths[0], + ErrorMessage); + } + if (!Compilations) { + llvm::errs() << "Error while trying to load a compilation database:\n" + << ErrorMessage << "Running without flags.\n"; + Compilations.reset( + new FixedCompilationDatabase(".", std::vector<std::string>())); + } + } + 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..957e401 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/CompilationDatabase.cpp @@ -0,0 +1,333 @@ +//===--- 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, StringRef(), + std::move(ToolCommandLine)); +} + +std::vector<CompileCommand> +FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const { + std::vector<CompileCommand> Result(CompileCommands); + Result[0].CommandLine.push_back(FilePath); + Result[0].Filename = 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 LLVM_ATTRIBUTE_UNUSED JSONAnchorDest = JSONAnchorSource; + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Core/Lookup.cpp b/contrib/llvm/tools/clang/lib/Tooling/Core/Lookup.cpp new file mode 100644 index 0000000..697eeb4 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Core/Lookup.cpp @@ -0,0 +1,113 @@ +//===--- Lookup.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. +// +//===----------------------------------------------------------------------===// +// +// This file defines helper methods for clang tools performing name lookup. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Core/Lookup.h" +#include "clang/AST/Decl.h" +using namespace clang; +using namespace clang::tooling; + +static bool isInsideDifferentNamespaceWithSameName(const DeclContext *DeclA, + const DeclContext *DeclB) { + while (true) { + // Look past non-namespaces on DeclA. + while (DeclA && !isa<NamespaceDecl>(DeclA)) + DeclA = DeclA->getParent(); + + // Look past non-namespaces on DeclB. + while (DeclB && !isa<NamespaceDecl>(DeclB)) + DeclB = DeclB->getParent(); + + // We hit the root, no namespace collision. + if (!DeclA || !DeclB) + return false; + + // Literally the same namespace, not a collision. + if (DeclA == DeclB) + return false; + + // Now check the names. If they match we have a different namespace with the + // same name. + if (cast<NamespaceDecl>(DeclA)->getDeclName() == + cast<NamespaceDecl>(DeclB)->getDeclName()) + return true; + + DeclA = DeclA->getParent(); + DeclB = DeclB->getParent(); + } +} + +static StringRef getBestNamespaceSubstr(const DeclContext *DeclA, + StringRef NewName, + bool HadLeadingColonColon) { + while (true) { + while (DeclA && !isa<NamespaceDecl>(DeclA)) + DeclA = DeclA->getParent(); + + // Fully qualified it is! Leave :: in place if it's there already. + if (!DeclA) + return HadLeadingColonColon ? NewName : NewName.substr(2); + + // Otherwise strip off redundant namespace qualifications from the new name. + // We use the fully qualified name of the namespace and remove that part + // from NewName if it has an identical prefix. + std::string NS = + "::" + cast<NamespaceDecl>(DeclA)->getQualifiedNameAsString() + "::"; + if (NewName.startswith(NS)) + return NewName.substr(NS.size()); + + // No match yet. Strip of a namespace from the end of the chain and try + // again. This allows to get optimal qualifications even if the old and new + // decl only share common namespaces at a higher level. + DeclA = DeclA->getParent(); + } +} + +/// Check if the name specifier begins with a written "::". +static bool isFullyQualified(const NestedNameSpecifier *NNS) { + while (NNS) { + if (NNS->getKind() == NestedNameSpecifier::Global) + return true; + NNS = NNS->getPrefix(); + } + return false; +} + +std::string tooling::replaceNestedName(const NestedNameSpecifier *Use, + const DeclContext *UseContext, + const NamedDecl *FromDecl, + StringRef ReplacementString) { + assert(ReplacementString.startswith("::") && + "Expected fully-qualified name!"); + + // We can do a raw name replacement when we are not inside the namespace for + // the original function and it is not in the global namespace. The + // assumption is that outside the original namespace we must have a using + // statement that makes this work out and that other parts of this refactor + // will automatically fix using statements to point to the new function + const bool class_name_only = !Use; + const bool in_global_namespace = + isa<TranslationUnitDecl>(FromDecl->getDeclContext()); + if (class_name_only && !in_global_namespace && + !isInsideDifferentNamespaceWithSameName(FromDecl->getDeclContext(), + UseContext)) { + auto Pos = ReplacementString.rfind("::"); + return Pos != StringRef::npos ? ReplacementString.substr(Pos + 2) + : ReplacementString; + } + // We did not match this because of a using statement, so we will need to + // figure out how good a namespace match we have with our destination type. + // We work backwards (from most specific possible namespace to least + // specific). + return getBestNamespaceSubstr(UseContext, ReplacementString, + isFullyQualified(Use)); +} 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..47bbdeb --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Core/Replacement.cpp @@ -0,0 +1,419 @@ +//===--- 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); + this->FilePath = Entry ? Entry->getName() : 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); +} + +template <typename T> +unsigned shiftedCodePositionInternal(const T &Replaces, unsigned Position) { + unsigned Offset = 0; + for (const auto& R : Replaces) { + if (R.getOffset() + R.getLength() <= Position) { + Offset += R.getReplacementText().size() - R.getLength(); + continue; + } + if (R.getOffset() < Position && + R.getOffset() + R.getReplacementText().size() <= Position) { + Position = R.getOffset() + R.getReplacementText().size() - 1; + } + break; + } + return Position + Offset; +} + +unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { + return shiftedCodePositionInternal(Replaces, Position); +} + +// FIXME: Remove this function when Replacements is implemented as std::vector +// instead of std::set. +unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces, + unsigned Position) { + return shiftedCodePositionInternal(Replaces, Position); +} + +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) { + IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem( + new vfs::InMemoryFileSystem); + FileManager Files(FileSystemOptions(), InMemoryFileSystem); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), + new DiagnosticOptions); + SourceManager SourceMgr(Diagnostics, Files); + Rewriter Rewrite(SourceMgr, LangOptions()); + InMemoryFileSystem->addFile( + "<stdin>", 0, llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>")); + FileID ID = SourceMgr.createFileID(Files.getFile("<stdin>"), 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; +} + +namespace { +// Represents a merged replacement, i.e. a replacement consisting of multiple +// overlapping replacements from 'First' and 'Second' in mergeReplacements. +// +// Position projection: +// Offsets and lengths of the replacements can generally refer to two different +// coordinate spaces. Replacements from 'First' refer to the original text +// whereas replacements from 'Second' refer to the text after applying 'First'. +// +// MergedReplacement always operates in the coordinate space of the original +// text, i.e. transforms elements from 'Second' to take into account what was +// changed based on the elements from 'First'. +// +// We can correctly calculate this projection as we look at the replacements in +// order of strictly increasing offsets. +// +// Invariants: +// * We always merge elements from 'First' into elements from 'Second' and vice +// versa. Within each set, the replacements are non-overlapping. +// * We only extend to the right, i.e. merge elements with strictly increasing +// offsets. +class MergedReplacement { +public: + MergedReplacement(const Replacement &R, bool MergeSecond, int D) + : MergeSecond(MergeSecond), Delta(D), FilePath(R.getFilePath()), + Offset(R.getOffset() + (MergeSecond ? 0 : Delta)), Length(R.getLength()), + Text(R.getReplacementText()) { + Delta += MergeSecond ? 0 : Text.size() - Length; + DeltaFirst = MergeSecond ? Text.size() - Length : 0; + } + + // Merges the next element 'R' into this merged element. As we always merge + // from 'First' into 'Second' or vice versa, the MergedReplacement knows what + // set the next element is coming from. + void merge(const Replacement &R) { + if (MergeSecond) { + unsigned REnd = R.getOffset() + Delta + R.getLength(); + unsigned End = Offset + Text.size(); + if (REnd > End) { + Length += REnd - End; + MergeSecond = false; + } + StringRef TextRef = Text; + StringRef Head = TextRef.substr(0, R.getOffset() + Delta - Offset); + StringRef Tail = TextRef.substr(REnd - Offset); + Text = (Head + R.getReplacementText() + Tail).str(); + Delta += R.getReplacementText().size() - R.getLength(); + } else { + unsigned End = Offset + Length; + StringRef RText = R.getReplacementText(); + StringRef Tail = RText.substr(End - R.getOffset()); + Text = (Text + Tail).str(); + if (R.getOffset() + RText.size() > End) { + Length = R.getOffset() + R.getLength() - Offset; + MergeSecond = true; + } else { + Length += R.getLength() - RText.size(); + } + DeltaFirst += RText.size() - R.getLength(); + } + } + + // Returns 'true' if 'R' starts strictly after the MergedReplacement and thus + // doesn't need to be merged. + bool endsBefore(const Replacement &R) const { + if (MergeSecond) + return Offset + Text.size() < R.getOffset() + Delta; + return Offset + Length < R.getOffset(); + } + + // Returns 'true' if an element from the second set should be merged next. + bool mergeSecond() const { return MergeSecond; } + int deltaFirst() const { return DeltaFirst; } + Replacement asReplacement() const { return {FilePath, Offset, Length, Text}; } + +private: + bool MergeSecond; + + // Amount of characters that elements from 'Second' need to be shifted by in + // order to refer to the original text. + int Delta; + + // Sum of all deltas (text-length - length) of elements from 'First' merged + // into this element. This is used to update 'Delta' once the + // MergedReplacement is completed. + int DeltaFirst; + + // Data of the actually merged replacement. FilePath and Offset aren't changed + // as the element is only extended to the right. + const StringRef FilePath; + const unsigned Offset; + unsigned Length; + std::string Text; +}; +} // namespace + +Replacements mergeReplacements(const Replacements &First, + const Replacements &Second) { + if (First.empty() || Second.empty()) + return First.empty() ? Second : First; + + // Delta is the amount of characters that replacements from 'Second' need to + // be shifted so that their offsets refer to the original text. + int Delta = 0; + Replacements Result; + + // Iterate over both sets and always add the next element (smallest total + // Offset) from either 'First' or 'Second'. Merge that element with + // subsequent replacements as long as they overlap. See more details in the + // comment on MergedReplacement. + for (auto FirstI = First.begin(), SecondI = Second.begin(); + FirstI != First.end() || SecondI != Second.end();) { + bool NextIsFirst = SecondI == Second.end() || + (FirstI != First.end() && + FirstI->getOffset() < SecondI->getOffset() + Delta); + MergedReplacement Merged(NextIsFirst ? *FirstI : *SecondI, NextIsFirst, + Delta); + ++(NextIsFirst ? FirstI : SecondI); + + while ((Merged.mergeSecond() && SecondI != Second.end()) || + (!Merged.mergeSecond() && FirstI != First.end())) { + auto &I = Merged.mergeSecond() ? SecondI : FirstI; + if (Merged.endsBefore(*I)) + break; + Merged.merge(*I); + ++I; + } + Delta -= Merged.deltaFirst(); + Result.insert(Merged.asReplacement()); + } + 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..299fbdc --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/JSONCompilationDatabase.cpp @@ -0,0 +1,345 @@ +//===--- 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; + getCommands(AllCommands, Commands); + return Commands; +} + +static std::vector<std::string> +nodeToCommandLine(const std::vector<llvm::yaml::ScalarNode *> &Nodes) { + SmallString<1024> Storage; + if (Nodes.size() == 1) { + return unescapeCommandLine(Nodes[0]->getValue(Storage)); + } + std::vector<std::string> Arguments; + for (auto *Node : Nodes) { + Arguments.push_back(Node->getValue(Storage)); + } + return Arguments; +} + +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<32> FilenameStorage; + Commands.emplace_back( + std::get<0>(CommandsRef[I])->getValue(DirectoryStorage), + std::get<1>(CommandsRef[I])->getValue(FilenameStorage), + nodeToCommandLine(std::get<2>(CommandsRef[I]))); + } +} + +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 (auto& NextObject : *Array) { + llvm::yaml::MappingNode *Object = dyn_cast<llvm::yaml::MappingNode>(&NextObject); + if (!Object) { + ErrorMessage = "Expected object."; + return false; + } + llvm::yaml::ScalarNode *Directory = nullptr; + llvm::Optional<std::vector<llvm::yaml::ScalarNode *>> Command; + llvm::yaml::ScalarNode *File = nullptr; + for (auto& NextKeyValue : *Object) { + llvm::yaml::ScalarNode *KeyString = + dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey()); + if (!KeyString) { + ErrorMessage = "Expected strings as key."; + return false; + } + SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + llvm::yaml::Node *Value = NextKeyValue.getValue(); + if (!Value) { + ErrorMessage = "Expected value."; + return false; + } + llvm::yaml::ScalarNode *ValueString = + dyn_cast<llvm::yaml::ScalarNode>(Value); + llvm::yaml::SequenceNode *SequenceString = + dyn_cast<llvm::yaml::SequenceNode>(Value); + if (KeyValue == "arguments" && !SequenceString) { + ErrorMessage = "Expected sequence as value."; + return false; + } else if (KeyValue != "arguments" && !ValueString) { + ErrorMessage = "Expected string as value."; + return false; + } + if (KeyValue == "directory") { + Directory = ValueString; + } else if (KeyValue == "arguments") { + Command = std::vector<llvm::yaml::ScalarNode *>(); + for (auto &Argument : *SequenceString) { + auto Scalar = dyn_cast<llvm::yaml::ScalarNode>(&Argument); + if (!Scalar) { + ErrorMessage = "Only strings are allowed in 'arguments'."; + return false; + } + Command->push_back(Scalar); + } + } else if (KeyValue == "command") { + if (!Command) + Command = std::vector<llvm::yaml::ScalarNode *>(1, ValueString); + } else if (KeyValue == "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\" or \"arguments\"."; + 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); + } + auto Cmd = CompileCommandRef(Directory, File, *Command); + IndexByFile[NativeFilePath].push_back(Cmd); + AllCommands.push_back(Cmd); + 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..fd5596e --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Tooling.cpp @@ -0,0 +1,507 @@ +//===--- 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/Driver/ToolChain.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" + +#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, + IntrusiveRefCntPtr<vfs::FileSystem> VFS) { + clang::driver::Driver *CompilerDriver = new clang::driver::Driver( + BinaryName, llvm::sys::getDefaultTargetTriple(), *Diagnostics, VFS); + 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<vfs::OverlayFileSystem> OverlayFileSystem( + new vfs::OverlayFileSystem(vfs::getRealFileSystem())); + llvm::IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem( + new vfs::InMemoryFileSystem); + OverlayFileSystem->pushOverlay(InMemoryFileSystem); + llvm::IntrusiveRefCntPtr<FileManager> Files( + new FileManager(FileSystemOptions(), OverlayFileSystem)); + ToolInvocation Invocation(getSyntaxOnlyToolArgs(Args, FileNameRef), + ToolAction, Files.get(), PCHContainerOps); + + SmallString<1024> CodeStorage; + InMemoryFileSystem->addFile(FileNameRef, 0, + llvm::MemoryBuffer::getMemBuffer( + Code.toNullTerminatedStringRef(CodeStorage))); + + for (auto &FilenameWithContent : VirtualMappedFiles) { + InMemoryFileSystem->addFile( + FilenameWithContent.first, 0, + llvm::MemoryBuffer::getMemBuffer(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(); +} + +void addTargetAndModeForProgramName(std::vector<std::string> &CommandLine, + StringRef InvokedAs) { + if (!CommandLine.empty() && !InvokedAs.empty()) { + bool AlreadyHasTarget = false; + bool AlreadyHasMode = false; + // Skip CommandLine[0]. + for (auto Token = ++CommandLine.begin(); Token != CommandLine.end(); + ++Token) { + StringRef TokenRef(*Token); + AlreadyHasTarget |= + (TokenRef == "-target" || TokenRef.startswith("-target=")); + AlreadyHasMode |= (TokenRef == "--driver-mode" || + TokenRef.startswith("--driver-mode=")); + } + auto TargetMode = + clang::driver::ToolChain::getTargetAndModeFromProgramName(InvokedAs); + if (!AlreadyHasMode && !TargetMode.second.empty()) { + CommandLine.insert(++CommandLine.begin(), TargetMode.second); + } + if (!AlreadyHasTarget && !TargetMode.first.empty()) { + CommandLine.insert(++CommandLine.begin(), {"-target", TargetMode.first}); + } + } +} + +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, Files->getVirtualFileSystem())); + // 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)); + // FIXME: remove this when all users have migrated! + 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), + OverlayFileSystem(new vfs::OverlayFileSystem(vfs::getRealFileSystem())), + InMemoryFileSystem(new vfs::InMemoryFileSystem), + Files(new FileManager(FileSystemOptions(), OverlayFileSystem)), + DiagConsumer(nullptr) { + OverlayFileSystem->pushOverlay(InMemoryFileSystem); + 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())); + + // First insert all absolute paths into the in-memory VFS. These are global + // for all compile commands. + if (SeenWorkingDirectories.insert("/").second) + for (const auto &MappedFile : MappedFileContents) + if (llvm::sys::path::is_absolute(MappedFile.first)) + InMemoryFileSystem->addFile( + MappedFile.first, 0, + llvm::MemoryBuffer::getMemBuffer(MappedFile.second)); + + 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 (OverlayFileSystem->setCurrentWorkingDirectory( + CompileCommand.Directory)) + llvm::report_fatal_error("Cannot chdir into \"" + + Twine(CompileCommand.Directory) + "\n!"); + + // Now fill the in-memory VFS with the relative file mappings so it will + // have the correct relative paths. We never remove mappings but that + // should be fine. + if (SeenWorkingDirectories.insert(CompileCommand.Directory).second) + for (const auto &MappedFile : MappedFileContents) + if (!llvm::sys::path::is_absolute(MappedFile.first)) + InMemoryFileSystem->addFile( + MappedFile.first, 0, + llvm::MemoryBuffer::getMemBuffer(MappedFile.second)); + + std::vector<std::string> CommandLine = CompileCommand.CommandLine; + if (ArgsAdjuster) + CommandLine = ArgsAdjuster(CommandLine, CompileCommand.Filename); + 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); + + 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 (OverlayFileSystem->setCurrentWorkingDirectory(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 { + std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation( + Invocation, PCHContainerOps, + CompilerInstance::createDiagnostics(&Invocation->getDiagnosticOpts(), + DiagConsumer, + /*ShouldOwnClient=*/false), + Files); + 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); + llvm::IntrusiveRefCntPtr<vfs::OverlayFileSystem> OverlayFileSystem( + new vfs::OverlayFileSystem(vfs::getRealFileSystem())); + llvm::IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem( + new vfs::InMemoryFileSystem); + OverlayFileSystem->pushOverlay(InMemoryFileSystem); + llvm::IntrusiveRefCntPtr<FileManager> Files( + new FileManager(FileSystemOptions(), OverlayFileSystem)); + ToolInvocation Invocation(getSyntaxOnlyToolArgs(Args, FileNameRef), &Action, + Files.get(), PCHContainerOps); + + SmallString<1024> CodeStorage; + InMemoryFileSystem->addFile(FileNameRef, 0, + llvm::MemoryBuffer::getMemBuffer( + Code.toNullTerminatedStringRef(CodeStorage))); + if (!Invocation.run()) + return nullptr; + + assert(ASTs.size() == 1); + return std::move(ASTs[0]); +} + +} // end namespace tooling +} // end namespace clang |