blob: 86ede5e319cb485020ec0b9feaba65a83c93e8d5 [file] [log] [blame]
//===- 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/Frontend/CompilerInstance.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Tooling/Tooling.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) {
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();
// 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());
}
}
}
}
}
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;
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, CTUDirIsHandledCorrectly) {
llvm::StringMap<std::string> Index;
Index["a"] = "/b/c/d";
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, "/ctudir");
EXPECT_TRUE((bool)IndexOrErr);
llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get();
EXPECT_EQ(ParsedIndex["a"], "/ctudir/b/c/d");
}
} // end namespace cross_tu
} // end namespace clang