xref: /freebsd/contrib/llvm-project/clang/lib/CrossTU/CrossTranslationUnit.cpp (revision f2530c80db7b29b95368fce956b3a778f096b368)
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(NumUnsupportedNodeFound, "The # of imports when the ASTImporter "
44                                    "encountered an unsupported AST Node");
45 STATISTIC(NumNameConflicts, "The # of imports when the ASTImporter "
46                             "encountered an ODR error");
47 STATISTIC(NumTripleMismatch, "The # of triple mismatches");
48 STATISTIC(NumLangMismatch, "The # of language mismatches");
49 STATISTIC(NumLangDialectMismatch, "The # of language dialect mismatches");
50 STATISTIC(NumASTLoadThresholdReached,
51           "The # of ASTs not loaded because of threshold");
52 
53 // Same as Triple's equality operator, but we check a field only if that is
54 // known in both instances.
55 bool hasEqualKnownFields(const llvm::Triple &Lhs, const llvm::Triple &Rhs) {
56   using llvm::Triple;
57   if (Lhs.getArch() != Triple::UnknownArch &&
58       Rhs.getArch() != Triple::UnknownArch && Lhs.getArch() != Rhs.getArch())
59     return false;
60   if (Lhs.getSubArch() != Triple::NoSubArch &&
61       Rhs.getSubArch() != Triple::NoSubArch &&
62       Lhs.getSubArch() != Rhs.getSubArch())
63     return false;
64   if (Lhs.getVendor() != Triple::UnknownVendor &&
65       Rhs.getVendor() != Triple::UnknownVendor &&
66       Lhs.getVendor() != Rhs.getVendor())
67     return false;
68   if (!Lhs.isOSUnknown() && !Rhs.isOSUnknown() &&
69       Lhs.getOS() != Rhs.getOS())
70     return false;
71   if (Lhs.getEnvironment() != Triple::UnknownEnvironment &&
72       Rhs.getEnvironment() != Triple::UnknownEnvironment &&
73       Lhs.getEnvironment() != Rhs.getEnvironment())
74     return false;
75   if (Lhs.getObjectFormat() != Triple::UnknownObjectFormat &&
76       Rhs.getObjectFormat() != Triple::UnknownObjectFormat &&
77       Lhs.getObjectFormat() != Rhs.getObjectFormat())
78     return false;
79   return true;
80 }
81 
82 // FIXME: This class is will be removed after the transition to llvm::Error.
83 class IndexErrorCategory : public std::error_category {
84 public:
85   const char *name() const noexcept override { return "clang.index"; }
86 
87   std::string message(int Condition) const override {
88     switch (static_cast<index_error_code>(Condition)) {
89     case index_error_code::unspecified:
90       return "An unknown error has occurred.";
91     case index_error_code::missing_index_file:
92       return "The index file is missing.";
93     case index_error_code::invalid_index_format:
94       return "Invalid index file format.";
95     case index_error_code::multiple_definitions:
96       return "Multiple definitions in the index file.";
97     case index_error_code::missing_definition:
98       return "Missing definition from the index file.";
99     case index_error_code::failed_import:
100       return "Failed to import the definition.";
101     case index_error_code::failed_to_get_external_ast:
102       return "Failed to load external AST source.";
103     case index_error_code::failed_to_generate_usr:
104       return "Failed to generate USR.";
105     case index_error_code::triple_mismatch:
106       return "Triple mismatch";
107     case index_error_code::lang_mismatch:
108       return "Language mismatch";
109     case index_error_code::lang_dialect_mismatch:
110       return "Language dialect mismatch";
111     case index_error_code::load_threshold_reached:
112       return "Load threshold reached";
113     }
114     llvm_unreachable("Unrecognized index_error_code.");
115   }
116 };
117 
118 static llvm::ManagedStatic<IndexErrorCategory> Category;
119 } // end anonymous namespace
120 
121 char IndexError::ID;
122 
123 void IndexError::log(raw_ostream &OS) const {
124   OS << Category->message(static_cast<int>(Code)) << '\n';
125 }
126 
127 std::error_code IndexError::convertToErrorCode() const {
128   return std::error_code(static_cast<int>(Code), *Category);
129 }
130 
131 llvm::Expected<llvm::StringMap<std::string>>
132 parseCrossTUIndex(StringRef IndexPath, StringRef CrossTUDir) {
133   std::ifstream ExternalMapFile(IndexPath);
134   if (!ExternalMapFile)
135     return llvm::make_error<IndexError>(index_error_code::missing_index_file,
136                                         IndexPath.str());
137 
138   llvm::StringMap<std::string> Result;
139   std::string Line;
140   unsigned LineNo = 1;
141   while (std::getline(ExternalMapFile, Line)) {
142     const size_t Pos = Line.find(" ");
143     if (Pos > 0 && Pos != std::string::npos) {
144       StringRef LineRef{Line};
145       StringRef LookupName = LineRef.substr(0, Pos);
146       if (Result.count(LookupName))
147         return llvm::make_error<IndexError>(
148             index_error_code::multiple_definitions, IndexPath.str(), LineNo);
149       StringRef FileName = LineRef.substr(Pos + 1);
150       SmallString<256> FilePath = CrossTUDir;
151       llvm::sys::path::append(FilePath, FileName);
152       Result[LookupName] = FilePath.str().str();
153     } else
154       return llvm::make_error<IndexError>(
155           index_error_code::invalid_index_format, IndexPath.str(), LineNo);
156     LineNo++;
157   }
158   return Result;
159 }
160 
161 std::string
162 createCrossTUIndexString(const llvm::StringMap<std::string> &Index) {
163   std::ostringstream Result;
164   for (const auto &E : Index)
165     Result << E.getKey().str() << " " << E.getValue() << '\n';
166   return Result.str();
167 }
168 
169 bool containsConst(const VarDecl *VD, const ASTContext &ACtx) {
170   CanQualType CT = ACtx.getCanonicalType(VD->getType());
171   if (!CT.isConstQualified()) {
172     const RecordType *RTy = CT->getAs<RecordType>();
173     if (!RTy || !RTy->hasConstFields())
174       return false;
175   }
176   return true;
177 }
178 
179 static bool hasBodyOrInit(const FunctionDecl *D, const FunctionDecl *&DefD) {
180   return D->hasBody(DefD);
181 }
182 static bool hasBodyOrInit(const VarDecl *D, const VarDecl *&DefD) {
183   return D->getAnyInitializer(DefD);
184 }
185 template <typename T> static bool hasBodyOrInit(const T *D) {
186   const T *Unused;
187   return hasBodyOrInit(D, Unused);
188 }
189 
190 CrossTranslationUnitContext::CrossTranslationUnitContext(CompilerInstance &CI)
191     : CI(CI), Context(CI.getASTContext()),
192       CTULoadThreshold(CI.getAnalyzerOpts()->CTUImportThreshold) {}
193 
194 CrossTranslationUnitContext::~CrossTranslationUnitContext() {}
195 
196 std::string CrossTranslationUnitContext::getLookupName(const NamedDecl *ND) {
197   SmallString<128> DeclUSR;
198   bool Ret = index::generateUSRForDecl(ND, DeclUSR);
199   (void)Ret;
200   assert(!Ret && "Unable to generate USR");
201   return DeclUSR.str();
202 }
203 
204 /// Recursively visits the decls of a DeclContext, and returns one with the
205 /// given USR.
206 template <typename T>
207 const T *
208 CrossTranslationUnitContext::findDefInDeclContext(const DeclContext *DC,
209                                                   StringRef LookupName) {
210   assert(DC && "Declaration Context must not be null");
211   for (const Decl *D : DC->decls()) {
212     const auto *SubDC = dyn_cast<DeclContext>(D);
213     if (SubDC)
214       if (const auto *ND = findDefInDeclContext<T>(SubDC, LookupName))
215         return ND;
216 
217     const auto *ND = dyn_cast<T>(D);
218     const T *ResultDecl;
219     if (!ND || !hasBodyOrInit(ND, ResultDecl))
220       continue;
221     if (getLookupName(ResultDecl) != LookupName)
222       continue;
223     return ResultDecl;
224   }
225   return nullptr;
226 }
227 
228 template <typename T>
229 llvm::Expected<const T *> CrossTranslationUnitContext::getCrossTUDefinitionImpl(
230     const T *D, StringRef CrossTUDir, StringRef IndexName,
231     bool DisplayCTUProgress) {
232   assert(D && "D is missing, bad call to this function!");
233   assert(!hasBodyOrInit(D) &&
234          "D has a body or init in current translation unit!");
235   ++NumGetCTUCalled;
236   const std::string LookupName = getLookupName(D);
237   if (LookupName.empty())
238     return llvm::make_error<IndexError>(
239         index_error_code::failed_to_generate_usr);
240   llvm::Expected<ASTUnit *> ASTUnitOrError = loadExternalAST(
241       LookupName, CrossTUDir, IndexName, DisplayCTUProgress);
242   if (!ASTUnitOrError)
243     return ASTUnitOrError.takeError();
244   ASTUnit *Unit = *ASTUnitOrError;
245   assert(&Unit->getFileManager() ==
246          &Unit->getASTContext().getSourceManager().getFileManager());
247 
248   const llvm::Triple &TripleTo = Context.getTargetInfo().getTriple();
249   const llvm::Triple &TripleFrom =
250       Unit->getASTContext().getTargetInfo().getTriple();
251   // The imported AST had been generated for a different target.
252   // Some parts of the triple in the loaded ASTContext can be unknown while the
253   // very same parts in the target ASTContext are known. Thus we check for the
254   // known parts only.
255   if (!hasEqualKnownFields(TripleTo, TripleFrom)) {
256     // TODO: Pass the SourceLocation of the CallExpression for more precise
257     // diagnostics.
258     ++NumTripleMismatch;
259     return llvm::make_error<IndexError>(index_error_code::triple_mismatch,
260                                         Unit->getMainFileName(), TripleTo.str(),
261                                         TripleFrom.str());
262   }
263 
264   const auto &LangTo = Context.getLangOpts();
265   const auto &LangFrom = Unit->getASTContext().getLangOpts();
266 
267   // FIXME: Currenty we do not support CTU across C++ and C and across
268   // different dialects of C++.
269   if (LangTo.CPlusPlus != LangFrom.CPlusPlus) {
270     ++NumLangMismatch;
271     return llvm::make_error<IndexError>(index_error_code::lang_mismatch);
272   }
273 
274   // If CPP dialects are different then return with error.
275   //
276   // Consider this STL code:
277   //   template<typename _Alloc>
278   //     struct __alloc_traits
279   //   #if __cplusplus >= 201103L
280   //     : std::allocator_traits<_Alloc>
281   //   #endif
282   //     { // ...
283   //     };
284   // This class template would create ODR errors during merging the two units,
285   // since in one translation unit the class template has a base class, however
286   // in the other unit it has none.
287   if (LangTo.CPlusPlus11 != LangFrom.CPlusPlus11 ||
288       LangTo.CPlusPlus14 != LangFrom.CPlusPlus14 ||
289       LangTo.CPlusPlus17 != LangFrom.CPlusPlus17 ||
290       LangTo.CPlusPlus2a != LangFrom.CPlusPlus2a) {
291     ++NumLangDialectMismatch;
292     return llvm::make_error<IndexError>(
293         index_error_code::lang_dialect_mismatch);
294   }
295 
296   TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl();
297   if (const T *ResultDecl = findDefInDeclContext<T>(TU, LookupName))
298     return importDefinition(ResultDecl);
299   return llvm::make_error<IndexError>(index_error_code::failed_import);
300 }
301 
302 llvm::Expected<const FunctionDecl *>
303 CrossTranslationUnitContext::getCrossTUDefinition(const FunctionDecl *FD,
304                                                   StringRef CrossTUDir,
305                                                   StringRef IndexName,
306                                                   bool DisplayCTUProgress) {
307   return getCrossTUDefinitionImpl(FD, CrossTUDir, IndexName,
308                                   DisplayCTUProgress);
309 }
310 
311 llvm::Expected<const VarDecl *>
312 CrossTranslationUnitContext::getCrossTUDefinition(const VarDecl *VD,
313                                                   StringRef CrossTUDir,
314                                                   StringRef IndexName,
315                                                   bool DisplayCTUProgress) {
316   return getCrossTUDefinitionImpl(VD, CrossTUDir, IndexName,
317                                   DisplayCTUProgress);
318 }
319 
320 void CrossTranslationUnitContext::emitCrossTUDiagnostics(const IndexError &IE) {
321   switch (IE.getCode()) {
322   case index_error_code::missing_index_file:
323     Context.getDiagnostics().Report(diag::err_ctu_error_opening)
324         << IE.getFileName();
325     break;
326   case index_error_code::invalid_index_format:
327     Context.getDiagnostics().Report(diag::err_extdefmap_parsing)
328         << IE.getFileName() << IE.getLineNum();
329     break;
330   case index_error_code::multiple_definitions:
331     Context.getDiagnostics().Report(diag::err_multiple_def_index)
332         << IE.getLineNum();
333     break;
334   case index_error_code::triple_mismatch:
335     Context.getDiagnostics().Report(diag::warn_ctu_incompat_triple)
336         << IE.getFileName() << IE.getTripleToName() << IE.getTripleFromName();
337     break;
338   default:
339     break;
340   }
341 }
342 
343 llvm::Expected<ASTUnit *> CrossTranslationUnitContext::loadExternalAST(
344     StringRef LookupName, StringRef CrossTUDir, StringRef IndexName,
345     bool DisplayCTUProgress) {
346   // FIXME: The current implementation only supports loading decls with
347   //        a lookup name from a single translation unit. If multiple
348   //        translation units contains decls with the same lookup name an
349   //        error will be returned.
350 
351   if (NumASTLoaded >= CTULoadThreshold) {
352     ++NumASTLoadThresholdReached;
353     return llvm::make_error<IndexError>(
354         index_error_code::load_threshold_reached);
355   }
356 
357   ASTUnit *Unit = nullptr;
358   auto NameUnitCacheEntry = NameASTUnitMap.find(LookupName);
359   if (NameUnitCacheEntry == NameASTUnitMap.end()) {
360     if (NameFileMap.empty()) {
361       SmallString<256> IndexFile = CrossTUDir;
362       if (llvm::sys::path::is_absolute(IndexName))
363         IndexFile = IndexName;
364       else
365         llvm::sys::path::append(IndexFile, IndexName);
366       llvm::Expected<llvm::StringMap<std::string>> IndexOrErr =
367           parseCrossTUIndex(IndexFile, CrossTUDir);
368       if (IndexOrErr)
369         NameFileMap = *IndexOrErr;
370       else
371         return IndexOrErr.takeError();
372     }
373 
374     auto It = NameFileMap.find(LookupName);
375     if (It == NameFileMap.end()) {
376       ++NumNotInOtherTU;
377       return llvm::make_error<IndexError>(index_error_code::missing_definition);
378     }
379     StringRef ASTFileName = It->second;
380     auto ASTCacheEntry = FileASTUnitMap.find(ASTFileName);
381     if (ASTCacheEntry == FileASTUnitMap.end()) {
382       IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
383       TextDiagnosticPrinter *DiagClient =
384           new TextDiagnosticPrinter(llvm::errs(), &*DiagOpts);
385       IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
386       IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
387           new DiagnosticsEngine(DiagID, &*DiagOpts, DiagClient));
388 
389       std::unique_ptr<ASTUnit> LoadedUnit(ASTUnit::LoadFromASTFile(
390           ASTFileName, CI.getPCHContainerOperations()->getRawReader(),
391           ASTUnit::LoadEverything, Diags, CI.getFileSystemOpts()));
392       Unit = LoadedUnit.get();
393       FileASTUnitMap[ASTFileName] = std::move(LoadedUnit);
394       ++NumASTLoaded;
395       if (DisplayCTUProgress) {
396         llvm::errs() << "CTU loaded AST file: "
397                      << ASTFileName << "\n";
398       }
399     } else {
400       Unit = ASTCacheEntry->second.get();
401     }
402     NameASTUnitMap[LookupName] = Unit;
403   } else {
404     Unit = NameUnitCacheEntry->second;
405   }
406   if (!Unit)
407     return llvm::make_error<IndexError>(
408         index_error_code::failed_to_get_external_ast);
409   return Unit;
410 }
411 
412 template <typename T>
413 llvm::Expected<const T *>
414 CrossTranslationUnitContext::importDefinitionImpl(const T *D) {
415   assert(hasBodyOrInit(D) && "Decls to be imported should have body or init.");
416 
417   ASTImporter &Importer = getOrCreateASTImporter(D->getASTContext());
418   auto ToDeclOrError = Importer.Import(D);
419   if (!ToDeclOrError) {
420     handleAllErrors(ToDeclOrError.takeError(),
421                     [&](const ImportError &IE) {
422                       switch (IE.Error) {
423                       case ImportError::NameConflict:
424                         ++NumNameConflicts;
425                          break;
426                       case ImportError::UnsupportedConstruct:
427                         ++NumUnsupportedNodeFound;
428                         break;
429                       case ImportError::Unknown:
430                         llvm_unreachable("Unknown import error happened.");
431                         break;
432                       }
433                     });
434     return llvm::make_error<IndexError>(index_error_code::failed_import);
435   }
436   auto *ToDecl = cast<T>(*ToDeclOrError);
437   assert(hasBodyOrInit(ToDecl) && "Imported Decl should have body or init.");
438   ++NumGetCTUSuccess;
439 
440   return ToDecl;
441 }
442 
443 llvm::Expected<const FunctionDecl *>
444 CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD) {
445   return importDefinitionImpl(FD);
446 }
447 
448 llvm::Expected<const VarDecl *>
449 CrossTranslationUnitContext::importDefinition(const VarDecl *VD) {
450   return importDefinitionImpl(VD);
451 }
452 
453 void CrossTranslationUnitContext::lazyInitImporterSharedSt(
454     TranslationUnitDecl *ToTU) {
455   if (!ImporterSharedSt)
456     ImporterSharedSt = std::make_shared<ASTImporterSharedState>(*ToTU);
457 }
458 
459 ASTImporter &
460 CrossTranslationUnitContext::getOrCreateASTImporter(ASTContext &From) {
461   auto I = ASTUnitImporterMap.find(From.getTranslationUnitDecl());
462   if (I != ASTUnitImporterMap.end())
463     return *I->second;
464   lazyInitImporterSharedSt(Context.getTranslationUnitDecl());
465   ASTImporter *NewImporter = new ASTImporter(
466       Context, Context.getSourceManager().getFileManager(), From,
467       From.getSourceManager().getFileManager(), false, ImporterSharedSt);
468   ASTUnitImporterMap[From.getTranslationUnitDecl()].reset(NewImporter);
469   return *NewImporter;
470 }
471 
472 } // namespace cross_tu
473 } // namespace clang
474