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