xref: /freebsd/contrib/llvm-project/clang/lib/Tooling/CompilationDatabase.cpp (revision c8c62548bffb83f3d25e062929c45d66fea755f1)
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