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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang/lib/APINotes/APINotesManager.cpp