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