Path: blob/main/contrib/llvm-project/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp
35233 views
//===- ExtractAPI/ExtractAPIConsumer.cpp ------------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7///8/// \file9/// This file implements the ExtractAPIAction, and ASTConsumer to collect API10/// information.11///12//===----------------------------------------------------------------------===//1314#include "clang/AST/ASTConcept.h"15#include "clang/AST/ASTConsumer.h"16#include "clang/AST/ASTContext.h"17#include "clang/AST/DeclObjC.h"18#include "clang/Basic/DiagnosticFrontend.h"19#include "clang/Basic/FileEntry.h"20#include "clang/Basic/SourceLocation.h"21#include "clang/Basic/SourceManager.h"22#include "clang/Basic/TargetInfo.h"23#include "clang/ExtractAPI/API.h"24#include "clang/ExtractAPI/APIIgnoresList.h"25#include "clang/ExtractAPI/ExtractAPIVisitor.h"26#include "clang/ExtractAPI/FrontendActions.h"27#include "clang/ExtractAPI/Serialization/SymbolGraphSerializer.h"28#include "clang/Frontend/ASTConsumers.h"29#include "clang/Frontend/CompilerInstance.h"30#include "clang/Frontend/FrontendOptions.h"31#include "clang/Frontend/MultiplexConsumer.h"32#include "clang/Index/USRGeneration.h"33#include "clang/InstallAPI/HeaderFile.h"34#include "clang/Lex/MacroInfo.h"35#include "clang/Lex/PPCallbacks.h"36#include "clang/Lex/Preprocessor.h"37#include "clang/Lex/PreprocessorOptions.h"38#include "llvm/ADT/DenseSet.h"39#include "llvm/ADT/STLExtras.h"40#include "llvm/ADT/SmallString.h"41#include "llvm/ADT/SmallVector.h"42#include "llvm/ADT/StringRef.h"43#include "llvm/Support/Casting.h"44#include "llvm/Support/Error.h"45#include "llvm/Support/FileSystem.h"46#include "llvm/Support/MemoryBuffer.h"47#include "llvm/Support/Path.h"48#include "llvm/Support/Regex.h"49#include "llvm/Support/raw_ostream.h"50#include <memory>51#include <optional>52#include <utility>5354using namespace clang;55using namespace extractapi;5657namespace {5859std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,60StringRef File,61bool *IsQuoted = nullptr) {62assert(CI.hasFileManager() &&63"CompilerInstance does not have a FileNamager!");6465using namespace llvm::sys;66const auto &FS = CI.getVirtualFileSystem();6768SmallString<128> FilePath(File.begin(), File.end());69FS.makeAbsolute(FilePath);70path::remove_dots(FilePath, true);71FilePath = path::convert_to_slash(FilePath);72File = FilePath;7374// Checks whether `Dir` is a strict path prefix of `File`. If so returns75// the prefix length. Otherwise return 0.76auto CheckDir = [&](llvm::StringRef Dir) -> unsigned {77llvm::SmallString<32> DirPath(Dir.begin(), Dir.end());78FS.makeAbsolute(DirPath);79path::remove_dots(DirPath, true);80Dir = DirPath;81for (auto NI = path::begin(File), NE = path::end(File),82DI = path::begin(Dir), DE = path::end(Dir);83/*termination condition in loop*/; ++NI, ++DI) {84// '.' components in File are ignored.85while (NI != NE && *NI == ".")86++NI;87if (NI == NE)88break;8990// '.' components in Dir are ignored.91while (DI != DE && *DI == ".")92++DI;9394// Dir is a prefix of File, up to '.' components and choice of path95// separators.96if (DI == DE)97return NI - path::begin(File);9899// Consider all path separators equal.100if (NI->size() == 1 && DI->size() == 1 &&101path::is_separator(NI->front()) && path::is_separator(DI->front()))102continue;103104// Special case Apple .sdk folders since the search path is typically a105// symlink like `iPhoneSimulator14.5.sdk` while the file is instead106// located in `iPhoneSimulator.sdk` (the real folder).107if (NI->ends_with(".sdk") && DI->ends_with(".sdk")) {108StringRef NBasename = path::stem(*NI);109StringRef DBasename = path::stem(*DI);110if (DBasename.starts_with(NBasename))111continue;112}113114if (*NI != *DI)115break;116}117return 0;118};119120unsigned PrefixLength = 0;121122// Go through the search paths and find the first one that is a prefix of123// the header.124for (const auto &Entry : CI.getHeaderSearchOpts().UserEntries) {125// Note whether the match is found in a quoted entry.126if (IsQuoted)127*IsQuoted = Entry.Group == frontend::Quoted;128129if (auto EntryFile = CI.getFileManager().getOptionalFileRef(Entry.Path)) {130if (auto HMap = HeaderMap::Create(*EntryFile, CI.getFileManager())) {131// If this is a headermap entry, try to reverse lookup the full path132// for a spelled name before mapping.133StringRef SpelledFilename = HMap->reverseLookupFilename(File);134if (!SpelledFilename.empty())135return SpelledFilename.str();136137// No matching mapping in this headermap, try next search entry.138continue;139}140}141142// Entry is a directory search entry, try to check if it's a prefix of File.143PrefixLength = CheckDir(Entry.Path);144if (PrefixLength > 0) {145// The header is found in a framework path, construct the framework-style146// include name `<Framework/Header.h>`147if (Entry.IsFramework) {148SmallVector<StringRef, 4> Matches;149clang::installapi::HeaderFile::getFrameworkIncludeRule().match(150File, &Matches);151// Returned matches are always in stable order.152if (Matches.size() != 4)153return std::nullopt;154155return path::convert_to_slash(156(Matches[1].drop_front(Matches[1].rfind('/') + 1) + "/" +157Matches[3])158.str());159}160161// The header is found in a normal search path, strip the search path162// prefix to get an include name.163return path::convert_to_slash(File.drop_front(PrefixLength));164}165}166167// Couldn't determine a include name, use full path instead.168return std::nullopt;169}170171std::optional<std::string> getRelativeIncludeName(const CompilerInstance &CI,172FileEntryRef FE,173bool *IsQuoted = nullptr) {174return getRelativeIncludeName(CI, FE.getNameAsRequested(), IsQuoted);175}176177struct LocationFileChecker {178bool operator()(SourceLocation Loc) {179// If the loc refers to a macro expansion we need to first get the file180// location of the expansion.181auto &SM = CI.getSourceManager();182auto FileLoc = SM.getFileLoc(Loc);183FileID FID = SM.getFileID(FileLoc);184if (FID.isInvalid())185return false;186187OptionalFileEntryRef File = SM.getFileEntryRefForID(FID);188if (!File)189return false;190191if (KnownFileEntries.count(*File))192return true;193194if (ExternalFileEntries.count(*File))195return false;196197// Try to reduce the include name the same way we tried to include it.198bool IsQuoted = false;199if (auto IncludeName = getRelativeIncludeName(CI, *File, &IsQuoted))200if (llvm::any_of(KnownFiles,201[&IsQuoted, &IncludeName](const auto &KnownFile) {202return KnownFile.first.equals(*IncludeName) &&203KnownFile.second == IsQuoted;204})) {205KnownFileEntries.insert(*File);206return true;207}208209// Record that the file was not found to avoid future reverse lookup for210// the same file.211ExternalFileEntries.insert(*File);212return false;213}214215LocationFileChecker(const CompilerInstance &CI,216SmallVector<std::pair<SmallString<32>, bool>> &KnownFiles)217: CI(CI), KnownFiles(KnownFiles), ExternalFileEntries() {218for (const auto &KnownFile : KnownFiles)219if (auto FileEntry = CI.getFileManager().getFile(KnownFile.first))220KnownFileEntries.insert(*FileEntry);221}222223private:224const CompilerInstance &CI;225SmallVector<std::pair<SmallString<32>, bool>> &KnownFiles;226llvm::DenseSet<const FileEntry *> KnownFileEntries;227llvm::DenseSet<const FileEntry *> ExternalFileEntries;228};229230struct BatchExtractAPIVisitor : ExtractAPIVisitor<BatchExtractAPIVisitor> {231bool shouldDeclBeIncluded(const Decl *D) const {232bool ShouldBeIncluded = true;233// Check that we have the definition for redeclarable types.234if (auto *TD = llvm::dyn_cast<TagDecl>(D))235ShouldBeIncluded = TD->isThisDeclarationADefinition();236else if (auto *Interface = llvm::dyn_cast<ObjCInterfaceDecl>(D))237ShouldBeIncluded = Interface->isThisDeclarationADefinition();238else if (auto *Protocol = llvm::dyn_cast<ObjCProtocolDecl>(D))239ShouldBeIncluded = Protocol->isThisDeclarationADefinition();240241ShouldBeIncluded = ShouldBeIncluded && LCF(D->getLocation());242return ShouldBeIncluded;243}244245BatchExtractAPIVisitor(LocationFileChecker &LCF, ASTContext &Context,246APISet &API)247: ExtractAPIVisitor<BatchExtractAPIVisitor>(Context, API), LCF(LCF) {}248249private:250LocationFileChecker &LCF;251};252253class WrappingExtractAPIConsumer : public ASTConsumer {254public:255WrappingExtractAPIConsumer(ASTContext &Context, APISet &API)256: Visitor(Context, API) {}257258void HandleTranslationUnit(ASTContext &Context) override {259// Use ExtractAPIVisitor to traverse symbol declarations in the context.260Visitor.TraverseDecl(Context.getTranslationUnitDecl());261}262263private:264ExtractAPIVisitor<> Visitor;265};266267class ExtractAPIConsumer : public ASTConsumer {268public:269ExtractAPIConsumer(ASTContext &Context,270std::unique_ptr<LocationFileChecker> LCF, APISet &API)271: Visitor(*LCF, Context, API), LCF(std::move(LCF)) {}272273void HandleTranslationUnit(ASTContext &Context) override {274// Use ExtractAPIVisitor to traverse symbol declarations in the context.275Visitor.TraverseDecl(Context.getTranslationUnitDecl());276}277278private:279BatchExtractAPIVisitor Visitor;280std::unique_ptr<LocationFileChecker> LCF;281};282283class MacroCallback : public PPCallbacks {284public:285MacroCallback(const SourceManager &SM, APISet &API, Preprocessor &PP)286: SM(SM), API(API), PP(PP) {}287288void MacroDefined(const Token &MacroNameToken,289const MacroDirective *MD) override {290auto *MacroInfo = MD->getMacroInfo();291292if (MacroInfo->isBuiltinMacro())293return;294295auto SourceLoc = MacroNameToken.getLocation();296if (SM.isWrittenInBuiltinFile(SourceLoc) ||297SM.isWrittenInCommandLineFile(SourceLoc))298return;299300PendingMacros.emplace_back(MacroNameToken, MD);301}302303// If a macro gets undefined at some point during preprocessing of the inputs304// it means that it isn't an exposed API and we should therefore not add a305// macro definition for it.306void MacroUndefined(const Token &MacroNameToken, const MacroDefinition &MD,307const MacroDirective *Undef) override {308// If this macro wasn't previously defined we don't need to do anything309// here.310if (!Undef)311return;312313llvm::erase_if(PendingMacros, [&MD, this](const PendingMacro &PM) {314return MD.getMacroInfo()->isIdenticalTo(*PM.MD->getMacroInfo(), PP,315/*Syntactically*/ false);316});317}318319void EndOfMainFile() override {320for (auto &PM : PendingMacros) {321// `isUsedForHeaderGuard` is only set when the preprocessor leaves the322// file so check for it here.323if (PM.MD->getMacroInfo()->isUsedForHeaderGuard())324continue;325326if (!shouldMacroBeIncluded(PM))327continue;328329StringRef Name = PM.MacroNameToken.getIdentifierInfo()->getName();330PresumedLoc Loc = SM.getPresumedLoc(PM.MacroNameToken.getLocation());331SmallString<128> USR;332index::generateUSRForMacro(Name, PM.MacroNameToken.getLocation(), SM,333USR);334335API.createRecord<extractapi::MacroDefinitionRecord>(336USR, Name, SymbolReference(), Loc,337DeclarationFragmentsBuilder::getFragmentsForMacro(Name, PM.MD),338DeclarationFragmentsBuilder::getSubHeadingForMacro(Name),339SM.isInSystemHeader(PM.MacroNameToken.getLocation()));340}341342PendingMacros.clear();343}344345protected:346struct PendingMacro {347Token MacroNameToken;348const MacroDirective *MD;349350PendingMacro(const Token &MacroNameToken, const MacroDirective *MD)351: MacroNameToken(MacroNameToken), MD(MD) {}352};353354virtual bool shouldMacroBeIncluded(const PendingMacro &PM) { return true; }355356const SourceManager &SM;357APISet &API;358Preprocessor &PP;359llvm::SmallVector<PendingMacro> PendingMacros;360};361362class APIMacroCallback : public MacroCallback {363public:364APIMacroCallback(const SourceManager &SM, APISet &API, Preprocessor &PP,365LocationFileChecker &LCF)366: MacroCallback(SM, API, PP), LCF(LCF) {}367368bool shouldMacroBeIncluded(const PendingMacro &PM) override {369// Do not include macros from external files370return LCF(PM.MacroNameToken.getLocation());371}372373private:374LocationFileChecker &LCF;375};376377std::unique_ptr<llvm::raw_pwrite_stream>378createAdditionalSymbolGraphFile(CompilerInstance &CI, Twine BaseName) {379auto OutputDirectory = CI.getFrontendOpts().SymbolGraphOutputDir;380381SmallString<256> FileName;382llvm::sys::path::append(FileName, OutputDirectory,383BaseName + ".symbols.json");384return CI.createOutputFile(385FileName, /*Binary*/ false, /*RemoveFileOnSignal*/ false,386/*UseTemporary*/ true, /*CreateMissingDirectories*/ true);387}388389} // namespace390391void ExtractAPIActionBase::ImplEndSourceFileAction(CompilerInstance &CI) {392SymbolGraphSerializerOption SerializationOptions;393SerializationOptions.Compact = !CI.getFrontendOpts().EmitPrettySymbolGraphs;394SerializationOptions.EmitSymbolLabelsForTesting =395CI.getFrontendOpts().EmitSymbolGraphSymbolLabelsForTesting;396397if (CI.getFrontendOpts().EmitExtensionSymbolGraphs) {398auto ConstructOutputFile = [&CI](Twine BaseName) {399return createAdditionalSymbolGraphFile(CI, BaseName);400};401402SymbolGraphSerializer::serializeWithExtensionGraphs(403*OS, *API, IgnoresList, ConstructOutputFile, SerializationOptions);404} else {405SymbolGraphSerializer::serializeMainSymbolGraph(*OS, *API, IgnoresList,406SerializationOptions);407}408409// Flush the stream and close the main output stream.410OS.reset();411}412413std::unique_ptr<ASTConsumer>414ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {415auto ProductName = CI.getFrontendOpts().ProductName;416417if (CI.getFrontendOpts().SymbolGraphOutputDir.empty())418OS = CI.createDefaultOutputFile(/*Binary*/ false, InFile,419/*Extension*/ "symbols.json",420/*RemoveFileOnSignal*/ false,421/*CreateMissingDirectories*/ true);422else423OS = createAdditionalSymbolGraphFile(CI, ProductName);424425if (!OS)426return nullptr;427428// Now that we have enough information about the language options and the429// target triple, let's create the APISet before anyone uses it.430API = std::make_unique<APISet>(431CI.getTarget().getTriple(),432CI.getFrontendOpts().Inputs.back().getKind().getLanguage(), ProductName);433434auto LCF = std::make_unique<LocationFileChecker>(CI, KnownInputFiles);435436CI.getPreprocessor().addPPCallbacks(std::make_unique<APIMacroCallback>(437CI.getSourceManager(), *API, CI.getPreprocessor(), *LCF));438439// Do not include location in anonymous decls.440PrintingPolicy Policy = CI.getASTContext().getPrintingPolicy();441Policy.AnonymousTagLocations = false;442CI.getASTContext().setPrintingPolicy(Policy);443444if (!CI.getFrontendOpts().ExtractAPIIgnoresFileList.empty()) {445llvm::handleAllErrors(446APIIgnoresList::create(CI.getFrontendOpts().ExtractAPIIgnoresFileList,447CI.getFileManager())448.moveInto(IgnoresList),449[&CI](const IgnoresFileNotFound &Err) {450CI.getDiagnostics().Report(451diag::err_extract_api_ignores_file_not_found)452<< Err.Path;453});454}455456return std::make_unique<ExtractAPIConsumer>(CI.getASTContext(),457std::move(LCF), *API);458}459460bool ExtractAPIAction::PrepareToExecuteAction(CompilerInstance &CI) {461auto &Inputs = CI.getFrontendOpts().Inputs;462if (Inputs.empty())463return true;464465if (!CI.hasFileManager())466if (!CI.createFileManager())467return false;468469auto Kind = Inputs[0].getKind();470471// Convert the header file inputs into a single input buffer.472SmallString<256> HeaderContents;473bool IsQuoted = false;474for (const FrontendInputFile &FIF : Inputs) {475if (Kind.isObjectiveC())476HeaderContents += "#import";477else478HeaderContents += "#include";479480StringRef FilePath = FIF.getFile();481if (auto RelativeName = getRelativeIncludeName(CI, FilePath, &IsQuoted)) {482if (IsQuoted)483HeaderContents += " \"";484else485HeaderContents += " <";486487HeaderContents += *RelativeName;488489if (IsQuoted)490HeaderContents += "\"\n";491else492HeaderContents += ">\n";493KnownInputFiles.emplace_back(static_cast<SmallString<32>>(*RelativeName),494IsQuoted);495} else {496HeaderContents += " \"";497HeaderContents += FilePath;498HeaderContents += "\"\n";499KnownInputFiles.emplace_back(FilePath, true);500}501}502503if (CI.getHeaderSearchOpts().Verbose)504CI.getVerboseOutputStream() << getInputBufferName() << ":\n"505<< HeaderContents << "\n";506507Buffer = llvm::MemoryBuffer::getMemBufferCopy(HeaderContents,508getInputBufferName());509510// Set that buffer up as our "real" input in the CompilerInstance.511Inputs.clear();512Inputs.emplace_back(Buffer->getMemBufferRef(), Kind, /*IsSystem*/ false);513514return true;515}516517void ExtractAPIAction::EndSourceFileAction() {518ImplEndSourceFileAction(getCompilerInstance());519}520521std::unique_ptr<ASTConsumer>522WrappingExtractAPIAction::CreateASTConsumer(CompilerInstance &CI,523StringRef InFile) {524auto OtherConsumer = WrapperFrontendAction::CreateASTConsumer(CI, InFile);525if (!OtherConsumer)526return nullptr;527528CreatedASTConsumer = true;529530ProductName = CI.getFrontendOpts().ProductName;531auto InputFilename = llvm::sys::path::filename(InFile);532OS = createAdditionalSymbolGraphFile(CI, InputFilename);533534// Now that we have enough information about the language options and the535// target triple, let's create the APISet before anyone uses it.536API = std::make_unique<APISet>(537CI.getTarget().getTriple(),538CI.getFrontendOpts().Inputs.back().getKind().getLanguage(), ProductName);539540CI.getPreprocessor().addPPCallbacks(std::make_unique<MacroCallback>(541CI.getSourceManager(), *API, CI.getPreprocessor()));542543// Do not include location in anonymous decls.544PrintingPolicy Policy = CI.getASTContext().getPrintingPolicy();545Policy.AnonymousTagLocations = false;546CI.getASTContext().setPrintingPolicy(Policy);547548if (!CI.getFrontendOpts().ExtractAPIIgnoresFileList.empty()) {549llvm::handleAllErrors(550APIIgnoresList::create(CI.getFrontendOpts().ExtractAPIIgnoresFileList,551CI.getFileManager())552.moveInto(IgnoresList),553[&CI](const IgnoresFileNotFound &Err) {554CI.getDiagnostics().Report(555diag::err_extract_api_ignores_file_not_found)556<< Err.Path;557});558}559560auto WrappingConsumer =561std::make_unique<WrappingExtractAPIConsumer>(CI.getASTContext(), *API);562std::vector<std::unique_ptr<ASTConsumer>> Consumers;563Consumers.push_back(std::move(OtherConsumer));564Consumers.push_back(std::move(WrappingConsumer));565566return std::make_unique<MultiplexConsumer>(std::move(Consumers));567}568569void WrappingExtractAPIAction::EndSourceFileAction() {570// Invoke wrapped action's method.571WrapperFrontendAction::EndSourceFileAction();572573if (CreatedASTConsumer) {574ImplEndSourceFileAction(getCompilerInstance());575}576}577578579