| //===- 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/LineIterator.h" | 
 | #include "llvm/Support/MemoryBuffer.h" | 
 | #include "llvm/Support/Path.h" | 
 | #include "llvm/Support/raw_ostream.h" | 
 | #include "llvm/TargetParser/Host.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: | 
 |     case driver::Action::PrecompileJobClass: | 
 |       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.starts_with("-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); | 
 | } | 
 |  | 
 | } // 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 || | 
 |         Cmd.getSource().getKind() == driver::Action::PrecompileJobClass) { | 
 |       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 |