1//===--- CompilationDatabase.cpp - ----------------------------------------===// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// This file contains implementations of the CompilationDatabase base class 11// and the FixedCompilationDatabase. 12// 13//===----------------------------------------------------------------------===// 14 15#include "clang/Tooling/CompilationDatabase.h" 16#include "clang/Basic/Diagnostic.h" 17#include "clang/Basic/DiagnosticOptions.h" 18#include "clang/Driver/Action.h" 19#include "clang/Driver/Compilation.h" 20#include "clang/Driver/Driver.h" 21#include "clang/Driver/DriverDiagnostic.h" 22#include "clang/Driver/Job.h" 23#include "clang/Frontend/TextDiagnosticPrinter.h" 24#include "clang/Tooling/CompilationDatabasePluginRegistry.h" 25#include "clang/Tooling/Tooling.h" 26#include "llvm/ADT/SmallString.h" 27#include "llvm/Option/Arg.h" 28#include "llvm/Support/Host.h" 29#include "llvm/Support/Path.h" 30#include <sstream> 31#include <system_error> 32 33namespace clang { 34namespace tooling { 35 36CompilationDatabase::~CompilationDatabase() {} 37 38CompilationDatabase * 39CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, 40 std::string &ErrorMessage) { 41 std::stringstream ErrorStream; 42 for (CompilationDatabasePluginRegistry::iterator 43 It = CompilationDatabasePluginRegistry::begin(), 44 Ie = CompilationDatabasePluginRegistry::end(); 45 It != Ie; ++It) { 46 std::string DatabaseErrorMessage; 47 std::unique_ptr<CompilationDatabasePlugin> Plugin(It->instantiate()); 48 if (CompilationDatabase *DB = 49 Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage)) 50 return DB; 51 else 52 ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; 53 } 54 ErrorMessage = ErrorStream.str(); 55 return nullptr; 56} 57 58static CompilationDatabase * 59findCompilationDatabaseFromDirectory(StringRef Directory, 60 std::string &ErrorMessage) { 61 std::stringstream ErrorStream; 62 bool HasErrorMessage = false; 63 while (!Directory.empty()) { 64 std::string LoadErrorMessage; 65 66 if (CompilationDatabase *DB = 67 CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage)) 68 return DB; 69 70 if (!HasErrorMessage) { 71 ErrorStream << "No compilation database found in " << Directory.str() 72 << " or any parent directory\n" << LoadErrorMessage; 73 HasErrorMessage = true; 74 } 75 76 Directory = llvm::sys::path::parent_path(Directory); 77 } 78 ErrorMessage = ErrorStream.str(); 79 return nullptr; 80} 81 82CompilationDatabase * 83CompilationDatabase::autoDetectFromSource(StringRef SourceFile, 84 std::string &ErrorMessage) { 85 SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile)); 86 StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); 87 88 CompilationDatabase *DB = findCompilationDatabaseFromDirectory(Directory, 89 ErrorMessage); 90 91 if (!DB) 92 ErrorMessage = ("Could not auto-detect compilation database for file \"" + 93 SourceFile + "\"\n" + ErrorMessage).str(); 94 return DB; 95} 96 97CompilationDatabase * 98CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir, 99 std::string &ErrorMessage) { 100 SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir)); 101 102 CompilationDatabase *DB = findCompilationDatabaseFromDirectory(AbsolutePath, 103 ErrorMessage); 104 105 if (!DB) 106 ErrorMessage = ("Could not auto-detect compilation database from directory \"" + 107 SourceDir + "\"\n" + ErrorMessage).str(); 108 return DB; 109} 110 111CompilationDatabasePlugin::~CompilationDatabasePlugin() {} 112 113// Helper for recursively searching through a chain of actions and collecting 114// all inputs, direct and indirect, of compile jobs. 115struct CompileJobAnalyzer { 116 void run(const driver::Action *A) { 117 runImpl(A, false); 118 } 119 120 SmallVector<std::string, 2> Inputs; 121 122private: 123 124 void runImpl(const driver::Action *A, bool Collect) { 125 bool CollectChildren = Collect; 126 switch (A->getKind()) { 127 case driver::Action::CompileJobClass: 128 CollectChildren = true; 129 break; 130 131 case driver::Action::InputClass: { 132 if (Collect) { 133 const driver::InputAction *IA = cast<driver::InputAction>(A); 134 Inputs.push_back(IA->getInputArg().getSpelling()); 135 } 136 } break; 137 138 default: 139 // Don't care about others 140 ; 141 } 142 143 for (driver::ActionList::const_iterator I = A->begin(), E = A->end(); 144 I != E; ++I) 145 runImpl(*I, CollectChildren); 146 } 147}; 148 149// Special DiagnosticConsumer that looks for warn_drv_input_file_unused 150// diagnostics from the driver and collects the option strings for those unused 151// options. 152class UnusedInputDiagConsumer : public DiagnosticConsumer { 153public: 154 UnusedInputDiagConsumer() : Other(nullptr) {} 155 156 // Useful for debugging, chain diagnostics to another consumer after 157 // recording for our own purposes. 158 UnusedInputDiagConsumer(DiagnosticConsumer *Other) : Other(Other) {} 159 160 virtual void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, 161 const Diagnostic &Info) override { 162 if (Info.getID() == clang::diag::warn_drv_input_file_unused) { 163 // Arg 1 for this diagnostic is the option that didn't get used. 164 UnusedInputs.push_back(Info.getArgStdStr(0)); 165 } 166 if (Other) 167 Other->HandleDiagnostic(DiagLevel, Info); 168 } 169 170 DiagnosticConsumer *Other; 171 SmallVector<std::string, 2> UnusedInputs; 172}; 173 174// Unary functor for asking "Given a StringRef S1, does there exist a string 175// S2 in Arr where S1 == S2?" 176struct MatchesAny { 177 MatchesAny(ArrayRef<std::string> Arr) : Arr(Arr) {} 178 bool operator() (StringRef S) { 179 for (const std::string *I = Arr.begin(), *E = Arr.end(); I != E; ++I) 180 if (*I == S) 181 return true; 182 return false; 183 } 184private: 185 ArrayRef<std::string> Arr; 186}; 187 188/// \brief Strips any positional args and possible argv[0] from a command-line 189/// provided by the user to construct a FixedCompilationDatabase. 190/// 191/// FixedCompilationDatabase requires a command line to be in this format as it 192/// constructs the command line for each file by appending the name of the file 193/// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the 194/// start of the command line although its value is not important as it's just 195/// ignored by the Driver invoked by the ClangTool using the 196/// FixedCompilationDatabase. 197/// 198/// FIXME: This functionality should probably be made available by 199/// clang::driver::Driver although what the interface should look like is not 200/// clear. 201/// 202/// \param[in] Args Args as provided by the user. 203/// \return Resulting stripped command line. 204/// \li true if successful. 205/// \li false if \c Args cannot be used for compilation jobs (e.g. 206/// contains an option like -E or -version). 207static bool stripPositionalArgs(std::vector<const char *> Args, 208 std::vector<std::string> &Result) { 209 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); 210 UnusedInputDiagConsumer DiagClient; 211 DiagnosticsEngine Diagnostics( 212 IntrusiveRefCntPtr<clang::DiagnosticIDs>(new DiagnosticIDs()), 213 &*DiagOpts, &DiagClient, false); 214 215 // The clang executable path isn't required since the jobs the driver builds 216 // will not be executed. 217 std::unique_ptr<driver::Driver> NewDriver(new driver::Driver( 218 /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(), 219 Diagnostics)); 220 NewDriver->setCheckInputsExist(false); 221 222 // This becomes the new argv[0]. The value is actually not important as it 223 // isn't used for invoking Tools. 224 Args.insert(Args.begin(), "clang-tool"); 225 226 // By adding -c, we force the driver to treat compilation as the last phase. 227 // It will then issue warnings via Diagnostics about un-used options that 228 // would have been used for linking. If the user provided a compiler name as 229 // the original argv[0], this will be treated as a linker input thanks to 230 // insertng a new argv[0] above. All un-used options get collected by 231 // UnusedInputdiagConsumer and get stripped out later. 232 Args.push_back("-c"); 233 234 // Put a dummy C++ file on to ensure there's at least one compile job for the 235 // driver to construct. If the user specified some other argument that 236 // prevents compilation, e.g. -E or something like -version, we may still end 237 // up with no jobs but then this is the user's fault. 238 Args.push_back("placeholder.cpp"); 239 240 // Remove -no-integrated-as; it's not used for syntax checking, 241 // and it confuses targets which don't support this option. 242 Args.erase(std::remove_if(Args.begin(), Args.end(), 243 MatchesAny(std::string("-no-integrated-as"))), 244 Args.end()); 245 246 const std::unique_ptr<driver::Compilation> Compilation( 247 NewDriver->BuildCompilation(Args)); 248 249 const driver::JobList &Jobs = Compilation->getJobs(); 250 251 CompileJobAnalyzer CompileAnalyzer; 252 253 for (driver::JobList::const_iterator I = Jobs.begin(), E = Jobs.end(); I != E; 254 ++I) { 255 if ((*I)->getKind() == driver::Job::CommandClass) { 256 const driver::Command *Cmd = cast<driver::Command>(*I); 257 // Collect only for Assemble jobs. If we do all jobs we get duplicates 258 // since Link jobs point to Assemble jobs as inputs. 259 if (Cmd->getSource().getKind() == driver::Action::AssembleJobClass) 260 CompileAnalyzer.run(&Cmd->getSource()); 261 } 262 } 263 264 if (CompileAnalyzer.Inputs.empty()) { 265 // No compile jobs found. 266 // FIXME: Emit a warning of some kind? 267 return false; 268 } 269 270 // Remove all compilation input files from the command line. This is 271 // necessary so that getCompileCommands() can construct a command line for 272 // each file. 273 std::vector<const char *>::iterator End = std::remove_if( 274 Args.begin(), Args.end(), MatchesAny(CompileAnalyzer.Inputs)); 275 276 // Remove all inputs deemed unused for compilation. 277 End = std::remove_if(Args.begin(), End, MatchesAny(DiagClient.UnusedInputs)); 278 279 // Remove the -c add above as well. It will be at the end right now. 280 assert(strcmp(*(End - 1), "-c") == 0); 281 --End; 282 283 Result = std::vector<std::string>(Args.begin() + 1, End); 284 return true; 285} 286 287FixedCompilationDatabase * 288FixedCompilationDatabase::loadFromCommandLine(int &Argc, 289 const char **Argv, 290 Twine Directory) { 291 const char **DoubleDash = std::find(Argv, Argv + Argc, StringRef("--")); 292 if (DoubleDash == Argv + Argc) 293 return nullptr; 294 std::vector<const char *> CommandLine(DoubleDash + 1, Argv + Argc); 295 Argc = DoubleDash - Argv; 296 297 std::vector<std::string> StrippedArgs; 298 if (!stripPositionalArgs(CommandLine, StrippedArgs)) 299 return nullptr; 300 return new FixedCompilationDatabase(Directory, StrippedArgs); 301} 302 303FixedCompilationDatabase:: 304FixedCompilationDatabase(Twine Directory, ArrayRef<std::string> CommandLine) { 305 std::vector<std::string> ToolCommandLine(1, "clang-tool"); 306 ToolCommandLine.insert(ToolCommandLine.end(), 307 CommandLine.begin(), CommandLine.end()); 308 CompileCommands.push_back( 309 CompileCommand(Directory, std::move(ToolCommandLine))); 310} 311 312std::vector<CompileCommand> 313FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const { 314 std::vector<CompileCommand> Result(CompileCommands); 315 Result[0].CommandLine.push_back(FilePath); 316 return Result; 317} 318 319std::vector<std::string> 320FixedCompilationDatabase::getAllFiles() const { 321 return std::vector<std::string>(); 322} 323 324std::vector<CompileCommand> 325FixedCompilationDatabase::getAllCompileCommands() const { 326 return std::vector<CompileCommand>(); 327} 328 329// This anchor is used to force the linker to link in the generated object file 330// and thus register the JSONCompilationDatabasePlugin. 331extern volatile int JSONAnchorSource; 332static int JSONAnchorDest = JSONAnchorSource; 333 334} // end namespace tooling 335} // end namespace clang 336