brintos

brintos / llvm-project-archived public Read only

0
0
Text · 16.5 KiB · 60868ab Raw
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