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/Frontend/CompilerInstance.h"
11 #include "clang/AST/ASTConsumer.h"
12 #include "clang/Frontend/FrontendAction.h"
13 #include "clang/Tooling/Tooling.h"
14 #include "llvm/Support/FileSystem.h"
15 #include "llvm/Support/Path.h"
16 #include "llvm/Support/ToolOutputFile.h"
17 #include "gtest/gtest.h"
18 #include <cassert>
19 
20 namespace clang {
21 namespace cross_tu {
22 
23 namespace {
24 
25 class CTUASTConsumer : public clang::ASTConsumer {
26 public:
27   explicit CTUASTConsumer(clang::CompilerInstance &CI, bool *Success)
28       : CTU(CI), Success(Success) {}
29 
30   void HandleTranslationUnit(ASTContext &Ctx) {
31     auto FindFInTU = [](const TranslationUnitDecl *TU) {
32       const FunctionDecl *FD = nullptr;
33       for (const Decl *D : TU->decls()) {
34         FD = dyn_cast<FunctionDecl>(D);
35         if (FD && FD->getName() == "f")
36           break;
37       }
38       return FD;
39     };
40 
41     const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl();
42     const FunctionDecl *FD = FindFInTU(TU);
43     assert(FD && FD->getName() == "f");
44     bool OrigFDHasBody = FD->hasBody();
45 
46     // Prepare the index file and the AST file.
47     int ASTFD;
48     llvm::SmallString<256> ASTFileName;
49     ASSERT_FALSE(
50         llvm::sys::fs::createTemporaryFile("f_ast", "ast", ASTFD, ASTFileName));
51     llvm::ToolOutputFile ASTFile(ASTFileName, ASTFD);
52 
53     int IndexFD;
54     llvm::SmallString<256> IndexFileName;
55     ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
56                                                     IndexFileName));
57     llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD);
58     IndexFile.os() << "c:@F@f#I# " << ASTFileName << "\n";
59     IndexFile.os().flush();
60     EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));
61 
62     StringRef SourceText = "int f(int) { return 0; }\n";
63     // This file must exist since the saved ASTFile will reference it.
64     int SourceFD;
65     llvm::SmallString<256> SourceFileName;
66     ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("input", "cpp", SourceFD,
67                                                     SourceFileName));
68     llvm::ToolOutputFile SourceFile(SourceFileName, SourceFD);
69     SourceFile.os() << SourceText;
70     SourceFile.os().flush();
71     EXPECT_TRUE(llvm::sys::fs::exists(SourceFileName));
72 
73     std::unique_ptr<ASTUnit> ASTWithDefinition =
74         tooling::buildASTFromCode(SourceText, SourceFileName);
75     ASTWithDefinition->Save(ASTFileName.str());
76     EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName));
77 
78     // Load the definition from the AST file.
79     llvm::Expected<const FunctionDecl *> NewFDorError = handleExpected(
80         CTU.getCrossTUDefinition(FD, "", IndexFileName, false),
81         []() { return nullptr; }, [](IndexError &) {});
82 
83     if (NewFDorError) {
84       const FunctionDecl *NewFD = *NewFDorError;
85       *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody;
86 
87       if (NewFD) {
88         // Check GetImportedFromSourceLocation.
89         llvm::Optional<std::pair<SourceLocation, ASTUnit *>> SLocResult =
90             CTU.getImportedFromSourceLocation(NewFD->getLocation());
91         EXPECT_TRUE(SLocResult);
92         if (SLocResult) {
93           SourceLocation OrigSLoc = (*SLocResult).first;
94           ASTUnit *OrigUnit = (*SLocResult).second;
95           // OrigUnit is created internally by CTU (is not the
96           // ASTWithDefinition).
97           TranslationUnitDecl *OrigTU =
98               OrigUnit->getASTContext().getTranslationUnitDecl();
99           const FunctionDecl *FDWithDefinition = FindFInTU(OrigTU);
100           EXPECT_TRUE(FDWithDefinition);
101           if (FDWithDefinition) {
102             EXPECT_EQ(FDWithDefinition->getName(), "f");
103             EXPECT_TRUE(FDWithDefinition->isThisDeclarationADefinition());
104             EXPECT_EQ(OrigSLoc, FDWithDefinition->getLocation());
105           }
106         }
107       }
108     }
109   }
110 
111 private:
112   CrossTranslationUnitContext CTU;
113   bool *Success;
114 };
115 
116 class CTUAction : public clang::ASTFrontendAction {
117 public:
118   CTUAction(bool *Success, unsigned OverrideLimit)
119       : Success(Success), OverrideLimit(OverrideLimit) {}
120 
121 protected:
122   std::unique_ptr<clang::ASTConsumer>
123   CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override {
124     CI.getAnalyzerOpts()->CTUImportThreshold = OverrideLimit;
125     return std::make_unique<CTUASTConsumer>(CI, Success);
126   }
127 
128 private:
129   bool *Success;
130   const unsigned OverrideLimit;
131 };
132 
133 } // end namespace
134 
135 TEST(CrossTranslationUnit, CanLoadFunctionDefinition) {
136   bool Success = false;
137   EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 1u),
138                                      "int f(int);"));
139   EXPECT_TRUE(Success);
140 }
141 
142 TEST(CrossTranslationUnit, RespectsLoadThreshold) {
143   bool Success = false;
144   EXPECT_TRUE(tooling::runToolOnCode(std::make_unique<CTUAction>(&Success, 0u),
145                                      "int f(int);"));
146   EXPECT_FALSE(Success);
147 }
148 
149 TEST(CrossTranslationUnit, IndexFormatCanBeParsed) {
150   llvm::StringMap<std::string> Index;
151   Index["a"] = "/b/f1";
152   Index["c"] = "/d/f2";
153   Index["e"] = "/f/f3";
154   std::string IndexText = createCrossTUIndexString(Index);
155 
156   int IndexFD;
157   llvm::SmallString<256> IndexFileName;
158   ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
159                                                   IndexFileName));
160   llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD);
161   IndexFile.os() << IndexText;
162   IndexFile.os().flush();
163   EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));
164   llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
165       parseCrossTUIndex(IndexFileName, "");
166   EXPECT_TRUE((bool)IndexOrErr);
167   llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get();
168   for (const auto &E : Index) {
169     EXPECT_TRUE(ParsedIndex.count(E.getKey()));
170     EXPECT_EQ(ParsedIndex[E.getKey()], E.getValue());
171   }
172   for (const auto &E : ParsedIndex)
173     EXPECT_TRUE(Index.count(E.getKey()));
174 }
175 
176 TEST(CrossTranslationUnit, CTUDirIsHandledCorrectly) {
177   llvm::StringMap<std::string> Index;
178   Index["a"] = "/b/c/d";
179   std::string IndexText = createCrossTUIndexString(Index);
180 
181   int IndexFD;
182   llvm::SmallString<256> IndexFileName;
183   ASSERT_FALSE(llvm::sys::fs::createTemporaryFile("index", "txt", IndexFD,
184                                                   IndexFileName));
185   llvm::ToolOutputFile IndexFile(IndexFileName, IndexFD);
186   IndexFile.os() << IndexText;
187   IndexFile.os().flush();
188   EXPECT_TRUE(llvm::sys::fs::exists(IndexFileName));
189   llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
190       parseCrossTUIndex(IndexFileName, "/ctudir");
191   EXPECT_TRUE((bool)IndexOrErr);
192   llvm::StringMap<std::string> ParsedIndex = IndexOrErr.get();
193   EXPECT_EQ(ParsedIndex["a"], "/ctudir/b/c/d");
194 }
195 
196 } // end namespace cross_tu
197 } // end namespace clang
198