xref: /llvm-project/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp (revision 8e0c9bb91f484b7d2fa86c01919d96a41a7071d7)
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 
21 namespace clang {
22 namespace cross_tu {
23 
24 namespace {
25 
26 class CTUASTConsumer : public clang::ASTConsumer {
27 public:
CTUASTConsumer(clang::CompilerInstance & CI,bool * Success)28   explicit CTUASTConsumer(clang::CompilerInstance &CI, bool *Success)
29       : CTU(CI), Success(Success) {}
30 
HandleTranslationUnit(ASTContext & Ctx)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 
checkParentListsEq(const DynTypedNodeList & L1,const DynTypedNodeList & L2)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 
115 private:
116   CrossTranslationUnitContext CTU;
117   bool *Success;
118 };
119 
120 class CTUAction : public clang::ASTFrontendAction {
121 public:
CTUAction(bool * Success,unsigned OverrideLimit)122   CTUAction(bool *Success, unsigned OverrideLimit)
123       : Success(Success), OverrideLimit(OverrideLimit) {}
124 
125 protected:
126   std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance & CI,StringRef)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 
133 private:
134   bool *Success;
135   const unsigned OverrideLimit;
136 };
137 
138 } // end namespace
139 
TEST(CrossTranslationUnit,CanLoadFunctionDefinition)140 TEST(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 
TEST(CrossTranslationUnit,RespectsLoadThreshold)147 TEST(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 
TEST(CrossTranslationUnit,IndexFormatCanBeParsed)154 TEST(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 
TEST(CrossTranslationUnit,EmptyInvocationListIsNotValid)181 TEST(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 
TEST(CrossTranslationUnit,AmbiguousInvocationListIsDetected)194 TEST(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(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 
TEST(CrossTranslationUnit,SingleInvocationCanBeParsed)224 TEST(CrossTranslationUnit, SingleInvocationCanBeParsed) {
225   auto Input = R"(
226   /tmp/main.cpp:
227     - clang++
228     - /tmp/main.cpp
229   )";
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 
TEST(CrossTranslationUnit,MultipleInvocationsCanBeParsed)241 TEST(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(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_tu
276 } // end namespace clang
277