277 lines · cpp
1//===- unittest/Tooling/CrossTranslationUnitTest.cpp - Tooling unit tests -===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/CrossTU/CrossTranslationUnit.h"10#include "clang/AST/ASTConsumer.h"11#include "clang/AST/ParentMapContext.h"12#include "clang/Frontend/CompilerInstance.h"13#include "clang/Frontend/FrontendAction.h"14#include "clang/Tooling/Tooling.h"15#include "llvm/Support/FileSystem.h"16#include "llvm/Support/Path.h"17#include "llvm/Support/ToolOutputFile.h"18#include "gtest/gtest.h"19#include <cassert>20 21namespace clang {22namespace cross_tu {23 24namespace {25 26class CTUASTConsumer : public clang::ASTConsumer {27public:28 explicit CTUASTConsumer(clang::CompilerInstance &CI, bool *Success)29 : CTU(CI), Success(Success) {}30 31 void HandleTranslationUnit(ASTContext &Ctx) override {32 auto FindFInTU = [](const TranslationUnitDecl *TU) {33 const FunctionDecl *FD = nullptr;34 for (const Decl *D : TU->decls()) {35 FD = dyn_cast<FunctionDecl>(D);36 if (FD && FD->getName() == "f")37 break;38 }39 return FD;40 };41 42 const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl();43 const FunctionDecl *FD = FindFInTU(TU);44 assert(FD && FD->getName() == "f");45 bool OrigFDHasBody = FD->hasBody();46 47 const DynTypedNodeList ParentsBeforeImport =48 Ctx.getParentMapContext().getParents<Decl>(*FD);49 ASSERT_FALSE(ParentsBeforeImport.empty());50 51 // Prepare the index file and the AST file.52 int ASTFD;53 llvm::SmallString<256> ASTFileName;54 ASSERT_FALSE(55 llvm::sys::fs::createTemporaryFile("f_ast", "ast", ASTFD, ASTFileName));56 llvm::ToolOutputFile ASTFile(ASTFileName, ASTFD);57 58 int IndexFD;59 llvm::SmallString<256> IndexFileName;60 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,61 IndexFileName));62 llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD);63 IndexFile.os() << "9:c:@F@f#I# " << ASTFileName << "\n";64 IndexFile.os().flush();65 EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));66 67 StringRef SourceText = "int f(int) { return 0; }\n";68 // This file must exist since the saved ASTFile will reference it.69 int SourceFD;70 llvm::SmallString<256> SourceFileName;71 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("input", "cpp", SourceFD,72 SourceFileName));73 llvm::ToolOutputFile SourceFile(SourceFileName, SourceFD);74 SourceFile.os() << SourceText;75 SourceFile.os().flush();76 EXPECT_TRUE(llvm::sys::fs::exists(SourceFileName));77 78 std::unique_ptr<ASTUnit> ASTWithDefinition =79 tooling::buildASTFromCode(SourceText, SourceFileName);80 ASTWithDefinition->Save(ASTFileName.str());81 EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName));82 83 // Load the definition from the AST file.84 llvm::Expected<const FunctionDecl *> NewFDorError = handleExpected(85 CTU.getCrossTUDefinition(FD, "", IndexFileName, false),86 []() { return nullptr; }, [](IndexError &) {});87 88 if (NewFDorError) {89 const FunctionDecl *NewFD = *NewFDorError;90 *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody;91 92 if (NewFD) {93 // Check parent map.94 const DynTypedNodeList ParentsAfterImport =95 Ctx.getParentMapContext().getParents<Decl>(*FD);96 const DynTypedNodeList ParentsOfImported =97 Ctx.getParentMapContext().getParents<Decl>(*NewFD);98 EXPECT_TRUE(99 checkParentListsEq(ParentsBeforeImport, ParentsAfterImport));100 EXPECT_FALSE(ParentsOfImported.empty());101 }102 }103 }104 105 static bool checkParentListsEq(const DynTypedNodeList &L1,106 const DynTypedNodeList &L2) {107 if (L1.size() != L2.size())108 return false;109 for (unsigned int I = 0; I < L1.size(); ++I)110 if (L1[I] != L2[I])111 return false;112 return true;113 }114 115private:116 CrossTranslationUnitContext CTU;117 bool *Success;118};119 120class CTUAction : public clang::ASTFrontendAction {121public:122 CTUAction(bool *Success, unsigned OverrideLimit)123 : Success(Success), OverrideLimit(OverrideLimit) {}124 125protected:126 std::unique_ptr<clang::ASTConsumer>127 CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override {128 CI.getAnalyzerOpts().CTUImportThreshold = OverrideLimit;129 CI.getAnalyzerOpts().CTUImportCppThreshold = OverrideLimit;130 return std::make_unique<CTUASTConsumer>(CI, Success);131 }132 133private:134 bool *Success;135 const unsigned OverrideLimit;136};137 138} // end namespace139 140TEST(CrossTranslationUnit, CanLoadFunctionDefinition) {141 bool Success = false;142 EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 1u),143 "int f(int);"));144 EXPECT_TRUE(Success);145}146 147TEST(CrossTranslationUnit, RespectsLoadThreshold) {148 bool Success = false;149 EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 0u),150 "int f(int);"));151 EXPECT_FALSE(Success);152}153 154TEST(CrossTranslationUnit, IndexFormatCanBeParsed) {155 llvm::StringMap<std::string> Index;156 Index["a"] = "/b/f1";157 Index["c"] = "/d/f2";158 Index["e"] = "/f/f3";159 std::string IndexText = createCrossTUIndexString(Index);160 161 int IndexFD;162 llvm::SmallString<256> IndexFileName;163 ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,164 IndexFileName));165 llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD);166 IndexFile.os() << IndexText;167 IndexFile.os().flush();168 EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));169 llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =170 parseCrossTUIndex(IndexFileName);171 EXPECT_TRUE((bool)IndexOrErr);172 llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get();173 for (const auto &E : Index) {174 EXPECT_TRUE(ParsedIndex.count(E.getKey()));175 EXPECT_EQ(ParsedIndex[E.getKey()], E.getValue());176 }177 for (const auto &E : ParsedIndex)178 EXPECT_TRUE(Index.count(E.getKey()));179}180 181TEST(CrossTranslationUnit, EmptyInvocationListIsNotValid) {182 auto Input = "";183 184 llvm::Expected<InvocationListTy> Result = parseInvocationList(Input);185 EXPECT_FALSE(static_cast<bool>(Result));186 bool IsWrongFromatError = false;187 llvm::handleAllErrors(Result.takeError(), [&](IndexError &Err) {188 IsWrongFromatError =189 Err.getCode() == index_error_code::invocation_list_wrong_format;190 });191 EXPECT_TRUE(IsWrongFromatError);192}193 194TEST(CrossTranslationUnit, AmbiguousInvocationListIsDetected) {195 // The same source file occurs twice (for two different architecture) in196 // this test case. The disambiguation is the responsibility of the user.197 auto Input = R"(198 /tmp/main.cpp:199 - clang++200 - -c201 - -m32202 - -o203 - main32.o204 - /tmp/main.cpp205 /tmp/main.cpp:206 - clang++207 - -c208 - -m64209 - -o210 - main64.o211 - /tmp/main.cpp212 )";213 214 llvm::Expected<InvocationListTy> Result = parseInvocationList(Input);215 EXPECT_FALSE(static_cast<bool>(Result));216 bool IsAmbiguousError = false;217 llvm::handleAllErrors(Result.takeError(), [&](IndexError &Err) {218 IsAmbiguousError =219 Err.getCode() == index_error_code::invocation_list_ambiguous;220 });221 EXPECT_TRUE(IsAmbiguousError);222}223 224TEST(CrossTranslationUnit, SingleInvocationCanBeParsed) {225 auto Input = R"(226 /tmp/main.cpp:227 - clang++228 - /tmp/main.cpp229 )";230 llvm::Expected<InvocationListTy> Result = parseInvocationList(Input);231 EXPECT_TRUE(static_cast<bool>(Result));232 233 EXPECT_EQ(Result->size(), 1u);234 235 auto It = Result->find("/tmp/main.cpp");236 EXPECT_TRUE(It != Result->end());237 EXPECT_EQ(It->getValue()[0], "clang++");238 EXPECT_EQ(It->getValue()[1], "/tmp/main.cpp");239}240 241TEST(CrossTranslationUnit, MultipleInvocationsCanBeParsed) {242 auto Input = R"(243 /tmp/main.cpp:244 - clang++245 - /tmp/other.o246 - /tmp/main.cpp247 /tmp/other.cpp:248 - g++249 - -c250 - -o251 - /tmp/other.o252 - /tmp/other.cpp253 )";254 llvm::Expected<InvocationListTy> Result = parseInvocationList(Input);255 EXPECT_TRUE(static_cast<bool>(Result));256 257 EXPECT_EQ(Result->size(), 2u);258 259 auto It = Result->find("/tmp/main.cpp");260 EXPECT_TRUE(It != Result->end());261 EXPECT_EQ(It->getKey(), "/tmp/main.cpp");262 EXPECT_EQ(It->getValue()[0], "clang++");263 EXPECT_EQ(It->getValue()[1], "/tmp/other.o");264 EXPECT_EQ(It->getValue()[2], "/tmp/main.cpp");265 266 It = Result->find("/tmp/other.cpp");267 EXPECT_TRUE(It != Result->end());268 EXPECT_EQ(It->getValue()[0], "g++");269 EXPECT_EQ(It->getValue()[1], "-c");270 EXPECT_EQ(It->getValue()[2], "-o");271 EXPECT_EQ(It->getValue()[3], "/tmp/other.o");272 EXPECT_EQ(It->getValue()[4], "/tmp/other.cpp");273}274 275} // end namespace cross_tu276} // end namespace clang277