llvm-project/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp

278 lines
8.8 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 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