include-what-you-use/iwyu_driver.cc

241 lines
7.2 KiB
C++

//===--- iwyu_driver.cc - iwyu driver implementation ----------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Driver, used to set up a clang CompilerInstance based on argc and
// argv.
// Everything below is adapted from clang/examples/clang-interpreter/main.cpp.
#include <cctype>
#include <cstdint>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/TargetParser/Triple.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/Tool.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
namespace llvm {
class LLVMContext;
} // namespace llvm
using clang::CompilerInstance;
using clang::CompilerInvocation;
using clang::DiagnosticIDs;
using clang::DiagnosticOptions;
using clang::DiagnosticsEngine;
using clang::TextDiagnosticPrinter;
using clang::driver::Command;
using clang::driver::Compilation;
using clang::driver::Driver;
using clang::driver::JobList;
using llvm::ErrorOr;
using llvm::IntrusiveRefCntPtr;
using llvm::SmallString;
using llvm::SmallVector;
using llvm::SmallVectorImpl;
using llvm::MemoryBuffer;
using llvm::StringRef;
using llvm::cast;
using llvm::errs;
using llvm::isa;
using llvm::opt::ArgStringList;
using llvm::raw_svector_ostream;
using llvm::sys::getDefaultTargetTriple;
using std::set;
using std::unique_ptr;
namespace include_what_you_use {
namespace {
std::string GetExecutablePath(const char *Argv0) {
// This just needs to be some symbol in the binary; C++ doesn't
// allow taking the address of ::main however.
void *main_addr = (void*) (intptr_t) GetExecutablePath;
return llvm::sys::fs::getMainExecutable(Argv0, main_addr);
}
const char *SaveStringInSet(std::set<std::string> &SavedStrings, StringRef S) {
return SavedStrings.insert(S.str()).first->c_str();
}
void ExpandArgsFromBuf(const char *Arg,
SmallVectorImpl<const char*> &ArgVector,
set<std::string> &SavedStrings) {
const char *FName = Arg + 1;
ErrorOr<unique_ptr<MemoryBuffer>> MemBufOrErr = MemoryBuffer::getFile(FName);
if (!MemBufOrErr) {
ArgVector.push_back(SaveStringInSet(SavedStrings, Arg));
return;
}
const char *Buf = MemBufOrErr.get()->getBufferStart();
char InQuote = ' ';
std::string CurArg;
for (const char *P = Buf; ; ++P) {
if (*P == '\0' || (isspace(*P) && InQuote == ' ')) {
if (!CurArg.empty()) {
if (CurArg[0] != '@') {
ArgVector.push_back(SaveStringInSet(SavedStrings, CurArg));
} else {
ExpandArgsFromBuf(CurArg.c_str(), ArgVector, SavedStrings);
}
CurArg = "";
}
if (*P == '\0')
break;
else
continue;
}
if (isspace(*P)) {
if (InQuote != ' ')
CurArg.push_back(*P);
continue;
}
if (*P == '"' || *P == '\'') {
if (InQuote == *P)
InQuote = ' ';
else if (InQuote == ' ')
InQuote = *P;
else
CurArg.push_back(*P);
continue;
}
if (*P == '\\') {
++P;
if (*P != '\0')
CurArg.push_back(*P);
continue;
}
CurArg.push_back(*P);
}
}
void ExpandArgv(int argc, const char **argv,
SmallVectorImpl<const char*> &ArgVector,
set<std::string> &SavedStrings) {
for (int i = 0; i < argc; ++i) {
const char *Arg = argv[i];
if (Arg[0] != '@') {
ArgVector.push_back(SaveStringInSet(SavedStrings, std::string(Arg)));
continue;
}
ExpandArgsFromBuf(Arg, ArgVector, SavedStrings);
}
}
} // anonymous namespace
CompilerInstance* CreateCompilerInstance(int argc, const char **argv) {
std::string path = GetExecutablePath(argv[0]);
IntrusiveRefCntPtr<DiagnosticOptions> diagnostic_options =
new DiagnosticOptions;
auto* diagnostic_client =
new TextDiagnosticPrinter(errs(), &*diagnostic_options);
IntrusiveRefCntPtr<DiagnosticIDs> diagnostic_id(new DiagnosticIDs());
DiagnosticsEngine diagnostics(diagnostic_id, &*diagnostic_options,
diagnostic_client);
Driver driver(path, getDefaultTargetTriple(), diagnostics);
driver.setTitle("include what you use");
// Expand out any response files passed on the command line
set<std::string> SavedStrings;
SmallVector<const char*, 256> args;
ExpandArgv(argc, argv, args, SavedStrings);
// Drop -save-temps arguments to avoid multiple compilation jobs.
llvm::erase_if(args, [](const char *v) {
StringRef arg(v);
return arg.startswith("-save-temps") || arg.startswith("--save-temps");
});
// FIXME: This is a hack to try to force the driver to do something we can
// recognize. We need to extend the driver library to support this use model
// (basically, exactly one input, and the operation mode is hard wired).
args.push_back("-fsyntax-only");
unique_ptr<Compilation> compilation(driver.BuildCompilation(args));
if (!compilation)
return nullptr;
// FIXME: This is copied from ASTUnit.cpp; simplify and eliminate.
// We expect to get back exactly one command job, if we didn't something
// failed. Extract that job from the compilation.
const JobList& jobs = compilation->getJobs();
if (jobs.size() != 1 || !isa<Command>(*jobs.begin())) {
SmallString<256> msg;
raw_svector_ostream out(msg);
jobs.Print(out, "; ", true);
diagnostics.Report(clang::diag::err_fe_expected_compiler_job) << out.str();
return nullptr;
}
const Command& command = cast<Command>(*jobs.begin());
if (StringRef(command.getCreator().getName()) != "clang") {
diagnostics.Report(clang::diag::err_fe_expected_clang_command);
return nullptr;
}
// Initialize a compiler invocation object from the clang (-cc1) arguments.
const ArgStringList &cc_arguments = command.getArguments();
std::shared_ptr<CompilerInvocation> invocation(new CompilerInvocation);
CompilerInvocation::CreateFromArgs(*invocation, cc_arguments, diagnostics);
invocation->getFrontendOpts().DisableFree = false;
// Show the invocation, with -v.
if (invocation->getHeaderSearchOpts().Verbose) {
errs() << "clang invocation:\n";
jobs.Print(errs(), "\n", true);
errs() << "\n";
}
// FIXME: This is copied from cc1_main.cpp; simplify and eliminate.
// Create a compiler instance to handle the actual work.
// The caller will be responsible for freeing this.
CompilerInstance* compiler = new CompilerInstance;
compiler->setInvocation(invocation);
// Create the compilers actual diagnostics engine.
compiler->createDiagnostics();
if (!compiler->hasDiagnostics())
return nullptr;
return compiler;
}
} // namespace include_what_you_use