//===- Visitor.cpp ---------------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/InstallAPI/Visitor.h" #include "clang/AST/Availability.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/VTableBuilder.h" #include "clang/Basic/Linkage.h" #include "clang/InstallAPI/DylibVerifier.h" #include "clang/InstallAPI/FrontendRecords.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/Mangler.h" using namespace llvm; using namespace llvm::MachO; namespace { enum class CXXLinkage { ExternalLinkage, LinkOnceODRLinkage, WeakODRLinkage, PrivateLinkage, }; } namespace clang::installapi { // Exported NamedDecl needs to have external linkage and // default visibility from LinkageComputer. static bool isExported(const NamedDecl *D) { auto LV = D->getLinkageAndVisibility(); return isExternallyVisible(LV.getLinkage()) && (LV.getVisibility() == DefaultVisibility); } static bool isInlined(const FunctionDecl *D) { bool HasInlineAttribute = false; bool NoCXXAttr = (!D->getASTContext().getLangOpts().CPlusPlus && !D->getASTContext().getTargetInfo().getCXXABI().isMicrosoft() && !D->hasAttr()); // Check all redeclarations to find an inline attribute or keyword. for (const auto *RD : D->redecls()) { if (!RD->isInlined()) continue; HasInlineAttribute = true; if (!(NoCXXAttr || RD->hasAttr())) continue; if (RD->doesThisDeclarationHaveABody() && RD->isInlineDefinitionExternallyVisible()) return false; } if (!HasInlineAttribute) return false; return true; } static SymbolFlags getFlags(bool WeakDef, bool ThreadLocal = false) { SymbolFlags Result = SymbolFlags::None; if (WeakDef) Result |= SymbolFlags::WeakDefined; if (ThreadLocal) Result |= SymbolFlags::ThreadLocalValue; return Result; } void InstallAPIVisitor::HandleTranslationUnit(ASTContext &ASTCtx) { if (ASTCtx.getDiagnostics().hasErrorOccurred()) return; auto *D = ASTCtx.getTranslationUnitDecl(); TraverseDecl(D); } std::string InstallAPIVisitor::getMangledName(const NamedDecl *D) const { SmallString<256> Name; if (MC->shouldMangleDeclName(D)) { raw_svector_ostream NStream(Name); MC->mangleName(D, NStream); } else Name += D->getNameAsString(); return getBackendMangledName(Name); } std::string InstallAPIVisitor::getBackendMangledName(Twine Name) const { SmallString<256> FinalName; Mangler::getNameWithPrefix(FinalName, Name, DataLayout(Layout)); return std::string(FinalName); } std::optional InstallAPIVisitor::getAccessForDecl(const NamedDecl *D) const { SourceLocation Loc = D->getLocation(); if (Loc.isInvalid()) return std::nullopt; // If the loc refers to a macro expansion, InstallAPI needs to first get the // file location of the expansion. auto FileLoc = SrcMgr.getFileLoc(Loc); FileID ID = SrcMgr.getFileID(FileLoc); if (ID.isInvalid()) return std::nullopt; const FileEntry *FE = SrcMgr.getFileEntryForID(ID); if (!FE) return std::nullopt; auto Header = Ctx.findAndRecordFile(FE, PP); if (!Header.has_value()) return std::nullopt; HeaderType Access = Header.value(); assert(Access != HeaderType::Unknown && "unexpected access level for global"); return Access; } /// Check if the interface itself or any of its super classes have an /// exception attribute. InstallAPI needs to export an additional symbol /// ("OBJC_EHTYPE_$CLASS_NAME") if any of the classes have the exception /// attribute. static bool hasObjCExceptionAttribute(const ObjCInterfaceDecl *D) { for (; D != nullptr; D = D->getSuperClass()) if (D->hasAttr()) return true; return false; } void InstallAPIVisitor::recordObjCInstanceVariables( const ASTContext &ASTCtx, ObjCContainerRecord *Record, StringRef SuperClass, const llvm::iterator_range< DeclContext::specific_decl_iterator> Ivars) { RecordLinkage Linkage = RecordLinkage::Exported; const RecordLinkage ContainerLinkage = Record->getLinkage(); // If fragile, set to unknown. if (ASTCtx.getLangOpts().ObjCRuntime.isFragile()) Linkage = RecordLinkage::Unknown; // Linkage should be inherited from container. else if (ContainerLinkage != RecordLinkage::Unknown) Linkage = ContainerLinkage; for (const auto *IV : Ivars) { auto Access = getAccessForDecl(IV); if (!Access) continue; StringRef Name = IV->getName(); const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(IV); auto AC = IV->getCanonicalAccessControl(); auto [ObjCIVR, FA] = Ctx.Slice->addObjCIVar(Record, Name, Linkage, Avail, IV, *Access, AC); Ctx.Verifier->verify(ObjCIVR, FA, SuperClass); } } bool InstallAPIVisitor::VisitObjCInterfaceDecl(const ObjCInterfaceDecl *D) { // Skip forward declaration for classes (@class) if (!D->isThisDeclarationADefinition()) return true; // Skip over declarations that access could not be collected for. auto Access = getAccessForDecl(D); if (!Access) return true; StringRef Name = D->getObjCRuntimeNameAsString(); const RecordLinkage Linkage = isExported(D) ? RecordLinkage::Exported : RecordLinkage::Internal; const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D); const bool IsEHType = (!D->getASTContext().getLangOpts().ObjCRuntime.isFragile() && hasObjCExceptionAttribute(D)); auto [Class, FA] = Ctx.Slice->addObjCInterface(Name, Linkage, Avail, D, *Access, IsEHType); Ctx.Verifier->verify(Class, FA); // Get base class. StringRef SuperClassName; if (const auto *SuperClass = D->getSuperClass()) SuperClassName = SuperClass->getObjCRuntimeNameAsString(); recordObjCInstanceVariables(D->getASTContext(), Class, Class->getName(), D->ivars()); return true; } bool InstallAPIVisitor::VisitObjCCategoryDecl(const ObjCCategoryDecl *D) { StringRef CategoryName = D->getName(); // Skip over declarations that access could not be collected for. auto Access = getAccessForDecl(D); if (!Access) return true; const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D); const ObjCInterfaceDecl *InterfaceD = D->getClassInterface(); const StringRef InterfaceName = InterfaceD->getName(); ObjCCategoryRecord *CategoryRecord = Ctx.Slice->addObjCCategory(InterfaceName, CategoryName, Avail, D, *Access) .first; recordObjCInstanceVariables(D->getASTContext(), CategoryRecord, InterfaceName, D->ivars()); return true; } bool InstallAPIVisitor::VisitVarDecl(const VarDecl *D) { // Skip function parameters. if (isa(D)) return true; // Skip variables in records. They are handled separately for C++. if (D->getDeclContext()->isRecord()) return true; // Skip anything inside functions or methods. if (!D->isDefinedOutsideFunctionOrMethod()) return true; // If this is a template but not specialization or instantiation, skip. if (D->getASTContext().getTemplateOrSpecializationInfo(D) && D->getTemplateSpecializationKind() == TSK_Undeclared) return true; // Skip over declarations that access could not collected for. auto Access = getAccessForDecl(D); if (!Access) return true; const RecordLinkage Linkage = isExported(D) ? RecordLinkage::Exported : RecordLinkage::Internal; const bool WeakDef = D->hasAttr(); const bool ThreadLocal = D->getTLSKind() != VarDecl::TLS_None; const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D); auto [GR, FA] = Ctx.Slice->addGlobal(getMangledName(D), Linkage, GlobalRecord::Kind::Variable, Avail, D, *Access, getFlags(WeakDef, ThreadLocal)); Ctx.Verifier->verify(GR, FA); return true; } bool InstallAPIVisitor::VisitFunctionDecl(const FunctionDecl *D) { if (const CXXMethodDecl *M = dyn_cast(D)) { // Skip member function in class templates. if (M->getParent()->getDescribedClassTemplate() != nullptr) return true; // Skip methods in CXX RecordDecls. for (const DynTypedNode &P : D->getASTContext().getParents(*M)) { if (P.get()) return true; } // Skip CXX ConstructorDecls and DestructorDecls. if (isa(M) || isa(M)) return true; } // Skip templated functions. switch (D->getTemplatedKind()) { case FunctionDecl::TK_NonTemplate: case FunctionDecl::TK_DependentNonTemplate: break; case FunctionDecl::TK_MemberSpecialization: case FunctionDecl::TK_FunctionTemplateSpecialization: if (auto *TempInfo = D->getTemplateSpecializationInfo()) { if (!TempInfo->isExplicitInstantiationOrSpecialization()) return true; } break; case FunctionDecl::TK_FunctionTemplate: case FunctionDecl::TK_DependentFunctionTemplateSpecialization: return true; } auto Access = getAccessForDecl(D); if (!Access) return true; auto Name = getMangledName(D); const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D); const bool ExplicitInstantiation = D->getTemplateSpecializationKind() == TSK_ExplicitInstantiationDeclaration; const bool WeakDef = ExplicitInstantiation || D->hasAttr(); const bool Inlined = isInlined(D); const RecordLinkage Linkage = (Inlined || !isExported(D)) ? RecordLinkage::Internal : RecordLinkage::Exported; auto [GR, FA] = Ctx.Slice->addGlobal(Name, Linkage, GlobalRecord::Kind::Function, Avail, D, *Access, getFlags(WeakDef), Inlined); Ctx.Verifier->verify(GR, FA); return true; } static bool hasVTable(const CXXRecordDecl *D) { // Check if vtable symbols should be emitted, only dynamic classes need // vtables. if (!D->hasDefinition() || !D->isDynamicClass()) return false; assert(D->isExternallyVisible() && "Should be externally visible"); assert(D->isCompleteDefinition() && "Only works on complete definitions"); const CXXMethodDecl *KeyFunctionD = D->getASTContext().getCurrentKeyFunction(D); // If this class has a key function, then there is a vtable, possibly internal // though. if (KeyFunctionD) { switch (KeyFunctionD->getTemplateSpecializationKind()) { case TSK_Undeclared: case TSK_ExplicitSpecialization: case TSK_ImplicitInstantiation: case TSK_ExplicitInstantiationDefinition: return true; case TSK_ExplicitInstantiationDeclaration: llvm_unreachable( "Unexpected TemplateSpecializationKind for key function"); } } else if (D->isAbstract()) { // If the class is abstract and it doesn't have a key function, it is a // 'pure' virtual class. It doesn't need a vtable. return false; } switch (D->getTemplateSpecializationKind()) { case TSK_Undeclared: case TSK_ExplicitSpecialization: case TSK_ImplicitInstantiation: return false; case TSK_ExplicitInstantiationDeclaration: case TSK_ExplicitInstantiationDefinition: return true; } llvm_unreachable("Invalid TemplateSpecializationKind!"); } static CXXLinkage getVTableLinkage(const CXXRecordDecl *D) { assert((D->hasDefinition() && D->isDynamicClass()) && "Record has no vtable"); assert(D->isExternallyVisible() && "Record should be externally visible"); if (D->getVisibility() == HiddenVisibility) return CXXLinkage::PrivateLinkage; const CXXMethodDecl *KeyFunctionD = D->getASTContext().getCurrentKeyFunction(D); if (KeyFunctionD) { // If this class has a key function, use that to determine the // linkage of the vtable. switch (KeyFunctionD->getTemplateSpecializationKind()) { case TSK_Undeclared: case TSK_ExplicitSpecialization: if (isInlined(KeyFunctionD)) return CXXLinkage::LinkOnceODRLinkage; return CXXLinkage::ExternalLinkage; case TSK_ImplicitInstantiation: llvm_unreachable("No external vtable for implicit instantiations"); case TSK_ExplicitInstantiationDefinition: return CXXLinkage::WeakODRLinkage; case TSK_ExplicitInstantiationDeclaration: llvm_unreachable( "Unexpected TemplateSpecializationKind for key function"); } } switch (D->getTemplateSpecializationKind()) { case TSK_Undeclared: case TSK_ExplicitSpecialization: case TSK_ImplicitInstantiation: return CXXLinkage::LinkOnceODRLinkage; case TSK_ExplicitInstantiationDeclaration: case TSK_ExplicitInstantiationDefinition: return CXXLinkage::WeakODRLinkage; } llvm_unreachable("Invalid TemplateSpecializationKind!"); } static bool isRTTIWeakDef(const CXXRecordDecl *D) { if (D->hasAttr()) return true; if (D->isAbstract() && D->getASTContext().getCurrentKeyFunction(D) == nullptr) return true; if (D->isDynamicClass()) return getVTableLinkage(D) != CXXLinkage::ExternalLinkage; return false; } static bool hasRTTI(const CXXRecordDecl *D) { if (!D->getASTContext().getLangOpts().RTTI) return false; if (!D->hasDefinition()) return false; if (!D->isDynamicClass()) return false; // Don't emit weak-def RTTI information. InstallAPI cannot reliably determine // if the final binary will have those weak defined RTTI symbols. This depends // on the optimization level and if the class has been instantiated and used. // // Luckily, the Apple static linker doesn't need those weak defined RTTI // symbols for linking. They are only needed by the runtime linker. That means // they can be safely dropped. if (isRTTIWeakDef(D)) return false; return true; } std::string InstallAPIVisitor::getMangledCXXRTTIName(const CXXRecordDecl *D) const { SmallString<256> Name; raw_svector_ostream NameStream(Name); MC->mangleCXXRTTIName(QualType(D->getTypeForDecl(), 0), NameStream); return getBackendMangledName(Name); } std::string InstallAPIVisitor::getMangledCXXRTTI(const CXXRecordDecl *D) const { SmallString<256> Name; raw_svector_ostream NameStream(Name); MC->mangleCXXRTTI(QualType(D->getTypeForDecl(), 0), NameStream); return getBackendMangledName(Name); } std::string InstallAPIVisitor::getMangledCXXVTableName(const CXXRecordDecl *D) const { SmallString<256> Name; raw_svector_ostream NameStream(Name); MC->mangleCXXVTable(D, NameStream); return getBackendMangledName(Name); } std::string InstallAPIVisitor::getMangledCXXThunk( const GlobalDecl &D, const ThunkInfo &Thunk, bool ElideOverrideInfo) const { SmallString<256> Name; raw_svector_ostream NameStream(Name); const auto *Method = cast(D.getDecl()); if (const auto *Dtor = dyn_cast(Method)) MC->mangleCXXDtorThunk(Dtor, D.getDtorType(), Thunk, ElideOverrideInfo, NameStream); else MC->mangleThunk(Method, Thunk, ElideOverrideInfo, NameStream); return getBackendMangledName(Name); } std::string InstallAPIVisitor::getMangledCtorDtor(const CXXMethodDecl *D, int Type) const { SmallString<256> Name; raw_svector_ostream NameStream(Name); GlobalDecl GD; if (const auto *Ctor = dyn_cast(D)) GD = GlobalDecl(Ctor, CXXCtorType(Type)); else { const auto *Dtor = cast(D); GD = GlobalDecl(Dtor, CXXDtorType(Type)); } MC->mangleName(GD, NameStream); return getBackendMangledName(Name); } void InstallAPIVisitor::emitVTableSymbols(const CXXRecordDecl *D, const AvailabilityInfo &Avail, const HeaderType Access, bool EmittedVTable) { if (hasVTable(D)) { EmittedVTable = true; const CXXLinkage VTableLinkage = getVTableLinkage(D); if (VTableLinkage == CXXLinkage::ExternalLinkage || VTableLinkage == CXXLinkage::WeakODRLinkage) { const std::string Name = getMangledCXXVTableName(D); const bool WeakDef = VTableLinkage == CXXLinkage::WeakODRLinkage; auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, GlobalRecord::Kind::Variable, Avail, D, Access, getFlags(WeakDef)); Ctx.Verifier->verify(GR, FA); if (!D->getDescribedClassTemplate() && !D->isInvalidDecl()) { VTableContextBase *VTable = D->getASTContext().getVTableContext(); auto AddThunk = [&](GlobalDecl GD) { const ItaniumVTableContext::ThunkInfoVectorTy *Thunks = VTable->getThunkInfo(GD); if (!Thunks) return; for (const auto &Thunk : *Thunks) { const std::string Name = getMangledCXXThunk(GD, Thunk, /*ElideOverrideInfo=*/true); auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, GD.getDecl(), Access); Ctx.Verifier->verify(GR, FA); } }; for (const auto *Method : D->methods()) { if (isa(Method) || !Method->isVirtual()) continue; if (auto Dtor = dyn_cast(Method)) { // Skip default destructor. if (Dtor->isDefaulted()) continue; AddThunk({Dtor, Dtor_Deleting}); AddThunk({Dtor, Dtor_Complete}); } else AddThunk(Method); } } } } if (!EmittedVTable) return; if (hasRTTI(D)) { std::string Name = getMangledCXXRTTI(D); auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, GlobalRecord::Kind::Variable, Avail, D, Access); Ctx.Verifier->verify(GR, FA); Name = getMangledCXXRTTIName(D); auto [NamedGR, NamedFA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, GlobalRecord::Kind::Variable, Avail, D, Access); Ctx.Verifier->verify(NamedGR, NamedFA); } for (const auto &It : D->bases()) { const CXXRecordDecl *Base = cast(It.getType()->castAs()->getDecl()); const auto BaseAccess = getAccessForDecl(Base); if (!BaseAccess) continue; const AvailabilityInfo BaseAvail = AvailabilityInfo::createFromDecl(Base); emitVTableSymbols(Base, BaseAvail, *BaseAccess, /*EmittedVTable=*/true); } } bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) { if (!D->isCompleteDefinition()) return true; // Skip templated classes. if (D->getDescribedClassTemplate() != nullptr) return true; // Skip partial templated classes too. if (isa(D)) return true; auto Access = getAccessForDecl(D); if (!Access) return true; const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D); // Check whether to emit the vtable/rtti symbols. if (isExported(D)) emitVTableSymbols(D, Avail, *Access); TemplateSpecializationKind ClassSK = TSK_Undeclared; bool KeepInlineAsWeak = false; if (auto *Templ = dyn_cast(D)) { ClassSK = Templ->getTemplateSpecializationKind(); if (ClassSK == TSK_ExplicitInstantiationDeclaration) KeepInlineAsWeak = true; } // Record the class methods. for (const auto *M : D->methods()) { // Inlined methods are usually not emitted, except when it comes from a // specialized template. bool WeakDef = false; if (isInlined(M)) { if (!KeepInlineAsWeak) continue; WeakDef = true; } if (!isExported(M)) continue; switch (M->getTemplateSpecializationKind()) { case TSK_Undeclared: case TSK_ExplicitSpecialization: break; case TSK_ImplicitInstantiation: continue; case TSK_ExplicitInstantiationDeclaration: if (ClassSK == TSK_ExplicitInstantiationDeclaration) WeakDef = true; break; case TSK_ExplicitInstantiationDefinition: WeakDef = true; break; } if (!M->isUserProvided()) continue; // Methods that are deleted are not exported. if (M->isDeleted()) continue; const auto Access = getAccessForDecl(M); if (!Access) return true; const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(M); if (const auto *Ctor = dyn_cast(M)) { // Defaulted constructors are not exported. if (Ctor->isDefaulted()) continue; std::string Name = getMangledCtorDtor(M, Ctor_Base); auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, D, *Access, getFlags(WeakDef)); Ctx.Verifier->verify(GR, FA); if (!D->isAbstract()) { std::string Name = getMangledCtorDtor(M, Ctor_Complete); auto [GR, FA] = Ctx.Slice->addGlobal( Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, D, *Access, getFlags(WeakDef)); Ctx.Verifier->verify(GR, FA); } continue; } if (const auto *Dtor = dyn_cast(M)) { // Defaulted destructors are not exported. if (Dtor->isDefaulted()) continue; std::string Name = getMangledCtorDtor(M, Dtor_Base); auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, D, *Access, getFlags(WeakDef)); Ctx.Verifier->verify(GR, FA); Name = getMangledCtorDtor(M, Dtor_Complete); auto [CompleteGR, CompleteFA] = Ctx.Slice->addGlobal( Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, D, *Access, getFlags(WeakDef)); Ctx.Verifier->verify(CompleteGR, CompleteFA); if (Dtor->isVirtual()) { Name = getMangledCtorDtor(M, Dtor_Deleting); auto [VirtualGR, VirtualFA] = Ctx.Slice->addGlobal( Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, D, *Access, getFlags(WeakDef)); Ctx.Verifier->verify(VirtualGR, VirtualFA); } continue; } // Though abstract methods can map to exports, this is generally unexpected. // Except in the case of destructors. Only ignore pure virtuals after // checking if the member function was a destructor. if (M->isPureVirtual()) continue; std::string Name = getMangledName(M); auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, M, *Access, getFlags(WeakDef)); Ctx.Verifier->verify(GR, FA); } if (auto *Templ = dyn_cast(D)) { if (!Templ->isExplicitInstantiationOrSpecialization()) return true; } using var_iter = CXXRecordDecl::specific_decl_iterator; using var_range = iterator_range; for (const auto *Var : var_range(D->decls())) { // Skip const static member variables. // \code // struct S { // static const int x = 0; // }; // \endcode if (Var->isStaticDataMember() && Var->hasInit()) continue; // Skip unexported var decls. if (!isExported(Var)) continue; const std::string Name = getMangledName(Var); const auto Access = getAccessForDecl(Var); if (!Access) return true; const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(Var); const bool WeakDef = Var->hasAttr() || KeepInlineAsWeak; auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, GlobalRecord::Kind::Variable, Avail, D, *Access, getFlags(WeakDef)); Ctx.Verifier->verify(GR, FA); } return true; } } // namespace clang::installapi