298 lines
9.7 KiB
C++
298 lines
9.7 KiB
C++
|
//===- unittest/Tooling/CrossTranslationUnitTest.cpp - Tooling unit tests -===//
|
||
|
//
|
||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "clang/CrossTU/CrossTranslationUnit.h"
|
||
|
#include "clang/AST/ASTConsumer.h"
|
||
|
#include "clang/AST/ParentMapContext.h"
|
||
|
#include "clang/Frontend/CompilerInstance.h"
|
||
|
#include "clang/Frontend/FrontendAction.h"
|
||
|
#include "clang/Tooling/Tooling.h"
|
||
|
#include "llvm/ADT/Optional.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) override {
|
||
|
auto FindFInTU = [](const TranslationUnitDecl *TU) {
|
||
|
const FunctionDecl *FD = nullptr;
|
||
|
for (const Decl *D : TU->decls()) {
|
||
|
FD = dyn_cast<FunctionDecl>(D);
|
||
|
if (FD && FD->getName() == "f")
|
||
|
break;
|
||
|
}
|
||
|
return FD;
|
||
|
};
|
||
|
|
||
|
const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl();
|
||
|
const FunctionDecl *FD = FindFInTU(TU);
|
||
|
assert(FD && FD->getName() == "f");
|
||
|
bool OrigFDHasBody = FD->hasBody();
|
||
|
|
||
|
const DynTypedNodeList ParentsBeforeImport =
|
||
|
Ctx.getParentMapContext().getParents<Decl>(*FD);
|
||
|
ASSERT_FALSE(ParentsBeforeImport.empty());
|
||
|
|
||
|
// 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::ToolOutputFile ASTFile(ASTFileName, ASTFD);
|
||
|
|
||
|
int IndexFD;
|
||
|
llvm::SmallString<256> IndexFileName;
|
||
|
ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
|
||
|
IndexFileName));
|
||
|
llvm::ToolOutputFile 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::ToolOutputFile 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 = handleExpected(
|
||
|
CTU.getCrossTUDefinition(FD, "", IndexFileName, false),
|
||
|
[]() { return nullptr; }, [](IndexError &) {});
|
||
|
|
||
|
if (NewFDorError) {
|
||
|
const FunctionDecl *NewFD = *NewFDorError;
|
||
|
*Success = NewFD && NewFD->hasBody() && !OrigFDHasBody;
|
||
|
|
||
|
if (NewFD) {
|
||
|
// Check GetImportedFromSourceLocation.
|
||
|
llvm::Optional<std::pair<SourceLocation, ASTUnit *>> SLocResult =
|
||
|
CTU.getImportedFromSourceLocation(NewFD->getLocation());
|
||
|
EXPECT_TRUE(SLocResult);
|
||
|
if (SLocResult) {
|
||
|
SourceLocation OrigSLoc = (*SLocResult).first;
|
||
|
ASTUnit *OrigUnit = (*SLocResult).second;
|
||
|
// OrigUnit is created internally by CTU (is not the
|
||
|
// ASTWithDefinition).
|
||
|
TranslationUnitDecl *OrigTU =
|
||
|
OrigUnit->getASTContext().getTranslationUnitDecl();
|
||
|
const FunctionDecl *FDWithDefinition = FindFInTU(OrigTU);
|
||
|
EXPECT_TRUE(FDWithDefinition);
|
||
|
if (FDWithDefinition) {
|
||
|
EXPECT_EQ(FDWithDefinition->getName(), "f");
|
||
|
EXPECT_TRUE(FDWithDefinition->isThisDeclarationADefinition());
|
||
|
EXPECT_EQ(OrigSLoc, FDWithDefinition->getLocation());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check parent map.
|
||
|
const DynTypedNodeList ParentsAfterImport =
|
||
|
Ctx.getParentMapContext().getParents<Decl>(*FD);
|
||
|
const DynTypedNodeList ParentsOfImported =
|
||
|
Ctx.getParentMapContext().getParents<Decl>(*NewFD);
|
||
|
EXPECT_TRUE(
|
||
|
checkParentListsEq(ParentsBeforeImport, ParentsAfterImport));
|
||
|
EXPECT_FALSE(ParentsOfImported.empty());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static bool checkParentListsEq(const DynTypedNodeList &L1,
|
||
|
const DynTypedNodeList &L2) {
|
||
|
if (L1.size() != L2.size())
|
||
|
return false;
|
||
|
for (unsigned int I = 0; I < L1.size(); ++I)
|
||
|
if (L1[I] != L2[I])
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
CrossTranslationUnitContext CTU;
|
||
|
bool *Success;
|
||
|
};
|
||
|
|
||
|
class CTUAction : public clang::ASTFrontendAction {
|
||
|
public:
|
||
|
CTUAction(bool *Success, unsigned OverrideLimit)
|
||
|
: Success(Success), OverrideLimit(OverrideLimit) {}
|
||
|
|
||
|
protected:
|
||
|
std::unique_ptr<clang::ASTConsumer>
|
||
|
CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override {
|
||
|
CI.getAnalyzerOpts()->CTUImportThreshold = OverrideLimit;
|
||
|
CI.getAnalyzerOpts()->CTUImportCppThreshold = OverrideLimit;
|
||
|
return std::make_unique<CTUASTConsumer>(CI, Success);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
bool *Success;
|
||
|
const unsigned OverrideLimit;
|
||
|
};
|
||
|
|
||
|
} // end namespace
|
||
|
|
||
|
TEST(CrossTranslationUnit, CanLoadFunctionDefinition) {
|
||
|
bool Success = false;
|
||
|
EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 1u),
|
||
|
"int f(int);"));
|
||
|
EXPECT_TRUE(Success);
|
||
|
}
|
||
|
|
||
|
TEST(CrossTranslationUnit, RespectsLoadThreshold) {
|
||
|
bool Success = false;
|
||
|
EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 0u),
|
||
|
"int f(int);"));
|
||
|
EXPECT_FALSE(Success);
|
||
|
}
|
||
|
|
||
|
TEST(CrossTranslationUnit, IndexFormatCanBeParsed) {
|
||
|
llvm::StringMap<std::string> Index;
|
||
|
Index["a"] = "/b/f1";
|
||
|
Index["c"] = "/d/f2";
|
||
|
Index["e"] = "/f/f3";
|
||
|
std::string IndexText = createCrossTUIndexString(Index);
|
||
|
|
||
|
int IndexFD;
|
||
|
llvm::SmallString<256> IndexFileName;
|
||
|
ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
|
||
|
IndexFileName));
|
||
|
llvm::ToolOutputFile 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()));
|
||
|
}
|
||
|
|
||
|
TEST(CrossTranslationUnit, EmptyInvocationListIsNotValid) {
|
||
|
auto Input = "";
|
||
|
|
||
|
llvm::Expected<InvocationListTy> Result = parseInvocationList(Input);
|
||
|
EXPECT_FALSE(static_cast<bool>(Result));
|
||
|
bool IsWrongFromatError = false;
|
||
|
llvm::handleAllErrors(Result.takeError(), [&](IndexError &Err) {
|
||
|
IsWrongFromatError =
|
||
|
Err.getCode() == index_error_code::invocation_list_wrong_format;
|
||
|
});
|
||
|
EXPECT_TRUE(IsWrongFromatError);
|
||
|
}
|
||
|
|
||
|
TEST(CrossTranslationUnit, AmbiguousInvocationListIsDetected) {
|
||
|
// The same source file occurs twice (for two different architecture) in
|
||
|
// this test case. The disambiguation is the responsibility of the user.
|
||
|
auto Input = R"(
|
||
|
/tmp/main.cpp:
|
||
|
- clang++
|
||
|
- -c
|
||
|
- -m32
|
||
|
- -o
|
||
|
- main32.o
|
||
|
- /tmp/main.cpp
|
||
|
/tmp/main.cpp:
|
||
|
- clang++
|
||
|
- -c
|
||
|
- -m64
|
||
|
- -o
|
||
|
- main64.o
|
||
|
- /tmp/main.cpp
|
||
|
)";
|
||
|
|
||
|
llvm::Expected<InvocationListTy> Result = parseInvocationList(Input);
|
||
|
EXPECT_FALSE(static_cast<bool>(Result));
|
||
|
bool IsAmbiguousError = false;
|
||
|
llvm::handleAllErrors(Result.takeError(), [&](IndexError &Err) {
|
||
|
IsAmbiguousError =
|
||
|
Err.getCode() == index_error_code::invocation_list_ambiguous;
|
||
|
});
|
||
|
EXPECT_TRUE(IsAmbiguousError);
|
||
|
}
|
||
|
|
||
|
TEST(CrossTranslationUnit, SingleInvocationCanBeParsed) {
|
||
|
auto Input = R"(
|
||
|
/tmp/main.cpp:
|
||
|
- clang++
|
||
|
- /tmp/main.cpp
|
||
|
)";
|
||
|
llvm::Expected<InvocationListTy> Result = parseInvocationList(Input);
|
||
|
EXPECT_TRUE(static_cast<bool>(Result));
|
||
|
|
||
|
EXPECT_EQ(Result->size(), 1u);
|
||
|
|
||
|
auto It = Result->find("/tmp/main.cpp");
|
||
|
EXPECT_TRUE(It != Result->end());
|
||
|
EXPECT_EQ(It->getValue()[0], "clang++");
|
||
|
EXPECT_EQ(It->getValue()[1], "/tmp/main.cpp");
|
||
|
}
|
||
|
|
||
|
TEST(CrossTranslationUnit, MultipleInvocationsCanBeParsed) {
|
||
|
auto Input = R"(
|
||
|
/tmp/main.cpp:
|
||
|
- clang++
|
||
|
- /tmp/other.o
|
||
|
- /tmp/main.cpp
|
||
|
/tmp/other.cpp:
|
||
|
- g++
|
||
|
- -c
|
||
|
- -o
|
||
|
- /tmp/other.o
|
||
|
- /tmp/other.cpp
|
||
|
)";
|
||
|
llvm::Expected<InvocationListTy> Result = parseInvocationList(Input);
|
||
|
EXPECT_TRUE(static_cast<bool>(Result));
|
||
|
|
||
|
EXPECT_EQ(Result->size(), 2u);
|
||
|
|
||
|
auto It = Result->find("/tmp/main.cpp");
|
||
|
EXPECT_TRUE(It != Result->end());
|
||
|
EXPECT_EQ(It->getKey(), "/tmp/main.cpp");
|
||
|
EXPECT_EQ(It->getValue()[0], "clang++");
|
||
|
EXPECT_EQ(It->getValue()[1], "/tmp/other.o");
|
||
|
EXPECT_EQ(It->getValue()[2], "/tmp/main.cpp");
|
||
|
|
||
|
It = Result->find("/tmp/other.cpp");
|
||
|
EXPECT_TRUE(It != Result->end());
|
||
|
EXPECT_EQ(It->getValue()[0], "g++");
|
||
|
EXPECT_EQ(It->getValue()[1], "-c");
|
||
|
EXPECT_EQ(It->getValue()[2], "-o");
|
||
|
EXPECT_EQ(It->getValue()[3], "/tmp/other.o");
|
||
|
EXPECT_EQ(It->getValue()[4], "/tmp/other.cpp");
|
||
|
}
|
||
|
|
||
|
} // end namespace cross_tu
|
||
|
} // end namespace clang
|