1 //===--- APINotesManager.cpp - Manage API Notes Files ---------------------===//
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
7 //===----------------------------------------------------------------------===//
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/SourceManager.h"
16 #include "clang/Basic/SourceMgrAdapter.h"
17 #include "clang/Basic/Version.h"
18 #include "llvm/ADT/APInt.h"
19 #include "llvm/ADT/Hashing.h"
20 #include "llvm/ADT/SetVector.h"
21 #include "llvm/ADT/SmallPtrSet.h"
22 #include "llvm/ADT/SmallVector.h"
23 #include "llvm/ADT/Statistic.h"
24 #include "llvm/Support/MemoryBuffer.h"
25 #include "llvm/Support/Path.h"
26 #include "llvm/Support/PrettyStackTrace.h"
28 using namespace clang
;
29 using namespace api_notes
;
31 #define DEBUG_TYPE "API Notes"
32 STATISTIC(NumHeaderAPINotes
, "non-framework API notes files loaded");
33 STATISTIC(NumPublicFrameworkAPINotes
, "framework public API notes loaded");
34 STATISTIC(NumPrivateFrameworkAPINotes
, "framework private API notes loaded");
35 STATISTIC(NumFrameworksSearched
, "frameworks searched");
36 STATISTIC(NumDirectoriesSearched
, "header directories searched");
37 STATISTIC(NumDirectoryCacheHits
, "directory cache hits");
40 /// Prints two successive strings, which much be kept alive as long as the
41 /// PrettyStackTrace entry.
42 class PrettyStackTraceDoubleString
: public llvm::PrettyStackTraceEntry
{
43 StringRef First
, Second
;
46 PrettyStackTraceDoubleString(StringRef First
, StringRef Second
)
47 : First(First
), Second(Second
) {}
48 void print(raw_ostream
&OS
) const override
{ OS
<< First
<< Second
; }
52 APINotesManager::APINotesManager(SourceManager
&SM
, const LangOptions
&LangOpts
)
53 : SM(SM
), ImplicitAPINotes(LangOpts
.APINotes
) {}
55 APINotesManager::~APINotesManager() {
56 // Free the API notes readers.
57 for (const auto &Entry
: Readers
) {
58 if (auto Reader
= Entry
.second
.dyn_cast
<APINotesReader
*>())
62 delete CurrentModuleReaders
[ReaderKind::Public
];
63 delete CurrentModuleReaders
[ReaderKind::Private
];
66 std::unique_ptr
<APINotesReader
>
67 APINotesManager::loadAPINotes(FileEntryRef APINotesFile
) {
68 PrettyStackTraceDoubleString
Trace("Loading API notes from ",
69 APINotesFile
.getName());
71 // Open the source file.
72 auto SourceFileID
= SM
.getOrCreateFileID(APINotesFile
, SrcMgr::C_User
);
73 auto SourceBuffer
= SM
.getBufferOrNone(SourceFileID
, SourceLocation());
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
;
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()))
94 // Make a copy of the compiled form into the buffer.
95 CompiledBuffer
= llvm::MemoryBuffer::getMemBufferCopy(
96 StringRef(APINotesBuffer
.data(), APINotesBuffer
.size()));
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?");
105 std::unique_ptr
<APINotesReader
>
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
);
114 if (api_notes::compileAPINotes(Buffer
, nullptr, OS
,
115 SMAdapter
.getDiagHandler(),
116 SMAdapter
.getDiagContext()))
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?");
126 bool APINotesManager::loadAPINotes(const DirectoryEntry
*HeaderDir
,
127 FileEntryRef APINotesFile
) {
128 assert(Readers
.find(HeaderDir
) == Readers
.end());
129 if (auto Reader
= loadAPINotes(APINotesFile
)) {
130 Readers
[HeaderDir
] = Reader
.release();
134 Readers
[HeaderDir
] = nullptr;
139 APINotesManager::findAPINotesFile(DirectoryEntryRef Directory
,
140 StringRef Basename
, bool WantPublic
) {
141 FileManager
&FM
= SM
.getFileManager();
143 llvm::SmallString
<128> Path(Directory
.getName());
145 StringRef Suffix
= WantPublic
? "" : "_private";
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);
153 OptionalDirectoryEntryRef
APINotesManager::loadFrameworkAPINotes(
154 llvm::StringRef FrameworkPath
, llvm::StringRef FrameworkName
, bool Public
) {
155 FileManager
&FM
= SM
.getFileManager();
157 llvm::SmallString
<128> Path(FrameworkPath
);
158 unsigned FrameworkNameLength
= Path
.size();
160 StringRef Suffix
= Public
? "" : "_private";
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
));
167 // Try to open the APINotes file.
168 auto APINotesFile
= FM
.getOptionalFileRef(Path
);
172 // Form the path to the corresponding header directory.
173 Path
.resize(FrameworkNameLength
);
174 llvm::sys::path::append(Path
, Public
? "Headers" : "PrivateHeaders");
176 // Try to access the header directory.
177 auto HeaderDir
= FM
.getOptionalDirectoryRef(Path
);
181 // Try to load the API notes.
182 if (loadAPINotes(*HeaderDir
, *APINotesFile
))
185 // Success: return the header directory.
187 ++NumPublicFrameworkAPINotes
;
189 ++NumPrivateFrameworkAPINotes
;
193 static void checkPrivateAPINotesName(DiagnosticsEngine
&Diags
,
194 const FileEntry
*File
, const Module
*M
) {
195 if (File
->tryGetRealPathName().empty())
198 StringRef RealFileName
=
199 llvm::sys::path::filename(File
->tryGetRealPathName());
200 StringRef RealStem
= llvm::sys::path::stem(RealFileName
);
201 if (RealStem
.ends_with("_private"))
204 unsigned DiagID
= diag::warn_apinotes_private_case
;
206 DiagID
= diag::warn_apinotes_private_case_system
;
208 Diags
.Report(SourceLocation(), DiagID
) << M
->Name
<< RealFileName
;
211 /// \returns true if any of \p module's immediate submodules are defined in a
212 /// private module map
213 static bool hasPrivateSubmodules(const Module
*M
) {
214 return llvm::any_of(M
->submodules(), [](const Module
*Submodule
) {
215 return Submodule
->ModuleMapIsPrivate
;
219 llvm::SmallVector
<FileEntryRef
, 2>
220 APINotesManager::getCurrentModuleAPINotes(Module
*M
, bool LookInModule
,
221 ArrayRef
<std::string
> SearchPaths
) {
222 FileManager
&FM
= SM
.getFileManager();
223 auto ModuleName
= M
->getTopLevelModuleName();
224 llvm::SmallVector
<FileEntryRef
, 2> APINotes
;
226 // First, look relative to the module itself.
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(Dir
, ModuleName
, WantPublic
)) {
232 checkPrivateAPINotesName(SM
.getDiagnostics(), *File
, M
);
234 APINotes
.push_back(*File
);
238 if (M
->IsFramework
) {
239 // For frameworks, we search in the "Headers" or "PrivateHeaders"
243 // - Headers/Foo.apinotes
244 // - PrivateHeaders/Foo_private.apinotes (if there are private submodules)
246 // - PrivateHeaders/Bar.apinotes (except that 'Bar' probably already has
247 // the word "Private" in it in practice)
248 llvm::SmallString
<128> Path(M
->Directory
->getName());
250 if (!M
->ModuleMapIsPrivate
) {
251 unsigned PathLen
= Path
.size();
253 llvm::sys::path::append(Path
, "Headers");
254 if (auto APINotesDir
= FM
.getOptionalDirectoryRef(Path
))
255 tryAPINotes(*APINotesDir
, /*wantPublic=*/true);
257 Path
.resize(PathLen
);
260 if (M
->ModuleMapIsPrivate
|| hasPrivateSubmodules(M
)) {
261 llvm::sys::path::append(Path
, "PrivateHeaders");
262 if (auto PrivateAPINotesDir
= FM
.getOptionalDirectoryRef(Path
))
263 tryAPINotes(*PrivateAPINotesDir
,
264 /*wantPublic=*/M
->ModuleMapIsPrivate
);
269 // - Foo_private.apinotes (if there are private submodules)
271 // - Bar.apinotes (except that 'Bar' probably already has the word
272 // "Private" in it in practice)
273 tryAPINotes(*M
->Directory
, /*wantPublic=*/true);
274 if (!M
->ModuleMapIsPrivate
&& hasPrivateSubmodules(M
))
275 tryAPINotes(*M
->Directory
, /*wantPublic=*/false);
278 if (!APINotes
.empty())
282 // Second, look for API notes for this module in the module API
283 // notes search paths.
284 for (const auto &SearchPath
: SearchPaths
) {
285 if (auto SearchDir
= FM
.getOptionalDirectoryRef(SearchPath
)) {
286 if (auto File
= findAPINotesFile(*SearchDir
, ModuleName
)) {
287 APINotes
.push_back(*File
);
293 // Didn't find any API notes.
297 bool APINotesManager::loadCurrentModuleAPINotes(
298 Module
*M
, bool LookInModule
, ArrayRef
<std::string
> SearchPaths
) {
299 assert(!CurrentModuleReaders
[ReaderKind::Public
] &&
300 "Already loaded API notes for the current module?");
302 auto APINotes
= getCurrentModuleAPINotes(M
, LookInModule
, SearchPaths
);
303 unsigned NumReaders
= 0;
304 for (auto File
: APINotes
) {
305 CurrentModuleReaders
[NumReaders
++] = loadAPINotes(File
).release();
306 if (!getCurrentModuleReaders().empty())
307 M
->APINotesFile
= File
.getName().str();
310 return NumReaders
> 0;
313 bool APINotesManager::loadCurrentModuleAPINotesFromBuffer(
314 ArrayRef
<StringRef
> Buffers
) {
315 unsigned NumReader
= 0;
316 for (auto Buf
: Buffers
) {
317 auto Reader
= loadAPINotes(Buf
);
318 assert(Reader
&& "Could not load the API notes we just generated?");
320 CurrentModuleReaders
[NumReader
++] = Reader
.release();
325 llvm::SmallVector
<APINotesReader
*, 2>
326 APINotesManager::findAPINotes(SourceLocation Loc
) {
327 llvm::SmallVector
<APINotesReader
*, 2> Results
;
329 // If there are readers for the current module, return them.
330 if (!getCurrentModuleReaders().empty()) {
331 Results
.append(getCurrentModuleReaders().begin(),
332 getCurrentModuleReaders().end());
336 // If we're not allowed to implicitly load API notes files, we're done.
337 if (!ImplicitAPINotes
)
340 // If we don't have source location information, we're done.
344 // API notes are associated with the expansion location. Retrieve the
345 // file for this location.
346 SourceLocation ExpansionLoc
= SM
.getExpansionLoc(Loc
);
347 FileID ID
= SM
.getFileID(ExpansionLoc
);
350 OptionalFileEntryRef File
= SM
.getFileEntryRefForID(ID
);
354 // Look for API notes in the directory corresponding to this file, or one of
355 // its its parent directories.
356 OptionalDirectoryEntryRef Dir
= File
->getDir();
357 FileManager
&FileMgr
= SM
.getFileManager();
358 llvm::SetVector
<const DirectoryEntry
*,
359 SmallVector
<const DirectoryEntry
*, 4>,
360 llvm::SmallPtrSet
<const DirectoryEntry
*, 4>>
363 // Look for an API notes reader for this header search directory.
364 auto Known
= Readers
.find(*Dir
);
366 // If we already know the answer, chase it.
367 if (Known
!= Readers
.end()) {
368 ++NumDirectoryCacheHits
;
370 // We've been redirected to another directory for answers. Follow it.
371 if (Known
->second
&& Known
->second
.is
<DirectoryEntryRef
>()) {
372 DirsVisited
.insert(*Dir
);
373 Dir
= Known
->second
.get
<DirectoryEntryRef
>();
377 // We have the answer.
378 if (auto Reader
= Known
->second
.dyn_cast
<APINotesReader
*>())
379 Results
.push_back(Reader
);
383 // Look for API notes corresponding to this directory.
384 StringRef Path
= Dir
->getName();
385 if (llvm::sys::path::extension(Path
) == ".framework") {
386 // If this is a framework directory, check whether there are API notes
387 // in the APINotes subdirectory.
388 auto FrameworkName
= llvm::sys::path::stem(Path
);
389 ++NumFrameworksSearched
;
391 // Look for API notes for both the public and private headers.
392 OptionalDirectoryEntryRef PublicDir
=
393 loadFrameworkAPINotes(Path
, FrameworkName
, /*Public=*/true);
394 OptionalDirectoryEntryRef PrivateDir
=
395 loadFrameworkAPINotes(Path
, FrameworkName
, /*Public=*/false);
397 if (PublicDir
|| PrivateDir
) {
398 // We found API notes: don't ever look past the framework directory.
399 Readers
[*Dir
] = nullptr;
401 // Pretend we found the result in the public or private directory,
402 // as appropriate. All headers should be in one of those two places,
403 // but be defensive here.
404 if (!DirsVisited
.empty()) {
405 if (PublicDir
&& DirsVisited
.back() == *PublicDir
) {
406 DirsVisited
.pop_back();
408 } else if (PrivateDir
&& DirsVisited
.back() == *PrivateDir
) {
409 DirsVisited
.pop_back();
415 if (auto Reader
= Readers
[*Dir
].dyn_cast
<APINotesReader
*>())
416 Results
.push_back(Reader
);
420 // Look for an APINotes file in this directory.
421 llvm::SmallString
<128> APINotesPath(Dir
->getName());
422 llvm::sys::path::append(
423 APINotesPath
, (llvm::Twine("APINotes.") + SOURCE_APINOTES_EXTENSION
));
425 // If there is an API notes file here, try to load it.
426 ++NumDirectoriesSearched
;
427 if (auto APINotesFile
= FileMgr
.getOptionalFileRef(APINotesPath
)) {
428 if (!loadAPINotes(*Dir
, *APINotesFile
)) {
430 if (auto Reader
= Readers
[*Dir
].dyn_cast
<APINotesReader
*>())
431 Results
.push_back(Reader
);
437 // We didn't find anything. Look at the parent directory.
438 if (!DirsVisited
.insert(*Dir
)) {
443 StringRef ParentPath
= llvm::sys::path::parent_path(Path
);
444 while (llvm::sys::path::stem(ParentPath
) == "..")
445 ParentPath
= llvm::sys::path::parent_path(ParentPath
);
447 Dir
= ParentPath
.empty() ? std::nullopt
448 : FileMgr
.getOptionalDirectoryRef(ParentPath
);
451 // Path compression for all of the directories we visited, redirecting
452 // them to the directory we ended on. If no API notes were found, the
453 // resulting directory will be NULL, indicating no API notes.
454 for (const auto Visited
: DirsVisited
)
455 Readers
[Visited
] = Dir
? ReaderEntry(*Dir
) : ReaderEntry();