Path: blob/main/contrib/llvm-project/clang/lib/Tooling/CompilationDatabase.cpp
35232 views
//===- CompilationDatabase.cpp --------------------------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file contains implementations of the CompilationDatabase base class9// and the FixedCompilationDatabase.10//11// FIXME: Various functions that take a string &ErrorMessage should be upgraded12// to Expected.13//14//===----------------------------------------------------------------------===//1516#include "clang/Tooling/CompilationDatabase.h"17#include "clang/Basic/Diagnostic.h"18#include "clang/Basic/DiagnosticIDs.h"19#include "clang/Basic/DiagnosticOptions.h"20#include "clang/Basic/LLVM.h"21#include "clang/Driver/Action.h"22#include "clang/Driver/Compilation.h"23#include "clang/Driver/Driver.h"24#include "clang/Driver/DriverDiagnostic.h"25#include "clang/Driver/Job.h"26#include "clang/Frontend/TextDiagnosticPrinter.h"27#include "clang/Tooling/CompilationDatabasePluginRegistry.h"28#include "clang/Tooling/Tooling.h"29#include "llvm/ADT/ArrayRef.h"30#include "llvm/ADT/IntrusiveRefCntPtr.h"31#include "llvm/ADT/STLExtras.h"32#include "llvm/ADT/SmallString.h"33#include "llvm/ADT/SmallVector.h"34#include "llvm/ADT/StringRef.h"35#include "llvm/Option/Arg.h"36#include "llvm/Support/Casting.h"37#include "llvm/Support/Compiler.h"38#include "llvm/Support/ErrorOr.h"39#include "llvm/Support/LineIterator.h"40#include "llvm/Support/MemoryBuffer.h"41#include "llvm/Support/Path.h"42#include "llvm/Support/raw_ostream.h"43#include "llvm/TargetParser/Host.h"44#include <algorithm>45#include <cassert>46#include <cstring>47#include <iterator>48#include <memory>49#include <sstream>50#include <string>51#include <system_error>52#include <utility>53#include <vector>5455using namespace clang;56using namespace tooling;5758LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry)5960CompilationDatabase::~CompilationDatabase() = default;6162std::unique_ptr<CompilationDatabase>63CompilationDatabase::loadFromDirectory(StringRef BuildDirectory,64std::string &ErrorMessage) {65llvm::raw_string_ostream ErrorStream(ErrorMessage);66for (const CompilationDatabasePluginRegistry::entry &Database :67CompilationDatabasePluginRegistry::entries()) {68std::string DatabaseErrorMessage;69std::unique_ptr<CompilationDatabasePlugin> Plugin(Database.instantiate());70if (std::unique_ptr<CompilationDatabase> DB =71Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage))72return DB;73ErrorStream << Database.getName() << ": " << DatabaseErrorMessage << "\n";74}75return nullptr;76}7778static std::unique_ptr<CompilationDatabase>79findCompilationDatabaseFromDirectory(StringRef Directory,80std::string &ErrorMessage) {81std::stringstream ErrorStream;82bool HasErrorMessage = false;83while (!Directory.empty()) {84std::string LoadErrorMessage;8586if (std::unique_ptr<CompilationDatabase> DB =87CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage))88return DB;8990if (!HasErrorMessage) {91ErrorStream << "No compilation database found in " << Directory.str()92<< " or any parent directory\n" << LoadErrorMessage;93HasErrorMessage = true;94}9596Directory = llvm::sys::path::parent_path(Directory);97}98ErrorMessage = ErrorStream.str();99return nullptr;100}101102std::unique_ptr<CompilationDatabase>103CompilationDatabase::autoDetectFromSource(StringRef SourceFile,104std::string &ErrorMessage) {105SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile));106StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);107108std::unique_ptr<CompilationDatabase> DB =109findCompilationDatabaseFromDirectory(Directory, ErrorMessage);110111if (!DB)112ErrorMessage = ("Could not auto-detect compilation database for file \"" +113SourceFile + "\"\n" + ErrorMessage).str();114return DB;115}116117std::unique_ptr<CompilationDatabase>118CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir,119std::string &ErrorMessage) {120SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir));121122std::unique_ptr<CompilationDatabase> DB =123findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage);124125if (!DB)126ErrorMessage = ("Could not auto-detect compilation database from directory \"" +127SourceDir + "\"\n" + ErrorMessage).str();128return DB;129}130131std::vector<CompileCommand> CompilationDatabase::getAllCompileCommands() const {132std::vector<CompileCommand> Result;133for (const auto &File : getAllFiles()) {134auto C = getCompileCommands(File);135std::move(C.begin(), C.end(), std::back_inserter(Result));136}137return Result;138}139140CompilationDatabasePlugin::~CompilationDatabasePlugin() = default;141142namespace {143144// Helper for recursively searching through a chain of actions and collecting145// all inputs, direct and indirect, of compile jobs.146struct CompileJobAnalyzer {147SmallVector<std::string, 2> Inputs;148149void run(const driver::Action *A) {150runImpl(A, false);151}152153private:154void runImpl(const driver::Action *A, bool Collect) {155bool CollectChildren = Collect;156switch (A->getKind()) {157case driver::Action::CompileJobClass:158case driver::Action::PrecompileJobClass:159CollectChildren = true;160break;161162case driver::Action::InputClass:163if (Collect) {164const auto *IA = cast<driver::InputAction>(A);165Inputs.push_back(std::string(IA->getInputArg().getSpelling()));166}167break;168169default:170// Don't care about others171break;172}173174for (const driver::Action *AI : A->inputs())175runImpl(AI, CollectChildren);176}177};178179// Special DiagnosticConsumer that looks for warn_drv_input_file_unused180// diagnostics from the driver and collects the option strings for those unused181// options.182class UnusedInputDiagConsumer : public DiagnosticConsumer {183public:184UnusedInputDiagConsumer(DiagnosticConsumer &Other) : Other(Other) {}185186void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,187const Diagnostic &Info) override {188if (Info.getID() == diag::warn_drv_input_file_unused) {189// Arg 1 for this diagnostic is the option that didn't get used.190UnusedInputs.push_back(Info.getArgStdStr(0));191} else if (DiagLevel >= DiagnosticsEngine::Error) {192// If driver failed to create compilation object, show the diagnostics193// to user.194Other.HandleDiagnostic(DiagLevel, Info);195}196}197198DiagnosticConsumer &Other;199SmallVector<std::string, 2> UnusedInputs;200};201202// Filter of tools unused flags such as -no-integrated-as and -Wa,*.203// They are not used for syntax checking, and could confuse targets204// which don't support these options.205struct FilterUnusedFlags {206bool operator() (StringRef S) {207return (S == "-no-integrated-as") || S.starts_with("-Wa,");208}209};210211std::string GetClangToolCommand() {212static int Dummy;213std::string ClangExecutable =214llvm::sys::fs::getMainExecutable("clang", (void *)&Dummy);215SmallString<128> ClangToolPath;216ClangToolPath = llvm::sys::path::parent_path(ClangExecutable);217llvm::sys::path::append(ClangToolPath, "clang-tool");218return std::string(ClangToolPath);219}220221} // namespace222223/// Strips any positional args and possible argv[0] from a command-line224/// provided by the user to construct a FixedCompilationDatabase.225///226/// FixedCompilationDatabase requires a command line to be in this format as it227/// constructs the command line for each file by appending the name of the file228/// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the229/// start of the command line although its value is not important as it's just230/// ignored by the Driver invoked by the ClangTool using the231/// FixedCompilationDatabase.232///233/// FIXME: This functionality should probably be made available by234/// clang::driver::Driver although what the interface should look like is not235/// clear.236///237/// \param[in] Args Args as provided by the user.238/// \return Resulting stripped command line.239/// \li true if successful.240/// \li false if \c Args cannot be used for compilation jobs (e.g.241/// contains an option like -E or -version).242static bool stripPositionalArgs(std::vector<const char *> Args,243std::vector<std::string> &Result,244std::string &ErrorMsg) {245IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();246llvm::raw_string_ostream Output(ErrorMsg);247TextDiagnosticPrinter DiagnosticPrinter(Output, &*DiagOpts);248UnusedInputDiagConsumer DiagClient(DiagnosticPrinter);249DiagnosticsEngine Diagnostics(250IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),251&*DiagOpts, &DiagClient, false);252253// The clang executable path isn't required since the jobs the driver builds254// will not be executed.255std::unique_ptr<driver::Driver> NewDriver(new driver::Driver(256/* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(),257Diagnostics));258NewDriver->setCheckInputsExist(false);259260// This becomes the new argv[0]. The value is used to detect libc++ include261// dirs on Mac, it isn't used for other platforms.262std::string Argv0 = GetClangToolCommand();263Args.insert(Args.begin(), Argv0.c_str());264265// By adding -c, we force the driver to treat compilation as the last phase.266// It will then issue warnings via Diagnostics about un-used options that267// would have been used for linking. If the user provided a compiler name as268// the original argv[0], this will be treated as a linker input thanks to269// insertng a new argv[0] above. All un-used options get collected by270// UnusedInputdiagConsumer and get stripped out later.271Args.push_back("-c");272273// Put a dummy C++ file on to ensure there's at least one compile job for the274// driver to construct. If the user specified some other argument that275// prevents compilation, e.g. -E or something like -version, we may still end276// up with no jobs but then this is the user's fault.277Args.push_back("placeholder.cpp");278279llvm::erase_if(Args, FilterUnusedFlags());280281const std::unique_ptr<driver::Compilation> Compilation(282NewDriver->BuildCompilation(Args));283if (!Compilation)284return false;285286const driver::JobList &Jobs = Compilation->getJobs();287288CompileJobAnalyzer CompileAnalyzer;289290for (const auto &Cmd : Jobs) {291// Collect only for Assemble, Backend, and Compile jobs. If we do all jobs292// we get duplicates since Link jobs point to Assemble jobs as inputs.293// -flto* flags make the BackendJobClass, which still needs analyzer.294if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass ||295Cmd.getSource().getKind() == driver::Action::BackendJobClass ||296Cmd.getSource().getKind() == driver::Action::CompileJobClass ||297Cmd.getSource().getKind() == driver::Action::PrecompileJobClass) {298CompileAnalyzer.run(&Cmd.getSource());299}300}301302if (CompileAnalyzer.Inputs.empty()) {303ErrorMsg = "warning: no compile jobs found\n";304return false;305}306307// Remove all compilation input files from the command line and inputs deemed308// unused for compilation. This is necessary so that getCompileCommands() can309// construct a command line for each file.310std::vector<const char *>::iterator End =311llvm::remove_if(Args, [&](StringRef S) {312return llvm::is_contained(CompileAnalyzer.Inputs, S) ||313llvm::is_contained(DiagClient.UnusedInputs, S);314});315// Remove the -c add above as well. It will be at the end right now.316assert(strcmp(*(End - 1), "-c") == 0);317--End;318319Result = std::vector<std::string>(Args.begin() + 1, End);320return true;321}322323std::unique_ptr<FixedCompilationDatabase>324FixedCompilationDatabase::loadFromCommandLine(int &Argc,325const char *const *Argv,326std::string &ErrorMsg,327const Twine &Directory) {328ErrorMsg.clear();329if (Argc == 0)330return nullptr;331const char *const *DoubleDash = std::find(Argv, Argv + Argc, StringRef("--"));332if (DoubleDash == Argv + Argc)333return nullptr;334std::vector<const char *> CommandLine(DoubleDash + 1, Argv + Argc);335Argc = DoubleDash - Argv;336337std::vector<std::string> StrippedArgs;338if (!stripPositionalArgs(CommandLine, StrippedArgs, ErrorMsg))339return nullptr;340return std::make_unique<FixedCompilationDatabase>(Directory, StrippedArgs);341}342343std::unique_ptr<FixedCompilationDatabase>344FixedCompilationDatabase::loadFromFile(StringRef Path, std::string &ErrorMsg) {345ErrorMsg.clear();346llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =347llvm::MemoryBuffer::getFile(Path);348if (std::error_code Result = File.getError()) {349ErrorMsg = "Error while opening fixed database: " + Result.message();350return nullptr;351}352return loadFromBuffer(llvm::sys::path::parent_path(Path),353(*File)->getBuffer(), ErrorMsg);354}355356std::unique_ptr<FixedCompilationDatabase>357FixedCompilationDatabase::loadFromBuffer(StringRef Directory, StringRef Data,358std::string &ErrorMsg) {359ErrorMsg.clear();360std::vector<std::string> Args;361StringRef Line;362while (!Data.empty()) {363std::tie(Line, Data) = Data.split('\n');364// Stray whitespace is almost certainly unintended.365Line = Line.trim();366if (!Line.empty())367Args.push_back(Line.str());368}369return std::make_unique<FixedCompilationDatabase>(Directory, std::move(Args));370}371372FixedCompilationDatabase::FixedCompilationDatabase(373const Twine &Directory, ArrayRef<std::string> CommandLine) {374std::vector<std::string> ToolCommandLine(1, GetClangToolCommand());375ToolCommandLine.insert(ToolCommandLine.end(),376CommandLine.begin(), CommandLine.end());377CompileCommands.emplace_back(Directory, StringRef(),378std::move(ToolCommandLine),379StringRef());380}381382std::vector<CompileCommand>383FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const {384std::vector<CompileCommand> Result(CompileCommands);385Result[0].CommandLine.push_back(std::string(FilePath));386Result[0].Filename = std::string(FilePath);387return Result;388}389390namespace {391392class FixedCompilationDatabasePlugin : public CompilationDatabasePlugin {393std::unique_ptr<CompilationDatabase>394loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override {395SmallString<1024> DatabasePath(Directory);396llvm::sys::path::append(DatabasePath, "compile_flags.txt");397return FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage);398}399};400401} // namespace402403static CompilationDatabasePluginRegistry::Add<FixedCompilationDatabasePlugin>404X("fixed-compilation-database", "Reads plain-text flags file");405406namespace clang {407namespace tooling {408409// This anchor is used to force the linker to link in the generated object file410// and thus register the JSONCompilationDatabasePlugin.411extern volatile int JSONAnchorSource;412static int LLVM_ATTRIBUTE_UNUSED JSONAnchorDest = JSONAnchorSource;413414} // namespace tooling415} // namespace clang416417418