xref: /freebsd/contrib/llvm-project/clang/lib/APINotes/APINotesManager.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1 //===--- APINotesManager.cpp - Manage API Notes Files ---------------------===//
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/APINotes/APINotesManager.h"
10 #include "clang/APINotes/APINotesReader.h"
11 #include "clang/APINotes/APINotesYAMLCompiler.h"
12 #include "clang/Basic/Diagnostic.h"
13 #include "clang/Basic/FileManager.h"
14 #include "clang/Basic/LangOptions.h"
15 #include "clang/Basic/Module.h"
16 #include "clang/Basic/SourceManager.h"
17 #include "clang/Basic/SourceMgrAdapter.h"
18 #include "clang/Basic/Version.h"
19 #include "llvm/ADT/APInt.h"
20 #include "llvm/ADT/Hashing.h"
21 #include "llvm/ADT/SetVector.h"
22 #include "llvm/ADT/SmallPtrSet.h"
23 #include "llvm/ADT/SmallVector.h"
24 #include "llvm/ADT/Statistic.h"
25 #include "llvm/Support/MemoryBuffer.h"
26 #include "llvm/Support/Path.h"
27 #include "llvm/Support/PrettyStackTrace.h"
28 
29 using namespace clang;
30 using namespace api_notes;
31 
32 #define DEBUG_TYPE "API Notes"
33 STATISTIC(NumHeaderAPINotes, "non-framework API notes files loaded");
34 STATISTIC(NumPublicFrameworkAPINotes, "framework public API notes loaded");
35 STATISTIC(NumPrivateFrameworkAPINotes, "framework private API notes loaded");
36 STATISTIC(NumFrameworksSearched, "frameworks searched");
37 STATISTIC(NumDirectoriesSearched, "header directories searched");
38 STATISTIC(NumDirectoryCacheHits, "directory cache hits");
39 
40 namespace {
41 /// Prints two successive strings, which much be kept alive as long as the
42 /// PrettyStackTrace entry.
43 class PrettyStackTraceDoubleString : public llvm::PrettyStackTraceEntry {
44   StringRef First, Second;
45 
46 public:
PrettyStackTraceDoubleString(StringRef First,StringRef Second)47   PrettyStackTraceDoubleString(StringRef First, StringRef Second)
48       : First(First), Second(Second) {}
print(raw_ostream & OS) const49   void print(raw_ostream &OS) const override { OS << First << Second; }
50 };
51 } // namespace
52 
APINotesManager(SourceManager & SM,const LangOptions & LangOpts)53 APINotesManager::APINotesManager(SourceManager &SM, const LangOptions &LangOpts)
54     : SM(SM), ImplicitAPINotes(LangOpts.APINotes) {}
55 
~APINotesManager()56 APINotesManager::~APINotesManager() {
57   // Free the API notes readers.
58   for (const auto &Entry : Readers) {
59     if (auto Reader = Entry.second.dyn_cast<APINotesReader *>())
60       delete Reader;
61   }
62 
63   delete CurrentModuleReaders[ReaderKind::Public];
64   delete CurrentModuleReaders[ReaderKind::Private];
65 }
66 
67 std::unique_ptr<APINotesReader>
loadAPINotes(FileEntryRef APINotesFile)68 APINotesManager::loadAPINotes(FileEntryRef APINotesFile) {
69   PrettyStackTraceDoubleString Trace("Loading API notes from ",
70                                      APINotesFile.getName());
71 
72   // Open the source file.
73   auto SourceFileID = SM.getOrCreateFileID(APINotesFile, SrcMgr::C_User);
74   auto SourceBuffer = SM.getBufferOrNone(SourceFileID, SourceLocation());
75   if (!SourceBuffer)
76     return nullptr;
77 
78   // Compile the API notes source into a buffer.
79   // FIXME: Either propagate OSType through or, better yet, improve the binary
80   // APINotes format to maintain complete availability information.
81   // FIXME: We don't even really need to go through the binary format at all;
82   // we're just going to immediately deserialize it again.
83   llvm::SmallVector<char, 1024> APINotesBuffer;
84   std::unique_ptr<llvm::MemoryBuffer> CompiledBuffer;
85   {
86     SourceMgrAdapter SMAdapter(
87         SM, SM.getDiagnostics(), diag::err_apinotes_message,
88         diag::warn_apinotes_message, diag::note_apinotes_message, APINotesFile);
89     llvm::raw_svector_ostream OS(APINotesBuffer);
90     if (api_notes::compileAPINotes(
91             SourceBuffer->getBuffer(), SM.getFileEntryForID(SourceFileID), OS,
92             SMAdapter.getDiagHandler(), SMAdapter.getDiagContext()))
93       return nullptr;
94 
95     // Make a copy of the compiled form into the buffer.
96     CompiledBuffer = llvm::MemoryBuffer::getMemBufferCopy(
97         StringRef(APINotesBuffer.data(), APINotesBuffer.size()));
98   }
99 
100   // Load the binary form we just compiled.
101   auto Reader = APINotesReader::Create(std::move(CompiledBuffer), SwiftVersion);
102   assert(Reader && "Could not load the API notes we just generated?");
103   return Reader;
104 }
105 
106 std::unique_ptr<APINotesReader>
loadAPINotes(StringRef Buffer)107 APINotesManager::loadAPINotes(StringRef Buffer) {
108   llvm::SmallVector<char, 1024> APINotesBuffer;
109   std::unique_ptr<llvm::MemoryBuffer> CompiledBuffer;
110   SourceMgrAdapter SMAdapter(
111       SM, SM.getDiagnostics(), diag::err_apinotes_message,
112       diag::warn_apinotes_message, diag::note_apinotes_message, std::nullopt);
113   llvm::raw_svector_ostream OS(APINotesBuffer);
114 
115   if (api_notes::compileAPINotes(Buffer, nullptr, OS,
116                                  SMAdapter.getDiagHandler(),
117                                  SMAdapter.getDiagContext()))
118     return nullptr;
119 
120   CompiledBuffer = llvm::MemoryBuffer::getMemBufferCopy(
121       StringRef(APINotesBuffer.data(), APINotesBuffer.size()));
122   auto Reader = APINotesReader::Create(std::move(CompiledBuffer), SwiftVersion);
123   assert(Reader && "Could not load the API notes we just generated?");
124   return Reader;
125 }
126 
loadAPINotes(const DirectoryEntry * HeaderDir,FileEntryRef APINotesFile)127 bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir,
128                                    FileEntryRef APINotesFile) {
129   assert(!Readers.contains(HeaderDir));
130   if (auto Reader = loadAPINotes(APINotesFile)) {
131     Readers[HeaderDir] = Reader.release();
132     return false;
133   }
134 
135   Readers[HeaderDir] = nullptr;
136   return true;
137 }
138 
139 OptionalFileEntryRef
findAPINotesFile(DirectoryEntryRef Directory,StringRef Basename,bool WantPublic)140 APINotesManager::findAPINotesFile(DirectoryEntryRef Directory,
141                                   StringRef Basename, bool WantPublic) {
142   FileManager &FM = SM.getFileManager();
143 
144   llvm::SmallString<128> Path(Directory.getName());
145 
146   StringRef Suffix = WantPublic ? "" : "_private";
147 
148   // Look for the source API notes file.
149   llvm::sys::path::append(Path, llvm::Twine(Basename) + Suffix + "." +
150                                     SOURCE_APINOTES_EXTENSION);
151   return FM.getOptionalFileRef(Path, /*Open*/ true);
152 }
153 
loadFrameworkAPINotes(llvm::StringRef FrameworkPath,llvm::StringRef FrameworkName,bool Public)154 OptionalDirectoryEntryRef APINotesManager::loadFrameworkAPINotes(
155     llvm::StringRef FrameworkPath, llvm::StringRef FrameworkName, bool Public) {
156   FileManager &FM = SM.getFileManager();
157 
158   llvm::SmallString<128> Path(FrameworkPath);
159   unsigned FrameworkNameLength = Path.size();
160 
161   StringRef Suffix = Public ? "" : "_private";
162 
163   // Form the path to the APINotes file.
164   llvm::sys::path::append(Path, "APINotes");
165   llvm::sys::path::append(Path, (llvm::Twine(FrameworkName) + Suffix + "." +
166                                  SOURCE_APINOTES_EXTENSION));
167 
168   // Try to open the APINotes file.
169   auto APINotesFile = FM.getOptionalFileRef(Path);
170   if (!APINotesFile)
171     return std::nullopt;
172 
173   // Form the path to the corresponding header directory.
174   Path.resize(FrameworkNameLength);
175   llvm::sys::path::append(Path, Public ? "Headers" : "PrivateHeaders");
176 
177   // Try to access the header directory.
178   auto HeaderDir = FM.getOptionalDirectoryRef(Path);
179   if (!HeaderDir)
180     return std::nullopt;
181 
182   // Try to load the API notes.
183   if (loadAPINotes(*HeaderDir, *APINotesFile))
184     return std::nullopt;
185 
186   // Success: return the header directory.
187   if (Public)
188     ++NumPublicFrameworkAPINotes;
189   else
190     ++NumPrivateFrameworkAPINotes;
191   return *HeaderDir;
192 }
193 
checkPrivateAPINotesName(DiagnosticsEngine & Diags,const FileEntry * File,const Module * M)194 static void checkPrivateAPINotesName(DiagnosticsEngine &Diags,
195                                      const FileEntry *File, const Module *M) {
196   if (File->tryGetRealPathName().empty())
197     return;
198 
199   StringRef RealFileName =
200       llvm::sys::path::filename(File->tryGetRealPathName());
201   StringRef RealStem = llvm::sys::path::stem(RealFileName);
202   if (RealStem.ends_with("_private"))
203     return;
204 
205   unsigned DiagID = diag::warn_apinotes_private_case;
206   if (M->IsSystem)
207     DiagID = diag::warn_apinotes_private_case_system;
208 
209   Diags.Report(SourceLocation(), DiagID) << M->Name << RealFileName;
210 }
211 
212 /// \returns true if any of \p module's immediate submodules are defined in a
213 /// private module map
hasPrivateSubmodules(const Module * M)214 static bool hasPrivateSubmodules(const Module *M) {
215   return llvm::any_of(M->submodules(), [](const Module *Submodule) {
216     return Submodule->ModuleMapIsPrivate;
217   });
218 }
219 
220 llvm::SmallVector<FileEntryRef, 2>
getCurrentModuleAPINotes(Module * M,bool LookInModule,ArrayRef<std::string> SearchPaths)221 APINotesManager::getCurrentModuleAPINotes(Module *M, bool LookInModule,
222                                           ArrayRef<std::string> SearchPaths) {
223   FileManager &FM = SM.getFileManager();
224   auto ModuleName = M->getTopLevelModuleName();
225   auto ExportedModuleName = M->getTopLevelModule()->ExportAsModule;
226   llvm::SmallVector<FileEntryRef, 2> APINotes;
227 
228   // First, look relative to the module itself.
229   if (LookInModule && M->Directory) {
230     // Local function to try loading an API notes file in the given directory.
231     auto tryAPINotes = [&](DirectoryEntryRef Dir, bool WantPublic) {
232       if (auto File = findAPINotesFile(Dir, ModuleName, WantPublic)) {
233         if (!WantPublic)
234           checkPrivateAPINotesName(SM.getDiagnostics(), *File, M);
235 
236         APINotes.push_back(*File);
237       }
238       // If module FooCore is re-exported through module Foo, try Foo.apinotes.
239       if (!ExportedModuleName.empty())
240         if (auto File = findAPINotesFile(Dir, ExportedModuleName, WantPublic))
241           APINotes.push_back(*File);
242     };
243 
244     if (M->IsFramework) {
245       // For frameworks, we search in the "Headers" or "PrivateHeaders"
246       // subdirectory.
247       //
248       // Public modules:
249       // - Headers/Foo.apinotes
250       // - PrivateHeaders/Foo_private.apinotes (if there are private submodules)
251       // Private modules:
252       // - PrivateHeaders/Bar.apinotes (except that 'Bar' probably already has
253       //   the word "Private" in it in practice)
254       llvm::SmallString<128> Path(M->Directory->getName());
255 
256       if (!M->ModuleMapIsPrivate) {
257         unsigned PathLen = Path.size();
258 
259         llvm::sys::path::append(Path, "Headers");
260         if (auto APINotesDir = FM.getOptionalDirectoryRef(Path))
261           tryAPINotes(*APINotesDir, /*wantPublic=*/true);
262 
263         Path.resize(PathLen);
264       }
265 
266       if (M->ModuleMapIsPrivate || hasPrivateSubmodules(M)) {
267         llvm::sys::path::append(Path, "PrivateHeaders");
268         if (auto PrivateAPINotesDir = FM.getOptionalDirectoryRef(Path))
269           tryAPINotes(*PrivateAPINotesDir,
270                       /*wantPublic=*/M->ModuleMapIsPrivate);
271       }
272     } else {
273       // Public modules:
274       // - Foo.apinotes
275       // - Foo_private.apinotes (if there are private submodules)
276       // Private modules:
277       // - Bar.apinotes (except that 'Bar' probably already has the word
278       //   "Private" in it in practice)
279       tryAPINotes(*M->Directory, /*wantPublic=*/true);
280       if (!M->ModuleMapIsPrivate && hasPrivateSubmodules(M))
281         tryAPINotes(*M->Directory, /*wantPublic=*/false);
282     }
283 
284     if (!APINotes.empty())
285       return APINotes;
286   }
287 
288   // Second, look for API notes for this module in the module API
289   // notes search paths.
290   for (const auto &SearchPath : SearchPaths) {
291     if (auto SearchDir = FM.getOptionalDirectoryRef(SearchPath)) {
292       if (auto File = findAPINotesFile(*SearchDir, ModuleName)) {
293         APINotes.push_back(*File);
294         return APINotes;
295       }
296     }
297   }
298 
299   // Didn't find any API notes.
300   return APINotes;
301 }
302 
loadCurrentModuleAPINotes(Module * M,bool LookInModule,ArrayRef<std::string> SearchPaths)303 bool APINotesManager::loadCurrentModuleAPINotes(
304     Module *M, bool LookInModule, ArrayRef<std::string> SearchPaths) {
305   assert(!CurrentModuleReaders[ReaderKind::Public] &&
306          "Already loaded API notes for the current module?");
307 
308   auto APINotes = getCurrentModuleAPINotes(M, LookInModule, SearchPaths);
309   unsigned NumReaders = 0;
310   for (auto File : APINotes) {
311     CurrentModuleReaders[NumReaders++] = loadAPINotes(File).release();
312     if (!getCurrentModuleReaders().empty())
313       M->APINotesFile = File.getName().str();
314   }
315 
316   return NumReaders > 0;
317 }
318 
loadCurrentModuleAPINotesFromBuffer(ArrayRef<StringRef> Buffers)319 bool APINotesManager::loadCurrentModuleAPINotesFromBuffer(
320     ArrayRef<StringRef> Buffers) {
321   unsigned NumReader = 0;
322   for (auto Buf : Buffers) {
323     auto Reader = loadAPINotes(Buf);
324     assert(Reader && "Could not load the API notes we just generated?");
325 
326     CurrentModuleReaders[NumReader++] = Reader.release();
327   }
328   return NumReader;
329 }
330 
331 llvm::SmallVector<APINotesReader *, 2>
findAPINotes(SourceLocation Loc)332 APINotesManager::findAPINotes(SourceLocation Loc) {
333   llvm::SmallVector<APINotesReader *, 2> Results;
334 
335   // If there are readers for the current module, return them.
336   if (!getCurrentModuleReaders().empty()) {
337     Results.append(getCurrentModuleReaders().begin(),
338                    getCurrentModuleReaders().end());
339     return Results;
340   }
341 
342   // If we're not allowed to implicitly load API notes files, we're done.
343   if (!ImplicitAPINotes)
344     return Results;
345 
346   // If we don't have source location information, we're done.
347   if (Loc.isInvalid())
348     return Results;
349 
350   // API notes are associated with the expansion location. Retrieve the
351   // file for this location.
352   SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc);
353   FileID ID = SM.getFileID(ExpansionLoc);
354   if (ID.isInvalid())
355     return Results;
356   OptionalFileEntryRef File = SM.getFileEntryRefForID(ID);
357   if (!File)
358     return Results;
359 
360   // Look for API notes in the directory corresponding to this file, or one of
361   // its its parent directories.
362   OptionalDirectoryEntryRef Dir = File->getDir();
363   FileManager &FileMgr = SM.getFileManager();
364   llvm::SetVector<const DirectoryEntry *,
365                   SmallVector<const DirectoryEntry *, 4>,
366                   llvm::SmallPtrSet<const DirectoryEntry *, 4>>
367       DirsVisited;
368   do {
369     // Look for an API notes reader for this header search directory.
370     auto Known = Readers.find(*Dir);
371 
372     // If we already know the answer, chase it.
373     if (Known != Readers.end()) {
374       ++NumDirectoryCacheHits;
375 
376       // We've been redirected to another directory for answers. Follow it.
377       if (Known->second && Known->second.is<DirectoryEntryRef>()) {
378         DirsVisited.insert(*Dir);
379         Dir = Known->second.get<DirectoryEntryRef>();
380         continue;
381       }
382 
383       // We have the answer.
384       if (auto Reader = Known->second.dyn_cast<APINotesReader *>())
385         Results.push_back(Reader);
386       break;
387     }
388 
389     // Look for API notes corresponding to this directory.
390     StringRef Path = Dir->getName();
391     if (llvm::sys::path::extension(Path) == ".framework") {
392       // If this is a framework directory, check whether there are API notes
393       // in the APINotes subdirectory.
394       auto FrameworkName = llvm::sys::path::stem(Path);
395       ++NumFrameworksSearched;
396 
397       // Look for API notes for both the public and private headers.
398       OptionalDirectoryEntryRef PublicDir =
399           loadFrameworkAPINotes(Path, FrameworkName, /*Public=*/true);
400       OptionalDirectoryEntryRef PrivateDir =
401           loadFrameworkAPINotes(Path, FrameworkName, /*Public=*/false);
402 
403       if (PublicDir || PrivateDir) {
404         // We found API notes: don't ever look past the framework directory.
405         Readers[*Dir] = nullptr;
406 
407         // Pretend we found the result in the public or private directory,
408         // as appropriate. All headers should be in one of those two places,
409         // but be defensive here.
410         if (!DirsVisited.empty()) {
411           if (PublicDir && DirsVisited.back() == *PublicDir) {
412             DirsVisited.pop_back();
413             Dir = *PublicDir;
414           } else if (PrivateDir && DirsVisited.back() == *PrivateDir) {
415             DirsVisited.pop_back();
416             Dir = *PrivateDir;
417           }
418         }
419 
420         // Grab the result.
421         if (auto Reader = Readers[*Dir].dyn_cast<APINotesReader *>())
422           Results.push_back(Reader);
423         break;
424       }
425     } else {
426       // Look for an APINotes file in this directory.
427       llvm::SmallString<128> APINotesPath(Dir->getName());
428       llvm::sys::path::append(
429           APINotesPath, (llvm::Twine("APINotes.") + SOURCE_APINOTES_EXTENSION));
430 
431       // If there is an API notes file here, try to load it.
432       ++NumDirectoriesSearched;
433       if (auto APINotesFile = FileMgr.getOptionalFileRef(APINotesPath)) {
434         if (!loadAPINotes(*Dir, *APINotesFile)) {
435           ++NumHeaderAPINotes;
436           if (auto Reader = Readers[*Dir].dyn_cast<APINotesReader *>())
437             Results.push_back(Reader);
438           break;
439         }
440       }
441     }
442 
443     // We didn't find anything. Look at the parent directory.
444     if (!DirsVisited.insert(*Dir)) {
445       Dir = std::nullopt;
446       break;
447     }
448 
449     StringRef ParentPath = llvm::sys::path::parent_path(Path);
450     while (llvm::sys::path::stem(ParentPath) == "..")
451       ParentPath = llvm::sys::path::parent_path(ParentPath);
452 
453     Dir = ParentPath.empty() ? std::nullopt
454                              : FileMgr.getOptionalDirectoryRef(ParentPath);
455   } while (Dir);
456 
457   // Path compression for all of the directories we visited, redirecting
458   // them to the directory we ended on. If no API notes were found, the
459   // resulting directory will be NULL, indicating no API notes.
460   for (const auto Visited : DirsVisited)
461     Readers[Visited] = Dir ? ReaderEntry(*Dir) : ReaderEntry();
462 
463   return Results;
464 }
465