1 //===--- CrossTranslationUnit.cpp - -----------------------------*- C++ -*-===//
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 //  This file implements the CrossTranslationUnit interface.
10 //
11 //===----------------------------------------------------------------------===//
12 #include "clang/CrossTU/CrossTranslationUnit.h"
13 #include "clang/AST/ASTImporter.h"
14 #include "clang/AST/Decl.h"
15 #include "clang/Basic/TargetInfo.h"
16 #include "clang/CrossTU/CrossTUDiagnostic.h"
17 #include "clang/Frontend/ASTUnit.h"
18 #include "clang/Frontend/CompilerInstance.h"
19 #include "clang/Frontend/TextDiagnosticPrinter.h"
20 #include "clang/Index/USRGeneration.h"
21 #include "llvm/ADT/Triple.h"
22 #include "llvm/ADT/Statistic.h"
23 #include "llvm/Support/ErrorHandling.h"
24 #include "llvm/Support/ManagedStatic.h"
25 #include "llvm/Support/Path.h"
26 #include "llvm/Support/raw_ostream.h"
27 #include <fstream>
28 #include <sstream>
29 
30 namespace clang {
31 namespace cross_tu {
32 
33 namespace {
34 
35 #define DEBUG_TYPE "CrossTranslationUnit"
36 STATISTIC(NumGetCTUCalled, "The # of getCTUDefinition function called");
37 STATISTIC(
38     NumNotInOtherTU,
39     "The # of getCTUDefinition called but the function is not in any other TU");
40 STATISTIC(NumGetCTUSuccess,
41           "The # of getCTUDefinition successfully returned the "
42           "requested function's body");
43 STATISTIC(NumTripleMismatch, "The # of triple mismatches");
44 STATISTIC(NumLangMismatch, "The # of language mismatches");
45 STATISTIC(NumLangDialectMismatch, "The # of language dialect mismatches");
46 
47 // Same as Triple's equality operator, but we check a field only if that is
48 // known in both instances.
49 bool hasEqualKnownFields(const llvm::Triple &Lhs, const llvm::Triple &Rhs) {
50   using llvm::Triple;
51   if (Lhs.getArch() != Triple::UnknownArch &&
52       Rhs.getArch() != Triple::UnknownArch && Lhs.getArch() != Rhs.getArch())
53     return false;
54   if (Lhs.getSubArch() != Triple::NoSubArch &&
55       Rhs.getSubArch() != Triple::NoSubArch &&
56       Lhs.getSubArch() != Rhs.getSubArch())
57     return false;
58   if (Lhs.getVendor() != Triple::UnknownVendor &&
59       Rhs.getVendor() != Triple::UnknownVendor &&
60       Lhs.getVendor() != Rhs.getVendor())
61     return false;
62   if (!Lhs.isOSUnknown() && !Rhs.isOSUnknown() &&
63       Lhs.getOS() != Rhs.getOS())
64     return false;
65   if (Lhs.getEnvironment() != Triple::UnknownEnvironment &&
66       Rhs.getEnvironment() != Triple::UnknownEnvironment &&
67       Lhs.getEnvironment() != Rhs.getEnvironment())
68     return false;
69   if (Lhs.getObjectFormat() != Triple::UnknownObjectFormat &&
70       Rhs.getObjectFormat() != Triple::UnknownObjectFormat &&
71       Lhs.getObjectFormat() != Rhs.getObjectFormat())
72     return false;
73   return true;
74 }
75 
76 // FIXME: This class is will be removed after the transition to llvm::Error.
77 class IndexErrorCategory : public std::error_category {
78 public:
79   const char *name() const noexcept override { return "clang.index"; }
80 
81   std::string message(int Condition) const override {
82     switch (static_cast<index_error_code>(Condition)) {
83     case index_error_code::unspecified:
84       return "An unknown error has occurred.";
85     case index_error_code::missing_index_file:
86       return "The index file is missing.";
87     case index_error_code::invalid_index_format:
88       return "Invalid index file format.";
89     case index_error_code::multiple_definitions:
90       return "Multiple definitions in the index file.";
91     case index_error_code::missing_definition:
92       return "Missing definition from the index file.";
93     case index_error_code::failed_import:
94       return "Failed to import the definition.";
95     case index_error_code::failed_to_get_external_ast:
96       return "Failed to load external AST source.";
97     case index_error_code::failed_to_generate_usr:
98       return "Failed to generate USR.";
99     case index_error_code::triple_mismatch:
100       return "Triple mismatch";
101     case index_error_code::lang_mismatch:
102       return "Language mismatch";
103     case index_error_code::lang_dialect_mismatch:
104       return "Language dialect mismatch";
105     }
106     llvm_unreachable("Unrecognized index_error_code.");
107   }
108 };
109 
110 static llvm::ManagedStatic<IndexErrorCategory> Category;
111 } // end anonymous namespace
112 
113 char IndexError::ID;
114 
115 void IndexError::log(raw_ostream &OS) const {
116   OS << Category->message(static_cast<int>(Code)) << '\n';
117 }
118 
119 std::error_code IndexError::convertToErrorCode() const {
120   return std::error_code(static_cast<int>(Code), *Category);
121 }
122 
123 llvm::Expected<llvm::StringMap<std::string>>
124 parseCrossTUIndex(StringRef IndexPath, StringRef CrossTUDir) {
125   std::ifstream ExternalMapFile(IndexPath);
126   if (!ExternalMapFile)
127     return llvm::make_error<IndexError>(index_error_code::missing_index_file,
128                                         IndexPath.str());
129 
130   llvm::StringMap<std::string> Result;
131   std::string Line;
132   unsigned LineNo = 1;
133   while (std::getline(ExternalMapFile, Line)) {
134     const size_t Pos = Line.find(" ");
135     if (Pos > 0 && Pos != std::string::npos) {
136       StringRef LineRef{Line};
137       StringRef LookupName = LineRef.substr(0, Pos);
138       if (Result.count(LookupName))
139         return llvm::make_error<IndexError>(
140             index_error_code::multiple_definitions, IndexPath.str(), LineNo);
141       StringRef FileName = LineRef.substr(Pos + 1);
142       SmallString<256> FilePath = CrossTUDir;
143       llvm::sys::path::append(FilePath, FileName);
144       Result[LookupName] = FilePath.str().str();
145     } else
146       return llvm::make_error<IndexError>(
147           index_error_code::invalid_index_format, IndexPath.str(), LineNo);
148     LineNo++;
149   }
150   return Result;
151 }
152 
153 std::string
154 createCrossTUIndexString(const llvm::StringMap<std::string> &Index) {
155   std::ostringstream Result;
156   for (const auto &E : Index)
157     Result << E.getKey().str() << " " << E.getValue() << '\n';
158   return Result.str();
159 }
160 
161 CrossTranslationUnitContext::CrossTranslationUnitContext(CompilerInstance &CI)
162     : CI(CI), Context(CI.getASTContext()) {}
163 
164 CrossTranslationUnitContext::~CrossTranslationUnitContext() {}
165 
166 std::string CrossTranslationUnitContext::getLookupName(const NamedDecl *ND) {
167   SmallString<128> DeclUSR;
168   bool Ret = index::generateUSRForDecl(ND, DeclUSR); (void)Ret;
169   assert(!Ret && "Unable to generate USR");
170   return DeclUSR.str();
171 }
172 
173 /// Recursively visits the function decls of a DeclContext, and looks up a
174 /// function based on USRs.
175 const FunctionDecl *
176 CrossTranslationUnitContext::findFunctionInDeclContext(const DeclContext *DC,
177                                                        StringRef LookupFnName) {
178   assert(DC && "Declaration Context must not be null");
179   for (const Decl *D : DC->decls()) {
180     const auto *SubDC = dyn_cast<DeclContext>(D);
181     if (SubDC)
182       if (const auto *FD = findFunctionInDeclContext(SubDC, LookupFnName))
183         return FD;
184 
185     const auto *ND = dyn_cast<FunctionDecl>(D);
186     const FunctionDecl *ResultDecl;
187     if (!ND || !ND->hasBody(ResultDecl))
188       continue;
189     if (getLookupName(ResultDecl) != LookupFnName)
190       continue;
191     return ResultDecl;
192   }
193   return nullptr;
194 }
195 
196 llvm::Expected<const FunctionDecl *>
197 CrossTranslationUnitContext::getCrossTUDefinition(const FunctionDecl *FD,
198                                                   StringRef CrossTUDir,
199                                                   StringRef IndexName,
200                                                   bool DisplayCTUProgress) {
201   assert(FD && "FD is missing, bad call to this function!");
202   assert(!FD->hasBody() && "FD has a definition in current translation unit!");
203   ++NumGetCTUCalled;
204   const std::string LookupFnName = getLookupName(FD);
205   if (LookupFnName.empty())
206     return llvm::make_error<IndexError>(
207         index_error_code::failed_to_generate_usr);
208   llvm::Expected<ASTUnit *> ASTUnitOrError =
209       loadExternalAST(LookupFnName, CrossTUDir, IndexName, DisplayCTUProgress);
210   if (!ASTUnitOrError)
211     return ASTUnitOrError.takeError();
212   ASTUnit *Unit = *ASTUnitOrError;
213   assert(&Unit->getFileManager() ==
214          &Unit->getASTContext().getSourceManager().getFileManager());
215 
216   const llvm::Triple &TripleTo = Context.getTargetInfo().getTriple();
217   const llvm::Triple &TripleFrom =
218       Unit->getASTContext().getTargetInfo().getTriple();
219   // The imported AST had been generated for a different target.
220   // Some parts of the triple in the loaded ASTContext can be unknown while the
221   // very same parts in the target ASTContext are known. Thus we check for the
222   // known parts only.
223   if (!hasEqualKnownFields(TripleTo, TripleFrom)) {
224     // TODO: Pass the SourceLocation of the CallExpression for more precise
225     // diagnostics.
226     ++NumTripleMismatch;
227     return llvm::make_error<IndexError>(index_error_code::triple_mismatch,
228                                         Unit->getMainFileName(), TripleTo.str(),
229                                         TripleFrom.str());
230   }
231 
232   const auto &LangTo = Context.getLangOpts();
233   const auto &LangFrom = Unit->getASTContext().getLangOpts();
234 
235   // FIXME: Currenty we do not support CTU across C++ and C and across
236   // different dialects of C++.
237   if (LangTo.CPlusPlus != LangFrom.CPlusPlus) {
238     ++NumLangMismatch;
239     return llvm::make_error<IndexError>(index_error_code::lang_mismatch);
240   }
241 
242   // If CPP dialects are different then return with error.
243   //
244   // Consider this STL code:
245   //   template<typename _Alloc>
246   //     struct __alloc_traits
247   //   #if __cplusplus >= 201103L
248   //     : std::allocator_traits<_Alloc>
249   //   #endif
250   //     { // ...
251   //     };
252   // This class template would create ODR errors during merging the two units,
253   // since in one translation unit the class template has a base class, however
254   // in the other unit it has none.
255   if (LangTo.CPlusPlus11 != LangFrom.CPlusPlus11 ||
256       LangTo.CPlusPlus14 != LangFrom.CPlusPlus14 ||
257       LangTo.CPlusPlus17 != LangFrom.CPlusPlus17 ||
258       LangTo.CPlusPlus2a != LangFrom.CPlusPlus2a) {
259     ++NumLangDialectMismatch;
260     return llvm::make_error<IndexError>(
261         index_error_code::lang_dialect_mismatch);
262   }
263 
264   TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl();
265   if (const FunctionDecl *ResultDecl =
266           findFunctionInDeclContext(TU, LookupFnName))
267     return importDefinition(ResultDecl);
268   return llvm::make_error<IndexError>(index_error_code::failed_import);
269 }
270 
271 void CrossTranslationUnitContext::emitCrossTUDiagnostics(const IndexError &IE) {
272   switch (IE.getCode()) {
273   case index_error_code::missing_index_file:
274     Context.getDiagnostics().Report(diag::err_ctu_error_opening)
275         << IE.getFileName();
276     break;
277   case index_error_code::invalid_index_format:
278     Context.getDiagnostics().Report(diag::err_extdefmap_parsing)
279         << IE.getFileName() << IE.getLineNum();
280     break;
281   case index_error_code::multiple_definitions:
282     Context.getDiagnostics().Report(diag::err_multiple_def_index)
283         << IE.getLineNum();
284     break;
285   case index_error_code::triple_mismatch:
286     Context.getDiagnostics().Report(diag::warn_ctu_incompat_triple)
287         << IE.getFileName() << IE.getTripleToName() << IE.getTripleFromName();
288     break;
289   default:
290     break;
291   }
292 }
293 
294 llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
295     StringRef LookupName, StringRef CrossTUDir, StringRef IndexName,
296     bool DisplayCTUProgress) {
297   // FIXME: The current implementation only supports loading functions with
298   //        a lookup name from a single translation unit. If multiple
299   //        translation units contains functions with the same lookup name an
300   //        error will be returned.
301   ASTUnit *Unit = nullptr;
302   auto FnUnitCacheEntry = FunctionASTUnitMap.find(LookupName);
303   if (FnUnitCacheEntry == FunctionASTUnitMap.end()) {
304     if (FunctionFileMap.empty()) {
305       SmallString<256> IndexFile = CrossTUDir;
306       if (llvm::sys::path::is_absolute(IndexName))
307         IndexFile = IndexName;
308       else
309         llvm::sys::path::append(IndexFile, IndexName);
310       llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
311           parseCrossTUIndex(IndexFile, CrossTUDir);
312       if (IndexOrErr)
313         FunctionFileMap = *IndexOrErr;
314       else
315         return IndexOrErr.takeError();
316     }
317 
318     auto It = FunctionFileMap.find(LookupName);
319     if (It == FunctionFileMap.end()) {
320       ++NumNotInOtherTU;
321       return llvm::make_error<IndexError>(index_error_code::missing_definition);
322     }
323     StringRef ASTFileName = It->second;
324     auto ASTCacheEntry = FileASTUnitMap.find(ASTFileName);
325     if (ASTCacheEntry == FileASTUnitMap.end()) {
326       IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
327       TextDiagnosticPrinter *DiagClient =
328           new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
329       IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
330       IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
331           new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient));
332 
333       std::unique_ptr<ASTUnit> LoadedUnit(ASTUnit::LoadFromASTFile(
334           ASTFileName, CI.getPCHContainerOperations()->getRawReader(),
335           ASTUnit::LoadEverything, Diags, CI.getFileSystemOpts()));
336       Unit = LoadedUnit.get();
337       FileASTUnitMap[ASTFileName] = std::move(LoadedUnit);
338       if (DisplayCTUProgress) {
339         llvm::errs() << "CTU loaded AST file: "
340                      << ASTFileName << "\n";
341       }
342     } else {
343       Unit = ASTCacheEntry->second.get();
344     }
345     FunctionASTUnitMap[LookupName] = Unit;
346   } else {
347     Unit = FnUnitCacheEntry->second;
348   }
349   if (!Unit)
350     return llvm::make_error<IndexError>(
351         index_error_code::failed_to_get_external_ast);
352   return Unit;
353 }
354 
355 llvm::Expected<const FunctionDecl *>
356 CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD) {
357   assert(FD->hasBody() && "Functions to be imported should have body.");
358 
359   ASTImporter &Importer = getOrCreateASTImporter(FD->getASTContext());
360   auto ToDeclOrError = Importer.Import_New(FD);
361   if (!ToDeclOrError) {
362     handleAllErrors(ToDeclOrError.takeError(),
363                     [&](const ImportError &IE) {
364                       switch (IE.Error) {
365                       case ImportError::NameConflict:
366                         // FIXME: Add statistic.
367                          break;
368                       case ImportError::UnsupportedConstruct:
369                         // FIXME: Add statistic.
370                         break;
371                       case ImportError::Unknown:
372                         llvm_unreachable("Unknown import error happened.");
373                         break;
374                       }
375                     });
376     return llvm::make_error<IndexError>(index_error_code::failed_import);
377   }
378   auto *ToDecl = cast<FunctionDecl>(*ToDeclOrError);
379   assert(ToDecl->hasBody() && "Imported function should have body.");
380   ++NumGetCTUSuccess;
381 
382   return ToDecl;
383 }
384 
385 void CrossTranslationUnitContext::lazyInitLookupTable(
386     TranslationUnitDecl *ToTU) {
387   if (!LookupTable)
388     LookupTable = llvm::make_unique<ASTImporterLookupTable>(*ToTU);
389 }
390 
391 ASTImporter &
392 CrossTranslationUnitContext::getOrCreateASTImporter(ASTContext &From) {
393   auto I = ASTUnitImporterMap.find(From.getTranslationUnitDecl());
394   if (I != ASTUnitImporterMap.end())
395     return *I->second;
396   lazyInitLookupTable(Context.getTranslationUnitDecl());
397   ASTImporter *NewImporter = new ASTImporter(
398       Context, Context.getSourceManager().getFileManager(), From,
399       From.getSourceManager().getFileManager(), false, LookupTable.get());
400   ASTUnitImporterMap[From.getTranslationUnitDecl()].reset(NewImporter);
401   return *NewImporter;
402 }
403 
404 } // namespace cross_tu
405 } // namespace clang
406