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