464 lines · cpp
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-exception6//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 the40/// 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} // namespace50 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 *>(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(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 binary79 // 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 105std::unique_ptr<APINotesReader>106APINotesManager::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 126bool 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 138OptionalFileEntryRef139APINotesManager::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 153OptionalDirectoryEntryRef 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 else189 ++NumPrivateFrameworkAPINotes;190 return *HeaderDir;191}192 193static 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 a212/// private module map213static bool hasPrivateSubmodules(const Module *M) {214 return llvm::any_of(M->submodules(), [](const Module *Submodule) {215 return Submodule->ModuleMapIsPrivate;216 });217}218 219llvm::SmallVector<FileEntryRef, 2>220APINotesManager::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.apinotes249 // - PrivateHeaders/Foo_private.apinotes (if there are private submodules)250 // Private modules:251 // - PrivateHeaders/Bar.apinotes (except that 'Bar' probably already has252 // 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.apinotes274 // - Foo_private.apinotes (if there are private submodules)275 // Private modules:276 // - Bar.apinotes (except that 'Bar' probably already has the word277 // "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 API288 // 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 302bool 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 318bool 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 330llvm::SmallVector<APINotesReader *, 2>331APINotesManager::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 the350 // 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 of360 // 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 notes392 // 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::nullopt453 : FileMgr.getOptionalDirectoryRef(ParentPath);454 } while (Dir);455 456 // Path compression for all of the directories we visited, redirecting457 // them to the directory we ended on. If no API notes were found, the458 // 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