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-exception
6//
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(Code: SourceText, FileName: SourceFileName);
80 ASTWithDefinition->Save(File: 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 ValOrErr: CTU.getCrossTUDefinition(FD, CrossTUDir: "", IndexName: IndexFileName, DisplayCTUProgress: false),
86 RecoveryPath: []() { return nullptr; }, Handlers: [](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>(args&: CI, args&: Success);
131 }
132
133private:
134 bool *Success;
135 const unsigned OverrideLimit;
136};
137
138} // end namespace
139
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(IndexPath: 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(FileContent: Input);
185 EXPECT_FALSE(static_cast<bool>(Result));
186 bool IsWrongFromatError = false;
187 llvm::handleAllErrors(E: Result.takeError(), Handlers: [&](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) in
196 // this test case. The disambiguation is the responsibility of the user.
197 auto Input = R"(
198 /tmp/main.cpp:
199 - clang++
200 - -c
201 - -m32
202 - -o
203 - main32.o
204 - /tmp/main.cpp
205 /tmp/main.cpp:
206 - clang++
207 - -c
208 - -m64
209 - -o
210 - main64.o
211 - /tmp/main.cpp
212 )";
213
214 llvm::Expected<InvocationListTy> Result = parseInvocationList(FileContent: Input);
215 EXPECT_FALSE(static_cast<bool>(Result));
216 bool IsAmbiguousError = false;
217 llvm::handleAllErrors(E: Result.takeError(), Handlers: [&](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.cpp
229 )";
230 llvm::Expected<InvocationListTy> Result = parseInvocationList(FileContent: Input);
231 EXPECT_TRUE(static_cast<bool>(Result));
232
233 EXPECT_EQ(Result->size(), 1u);
234
235 auto It = Result->find(Key: "/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.o
246 - /tmp/main.cpp
247 /tmp/other.cpp:
248 - g++
249 - -c
250 - -o
251 - /tmp/other.o
252 - /tmp/other.cpp
253 )";
254 llvm::Expected<InvocationListTy> Result = parseInvocationList(FileContent: Input);
255 EXPECT_TRUE(static_cast<bool>(Result));
256
257 EXPECT_EQ(Result->size(), 2u);
258
259 auto It = Result->find(Key: "/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(Key: "/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_tu
276} // end namespace clang
277

source code of clang/unittests/CrossTU/CrossTranslationUnitTest.cpp