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