271 changes: 271 additions & 0 deletions clang/lib/CrossTU/CrossTranslationUnit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
//===--- CrossTranslationUnit.cpp - -----------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the CrossTranslationUnit interface.
//
//===----------------------------------------------------------------------===//
#include "clang/CrossTU/CrossTranslationUnit.h"
#include "clang/AST/ASTImporter.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/CrossTU/CrossTUDiagnostic.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Index/USRGeneration.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <fstream>
#include <sstream>

namespace clang {
namespace cross_tu {

namespace {
// FIXME: This class is will be removed after the transition to llvm::Error.
class IndexErrorCategory : public std::error_category {
public:
const char *name() const noexcept override { return "clang.index"; }

std::string message(int Condition) const override {
switch (static_cast<index_error_code>(Condition)) {
case index_error_code::unspecified:
return "An unknown error has occurred.";
case index_error_code::missing_index_file:
return "The index file is missing.";
case index_error_code::invalid_index_format:
return "Invalid index file format.";
case index_error_code::multiple_definitions:
return "Multiple definitions in the index file.";
case index_error_code::missing_definition:
return "Missing definition from the index file.";
case index_error_code::failed_import:
return "Failed to import the definition.";
case index_error_code::failed_to_get_external_ast:
return "Failed to load external AST source.";
case index_error_code::failed_to_generate_usr:
return "Failed to generate USR.";
}
llvm_unreachable("Unrecognized index_error_code.");
}
};

static llvm::ManagedStatic<IndexErrorCategory> Category;
} // end anonymous namespace

char IndexError::ID;

void IndexError::log(raw_ostream &OS) const {
OS << Category->message(static_cast<int>(Code)) << '\n';
}

std::error_code IndexError::convertToErrorCode() const {
return std::error_code(static_cast<int>(Code), *Category);
}

llvm::Expected<llvm::StringMap<std::string>>
parseCrossTUIndex(StringRef IndexPath, StringRef CrossTUDir) {
std::ifstream ExternalFnMapFile(IndexPath);
if (!ExternalFnMapFile)
return llvm::make_error<IndexError>(index_error_code::missing_index_file,
IndexPath.str());

llvm::StringMap<std::string> Result;
std::string Line;
unsigned LineNo = 1;
while (std::getline(ExternalFnMapFile, Line)) {
const size_t Pos = Line.find(" ");
if (Pos > 0 && Pos != std::string::npos) {
StringRef LineRef{Line};
StringRef FunctionLookupName = LineRef.substr(0, Pos);
if (Result.count(FunctionLookupName))
return llvm::make_error<IndexError>(
index_error_code::multiple_definitions, IndexPath.str(), LineNo);
StringRef FileName = LineRef.substr(Pos + 1);
SmallString<256> FilePath = CrossTUDir;
if (llvm::sys::path::is_absolute(FileName))
FilePath = FileName;
else
llvm::sys::path::append(FilePath, FileName);
Result[FunctionLookupName] = FilePath.str().str();
} else
return llvm::make_error<IndexError>(
index_error_code::invalid_index_format, IndexPath.str(), LineNo);
LineNo++;
}
return Result;
}

std::string
createCrossTUIndexString(const llvm::StringMap<std::string> &Index) {
std::ostringstream Result;
for (const auto &E : Index)
Result << E.getKey().str() << " " << E.getValue() << '\n';
return Result.str();
}

CrossTranslationUnitContext::CrossTranslationUnitContext(CompilerInstance &CI)
: CI(CI), Context(CI.getASTContext()) {}

CrossTranslationUnitContext::~CrossTranslationUnitContext() {}

std::string CrossTranslationUnitContext::getLookupName(const NamedDecl *ND) {
SmallString<128> DeclUSR;
bool Ret = index::generateUSRForDecl(ND, DeclUSR);
assert(!Ret && "Unable to generate USR");
return DeclUSR.str();
}

/// Recursively visits the function decls of a DeclContext, and looks up a
/// function based on USRs.
const FunctionDecl *
CrossTranslationUnitContext::findFunctionInDeclContext(const DeclContext *DC,
StringRef LookupFnName) {
assert(DC && "Declaration Context must not be null");
for (const Decl *D : DC->decls()) {
const auto *SubDC = dyn_cast<DeclContext>(D);
if (SubDC)
if (const auto *FD = findFunctionInDeclContext(SubDC, LookupFnName))
return FD;

const auto *ND = dyn_cast<FunctionDecl>(D);
const FunctionDecl *ResultDecl;
if (!ND || !ND->hasBody(ResultDecl))
continue;
if (getLookupName(ResultDecl) != LookupFnName)
continue;
return ResultDecl;
}
return nullptr;
}

llvm::Expected<const FunctionDecl *>
CrossTranslationUnitContext::getCrossTUDefinition(const FunctionDecl *FD,
StringRef CrossTUDir,
StringRef IndexName) {
assert(!FD->hasBody() && "FD has a definition in current translation unit!");
const std::string LookupFnName = getLookupName(FD);
if (LookupFnName.empty())
return llvm::make_error<IndexError>(
index_error_code::failed_to_generate_usr);
llvm::Expected<ASTUnit *> ASTUnitOrError =
loadExternalAST(LookupFnName, CrossTUDir, IndexName);
if (!ASTUnitOrError)
return ASTUnitOrError.takeError();
ASTUnit *Unit = *ASTUnitOrError;
if (!Unit)
return llvm::make_error<IndexError>(
index_error_code::failed_to_get_external_ast);
assert(&Unit->getFileManager() ==
&Unit->getASTContext().getSourceManager().getFileManager());

TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl();
if (const FunctionDecl *ResultDecl =
findFunctionInDeclContext(TU, LookupFnName))
return importDefinition(ResultDecl);
return llvm::make_error<IndexError>(index_error_code::failed_import);
}

void CrossTranslationUnitContext::emitCrossTUDiagnostics(const IndexError &IE) {
switch (IE.getCode()) {
case index_error_code::missing_index_file:
Context.getDiagnostics().Report(diag::err_fe_error_opening)
<< IE.getFileName() << "required by the CrossTU functionality";
break;
case index_error_code::invalid_index_format:
Context.getDiagnostics().Report(diag::err_fnmap_parsing)
<< IE.getFileName() << IE.getLineNum();
case index_error_code::multiple_definitions:
Context.getDiagnostics().Report(diag::err_multiple_def_index)
<< IE.getLineNum();
break;
default:
break;
}
}

llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
StringRef LookupName, StringRef CrossTUDir, StringRef IndexName) {
// FIXME: The current implementation only supports loading functions with
// a lookup name from a single translation unit. If multiple
// translation units contains functions with the same lookup name an
// error will be returned.
ASTUnit *Unit = nullptr;
auto FnUnitCacheEntry = FunctionASTUnitMap.find(LookupName);
if (FnUnitCacheEntry == FunctionASTUnitMap.end()) {
if (FunctionFileMap.empty()) {
SmallString<256> IndexFile = CrossTUDir;
if (llvm::sys::path::is_absolute(IndexName))
IndexFile = IndexName;
else
llvm::sys::path::append(IndexFile, IndexName);
llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
parseCrossTUIndex(IndexFile, CrossTUDir);
if (IndexOrErr)
FunctionFileMap = *IndexOrErr;
else
return IndexOrErr.takeError();
}

auto It = FunctionFileMap.find(LookupName);
if (It == FunctionFileMap.end())
return llvm::make_error<IndexError>(index_error_code::missing_definition);
StringRef ASTFileName = It->second;
auto ASTCacheEntry = FileASTUnitMap.find(ASTFileName);
if (ASTCacheEntry == FileASTUnitMap.end()) {
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter *DiagClient =
new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient));

std::unique_ptr<ASTUnit> LoadedUnit(ASTUnit::LoadFromASTFile(
ASTFileName, CI.getPCHContainerOperations()->getRawReader(),
ASTUnit::LoadEverything, Diags, CI.getFileSystemOpts()));
Unit = LoadedUnit.get();
FileASTUnitMap[ASTFileName] = std::move(LoadedUnit);
} else {
Unit = ASTCacheEntry->second.get();
}
FunctionASTUnitMap[LookupName] = Unit;
} else {
Unit = FnUnitCacheEntry->second;
}
return Unit;
}

llvm::Expected<const FunctionDecl *>
CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD) {
ASTImporter &Importer = getOrCreateASTImporter(FD->getASTContext());
auto *ToDecl =
cast<FunctionDecl>(Importer.Import(const_cast<FunctionDecl *>(FD)));
assert(ToDecl->hasBody());
assert(FD->hasBody() && "Functions already imported should have body.");
return ToDecl;
}

ASTImporter &
CrossTranslationUnitContext::getOrCreateASTImporter(ASTContext &From) {
auto I = ASTUnitImporterMap.find(From.getTranslationUnitDecl());
if (I != ASTUnitImporterMap.end())
return *I->second;
ASTImporter *NewImporter =
new ASTImporter(Context, Context.getSourceManager().getFileManager(),
From, From.getSourceManager().getFileManager(), false);
ASTUnitImporterMap[From.getTranslationUnitDecl()].reset(NewImporter);
return *NewImporter;
}

} // namespace cross_tu
} // namespace clang
7 changes: 7 additions & 0 deletions clang/test/Analysis/func-mapping-test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// RUN: %clang_func_map %s -- | FileCheck %s

int f(int) {
return 0;
}

// CHECK: c:@F@f#I#
1 change: 1 addition & 0 deletions clang/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ list(APPEND CLANG_TEST_DEPS
clang-rename
clang-refactor
clang-diff
clang-func-mapping
)

if(CLANG_ENABLE_STATIC_ANALYZER)
Expand Down
1 change: 1 addition & 0 deletions clang/test/lit.cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def inferClang(PATH):
' --driver-mode=cl '))
config.substitutions.append( ('%clangxx', ' ' + config.clang +
' --driver-mode=g++ '))
config.substitutions.append( ('%clang_func_map', ' ' + lit.util.which('clang-func-mapping', config.environment['PATH']) + ' ') )
config.substitutions.append( ('%clang', ' ' + config.clang + ' ') )
config.substitutions.append( ('%test_debuginfo',
' ' + config.llvm_src_root + '/utils/test_debuginfo.pl ') )
Expand Down
1 change: 1 addition & 0 deletions clang/tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ endif()

if(CLANG_ENABLE_STATIC_ANALYZER)
add_clang_subdirectory(clang-check)
add_clang_subdirectory(clang-func-mapping)
add_clang_subdirectory(scan-build)
add_clang_subdirectory(scan-view)
endif()
Expand Down
22 changes: 22 additions & 0 deletions clang/tools/clang-func-mapping/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
set(LLVM_LINK_COMPONENTS
${LLVM_TARGETS_TO_BUILD}
asmparser
support
mc
)

add_clang_executable(clang-func-mapping
ClangFnMapGen.cpp
)

target_link_libraries(clang-func-mapping
clangAST
clangBasic
clangCrossTU
clangFrontend
clangIndex
clangTooling
)

install(TARGETS clang-func-mapping
RUNTIME DESTINATION bin)
124 changes: 124 additions & 0 deletions clang/tools/clang-func-mapping/ClangFnMapGen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//===- ClangFnMapGen.cpp -----------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===--------------------------------------------------------------------===//
//
// Clang tool which creates a list of defined functions and the files in which
// they are defined.
//
//===--------------------------------------------------------------------===//

#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/GlobalDecl.h"
#include "clang/AST/Mangle.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/CrossTU/CrossTranslationUnit.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Index/USRGeneration.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Signals.h"
#include <sstream>
#include <string>
#include <vector>

using namespace llvm;
using namespace clang;
using namespace clang::cross_tu;
using namespace clang::tooling;

static cl::OptionCategory ClangFnMapGenCategory("clang-fnmapgen options");

class MapFunctionNamesConsumer : public ASTConsumer {
public:
MapFunctionNamesConsumer(ASTContext &Context) : Ctx(Context) {}

~MapFunctionNamesConsumer() {
// Flush results to standard output.
llvm::outs() << createCrossTUIndexString(Index);
}

virtual void HandleTranslationUnit(ASTContext &Ctx) {
handleDecl(Ctx.getTranslationUnitDecl());
}

private:
void handleDecl(const Decl *D);

ASTContext &Ctx;
llvm::StringMap<std::string> Index;
std::string CurrentFileName;
};

void MapFunctionNamesConsumer::handleDecl(const Decl *D) {
if (!D)
return;

if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
if (FD->isThisDeclarationADefinition()) {
if (const Stmt *Body = FD->getBody()) {
std::string LookupName = CrossTranslationUnitContext::getLookupName(FD);
const SourceManager &SM = Ctx.getSourceManager();
if (CurrentFileName.empty()) {
CurrentFileName =
SM.getFileEntryForID(SM.getMainFileID())->tryGetRealPathName();
if (CurrentFileName.empty())
CurrentFileName = "invalid_file";
}

switch (FD->getLinkageInternal()) {
case ExternalLinkage:
case VisibleNoLinkage:
case UniqueExternalLinkage:
if (SM.isInMainFile(Body->getLocStart()))
Index[LookupName] = CurrentFileName;
default:
break;
}
}
}
}

if (const auto *DC = dyn_cast<DeclContext>(D))
for (const Decl *D : DC->decls())
handleDecl(D);
}

class MapFunctionNamesAction : public ASTFrontendAction {
protected:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
llvm::StringRef) {
std::unique_ptr<ASTConsumer> PFC(
new MapFunctionNamesConsumer(CI.getASTContext()));
return PFC;
}
};

static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);

int main(int argc, const char **argv) {
// Print a stack trace if we signal out.
sys::PrintStackTraceOnErrorSignal(argv[0], false);
PrettyStackTraceProgram X(argc, argv);

const char *Overview = "\nThis tool collects the USR name and location "
"of all functions definitions in the source files "
"(excluding headers).\n";
CommonOptionsParser OptionsParser(argc, argv, ClangFnMapGenCategory,
cl::ZeroOrMore, Overview);

ClangTool Tool(OptionsParser.getCompilations(),
OptionsParser.getSourcePathList());
Tool.run(newFrontendActionFactory<MapFunctionNamesAction>().get());
return 0;
}
1 change: 1 addition & 0 deletions clang/tools/diagtool/DiagnosticNames.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ static const DiagnosticRecord BuiltinDiagnosticsByID[] = {
SFINAE,NOWERROR,SHOWINSYSHEADER,CATEGORY) \
{ #ENUM, diag::ENUM, STR_SIZE(#ENUM, uint8_t) },
#include "clang/Basic/DiagnosticCommonKinds.inc"
#include "clang/Basic/DiagnosticCrossTUKinds.inc"
#include "clang/Basic/DiagnosticDriverKinds.inc"
#include "clang/Basic/DiagnosticFrontendKinds.inc"
#include "clang/Basic/DiagnosticSerializationKinds.inc"
Expand Down
1 change: 1 addition & 0 deletions clang/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ if(CLANG_ENABLE_STATIC_ANALYZER)
endif()
add_subdirectory(ASTMatchers)
add_subdirectory(AST)
add_subdirectory(CrossTU)
add_subdirectory(Tooling)
add_subdirectory(Format)
add_subdirectory(Rewrite)
Expand Down
16 changes: 16 additions & 0 deletions clang/unittests/CrossTU/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
set(LLVM_LINK_COMPONENTS
${LLVM_TARGETS_TO_BUILD}
Support
)

add_clang_unittest(CrossTUTests
CrossTranslationUnitTest.cpp
)

target_link_libraries(CrossTUTests
clangAST
clangBasic
clangCrossTU
clangFrontend
clangTooling
)
138 changes: 138 additions & 0 deletions clang/unittests/CrossTU/CrossTranslationUnitTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//===- unittest/Tooling/CrossTranslationUnitTest.cpp - Tooling unit tests -===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "clang/CrossTU/CrossTranslationUnit.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ToolOutputFile.h"
#include "gtest/gtest.h"
#include <cassert>

namespace clang {
namespace cross_tu {

namespace {

class CTUASTConsumer : public clang::ASTConsumer {
public:
explicit CTUASTConsumer(clang::CompilerInstance &CI, bool *Success)
: CTU(CI), Success(Success) {}

void HandleTranslationUnit(ASTContext &Ctx) {
const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl();
const FunctionDecl *FD = nullptr;
for (const Decl *D : TU->decls()) {
FD = dyn_cast<FunctionDecl>(D);
if (FD && FD->getName() == "f")
break;
}
assert(FD && FD->getName() == "f");
bool OrigFDHasBody = FD->hasBody();

// Prepare the index file and the AST file.
int ASTFD;
llvm::SmallString<256> ASTFileName;
ASSERT_FALSE(
llvm::sys::fs::createTemporaryFile("f_ast", "ast", ASTFD, ASTFileName));
llvm::tool_output_file ASTFile(ASTFileName, ASTFD);

int IndexFD;
llvm::SmallString<256> IndexFileName;
ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
IndexFileName));
llvm::tool_output_file IndexFile(IndexFileName, IndexFD);
IndexFile.os() << "c:@F@f#I# " << ASTFileName << "\n";
IndexFile.os().flush();
EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));

StringRef SourceText = "int f(int) { return 0; }\n";
// This file must exist since the saved ASTFile will reference it.
int SourceFD;
llvm::SmallString<256> SourceFileName;
ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("input", "cpp", SourceFD,
SourceFileName));
llvm::tool_output_file SourceFile(SourceFileName, SourceFD);
SourceFile.os() << SourceText;
SourceFile.os().flush();
EXPECT_TRUE(llvm::sys::fs::exists(SourceFileName));

std::unique_ptr<ASTUnit> ASTWithDefinition =
tooling::buildASTFromCode(SourceText, SourceFileName);
ASTWithDefinition->Save(ASTFileName.str());
EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName));

// Load the definition from the AST file.
llvm::Expected<const FunctionDecl *> NewFDorError =
CTU.getCrossTUDefinition(FD, "", IndexFileName);
EXPECT_TRUE((bool)NewFDorError);
const FunctionDecl *NewFD = *NewFDorError;

*Success = NewFD && NewFD->hasBody() && !OrigFDHasBody;
}

private:
CrossTranslationUnitContext CTU;
bool *Success;
};

class CTUAction : public clang::ASTFrontendAction {
public:
CTUAction(bool *Success) : Success(Success) {}

protected:
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override {
return llvm::make_unique<CTUASTConsumer>(CI, Success);
}

private:
bool *Success;
};

} // end namespace

TEST(CrossTranslationUnit, CanLoadFunctionDefinition) {
bool Success = false;
EXPECT_TRUE(tooling::runToolOnCode(new CTUAction(&Success), "int f(int);"));
EXPECT_TRUE(Success);
}

TEST(CrossTranslationUnit, IndexFormatCanBeParsed) {
llvm::StringMap<std::string> Index;
Index["a"] = "b";
Index["c"] = "d";
Index["e"] = "f";
std::string IndexText = createCrossTUIndexString(Index);

int IndexFD;
llvm::SmallString<256> IndexFileName;
ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
IndexFileName));
llvm::tool_output_file IndexFile(IndexFileName, IndexFD);
IndexFile.os() << IndexText;
IndexFile.os().flush();
EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));
llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
parseCrossTUIndex(IndexFileName, "");
EXPECT_TRUE((bool)IndexOrErr);
llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get();
for (const auto &E : Index) {
EXPECT_TRUE(ParsedIndex.count(E.getKey()));
EXPECT_EQ(ParsedIndex[E.getKey()], E.getValue());
}
for (const auto &E : ParsedIndex)
EXPECT_TRUE(Index.count(E.getKey()));
}

} // end namespace cross_tu
} // end namespace clang