/// Strips any positional args and possible argv[0] from a command-line
/// provided by the user to construct a FixedCompilationDatabase.
///
/// FixedCompilationDatabase requires a command line to be in this format as it
/// constructs the command line for each file by appending the name of the file
/// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the
/// start of the command line although its value is not important as it's just
/// ignored by the Driver invoked by the ClangTool using the
/// FixedCompilationDatabase.
///
/// FIXME: This functionality should probably be made available by
/// clang::driver::Driver although what the interface should look like is not
/// clear.
///
/// \param[in] Args Args as provided by the user.
/// \return Resulting stripped command line.
///          \li true if successful.
///          \li false if \c Args cannot be used for compilation jobs (e.g.
///          contains an option like -E or -version).
static bool stripPositionalArgs(std::vector<const char *> Args,
                                std::vector<std::string> &Result,
                                std::string &ErrorMsg) {
  IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
  llvm::raw_string_ostream Output(ErrorMsg);
  TextDiagnosticPrinter DiagnosticPrinter(Output, &*DiagOpts);
  UnusedInputDiagConsumer DiagClient(DiagnosticPrinter);
  DiagnosticsEngine Diagnostics(
      IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()),
      &*DiagOpts, &DiagClient, false);

  // The clang executable path isn't required since the jobs the driver builds
  // will not be executed.
  std::unique_ptr<driver::Driver> NewDriver(new driver::Driver(
      /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(),
      Diagnostics));
  NewDriver->setCheckInputsExist(false);

  // This becomes the new argv[0]. The value is used to detect libc++ include
  // dirs on Mac, it isn't used for other platforms.
  std::string Argv0 = GetClangToolCommand();
  Args.insert(Args.begin(), Argv0.c_str());

  // By adding -c, we force the driver to treat compilation as the last phase.
  // It will then issue warnings via Diagnostics about un-used options that
  // would have been used for linking. If the user provided a compiler name as
  // the original argv[0], this will be treated as a linker input thanks to
  // insertng a new argv[0] above. All un-used options get collected by
  // UnusedInputdiagConsumer and get stripped out later.
  Args.push_back("-c");

  // Put a dummy C++ file on to ensure there's at least one compile job for the
  // driver to construct. If the user specified some other argument that
  // prevents compilation, e.g. -E or something like -version, we may still end
  // up with no jobs but then this is the user's fault.
  Args.push_back("placeholder.cpp");

  Args.erase(std::remove_if(Args.begin(), Args.end(), FilterUnusedFlags()),
             Args.end());

  const std::unique_ptr<driver::Compilation> Compilation(
      NewDriver->BuildCompilation(Args));
  if (!Compilation)
    return false;

  const driver::JobList &Jobs = Compilation->getJobs();

  CompileJobAnalyzer CompileAnalyzer;

  for (const auto &Cmd : Jobs) {
    // Collect only for Assemble, Backend, and Compile jobs. If we do all jobs
    // we get duplicates since Link jobs point to Assemble jobs as inputs.
    // -flto* flags make the BackendJobClass, which still needs analyzer.
    if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass ||
        Cmd.getSource().getKind() == driver::Action::BackendJobClass ||
        Cmd.getSource().getKind() == driver::Action::CompileJobClass) {
      CompileAnalyzer.run(&Cmd.getSource());
    }
  }

  if (CompileAnalyzer.Inputs.empty()) {
    ErrorMsg = "warning: no compile jobs found\n";
    return false;
  }

  // Remove all compilation input files from the command line. This is
  // necessary so that getCompileCommands() can construct a command line for
  // each file.
  std::vector<const char *>::iterator End = std::remove_if(
      Args.begin(), Args.end(), MatchesAny(CompileAnalyzer.Inputs));

  // Remove all inputs deemed unused for compilation.
  End = std::remove_if(Args.begin(), End, MatchesAny(DiagClient.UnusedInputs));

  // Remove the -c add above as well. It will be at the end right now.
  assert(strcmp(*(End - 1), "-c") == 0);
  --End;

  Result = std::vector<std::string>(Args.begin() + 1, End);
  return true;
}
Example #2
0
/// \brief Strips any positional args and possible argv[0] from a command-line
/// provided by the user to construct a FixedCompilationDatabase.
///
/// FixedCompilationDatabase requires a command line to be in this format as it
/// constructs the command line for each file by appending the name of the file
/// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the
/// start of the command line although its value is not important as it's just
/// ignored by the Driver invoked by the ClangTool using the
/// FixedCompilationDatabase.
///
/// FIXME: This functionality should probably be made available by
/// clang::driver::Driver although what the interface should look like is not
/// clear.
///
/// \param[in] Args Args as provided by the user.
/// \return Resulting stripped command line.
///          \li true if successful.
///          \li false if \c Args cannot be used for compilation jobs (e.g.
///          contains an option like -E or -version).
static bool stripPositionalArgs(std::vector<const char *> Args,
                                std::vector<std::string> &Result) {
  IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
  UnusedInputDiagConsumer DiagClient;
  DiagnosticsEngine Diagnostics(
      IntrusiveRefCntPtr<clang::DiagnosticIDs>(new DiagnosticIDs()),
      &*DiagOpts, &DiagClient, false);

  // The clang executable path isn't required since the jobs the driver builds
  // will not be executed.
  std::unique_ptr<driver::Driver> NewDriver(new driver::Driver(
      /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(),
      Diagnostics));
  NewDriver->setCheckInputsExist(false);

  // This becomes the new argv[0]. The value is actually not important as it
  // isn't used for invoking Tools.
  Args.insert(Args.begin(), "clang-tool");

  // By adding -c, we force the driver to treat compilation as the last phase.
  // It will then issue warnings via Diagnostics about un-used options that
  // would have been used for linking. If the user provided a compiler name as
  // the original argv[0], this will be treated as a linker input thanks to
  // insertng a new argv[0] above. All un-used options get collected by
  // UnusedInputdiagConsumer and get stripped out later.
  Args.push_back("-c");

  // Put a dummy C++ file on to ensure there's at least one compile job for the
  // driver to construct. If the user specified some other argument that
  // prevents compilation, e.g. -E or something like -version, we may still end
  // up with no jobs but then this is the user's fault.
  Args.push_back("placeholder.cpp");

  // Remove -no-integrated-as; it's not used for syntax checking,
  // and it confuses targets which don't support this option.
  Args.erase(std::remove_if(Args.begin(), Args.end(),
                            MatchesAny(std::string("-no-integrated-as"))),
             Args.end());

  const std::unique_ptr<driver::Compilation> Compilation(
      NewDriver->BuildCompilation(Args));

  const driver::JobList &Jobs = Compilation->getJobs();

  CompileJobAnalyzer CompileAnalyzer;

  for (driver::JobList::const_iterator I = Jobs.begin(), E = Jobs.end(); I != E;
       ++I) {
    if ((*I)->getKind() == driver::Job::CommandClass) {
      const driver::Command *Cmd = cast<driver::Command>(*I);
      // Collect only for Assemble jobs. If we do all jobs we get duplicates
      // since Link jobs point to Assemble jobs as inputs.
      if (Cmd->getSource().getKind() == driver::Action::AssembleJobClass)
        CompileAnalyzer.run(&Cmd->getSource());
    }
  }

  if (CompileAnalyzer.Inputs.empty()) {
    // No compile jobs found.
    // FIXME: Emit a warning of some kind?
    return false;
  }

  // Remove all compilation input files from the command line. This is
  // necessary so that getCompileCommands() can construct a command line for
  // each file.
  std::vector<const char *>::iterator End = std::remove_if(
      Args.begin(), Args.end(), MatchesAny(CompileAnalyzer.Inputs));

  // Remove all inputs deemed unused for compilation.
  End = std::remove_if(Args.begin(), End, MatchesAny(DiagClient.UnusedInputs));

  // Remove the -c add above as well. It will be at the end right now.
  assert(strcmp(*(End - 1), "-c") == 0);
  --End;

  Result = std::vector<std::string>(Args.begin() + 1, End);
  return true;
}