Coverage Report

Created: 2024-01-17 10:31

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