diff --git a/clang/include/clang/InstallAPI/Frontend.h b/clang/include/clang/InstallAPI/Frontend.h index d72b4680fde40..8774321e990c1 100644 --- a/clang/include/clang/InstallAPI/Frontend.h +++ b/clang/include/clang/InstallAPI/Frontend.h @@ -28,7 +28,10 @@ namespace installapi { using SymbolFlags = llvm::MachO::SymbolFlags; using RecordLinkage = llvm::MachO::RecordLinkage; using GlobalRecord = llvm::MachO::GlobalRecord; +using ObjCContainerRecord = llvm::MachO::ObjCContainerRecord; using ObjCInterfaceRecord = llvm::MachO::ObjCInterfaceRecord; +using ObjCCategoryRecord = llvm::MachO::ObjCCategoryRecord; +using ObjCIVarRecord = llvm::MachO::ObjCIVarRecord; // Represents a collection of frontend records for a library that are tied to a // darwin target triple. @@ -69,6 +72,38 @@ class FrontendRecordsSlice : public llvm::MachO::RecordsSlice { const Decl *D, HeaderType Access, bool IsEHType); + /// Add ObjC Category record with attributes from AST. + /// + /// \param ClassToExtend The name of class that is extended by category, not + /// symbol. + /// \param CategoryName The name of category, not symbol. + /// \param Avail The availability information tied + /// to the active target triple. + /// \param D The pointer to the declaration from traversing AST. + /// \param Access The intended access level of symbol. + /// \return The non-owning pointer to added record in slice. + ObjCCategoryRecord *addObjCCategory(StringRef ClassToExtend, + StringRef CategoryName, + const clang::AvailabilityInfo Avail, + const Decl *D, HeaderType Access); + + /// Add ObjC IVar record with attributes from AST. + /// + /// \param Container The owning pointer for instance variable. + /// \param Name The name of ivar, not symbol. + /// \param Linkage The linkage of symbol. + /// \param Avail The availability information tied to the active target + /// triple. + /// \param D The pointer to the declaration from traversing AST. + /// \param Access The intended access level of symbol. + /// \param AC The access control tied to the ivar declaration. + /// \return The non-owning pointer to added record in slice. + ObjCIVarRecord *addObjCIVar(ObjCContainerRecord *Container, + StringRef IvarName, RecordLinkage Linkage, + const clang::AvailabilityInfo Avail, + const Decl *D, HeaderType Access, + const clang::ObjCIvarDecl::AccessControl AC); + private: /// Frontend information captured about records. struct FrontendAttrs { diff --git a/clang/include/clang/InstallAPI/Visitor.h b/clang/include/clang/InstallAPI/Visitor.h index 60a05005df841..ff0a9957aa86b 100644 --- a/clang/include/clang/InstallAPI/Visitor.h +++ b/clang/include/clang/InstallAPI/Visitor.h @@ -42,10 +42,22 @@ class InstallAPIVisitor final : public ASTConsumer, /// ivars, properties, and methods of the class. bool VisitObjCInterfaceDecl(const ObjCInterfaceDecl *D); + /// Collect Objective-C Category/Extension declarations. + /// + /// The class that is being extended might come from a different library and + /// is therefore itself not collected. + bool VisitObjCCategoryDecl(const ObjCCategoryDecl *D); + private: std::string getMangledName(const NamedDecl *D) const; std::string getBackendMangledName(llvm::Twine Name) const; std::optional getAccessForDecl(const NamedDecl *D) const; + void recordObjCInstanceVariables( + const ASTContext &ASTCtx, llvm::MachO::ObjCContainerRecord *Record, + StringRef SuperClass, + const llvm::iterator_range< + DeclContext::specific_decl_iterator> + Ivars); InstallAPIContext &Ctx; SourceManager &SrcMgr; diff --git a/clang/lib/InstallAPI/Frontend.cpp b/clang/lib/InstallAPI/Frontend.cpp index caa6e7e8a4054..240a80e1d3d82 100644 --- a/clang/lib/InstallAPI/Frontend.cpp +++ b/clang/lib/InstallAPI/Frontend.cpp @@ -39,6 +39,31 @@ ObjCInterfaceRecord *FrontendRecordsSlice::addObjCInterface( return ObjCR; } +ObjCCategoryRecord *FrontendRecordsSlice::addObjCCategory( + StringRef ClassToExtend, StringRef CategoryName, + const clang::AvailabilityInfo Avail, const Decl *D, HeaderType Access) { + auto *ObjCR = + llvm::MachO::RecordsSlice::addObjCCategory(ClassToExtend, CategoryName); + FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}}); + return ObjCR; +} + +ObjCIVarRecord *FrontendRecordsSlice::addObjCIVar( + ObjCContainerRecord *Container, StringRef IvarName, RecordLinkage Linkage, + const clang::AvailabilityInfo Avail, const Decl *D, HeaderType Access, + const clang::ObjCIvarDecl::AccessControl AC) { + // If the decl otherwise would have been exported, check their access control. + // Ivar's linkage is also determined by this. + if ((Linkage == RecordLinkage::Exported) && + ((AC == ObjCIvarDecl::Private) || (AC == ObjCIvarDecl::Package))) + Linkage = RecordLinkage::Internal; + auto *ObjCR = + llvm::MachO::RecordsSlice::addObjCIVar(Container, IvarName, Linkage); + FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}}); + + return nullptr; +} + std::optional InstallAPIContext::findAndRecordFile(const FileEntry *FE, const Preprocessor &PP) { diff --git a/clang/lib/InstallAPI/Visitor.cpp b/clang/lib/InstallAPI/Visitor.cpp index 355a092520c3c..fbe6f1dabe005 100644 --- a/clang/lib/InstallAPI/Visitor.cpp +++ b/clang/lib/InstallAPI/Visitor.cpp @@ -99,6 +99,29 @@ static bool hasObjCExceptionAttribute(const ObjCInterfaceDecl *D) { 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(); + Ctx.Slice->addObjCIVar(Record, Name, Linkage, Avail, IV, *Access, AC); + } +} bool InstallAPIVisitor::VisitObjCInterfaceDecl(const ObjCInterfaceDecl *D) { // Skip forward declaration for classes (@class) @@ -118,7 +141,33 @@ bool InstallAPIVisitor::VisitObjCInterfaceDecl(const ObjCInterfaceDecl *D) { (!D->getASTContext().getLangOpts().ObjCRuntime.isFragile() && hasObjCExceptionAttribute(D)); - Ctx.Slice->addObjCInterface(Name, Linkage, Avail, D, *Access, IsEHType); + ObjCInterfaceRecord *Class = + Ctx.Slice->addObjCInterface(Name, Linkage, Avail, D, *Access, IsEHType); + + // Get base class. + StringRef SuperClassName; + if (const auto *SuperClass = D->getSuperClass()) + SuperClassName = SuperClass->getObjCRuntimeNameAsString(); + + recordObjCInstanceVariables(D->getASTContext(), Class, SuperClassName, + 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 *Category = Ctx.Slice->addObjCCategory( + InterfaceName, CategoryName, Avail, D, *Access); + recordObjCInstanceVariables(D->getASTContext(), Category, InterfaceName, + D->ivars()); return true; } diff --git a/clang/test/InstallAPI/objcclasses.test b/clang/test/InstallAPI/objcclasses.test index d32291c64c472..7b73dffbe9208 100644 --- a/clang/test/InstallAPI/objcclasses.test +++ b/clang/test/InstallAPI/objcclasses.test @@ -20,19 +20,68 @@ @end __attribute__((visibility("hidden"))) -@interface Hidden +@interface Hidden +@end + +__attribute__((visibility("hidden"))) +@interface HiddenWithIvars { +@public +char _ivar; +} @end __attribute__((objc_exception)) @interface Exception @end +@interface PublicClass : Visible { +@package + int _internal; +@protected + int _external; +@private + int private; +@public +char _public; +} +@end + + +//--- Foo.framework/PrivateHeaders/Foo_Private.h +#import + +@interface ClassWithIvars : Visible { + char _ivar1; + char _ivar2; +@private + int _privateIVar; +@protected + int _externalIVar; +@package + int _internalIVar; +} +@end + +@interface Exception () { +@public + char _ivarFromExtension; +@private + int _privateIvarFromExtension; +} +@end + + //--- inputs.json.in { "headers": [ { "path" : "DSTROOT/Foo.framework/Headers/Foo.h", "type" : "public" - }], + }, + { + "path" : "DSTROOT/Foo.framework/PrivateHeaders/Foo_Private.h", + "type" : "private" + } + ], "version": "3" } @@ -53,11 +102,21 @@ __attribute__((objc_exception)) { "data": { "objc_class": [ + "PublicClass", "Exception", - "Visible" + "Visible", + "ClassWithIvars" ], "objc_eh_type": [ "Exception" + ], + "objc_ivar": [ + "Exception._ivarFromExtension", + "ClassWithIvars._ivar2", + "PublicClass._external", + "ClassWithIvars._ivar1", + "ClassWithIvars._externalIVar", + "PublicClass._public" ] } } diff --git a/llvm/include/llvm/TextAPI/Record.h b/llvm/include/llvm/TextAPI/Record.h index 3b30e6c8c2676..867d6a2358832 100644 --- a/llvm/include/llvm/TextAPI/Record.h +++ b/llvm/include/llvm/TextAPI/Record.h @@ -143,6 +143,7 @@ class ObjCContainerRecord : public Record { ObjCIVarRecord *addObjCIVar(StringRef IVar, RecordLinkage Linkage); ObjCIVarRecord *findObjCIVar(StringRef IVar) const; std::vector getObjCIVars() const; + RecordLinkage getLinkage() const { return Linkage; } private: RecordMap IVars; diff --git a/llvm/lib/TextAPI/RecordsSlice.cpp b/llvm/lib/TextAPI/RecordsSlice.cpp index fb961a91cc656..db52a2cdd85c9 100644 --- a/llvm/lib/TextAPI/RecordsSlice.cpp +++ b/llvm/lib/TextAPI/RecordsSlice.cpp @@ -225,6 +225,7 @@ bool ObjCInterfaceRecord::addObjCCategory(ObjCCategoryRecord *Record) { ObjCCategoryRecord *RecordsSlice::addObjCCategory(StringRef ClassToExtend, StringRef Category) { Category = copyString(Category); + ClassToExtend = copyString(ClassToExtend); // Add owning record first into record slice. auto Result =