#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/CrossTU/CrossTranslationUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Signals.h"
#include <sstream>
#include <string>
using namespace llvm;
using namespace clang;
using namespace clang::cross_tu;
using namespace clang::tooling;
static cl::OptionCategory
ClangExtDefMapGenCategory("clang-extdefmapgen options");
class MapExtDefNamesConsumer : public ASTConsumer {
public:
MapExtDefNamesConsumer(ASTContext &Context,
StringRef astFilePath = StringRef())
: Ctx(Context), SM(Context.getSourceManager()) {
CurrentFileName = astFilePath.str();
}
~MapExtDefNamesConsumer() {
llvm::outs() << createCrossTUIndexString(Index);
}
void HandleTranslationUnit(ASTContext &Context) override {
handleDecl(Context.getTranslationUnitDecl());
}
private:
void handleDecl(const Decl *D);
void addIfInMain(const DeclaratorDecl *DD, SourceLocation defStart);
ASTContext &Ctx;
SourceManager &SM;
llvm::StringMap<std::string> Index;
std::string CurrentFileName;
};
void MapExtDefNamesConsumer::handleDecl(const Decl *D) {
if (!D)
return;
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
if (FD->isThisDeclarationADefinition())
if (const Stmt *Body = FD->getBody())
addIfInMain(FD, Body->getBeginLoc());
} else if (const auto *VD = dyn_cast<VarDecl>(D)) {
if (cross_tu::shouldImport(VD, Ctx) && VD->hasInit())
if (const Expr *Init = VD->getInit())
addIfInMain(VD, Init->getBeginLoc());
}
if (const auto *DC = dyn_cast<DeclContext>(D))
for (const Decl *D : DC->decls())
handleDecl(D);
}
void MapExtDefNamesConsumer::addIfInMain(const DeclaratorDecl *DD,
SourceLocation defStart) {
llvm::Optional<std::string> LookupName =
CrossTranslationUnitContext::getLookupName(DD);
if (!LookupName)
return;
assert(!LookupName->empty() && "Lookup name should be non-empty.");
if (CurrentFileName.empty()) {
CurrentFileName = std::string(
SM.getFileEntryForID(SM.getMainFileID())->tryGetRealPathName());
if (CurrentFileName.empty())
CurrentFileName = "invalid_file";
}
switch (DD->getLinkageInternal()) {
case ExternalLinkage:
case VisibleNoLinkage:
case UniqueExternalLinkage:
if (SM.isInMainFile(defStart))
Index[*LookupName] = CurrentFileName;
break;
default:
break;
}
}
class MapExtDefNamesAction : public ASTFrontendAction {
protected:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
llvm::StringRef) override {
return std::make_unique<MapExtDefNamesConsumer>(CI.getASTContext());
}
};
static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
static IntrusiveRefCntPtr<DiagnosticsEngine> Diags;
IntrusiveRefCntPtr<DiagnosticsEngine> GetDiagnosticsEngine() {
if (Diags) {
Diags->Reset(false);
return Diags;
}
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter *DiagClient =
new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
DiagClient->setPrefix("clang-extdef-mappping");
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
IntrusiveRefCntPtr<DiagnosticsEngine> DiagEngine(
new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient));
Diags.swap(DiagEngine);
Diags->Retain();
return Diags;
}
static CompilerInstance *CI = nullptr;
static bool HandleAST(StringRef AstPath) {
if (!CI)
CI = new CompilerInstance();
IntrusiveRefCntPtr<DiagnosticsEngine> DiagEngine = GetDiagnosticsEngine();
std::unique_ptr<ASTUnit> Unit = ASTUnit::LoadFromASTFile(
AstPath.str(), CI->getPCHContainerOperations()->getRawReader(),
ASTUnit::LoadASTOnly, DiagEngine, CI->getFileSystemOpts());
if (!Unit)
return false;
FileManager FM(CI->getFileSystemOpts());
SmallString<128> AbsPath(AstPath);
FM.makeAbsolutePath(AbsPath);
MapExtDefNamesConsumer Consumer =
MapExtDefNamesConsumer(Unit->getASTContext(), AbsPath);
Consumer.HandleTranslationUnit(Unit->getASTContext());
return true;
}
static int HandleFiles(ArrayRef<std::string> SourceFiles,
CompilationDatabase &compilations) {
std::vector<std::string> SourcesToBeParsed;
for (StringRef Src : SourceFiles) {
if (Src.endswith(".ast")) {
if (!HandleAST(Src)) {
return 1;
}
} else {
SourcesToBeParsed.push_back(Src.str());
}
}
if (!SourcesToBeParsed.empty()) {
ClangTool Tool(compilations, SourcesToBeParsed);
return Tool.run(newFrontendActionFactory<MapExtDefNamesAction>().get());
}
return 0;
}
int main(int argc, const char **argv) {
sys::PrintStackTraceOnErrorSignal(argv[0], false);
PrettyStackTraceProgram X(argc, argv);
const char *Overview = "\nThis tool collects the USR name and location "
"of external definitions in the source files "
"(excluding headers).\n"
"Input can be either source files that are compiled "
"with compile database or .ast files that are "
"created from clang's -emit-ast option.\n";
auto ExpectedParser = CommonOptionsParser::create(
argc, argv, ClangExtDefMapGenCategory, cl::ZeroOrMore, Overview);
if (!ExpectedParser) {
llvm::errs() << ExpectedParser.takeError();
return 1;
}
CommonOptionsParser &OptionsParser = ExpectedParser.get();
return HandleFiles(OptionsParser.getSourcePathList(),
OptionsParser.getCompilations());
}