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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file contains implementations of the CompilationDatabase base class 10 // and the FixedCompilationDatabase. 11 // 12 // FIXME: Various functions that take a string &ErrorMessage should be upgraded 13 // 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 50 using namespace clang; 51 using namespace tooling; 52 53 LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry) 54 55 CompilationDatabase::~CompilationDatabase() = default; 56 57 std::unique_ptr<CompilationDatabase> 58 CompilationDatabase::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 73 static std::unique_ptr<CompilationDatabase> 74 findCompilationDatabaseFromDirectory(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 97 std::unique_ptr<CompilationDatabase> 98 CompilationDatabase::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 112 std::unique_ptr<CompilationDatabase> 113 CompilationDatabase::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 126 std::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 135 CompilationDatabasePlugin::~CompilationDatabasePlugin() = default; 136 137 namespace { 138 139 // Helper for recursively searching through a chain of actions and collecting 140 // all inputs, direct and indirect, of compile jobs. 141 struct CompileJobAnalyzer { 142 SmallVector<std::string, 2> Inputs; 143 144 void run(const driver::Action *A) { 145 runImpl(A, false); 146 } 147 148 private: 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 others 166 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_unused 175 // diagnostics from the driver and collects the option strings for those unused 176 // options. 177 class UnusedInputDiagConsumer : public DiagnosticConsumer { 178 public: 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 diagnostics 188 // 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 targets 199 // which don't support these options. 200 struct FilterUnusedFlags { 201 bool operator() (StringRef S) { 202 return (S == "-no-integrated-as") || S.starts_with("-Wa,"); 203 } 204 }; 205 206 std::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 } // namespace 217 218 /// Strips any positional args and possible argv[0] from a command-line 219 /// provided by the user to construct a FixedCompilationDatabase. 220 /// 221 /// FixedCompilationDatabase requires a command line to be in this format as it 222 /// constructs the command line for each file by appending the name of the file 223 /// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the 224 /// start of the command line although its value is not important as it's just 225 /// ignored by the Driver invoked by the ClangTool using the 226 /// FixedCompilationDatabase. 227 /// 228 /// FIXME: This functionality should probably be made available by 229 /// clang::driver::Driver although what the interface should look like is not 230 /// 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). 237 static 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( 245 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), DiagOpts, 246 &DiagClient, false); 247 248 // The clang executable path isn't required since the jobs the driver builds 249 // will not be executed. 250 std::unique_ptr<driver::Driver> NewDriver(new driver::Driver( 251 /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(), 252 Diagnostics)); 253 NewDriver->setCheckInputsExist(false); 254 255 // This becomes the new argv[0]. The value is used to detect libc++ include 256 // dirs on Mac, it isn't used for other platforms. 257 std::string Argv0 = GetClangToolCommand(); 258 Args.insert(Args.begin(), Argv0.c_str()); 259 260 // By adding -c, we force the driver to treat compilation as the last phase. 261 // It will then issue warnings via Diagnostics about un-used options that 262 // would have been used for linking. If the user provided a compiler name as 263 // the original argv[0], this will be treated as a linker input thanks to 264 // insertng a new argv[0] above. All un-used options get collected by 265 // UnusedInputdiagConsumer and get stripped out later. 266 Args.push_back("-c"); 267 268 // Put a dummy C++ file on to ensure there's at least one compile job for the 269 // driver to construct. If the user specified some other argument that 270 // prevents compilation, e.g. -E or something like -version, we may still end 271 // up with no jobs but then this is the user's fault. 272 Args.push_back("placeholder.cpp"); 273 274 llvm::erase_if(Args, FilterUnusedFlags()); 275 276 const std::unique_ptr<driver::Compilation> Compilation( 277 NewDriver->BuildCompilation(Args)); 278 if (!Compilation) 279 return false; 280 281 const driver::JobList &Jobs = Compilation->getJobs(); 282 283 CompileJobAnalyzer CompileAnalyzer; 284 285 for (const auto &Cmd : Jobs) { 286 // Collect only for Assemble, Backend, and Compile jobs. If we do all jobs 287 // we get duplicates since Link jobs point to Assemble jobs as inputs. 288 // -flto* flags make the BackendJobClass, which still needs analyzer. 289 if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass || 290 Cmd.getSource().getKind() == driver::Action::BackendJobClass || 291 Cmd.getSource().getKind() == driver::Action::CompileJobClass || 292 Cmd.getSource().getKind() == driver::Action::PrecompileJobClass) { 293 CompileAnalyzer.run(&Cmd.getSource()); 294 } 295 } 296 297 if (CompileAnalyzer.Inputs.empty()) { 298 ErrorMsg = "warning: no compile jobs found\n"; 299 return false; 300 } 301 302 // Remove all compilation input files from the command line and inputs deemed 303 // unused for compilation. This is necessary so that getCompileCommands() can 304 // construct a command line for each file. 305 std::vector<const char *>::iterator End = 306 llvm::remove_if(Args, [&](StringRef S) { 307 return llvm::is_contained(CompileAnalyzer.Inputs, S) || 308 llvm::is_contained(DiagClient.UnusedInputs, S); 309 }); 310 // Remove the -c add above as well. It will be at the end right now. 311 assert(strcmp(*(End - 1), "-c") == 0); 312 --End; 313 314 Result = std::vector<std::string>(Args.begin() + 1, End); 315 return true; 316 } 317 318 std::unique_ptr<FixedCompilationDatabase> 319 FixedCompilationDatabase::loadFromCommandLine(int &Argc, 320 const char *const *Argv, 321 std::string &ErrorMsg, 322 const Twine &Directory) { 323 ErrorMsg.clear(); 324 if (Argc == 0) 325 return nullptr; 326 const char *const *DoubleDash = std::find(Argv, Argv + Argc, StringRef("--")); 327 if (DoubleDash == Argv + Argc) 328 return nullptr; 329 std::vector<const char *> CommandLine(DoubleDash + 1, Argv + Argc); 330 Argc = DoubleDash - Argv; 331 332 std::vector<std::string> StrippedArgs; 333 if (!stripPositionalArgs(CommandLine, StrippedArgs, ErrorMsg)) 334 return nullptr; 335 return std::make_unique<FixedCompilationDatabase>(Directory, StrippedArgs); 336 } 337 338 std::unique_ptr<FixedCompilationDatabase> 339 FixedCompilationDatabase::loadFromFile(StringRef Path, std::string &ErrorMsg) { 340 ErrorMsg.clear(); 341 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File = 342 llvm::MemoryBuffer::getFile(Path); 343 if (std::error_code Result = File.getError()) { 344 ErrorMsg = "Error while opening fixed database: " + Result.message(); 345 return nullptr; 346 } 347 return loadFromBuffer(llvm::sys::path::parent_path(Path), 348 (*File)->getBuffer(), ErrorMsg); 349 } 350 351 std::unique_ptr<FixedCompilationDatabase> 352 FixedCompilationDatabase::loadFromBuffer(StringRef Directory, StringRef Data, 353 std::string &ErrorMsg) { 354 ErrorMsg.clear(); 355 std::vector<std::string> Args; 356 StringRef Line; 357 while (!Data.empty()) { 358 std::tie(Line, Data) = Data.split('\n'); 359 // Stray whitespace is almost certainly unintended. 360 Line = Line.trim(); 361 if (!Line.empty()) 362 Args.push_back(Line.str()); 363 } 364 return std::make_unique<FixedCompilationDatabase>(Directory, std::move(Args)); 365 } 366 367 FixedCompilationDatabase::FixedCompilationDatabase( 368 const Twine &Directory, ArrayRef<std::string> CommandLine) { 369 std::vector<std::string> ToolCommandLine(1, GetClangToolCommand()); 370 ToolCommandLine.insert(ToolCommandLine.end(), 371 CommandLine.begin(), CommandLine.end()); 372 CompileCommands.emplace_back(Directory, StringRef(), 373 std::move(ToolCommandLine), 374 StringRef()); 375 } 376 377 std::vector<CompileCommand> 378 FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const { 379 std::vector<CompileCommand> Result(CompileCommands); 380 Result[0].CommandLine.push_back(std::string(FilePath)); 381 Result[0].Filename = std::string(FilePath); 382 return Result; 383 } 384 385 namespace { 386 387 class FixedCompilationDatabasePlugin : public CompilationDatabasePlugin { 388 std::unique_ptr<CompilationDatabase> 389 loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override { 390 SmallString<1024> DatabasePath(Directory); 391 llvm::sys::path::append(DatabasePath, "compile_flags.txt"); 392 return FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage); 393 } 394 }; 395 396 } // namespace 397 398 static CompilationDatabasePluginRegistry::Add<FixedCompilationDatabasePlugin> 399 X("fixed-compilation-database", "Reads plain-text flags file"); 400 401 namespace clang { 402 namespace tooling { 403 404 // This anchor is used to force the linker to link in the generated object file 405 // and thus register the JSONCompilationDatabasePlugin. 406 extern volatile int JSONAnchorSource; 407 static int LLVM_ATTRIBUTE_UNUSED JSONAnchorDest = JSONAnchorSource; 408 409 } // namespace tooling 410 } // namespace clang 411