410 lines · cpp
1//===- CompilationDatabase.cpp --------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file contains implementations of the CompilationDatabase base class10// and the FixedCompilationDatabase.11//12// FIXME: Various functions that take a string &ErrorMessage should be upgraded13// to Expected.14//15//===----------------------------------------------------------------------===//16 17#include "clang/Tooling/CompilationDatabase.h"18#include "clang/Basic/Diagnostic.h"19#include "clang/Basic/DiagnosticIDs.h"20#include "clang/Basic/DiagnosticOptions.h"21#include "clang/Basic/LLVM.h"22#include "clang/Driver/Action.h"23#include "clang/Driver/Compilation.h"24#include "clang/Driver/Driver.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/STLExtras.h"30#include "llvm/ADT/StringRef.h"31#include "llvm/Option/Arg.h"32#include "llvm/Support/Compiler.h"33#include "llvm/Support/ErrorOr.h"34#include "llvm/Support/LineIterator.h"35#include "llvm/Support/MemoryBuffer.h"36#include "llvm/Support/Path.h"37#include "llvm/Support/raw_ostream.h"38#include "llvm/TargetParser/Host.h"39#include <algorithm>40#include <cassert>41#include <cstring>42#include <iterator>43#include <memory>44#include <sstream>45#include <string>46#include <system_error>47#include <utility>48#include <vector>49 50using namespace clang;51using namespace tooling;52 53LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry)54 55CompilationDatabase::~CompilationDatabase() = default;56 57std::unique_ptr<CompilationDatabase>58CompilationDatabase::loadFromDirectory(StringRef BuildDirectory,59 std::string &ErrorMessage) {60 llvm::raw_string_ostream ErrorStream(ErrorMessage);61 for (const CompilationDatabasePluginRegistry::entry &Database :62 CompilationDatabasePluginRegistry::entries()) {63 std::string DatabaseErrorMessage;64 std::unique_ptr<CompilationDatabasePlugin> Plugin(Database.instantiate());65 if (std::unique_ptr<CompilationDatabase> DB =66 Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage))67 return DB;68 ErrorStream << Database.getName() << ": " << DatabaseErrorMessage << "\n";69 }70 return nullptr;71}72 73static std::unique_ptr<CompilationDatabase>74findCompilationDatabaseFromDirectory(StringRef Directory,75 std::string &ErrorMessage) {76 std::stringstream ErrorStream;77 bool HasErrorMessage = false;78 while (!Directory.empty()) {79 std::string LoadErrorMessage;80 81 if (std::unique_ptr<CompilationDatabase> DB =82 CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage))83 return DB;84 85 if (!HasErrorMessage) {86 ErrorStream << "No compilation database found in " << Directory.str()87 << " or any parent directory\n" << LoadErrorMessage;88 HasErrorMessage = true;89 }90 91 Directory = llvm::sys::path::parent_path(Directory);92 }93 ErrorMessage = ErrorStream.str();94 return nullptr;95}96 97std::unique_ptr<CompilationDatabase>98CompilationDatabase::autoDetectFromSource(StringRef SourceFile,99 std::string &ErrorMessage) {100 SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile));101 StringRef Directory = llvm::sys::path::parent_path(AbsolutePath);102 103 std::unique_ptr<CompilationDatabase> DB =104 findCompilationDatabaseFromDirectory(Directory, ErrorMessage);105 106 if (!DB)107 ErrorMessage = ("Could not auto-detect compilation database for file \"" +108 SourceFile + "\"\n" + ErrorMessage).str();109 return DB;110}111 112std::unique_ptr<CompilationDatabase>113CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir,114 std::string &ErrorMessage) {115 SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir));116 117 std::unique_ptr<CompilationDatabase> DB =118 findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage);119 120 if (!DB)121 ErrorMessage = ("Could not auto-detect compilation database from directory \"" +122 SourceDir + "\"\n" + ErrorMessage).str();123 return DB;124}125 126std::vector<CompileCommand> CompilationDatabase::getAllCompileCommands() const {127 std::vector<CompileCommand> Result;128 for (const auto &File : getAllFiles()) {129 auto C = getCompileCommands(File);130 std::move(C.begin(), C.end(), std::back_inserter(Result));131 }132 return Result;133}134 135CompilationDatabasePlugin::~CompilationDatabasePlugin() = default;136 137namespace {138 139// Helper for recursively searching through a chain of actions and collecting140// all inputs, direct and indirect, of compile jobs.141struct CompileJobAnalyzer {142 SmallVector<std::string, 2> Inputs;143 144 void run(const driver::Action *A) {145 runImpl(A, false);146 }147 148private:149 void runImpl(const driver::Action *A, bool Collect) {150 bool CollectChildren = Collect;151 switch (A->getKind()) {152 case driver::Action::CompileJobClass:153 case driver::Action::PrecompileJobClass:154 CollectChildren = true;155 break;156 157 case driver::Action::InputClass:158 if (Collect) {159 const auto *IA = cast<driver::InputAction>(A);160 Inputs.push_back(std::string(IA->getInputArg().getSpelling()));161 }162 break;163 164 default:165 // Don't care about others166 break;167 }168 169 for (const driver::Action *AI : A->inputs())170 runImpl(AI, CollectChildren);171 }172};173 174// Special DiagnosticConsumer that looks for warn_drv_input_file_unused175// diagnostics from the driver and collects the option strings for those unused176// options.177class UnusedInputDiagConsumer : public DiagnosticConsumer {178public:179 UnusedInputDiagConsumer(DiagnosticConsumer &Other) : Other(Other) {}180 181 void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,182 const Diagnostic &Info) override {183 if (Info.getID() == diag::warn_drv_input_file_unused) {184 // Arg 1 for this diagnostic is the option that didn't get used.185 UnusedInputs.push_back(Info.getArgStdStr(0));186 } else if (DiagLevel >= DiagnosticsEngine::Error) {187 // If driver failed to create compilation object, show the diagnostics188 // to user.189 Other.HandleDiagnostic(DiagLevel, Info);190 }191 }192 193 DiagnosticConsumer &Other;194 SmallVector<std::string, 2> UnusedInputs;195};196 197// Filter of tools unused flags such as -no-integrated-as and -Wa,*.198// They are not used for syntax checking, and could confuse targets199// which don't support these options.200struct FilterUnusedFlags {201 bool operator() (StringRef S) {202 return (S == "-no-integrated-as") || S.starts_with("-Wa,");203 }204};205 206std::string GetClangToolCommand() {207 static int Dummy;208 std::string ClangExecutable =209 llvm::sys::fs::getMainExecutable("clang", (void *)&Dummy);210 SmallString<128> ClangToolPath;211 ClangToolPath = llvm::sys::path::parent_path(ClangExecutable);212 llvm::sys::path::append(ClangToolPath, "clang-tool");213 return std::string(ClangToolPath);214}215 216} // namespace217 218/// Strips any positional args and possible argv[0] from a command-line219/// provided by the user to construct a FixedCompilationDatabase.220///221/// FixedCompilationDatabase requires a command line to be in this format as it222/// constructs the command line for each file by appending the name of the file223/// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the224/// start of the command line although its value is not important as it's just225/// ignored by the Driver invoked by the ClangTool using the226/// FixedCompilationDatabase.227///228/// FIXME: This functionality should probably be made available by229/// clang::driver::Driver although what the interface should look like is not230/// clear.231///232/// \param[in] Args Args as provided by the user.233/// \return Resulting stripped command line.234/// \li true if successful.235/// \li false if \c Args cannot be used for compilation jobs (e.g.236/// contains an option like -E or -version).237static bool stripPositionalArgs(std::vector<const char *> Args,238 std::vector<std::string> &Result,239 std::string &ErrorMsg) {240 DiagnosticOptions DiagOpts;241 llvm::raw_string_ostream Output(ErrorMsg);242 TextDiagnosticPrinter DiagnosticPrinter(Output, DiagOpts);243 UnusedInputDiagConsumer DiagClient(DiagnosticPrinter);244 DiagnosticsEngine Diagnostics(DiagnosticIDs::create(), DiagOpts, &DiagClient,245 false);246 247 // The clang executable path isn't required since the jobs the driver builds248 // will not be executed.249 std::unique_ptr<driver::Driver> NewDriver(new driver::Driver(250 /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(),251 Diagnostics));252 NewDriver->setCheckInputsExist(false);253 254 // This becomes the new argv[0]. The value is used to detect libc++ include255 // dirs on Mac, it isn't used for other platforms.256 std::string Argv0 = GetClangToolCommand();257 Args.insert(Args.begin(), Argv0.c_str());258 259 // By adding -c, we force the driver to treat compilation as the last phase.260 // It will then issue warnings via Diagnostics about un-used options that261 // would have been used for linking. If the user provided a compiler name as262 // the original argv[0], this will be treated as a linker input thanks to263 // insertng a new argv[0] above. All un-used options get collected by264 // UnusedInputdiagConsumer and get stripped out later.265 Args.push_back("-c");266 267 // Put a dummy C++ file on to ensure there's at least one compile job for the268 // driver to construct. If the user specified some other argument that269 // prevents compilation, e.g. -E or something like -version, we may still end270 // up with no jobs but then this is the user's fault.271 Args.push_back("placeholder.cpp");272 273 llvm::erase_if(Args, FilterUnusedFlags());274 275 const std::unique_ptr<driver::Compilation> Compilation(276 NewDriver->BuildCompilation(Args));277 if (!Compilation)278 return false;279 280 const driver::JobList &Jobs = Compilation->getJobs();281 282 CompileJobAnalyzer CompileAnalyzer;283 284 for (const auto &Cmd : Jobs) {285 // Collect only for Assemble, Backend, and Compile jobs. If we do all jobs286 // we get duplicates since Link jobs point to Assemble jobs as inputs.287 // -flto* flags make the BackendJobClass, which still needs analyzer.288 if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass ||289 Cmd.getSource().getKind() == driver::Action::BackendJobClass ||290 Cmd.getSource().getKind() == driver::Action::CompileJobClass ||291 Cmd.getSource().getKind() == driver::Action::PrecompileJobClass) {292 CompileAnalyzer.run(&Cmd.getSource());293 }294 }295 296 if (CompileAnalyzer.Inputs.empty()) {297 ErrorMsg = "warning: no compile jobs found\n";298 return false;299 }300 301 // Remove all compilation input files from the command line and inputs deemed302 // unused for compilation. This is necessary so that getCompileCommands() can303 // construct a command line for each file.304 std::vector<const char *>::iterator End =305 llvm::remove_if(Args, [&](StringRef S) {306 return llvm::is_contained(CompileAnalyzer.Inputs, S) ||307 llvm::is_contained(DiagClient.UnusedInputs, S);308 });309 // Remove the -c add above as well. It will be at the end right now.310 assert(strcmp(*(End - 1), "-c") == 0);311 --End;312 313 Result = std::vector<std::string>(Args.begin() + 1, End);314 return true;315}316 317std::unique_ptr<FixedCompilationDatabase>318FixedCompilationDatabase::loadFromCommandLine(int &Argc,319 const char *const *Argv,320 std::string &ErrorMsg,321 const Twine &Directory) {322 ErrorMsg.clear();323 if (Argc == 0)324 return nullptr;325 const char *const *DoubleDash = std::find(Argv, Argv + Argc, StringRef("--"));326 if (DoubleDash == Argv + Argc)327 return nullptr;328 std::vector<const char *> CommandLine(DoubleDash + 1, Argv + Argc);329 Argc = DoubleDash - Argv;330 331 std::vector<std::string> StrippedArgs;332 if (!stripPositionalArgs(CommandLine, StrippedArgs, ErrorMsg))333 return nullptr;334 return std::make_unique<FixedCompilationDatabase>(Directory, StrippedArgs);335}336 337std::unique_ptr<FixedCompilationDatabase>338FixedCompilationDatabase::loadFromFile(StringRef Path, std::string &ErrorMsg) {339 ErrorMsg.clear();340 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File =341 llvm::MemoryBuffer::getFile(Path);342 if (std::error_code Result = File.getError()) {343 ErrorMsg = "Error while opening fixed database: " + Result.message();344 return nullptr;345 }346 return loadFromBuffer(llvm::sys::path::parent_path(Path),347 (*File)->getBuffer(), ErrorMsg);348}349 350std::unique_ptr<FixedCompilationDatabase>351FixedCompilationDatabase::loadFromBuffer(StringRef Directory, StringRef Data,352 std::string &ErrorMsg) {353 ErrorMsg.clear();354 std::vector<std::string> Args;355 StringRef Line;356 while (!Data.empty()) {357 std::tie(Line, Data) = Data.split('\n');358 // Stray whitespace is almost certainly unintended.359 Line = Line.trim();360 if (!Line.empty())361 Args.push_back(Line.str());362 }363 return std::make_unique<FixedCompilationDatabase>(Directory, std::move(Args));364}365 366FixedCompilationDatabase::FixedCompilationDatabase(367 const Twine &Directory, ArrayRef<std::string> CommandLine) {368 std::vector<std::string> ToolCommandLine(1, GetClangToolCommand());369 ToolCommandLine.insert(ToolCommandLine.end(),370 CommandLine.begin(), CommandLine.end());371 CompileCommands.emplace_back(Directory, StringRef(),372 std::move(ToolCommandLine),373 StringRef());374}375 376std::vector<CompileCommand>377FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const {378 std::vector<CompileCommand> Result(CompileCommands);379 Result[0].CommandLine.push_back(std::string(FilePath));380 Result[0].Filename = std::string(FilePath);381 return Result;382}383 384namespace {385 386class FixedCompilationDatabasePlugin : public CompilationDatabasePlugin {387 std::unique_ptr<CompilationDatabase>388 loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override {389 SmallString<1024> DatabasePath(Directory);390 llvm::sys::path::append(DatabasePath, "compile_flags.txt");391 return FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage);392 }393};394 395} // namespace396 397static CompilationDatabasePluginRegistry::Add<FixedCompilationDatabasePlugin>398X("fixed-compilation-database", "Reads plain-text flags file");399 400namespace clang {401namespace tooling {402 403// This anchor is used to force the linker to link in the generated object file404// and thus register the JSONCompilationDatabasePlugin.405extern volatile int JSONAnchorSource;406[[maybe_unused]] static int JSONAnchorDest = JSONAnchorSource;407 408} // namespace tooling409} // namespace clang410