ExtractAPIConsumer.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. //===- ExtractAPI/ExtractAPIConsumer.cpp ------------------------*- C++ -*-===//
  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. /// \file
  10. /// This file implements the ExtractAPIAction, and ASTConsumer to collect API
  11. /// information.
  12. ///
  13. //===----------------------------------------------------------------------===//
  14. #include "clang/AST/ASTConsumer.h"
  15. #include "clang/AST/ASTContext.h"
  16. #include "clang/Basic/DiagnosticFrontend.h"
  17. #include "clang/Basic/SourceLocation.h"
  18. #include "clang/Basic/SourceManager.h"
  19. #include "clang/Basic/TargetInfo.h"
  20. #include "clang/ExtractAPI/API.h"
  21. #include "clang/ExtractAPI/APIIgnoresList.h"
  22. #include "clang/ExtractAPI/ExtractAPIVisitor.h"
  23. #include "clang/ExtractAPI/FrontendActions.h"
  24. #include "clang/ExtractAPI/Serialization/SymbolGraphSerializer.h"
  25. #include "clang/Frontend/ASTConsumers.h"
  26. #include "clang/Frontend/CompilerInstance.h"
  27. #include "clang/Frontend/FrontendOptions.h"
  28. #include "clang/Lex/MacroInfo.h"
  29. #include "clang/Lex/PPCallbacks.h"
  30. #include "clang/Lex/Preprocessor.h"
  31. #include "clang/Lex/PreprocessorOptions.h"
  32. #include "llvm/ADT/DenseSet.h"
  33. #include "llvm/ADT/STLExtras.h"
  34. #include "llvm/ADT/SmallVector.h"
  35. #include "llvm/Support/Error.h"
  36. #include "llvm/Support/FileSystem.h"
  37. #include "llvm/Support/MemoryBuffer.h"
  38. #include "llvm/Support/Path.h"
  39. #include "llvm/Support/Regex.h"
  40. #include "llvm/Support/raw_ostream.h"
  41. #include <memory>
  42. #include <optional>
  43. #include <utility>
  44. using namespace clang;
  45. using namespace extractapi;
  46. namespace {
  47. std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,
  48. StringRef File,
  49. bool *IsQuoted = nullptr) {
  50. assert(CI.hasFileManager() &&
  51. "CompilerInstance does not have a FileNamager!");
  52. using namespace llvm::sys;
  53. // Matches framework include patterns
  54. const llvm::Regex Rule("/(.+)\\.framework/(.+)?Headers/(.+)");
  55. const auto &FS = CI.getVirtualFileSystem();
  56. SmallString<128> FilePath(File.begin(), File.end());
  57. FS.makeAbsolute(FilePath);
  58. path::remove_dots(FilePath, true);
  59. FilePath = path::convert_to_slash(FilePath);
  60. File = FilePath;
  61. // Checks whether `Dir` is a strict path prefix of `File`. If so returns
  62. // the prefix length. Otherwise return 0.
  63. auto CheckDir = [&](llvm::StringRef Dir) -> unsigned {
  64. llvm::SmallString<32> DirPath(Dir.begin(), Dir.end());
  65. FS.makeAbsolute(DirPath);
  66. path::remove_dots(DirPath, true);
  67. Dir = DirPath;
  68. for (auto NI = path::begin(File), NE = path::end(File),
  69. DI = path::begin(Dir), DE = path::end(Dir);
  70. /*termination condition in loop*/; ++NI, ++DI) {
  71. // '.' components in File are ignored.
  72. while (NI != NE && *NI == ".")
  73. ++NI;
  74. if (NI == NE)
  75. break;
  76. // '.' components in Dir are ignored.
  77. while (DI != DE && *DI == ".")
  78. ++DI;
  79. // Dir is a prefix of File, up to '.' components and choice of path
  80. // separators.
  81. if (DI == DE)
  82. return NI - path::begin(File);
  83. // Consider all path separators equal.
  84. if (NI->size() == 1 && DI->size() == 1 &&
  85. path::is_separator(NI->front()) && path::is_separator(DI->front()))
  86. continue;
  87. // Special case Apple .sdk folders since the search path is typically a
  88. // symlink like `iPhoneSimulator14.5.sdk` while the file is instead
  89. // located in `iPhoneSimulator.sdk` (the real folder).
  90. if (NI->endswith(".sdk") && DI->endswith(".sdk")) {
  91. StringRef NBasename = path::stem(*NI);
  92. StringRef DBasename = path::stem(*DI);
  93. if (DBasename.startswith(NBasename))
  94. continue;
  95. }
  96. if (*NI != *DI)
  97. break;
  98. }
  99. return 0;
  100. };
  101. unsigned PrefixLength = 0;
  102. // Go through the search paths and find the first one that is a prefix of
  103. // the header.
  104. for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries) {
  105. // Note whether the match is found in a quoted entry.
  106. if (IsQuoted)
  107. *IsQuoted = Entry.Group == frontend::Quoted;
  108. if (auto EntryFile = CI.getFileManager().getOptionalFileRef(Entry.Path)) {
  109. if (auto HMap = HeaderMap::Create(*EntryFile, CI.getFileManager())) {
  110. // If this is a headermap entry, try to reverse lookup the full path
  111. // for a spelled name before mapping.
  112. StringRef SpelledFilename = HMap->reverseLookupFilename(File);
  113. if (!SpelledFilename.empty())
  114. return SpelledFilename.str();
  115. // No matching mapping in this headermap, try next search entry.
  116. continue;
  117. }
  118. }
  119. // Entry is a directory search entry, try to check if it's a prefix of File.
  120. PrefixLength = CheckDir(Entry.Path);
  121. if (PrefixLength > 0) {
  122. // The header is found in a framework path, construct the framework-style
  123. // include name `<Framework/Header.h>`
  124. if (Entry.IsFramework) {
  125. SmallVector<StringRef, 4> Matches;
  126. Rule.match(File, &Matches);
  127. // Returned matches are always in stable order.
  128. if (Matches.size() != 4)
  129. return std::nullopt;
  130. return path::convert_to_slash(
  131. (Matches[1].drop_front(Matches[1].rfind('/') + 1) + "/" +
  132. Matches[3])
  133. .str());
  134. }
  135. // The header is found in a normal search path, strip the search path
  136. // prefix to get an include name.
  137. return path::convert_to_slash(File.drop_front(PrefixLength));
  138. }
  139. }
  140. // Couldn't determine a include name, use full path instead.
  141. return std::nullopt;
  142. }
  143. struct LocationFileChecker {
  144. bool operator()(SourceLocation Loc) {
  145. // If the loc refers to a macro expansion we need to first get the file
  146. // location of the expansion.
  147. auto &SM = CI.getSourceManager();
  148. auto FileLoc = SM.getFileLoc(Loc);
  149. FileID FID = SM.getFileID(FileLoc);
  150. if (FID.isInvalid())
  151. return false;
  152. const auto *File = SM.getFileEntryForID(FID);
  153. if (!File)
  154. return false;
  155. if (KnownFileEntries.count(File))
  156. return true;
  157. if (ExternalFileEntries.count(File))
  158. return false;
  159. StringRef FileName = File->tryGetRealPathName().empty()
  160. ? File->getName()
  161. : File->tryGetRealPathName();
  162. // Try to reduce the include name the same way we tried to include it.
  163. bool IsQuoted = false;
  164. if (auto IncludeName = getRelativeIncludeName(CI, FileName, &IsQuoted))
  165. if (llvm::any_of(KnownFiles,
  166. [&IsQuoted, &IncludeName](const auto &KnownFile) {
  167. return KnownFile.first.equals(*IncludeName) &&
  168. KnownFile.second == IsQuoted;
  169. })) {
  170. KnownFileEntries.insert(File);
  171. return true;
  172. }
  173. // Record that the file was not found to avoid future reverse lookup for
  174. // the same file.
  175. ExternalFileEntries.insert(File);
  176. return false;
  177. }
  178. LocationFileChecker(const CompilerInstance &CI,
  179. SmallVector<std::pair<SmallString<32>, bool>> &KnownFiles)
  180. : CI(CI), KnownFiles(KnownFiles), ExternalFileEntries() {
  181. for (const auto &KnownFile : KnownFiles)
  182. if (auto FileEntry = CI.getFileManager().getFile(KnownFile.first))
  183. KnownFileEntries.insert(*FileEntry);
  184. }
  185. private:
  186. const CompilerInstance &CI;
  187. SmallVector<std::pair<SmallString<32>, bool>> &KnownFiles;
  188. llvm::DenseSet<const FileEntry *> KnownFileEntries;
  189. llvm::DenseSet<const FileEntry *> ExternalFileEntries;
  190. };
  191. class ExtractAPIConsumer : public ASTConsumer {
  192. public:
  193. ExtractAPIConsumer(ASTContext &Context,
  194. std::unique_ptr<LocationFileChecker> LCF, APISet &API)
  195. : Visitor(Context, *LCF, API), LCF(std::move(LCF)) {}
  196. void HandleTranslationUnit(ASTContext &Context) override {
  197. // Use ExtractAPIVisitor to traverse symbol declarations in the context.
  198. Visitor.TraverseDecl(Context.getTranslationUnitDecl());
  199. }
  200. private:
  201. ExtractAPIVisitor Visitor;
  202. std::unique_ptr<LocationFileChecker> LCF;
  203. };
  204. class MacroCallback : public PPCallbacks {
  205. public:
  206. MacroCallback(const SourceManager &SM, LocationFileChecker &LCF, APISet &API,
  207. Preprocessor &PP)
  208. : SM(SM), LCF(LCF), API(API), PP(PP) {}
  209. void MacroDefined(const Token &MacroNameToken,
  210. const MacroDirective *MD) override {
  211. auto *MacroInfo = MD->getMacroInfo();
  212. if (MacroInfo->isBuiltinMacro())
  213. return;
  214. auto SourceLoc = MacroNameToken.getLocation();
  215. if (SM.isWrittenInBuiltinFile(SourceLoc) ||
  216. SM.isWrittenInCommandLineFile(SourceLoc))
  217. return;
  218. PendingMacros.emplace_back(MacroNameToken, MD);
  219. }
  220. // If a macro gets undefined at some point during preprocessing of the inputs
  221. // it means that it isn't an exposed API and we should therefore not add a
  222. // macro definition for it.
  223. void MacroUndefined(const Token &MacroNameToken, const MacroDefinition &MD,
  224. const MacroDirective *Undef) override {
  225. // If this macro wasn't previously defined we don't need to do anything
  226. // here.
  227. if (!Undef)
  228. return;
  229. llvm::erase_if(PendingMacros, [&MD, this](const PendingMacro &PM) {
  230. return MD.getMacroInfo()->isIdenticalTo(*PM.MD->getMacroInfo(), PP,
  231. /*Syntactically*/ false);
  232. });
  233. }
  234. void EndOfMainFile() override {
  235. for (auto &PM : PendingMacros) {
  236. // `isUsedForHeaderGuard` is only set when the preprocessor leaves the
  237. // file so check for it here.
  238. if (PM.MD->getMacroInfo()->isUsedForHeaderGuard())
  239. continue;
  240. if (!LCF(PM.MacroNameToken.getLocation()))
  241. continue;
  242. StringRef Name = PM.MacroNameToken.getIdentifierInfo()->getName();
  243. PresumedLoc Loc = SM.getPresumedLoc(PM.MacroNameToken.getLocation());
  244. StringRef USR =
  245. API.recordUSRForMacro(Name, PM.MacroNameToken.getLocation(), SM);
  246. API.addMacroDefinition(
  247. Name, USR, Loc,
  248. DeclarationFragmentsBuilder::getFragmentsForMacro(Name, PM.MD),
  249. DeclarationFragmentsBuilder::getSubHeadingForMacro(Name),
  250. SM.isInSystemHeader(PM.MacroNameToken.getLocation()));
  251. }
  252. PendingMacros.clear();
  253. }
  254. private:
  255. struct PendingMacro {
  256. Token MacroNameToken;
  257. const MacroDirective *MD;
  258. PendingMacro(const Token &MacroNameToken, const MacroDirective *MD)
  259. : MacroNameToken(MacroNameToken), MD(MD) {}
  260. };
  261. const SourceManager &SM;
  262. LocationFileChecker &LCF;
  263. APISet &API;
  264. Preprocessor &PP;
  265. llvm::SmallVector<PendingMacro> PendingMacros;
  266. };
  267. } // namespace
  268. std::unique_ptr<ASTConsumer>
  269. ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
  270. OS = CreateOutputFile(CI, InFile);
  271. if (!OS)
  272. return nullptr;
  273. auto ProductName = CI.getFrontendOpts().ProductName;
  274. // Now that we have enough information about the language options and the
  275. // target triple, let's create the APISet before anyone uses it.
  276. API = std::make_unique<APISet>(
  277. CI.getTarget().getTriple(),
  278. CI.getFrontendOpts().Inputs.back().getKind().getLanguage(), ProductName);
  279. auto LCF = std::make_unique<LocationFileChecker>(CI, KnownInputFiles);
  280. CI.getPreprocessor().addPPCallbacks(std::make_unique<MacroCallback>(
  281. CI.getSourceManager(), *LCF, *API, CI.getPreprocessor()));
  282. // Do not include location in anonymous decls.
  283. PrintingPolicy Policy = CI.getASTContext().getPrintingPolicy();
  284. Policy.AnonymousTagLocations = false;
  285. CI.getASTContext().setPrintingPolicy(Policy);
  286. if (!CI.getFrontendOpts().ExtractAPIIgnoresFile.empty()) {
  287. llvm::handleAllErrors(
  288. APIIgnoresList::create(CI.getFrontendOpts().ExtractAPIIgnoresFile,
  289. CI.getFileManager())
  290. .moveInto(IgnoresList),
  291. [&CI](const IgnoresFileNotFound &Err) {
  292. CI.getDiagnostics().Report(
  293. diag::err_extract_api_ignores_file_not_found)
  294. << Err.Path;
  295. });
  296. }
  297. return std::make_unique<ExtractAPIConsumer>(CI.getASTContext(),
  298. std::move(LCF), *API);
  299. }
  300. bool ExtractAPIAction::PrepareToExecuteAction(CompilerInstance &CI) {
  301. auto &Inputs = CI.getFrontendOpts().Inputs;
  302. if (Inputs.empty())
  303. return true;
  304. if (!CI.hasFileManager())
  305. if (!CI.createFileManager())
  306. return false;
  307. auto Kind = Inputs[0].getKind();
  308. // Convert the header file inputs into a single input buffer.
  309. SmallString<256> HeaderContents;
  310. bool IsQuoted = false;
  311. for (const FrontendInputFile &FIF : Inputs) {
  312. if (Kind.isObjectiveC())
  313. HeaderContents += "#import";
  314. else
  315. HeaderContents += "#include";
  316. StringRef FilePath = FIF.getFile();
  317. if (auto RelativeName = getRelativeIncludeName(CI, FilePath, &IsQuoted)) {
  318. if (IsQuoted)
  319. HeaderContents += " \"";
  320. else
  321. HeaderContents += " <";
  322. HeaderContents += *RelativeName;
  323. if (IsQuoted)
  324. HeaderContents += "\"\n";
  325. else
  326. HeaderContents += ">\n";
  327. KnownInputFiles.emplace_back(static_cast<SmallString<32>>(*RelativeName),
  328. IsQuoted);
  329. } else {
  330. HeaderContents += " \"";
  331. HeaderContents += FilePath;
  332. HeaderContents += "\"\n";
  333. KnownInputFiles.emplace_back(FilePath, true);
  334. }
  335. }
  336. if (CI.getHeaderSearchOpts().Verbose)
  337. CI.getVerboseOutputStream() << getInputBufferName() << ":\n"
  338. << HeaderContents << "\n";
  339. Buffer = llvm::MemoryBuffer::getMemBufferCopy(HeaderContents,
  340. getInputBufferName());
  341. // Set that buffer up as our "real" input in the CompilerInstance.
  342. Inputs.clear();
  343. Inputs.emplace_back(Buffer->getMemBufferRef(), Kind, /*IsSystem*/ false);
  344. return true;
  345. }
  346. void ExtractAPIAction::EndSourceFileAction() {
  347. if (!OS)
  348. return;
  349. // Setup a SymbolGraphSerializer to write out collected API information in
  350. // the Symbol Graph format.
  351. // FIXME: Make the kind of APISerializer configurable.
  352. SymbolGraphSerializer SGSerializer(*API, IgnoresList);
  353. SGSerializer.serialize(*OS);
  354. OS.reset();
  355. }
  356. std::unique_ptr<raw_pwrite_stream>
  357. ExtractAPIAction::CreateOutputFile(CompilerInstance &CI, StringRef InFile) {
  358. std::unique_ptr<raw_pwrite_stream> OS =
  359. CI.createDefaultOutputFile(/*Binary=*/false, InFile, /*Extension=*/"json",
  360. /*RemoveFileOnSignal=*/false);
  361. if (!OS)
  362. return nullptr;
  363. return OS;
  364. }