| //===- CompilationDatabase.cpp --------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file contains implementations of the CompilationDatabase base class |
| // and the FixedCompilationDatabase. |
| // |
| // FIXME: Various functions that take a string &ErrorMessage should be upgraded |
| // to Expected. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/Tooling/CompilationDatabase.h" |
| #include "clang/Basic/Diagnostic.h" |
| #include "clang/Basic/DiagnosticIDs.h" |
| #include "clang/Basic/DiagnosticOptions.h" |
| #include "clang/Basic/LLVM.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/ArrayRef.h" |
| #include "llvm/ADT/IntrusiveRefCntPtr.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/SmallString.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Option/Arg.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/Compiler.h" |
| #include "llvm/Support/ErrorOr.h" |
| #include "llvm/Support/Host.h" |
| #include "llvm/Support/LineIterator.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include <algorithm> |
| #include <cassert> |
| #include <cstring> |
| #include <iterator> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <system_error> |
| #include <utility> |
| #include <vector> |
| |
| using namespace clang; |
| using namespace tooling; |
| |
| LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry) |
| |
| CompilationDatabase::~CompilationDatabase() = default; |
| |
| std::unique_ptr<CompilationDatabase> |
| CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, |
| std::string &ErrorMessage) { |
| llvm::raw_string_ostream ErrorStream(ErrorMessage); |
| for (const CompilationDatabasePluginRegistry::entry &Database : |
| CompilationDatabasePluginRegistry::entries()) { |
| std::string DatabaseErrorMessage; |
| std::unique_ptr<CompilationDatabasePlugin> Plugin(Database.instantiate()); |
| if (std::unique_ptr<CompilationDatabase> DB = |
| Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage)) |
| return DB; |
| ErrorStream << Database.getName() << ": " << DatabaseErrorMessage << "\n"; |
| } |
| 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; |
| } |
| |
| std::vector<CompileCommand> CompilationDatabase::getAllCompileCommands() const { |
| std::vector<CompileCommand> Result; |
| for (const auto &File : getAllFiles()) { |
| auto C = getCompileCommands(File); |
| std::move(C.begin(), C.end(), std::back_inserter(Result)); |
| } |
| return Result; |
| } |
| |
| CompilationDatabasePlugin::~CompilationDatabasePlugin() = default; |
| |
| namespace { |
| |
| // Helper for recursively searching through a chain of actions and collecting |
| // all inputs, direct and indirect, of compile jobs. |
| struct CompileJobAnalyzer { |
| SmallVector<std::string, 2> Inputs; |
| |
| void run(const driver::Action *A) { |
| runImpl(A, false); |
| } |
| |
| 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 auto *IA = cast<driver::InputAction>(A); |
| Inputs.push_back(std::string(IA->getInputArg().getSpelling())); |
| } |
| break; |
| |
| default: |
| // Don't care about others |
| break; |
| } |
| |
| for (const driver::Action *AI : A->inputs()) |
| runImpl(AI, 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(DiagnosticConsumer &Other) : Other(Other) {} |
| |
| void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, |
| const Diagnostic &Info) override { |
| if (Info.getID() == 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)); |
| } else if (DiagLevel >= DiagnosticsEngine::Error) { |
| // If driver failed to create compilation object, show the diagnostics |
| // to user. |
| Other.HandleDiagnostic(DiagLevel, Info); |
| } |
| } |
| |
| DiagnosticConsumer &Other; |
| SmallVector<std::string, 2> UnusedInputs; |
| }; |
| |
| // Filter of tools unused flags such as -no-integrated-as and -Wa,*. |
| // They are not used for syntax checking, and could confuse targets |
| // which don't support these options. |
| struct FilterUnusedFlags { |
| bool operator() (StringRef S) { |
| return (S == "-no-integrated-as") || S.startswith("-Wa,"); |
| } |
| }; |
| |
| std::string GetClangToolCommand() { |
| static int Dummy; |
| std::string ClangExecutable = |
| llvm::sys::fs::getMainExecutable("clang", (void *)&Dummy); |
| SmallString<128> ClangToolPath; |
| ClangToolPath = llvm::sys::path::parent_path(ClangExecutable); |
| llvm::sys::path::append(ClangToolPath, "clang-tool"); |
| return std::string(ClangToolPath.str()); |
| } |
| |
| } // namespace |
| |
| /// 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, |
| std::string &ErrorMsg) { |
| IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); |
| llvm::raw_string_ostream Output(ErrorMsg); |
| TextDiagnosticPrinter DiagnosticPrinter(Output, &*DiagOpts); |
| UnusedInputDiagConsumer DiagClient(DiagnosticPrinter); |
| DiagnosticsEngine Diagnostics( |
| IntrusiveRefCntPtr<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 used to detect libc++ include |
| // dirs on Mac, it isn't used for other platforms. |
| std::string Argv0 = GetClangToolCommand(); |
| Args.insert(Args.begin(), Argv0.c_str()); |
| |
| // 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"); |
| |
| llvm::erase_if(Args, FilterUnusedFlags()); |
| |
| const std::unique_ptr<driver::Compilation> Compilation( |
| NewDriver->BuildCompilation(Args)); |
| if (!Compilation) |
| return false; |
| |
| const driver::JobList &Jobs = Compilation->getJobs(); |
| |
| CompileJobAnalyzer CompileAnalyzer; |
| |
| for (const auto &Cmd : Jobs) { |
| // Collect only for Assemble, Backend, and Compile jobs. If we do all jobs |
| // we get duplicates since Link jobs point to Assemble jobs as inputs. |
| // -flto* flags make the BackendJobClass, which still needs analyzer. |
| if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass || |
| Cmd.getSource().getKind() == driver::Action::BackendJobClass || |
| Cmd.getSource().getKind() == driver::Action::CompileJobClass) { |
| CompileAnalyzer.run(&Cmd.getSource()); |
| } |
| } |
| |
| if (CompileAnalyzer.Inputs.empty()) { |
| ErrorMsg = "warning: no compile jobs found\n"; |
| return false; |
| } |
| |
| // Remove all compilation input files from the command line and inputs deemed |
| // unused for compilation. This is necessary so that getCompileCommands() can |
| // construct a command line for each file. |
| std::vector<const char *>::iterator End = |
| llvm::remove_if(Args, [&](StringRef S) { |
| return llvm::is_contained(CompileAnalyzer.Inputs, S) || |
| llvm::is_contained(DiagClient.UnusedInputs, S); |
| }); |
| // 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; |
| } |
| |
| std::unique_ptr<FixedCompilationDatabase> |
| FixedCompilationDatabase::loadFromCommandLine(int &Argc, |
| const char *const *Argv, |
| std::string &ErrorMsg, |
| const Twine &Directory) { |
| ErrorMsg.clear(); |
| if (Argc == 0) |
| return nullptr; |
| 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, ErrorMsg)) |
| return nullptr; |
| return std::make_unique<FixedCompilationDatabase>(Directory, StrippedArgs); |
| } |
| |
| std::unique_ptr<FixedCompilationDatabase> |
| FixedCompilationDatabase::loadFromFile(StringRef Path, std::string &ErrorMsg) { |
| ErrorMsg.clear(); |
| llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File = |
| llvm::MemoryBuffer::getFile(Path); |
| if (std::error_code Result = File.getError()) { |
| ErrorMsg = "Error while opening fixed database: " + Result.message(); |
| return nullptr; |
| } |
| return loadFromBuffer(llvm::sys::path::parent_path(Path), |
| (*File)->getBuffer(), ErrorMsg); |
| } |
| |
| std::unique_ptr<FixedCompilationDatabase> |
| FixedCompilationDatabase::loadFromBuffer(StringRef Directory, StringRef Data, |
| std::string &ErrorMsg) { |
| ErrorMsg.clear(); |
| std::vector<std::string> Args; |
| StringRef Line; |
| while (!Data.empty()) { |
| std::tie(Line, Data) = Data.split('\n'); |
| // Stray whitespace is almost certainly unintended. |
| Line = Line.trim(); |
| if (!Line.empty()) |
| Args.push_back(Line.str()); |
| } |
| return std::make_unique<FixedCompilationDatabase>(Directory, std::move(Args)); |
| } |
| |
| FixedCompilationDatabase::FixedCompilationDatabase( |
| const Twine &Directory, ArrayRef<std::string> CommandLine) { |
| std::vector<std::string> ToolCommandLine(1, GetClangToolCommand()); |
| ToolCommandLine.insert(ToolCommandLine.end(), |
| CommandLine.begin(), CommandLine.end()); |
| CompileCommands.emplace_back(Directory, StringRef(), |
| std::move(ToolCommandLine), |
| StringRef()); |
| } |
| |
| std::vector<CompileCommand> |
| FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const { |
| std::vector<CompileCommand> Result(CompileCommands); |
| Result[0].CommandLine.push_back(std::string(FilePath)); |
| Result[0].Filename = std::string(FilePath); |
| return Result; |
| } |
| |
| namespace { |
| |
| class FixedCompilationDatabasePlugin : public CompilationDatabasePlugin { |
| std::unique_ptr<CompilationDatabase> |
| loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override { |
| SmallString<1024> DatabasePath(Directory); |
| llvm::sys::path::append(DatabasePath, "compile_flags.txt"); |
| return FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage); |
| } |
| }; |
| |
| } // namespace |
| |
| static CompilationDatabasePluginRegistry::Add<FixedCompilationDatabasePlugin> |
| X("fixed-compilation-database", "Reads plain-text flags file"); |
| |
| 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; |
| |
| } // namespace tooling |
| } // namespace clang |