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