From e6b8d68a927368b06ac06cc9ac9e7f60aa966d5f Mon Sep 17 00:00:00 2001 From: Argyrios Kyrtzidis Date: Thu, 1 Sep 2011 00:58:55 +0000 Subject: [PATCH] Support importing of ObjC categories from modules. The initial incentive was to fix a crash when PCH chaining categories to an interface, but the fix was done in the "modules way" that I hear is popular with the kids these days. Each module stores the local chain of categories and we combine them when the interface is loaded. We also warn if non-dependent modules introduce duplicate named categories. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@138926 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/AST/ASTMutationListener.h | 6 + include/clang/Serialization/ASTBitCodes.h | 11 +- include/clang/Serialization/ASTReader.h | 11 ++ include/clang/Serialization/ASTWriter.h | 16 +++ include/clang/Serialization/Module.h | 9 ++ lib/AST/DeclObjC.cpp | 4 +- lib/Serialization/ASTReader.cpp | 21 ++++ lib/Serialization/ASTReaderDecl.cpp | 134 ++++++++++++++++++++++ lib/Serialization/ASTWriter.cpp | 34 ++++++ test/Modules/objc-categories.m | 85 ++++++++++++++ test/PCH/chain-categories.m | 51 ++++++++ 11 files changed, 380 insertions(+), 2 deletions(-) create mode 100644 test/Modules/objc-categories.m create mode 100644 test/PCH/chain-categories.m diff --git a/include/clang/AST/ASTMutationListener.h b/include/clang/AST/ASTMutationListener.h index 470cca8ee7..793d3ee2b1 100644 --- a/include/clang/AST/ASTMutationListener.h +++ b/include/clang/AST/ASTMutationListener.h @@ -22,6 +22,8 @@ namespace clang { class ClassTemplateSpecializationDecl; class FunctionDecl; class FunctionTemplateDecl; + class ObjCCategoryDecl; + class ObjCInterfaceDecl; /// \brief An abstract interface that should be implemented by listeners /// that want to be notified when an AST entity gets modified after its @@ -54,6 +56,10 @@ public: /// \brief A static data member was implicitly instantiated. virtual void StaticDataMemberInstantiated(const VarDecl *D) {} + + /// \brief A new objc category class was added for an interface. + virtual void AddedObjCCategoryToInterface(const ObjCCategoryDecl *CatD, + const ObjCInterfaceDecl *IFD) {} }; } // end namespace clang diff --git a/include/clang/Serialization/ASTBitCodes.h b/include/clang/Serialization/ASTBitCodes.h index 166a7b9a93..4c8eb28f79 100644 --- a/include/clang/Serialization/ASTBitCodes.h +++ b/include/clang/Serialization/ASTBitCodes.h @@ -64,6 +64,11 @@ namespace clang { /// \brief a Decl::Kind/DeclID pair. typedef std::pair KindDeclIDPair; + // FIXME: Turn these into classes so we can have some type safety when + // we go from local ID to global and vice-versa. + typedef DeclID LocalDeclID; + typedef DeclID GlobalDeclID; + /// \brief An ID number that refers to a type in an AST file. /// /// The ID of a type is partitioned into two parts: the lower @@ -402,7 +407,11 @@ namespace clang { /// \brief Record code for the source manager line table information, /// which stores information about #line directives. - SOURCE_MANAGER_LINE_TABLE = 48 + SOURCE_MANAGER_LINE_TABLE = 48, + + /// \brief Record code for ObjC categories in a module that are chained to + /// an interface. + OBJC_CHAINED_CATEGORIES }; /// \brief Record types used within a source manager block. diff --git a/include/clang/Serialization/ASTReader.h b/include/clang/Serialization/ASTReader.h index 7eb512a51a..0cec2f455b 100644 --- a/include/clang/Serialization/ASTReader.h +++ b/include/clang/Serialization/ASTReader.h @@ -36,6 +36,7 @@ #include "llvm/ADT/OwningPtr.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/Bitcode/BitstreamReader.h" #include "llvm/Support/DataTypes.h" #include @@ -315,6 +316,10 @@ private: /// most recent declarations in another AST file. FirstLatestDeclIDMap FirstLatestDeclIDs; + /// \brief Set of ObjC interfaces that have categories chained to them in + /// other modules. + llvm::DenseSet ObjCChainedCategoriesInterfaces; + /// \brief Read the records that describe the contents of declcontexts. bool ReadDeclContextStorage(Module &M, llvm::BitstreamCursor &Cursor, @@ -681,6 +686,8 @@ private: Decl *ReadDeclRecord(serialization::DeclID ID); RecordLocation DeclCursorForID(serialization::DeclID ID); void loadDeclUpdateRecords(serialization::DeclID ID, Decl *D); + void loadObjCChainedCategories(serialization::GlobalDeclID ID, + ObjCInterfaceDecl *D); RecordLocation getLocalBitOffset(uint64_t GlobalOffset); uint64_t getGlobalBitOffset(Module &M, uint32_t LocalOffset); @@ -899,6 +906,10 @@ public: /// \brief Map from a local declaration ID within a given module to a /// global declaration ID. serialization::DeclID getGlobalDeclID(Module &F, unsigned LocalID) const; + + /// \brief Returns true if global DeclID \arg ID originated from module + /// \arg M. + bool isDeclIDFromModule(serialization::GlobalDeclID ID, Module &M) const; /// \brief Resolve a declaration ID into a declaration, potentially /// building a new declaration. diff --git a/include/clang/Serialization/ASTWriter.h b/include/clang/Serialization/ASTWriter.h index a3a5b433b5..cb71415d34 100644 --- a/include/clang/Serialization/ASTWriter.h +++ b/include/clang/Serialization/ASTWriter.h @@ -258,6 +258,19 @@ private: /// \brief Decls that will be replaced in the current dependent AST file. DeclsToRewriteTy DeclsToRewrite; + struct ChainedObjCCategoriesData { + /// \brief The interface in the imported module. + const ObjCInterfaceDecl *Interface; + /// \brief ID of the interface. + serialization::DeclID InterfaceID; + /// \brief ID of the locally tail category ID that got chained to the + /// imported interface. + serialization::DeclID TailCatID; + }; + /// \brief ObjC categories that got chained to an interface imported from + /// another module. + SmallVector LocalChainedObjCCategories; + /// \brief Decls that have been replaced in the current dependent AST file. /// /// When a decl changes fundamentally after being deserialized (this shouldn't @@ -350,6 +363,7 @@ private: void WriteAttributes(const AttrVec &Attrs, RecordDataImpl &Record); void WriteDeclUpdatesBlocks(); void WriteDeclReplacementsBlock(); + void WriteChainedObjCCategories(); void WriteDeclContextVisibleUpdate(const DeclContext *DC); void WriteFPPragmaOptions(const FPOptions &Opts); void WriteOpenCLExtensions(Sema &SemaRef); @@ -620,6 +634,8 @@ public: const FunctionDecl *D); virtual void CompletedImplicitDefinition(const FunctionDecl *D); virtual void StaticDataMemberInstantiated(const VarDecl *D); + virtual void AddedObjCCategoryToInterface(const ObjCCategoryDecl *CatD, + const ObjCInterfaceDecl *IFD); }; /// \brief AST and semantic-analysis consumer that generates a diff --git a/include/clang/Serialization/Module.h b/include/clang/Serialization/Module.h index b3c3bc3271..c35a4f0096 100644 --- a/include/clang/Serialization/Module.h +++ b/include/clang/Serialization/Module.h @@ -273,6 +273,15 @@ public: /// \brief Information about the lexical and visible declarations /// for each DeclContext. DeclContextInfosMap DeclContextInfos; + + typedef llvm::DenseMap > + ChainedObjCCategoriesMap; + /// \brief ObjC categories that got chained to an interface from another + /// module. + /// Key is the ID of the interface. + /// Value is a pair of linked category DeclIDs (head category, tail category). + ChainedObjCCategoriesMap ChainedObjCCategories; // === Types === diff --git a/lib/AST/DeclObjC.cpp b/lib/AST/DeclObjC.cpp index ad2fd289a7..45e34811ea 100644 --- a/lib/AST/DeclObjC.cpp +++ b/lib/AST/DeclObjC.cpp @@ -14,6 +14,7 @@ #include "clang/AST/DeclObjC.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Stmt.h" +#include "clang/AST/ASTMutationListener.h" #include "llvm/ADT/STLExtras.h" using namespace clang; @@ -918,7 +919,8 @@ ObjCCategoryDecl *ObjCCategoryDecl::Create(ASTContext &C, DeclContext *DC, // Link this category into its class's category list. CatDecl->NextClassCategory = IDecl->getCategoryList(); IDecl->setCategoryList(CatDecl); - IDecl->setChangedSinceDeserialization(true); + if (ASTMutationListener *L = C.getASTMutationListener()) + L->AddedObjCCategoryToInterface(CatDecl, IDecl); } return CatDecl; diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp index fe714300ac..bc022f5721 100644 --- a/lib/Serialization/ASTReader.cpp +++ b/lib/Serialization/ASTReader.cpp @@ -2391,6 +2391,20 @@ ASTReader::ReadASTBlock(Module &F) { = std::make_pair(&F, Record[I+1]); break; } + + case OBJC_CHAINED_CATEGORIES: { + if (Record.size() % 3 != 0) { + Error("invalid OBJC_CHAINED_CATEGORIES block in AST file"); + return Failure; + } + for (unsigned I = 0, N = Record.size(); I != N; I += 3) { + serialization::GlobalDeclID GlobID = getGlobalDeclID(F, Record[I]); + F.ChainedObjCCategories[GlobID] = std::make_pair(Record[I+1], + Record[I+2]); + ObjCChainedCategoriesInterfaces.insert(GlobID); + } + break; + } case CXX_BASE_SPECIFIER_OFFSETS: { if (F.LocalNumCXXBaseSpecifiers != 0) { @@ -4075,6 +4089,13 @@ ASTReader::getGlobalDeclID(Module &F, unsigned LocalID) const { return LocalID + I->second; } +bool ASTReader::isDeclIDFromModule(serialization::GlobalDeclID ID, + Module &M) const { + GlobalDeclMapType::const_iterator I = GlobalDeclMap.find(ID); + assert(I != GlobalDeclMap.end() && "Corrupted global declaration map"); + return &M == I->second; +} + Decl *ASTReader::GetDecl(DeclID ID) { if (ID < NUM_PREDEF_DECL_IDS) { switch ((PredefinedDeclIDs)ID) { diff --git a/lib/Serialization/ASTReaderDecl.cpp b/lib/Serialization/ASTReaderDecl.cpp index 5b73131595..058803cafb 100644 --- a/lib/Serialization/ASTReaderDecl.cpp +++ b/lib/Serialization/ASTReaderDecl.cpp @@ -14,6 +14,7 @@ #include "ASTCommon.h" #include "clang/Serialization/ASTReader.h" +#include "clang/Sema/SemaDiagnostic.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclVisitor.h" @@ -104,6 +105,11 @@ namespace clang { void UpdateDecl(Decl *D, Module &Module, const RecordData &Record); + static void setNextObjCCategory(ObjCCategoryDecl *Cat, + ObjCCategoryDecl *Next) { + Cat->NextClassCategory = Next; + } + void VisitDecl(Decl *D); void VisitTranslationUnitDecl(TranslationUnitDecl *TU); void VisitNamedDecl(NamedDecl *ND); @@ -1714,6 +1720,9 @@ Decl *ASTReader::ReadDeclRecord(DeclID ID) { // Load any relevant update records. loadDeclUpdateRecords(ID, D); + if (ObjCChainedCategoriesInterfaces.count(ID)) + loadObjCChainedCategories(ID, cast(D)); + // If we have deserialized a declaration that has a definition the // AST consumer might need to know about, queue it. // We don't pass it to the consumer immediately because we may be in recursive @@ -1751,6 +1760,131 @@ void ASTReader::loadDeclUpdateRecords(serialization::DeclID ID, Decl *D) { } } +namespace { + /// \brief Given an ObjC interface, goes through the modules and links to the + /// interface all the categories for it. + class ObjCChainedCategoriesVisitor { + ASTReader &Reader; + serialization::GlobalDeclID InterfaceID; + ObjCInterfaceDecl *Interface; + ObjCCategoryDecl *GlobHeadCat, *GlobTailCat; + llvm::DenseMap NameCategoryMap; + + public: + ObjCChainedCategoriesVisitor(ASTReader &Reader, + serialization::GlobalDeclID InterfaceID, + ObjCInterfaceDecl *Interface) + : Reader(Reader), InterfaceID(InterfaceID), Interface(Interface), + GlobHeadCat(0), GlobTailCat(0) { } + + static bool visit(Module &M, void *UserData) { + return static_cast(UserData)->visit(M); + } + + bool visit(Module &M) { + if (Reader.isDeclIDFromModule(InterfaceID, M)) + return true; // We reached the module where the interface originated + // from. Stop traversing the imported modules. + + Module::ChainedObjCCategoriesMap::iterator + I = M.ChainedObjCCategories.find(InterfaceID); + if (I == M.ChainedObjCCategories.end()) + return false; + + ObjCCategoryDecl * + HeadCat = Reader.GetLocalDeclAs(M, I->second.first); + ObjCCategoryDecl * + TailCat = Reader.GetLocalDeclAs(M, I->second.second); + + addCategories(HeadCat, TailCat); + return false; + } + + void addCategories(ObjCCategoryDecl *HeadCat, + ObjCCategoryDecl *TailCat = 0) { + if (!HeadCat) { + assert(!TailCat); + return; + } + + if (!TailCat) { + TailCat = HeadCat; + while (TailCat->getNextClassCategory()) + TailCat = TailCat->getNextClassCategory(); + } + + if (!GlobHeadCat) { + GlobHeadCat = HeadCat; + GlobTailCat = TailCat; + } else { + ASTDeclReader::setNextObjCCategory(GlobTailCat, HeadCat); + GlobTailCat = TailCat; + } + + llvm::DenseSet Checked; + for (ObjCCategoryDecl *Cat = HeadCat, + *CatEnd = TailCat->getNextClassCategory(); + Cat != CatEnd; Cat = Cat->getNextClassCategory()) { + if (Checked.count(Cat->getDeclName())) + continue; + Checked.insert(Cat->getDeclName()); + checkForDuplicate(Cat); + } + } + + /// \brief Warns for duplicate categories that come from different modules. + void checkForDuplicate(ObjCCategoryDecl *Cat) { + DeclarationName Name = Cat->getDeclName(); + // Find the top category with the same name. We do not want to warn for + // duplicates along the established chain because there were already + // warnings for them when the module was created. We only want to warn for + // duplicates between non-dependent modules: + // + // MT + // / \ + // ML MR + // + // We want to warn for duplicates between ML and MR,not between ML and MT. + // + // FIXME: We should not warn for duplicates in diamond: + // + // MT + // / \ + // ML MR + // \ / + // MB + // + // If there are duplicates in ML/MR, there will be warning when creating + // MB *and* when importing MB. We should not warn when importing. + for (ObjCCategoryDecl *Next = Cat->getNextClassCategory(); Next; + Next = Next->getNextClassCategory()) { + if (Next->getDeclName() == Name) + Cat = Next; + } + + ObjCCategoryDecl *&PrevCat = NameCategoryMap[Name]; + if (!PrevCat) + PrevCat = Cat; + + if (PrevCat != Cat) { + Reader.Diag(Cat->getLocation(), diag::warn_dup_category_def) + << Interface->getDeclName() << Name; + Reader.Diag(PrevCat->getLocation(), diag::note_previous_definition); + } + } + + ObjCCategoryDecl *getHeadCategory() const { return GlobHeadCat; } + }; +} + +void ASTReader::loadObjCChainedCategories(serialization::GlobalDeclID ID, + ObjCInterfaceDecl *D) { + ObjCChainedCategoriesVisitor Visitor(*this, ID, D); + ModuleMgr.visit(ObjCChainedCategoriesVisitor::visit, &Visitor); + // Also add the categories that the interface already links to. + Visitor.addCategories(D->getCategoryList()); + D->setCategoryList(Visitor.getHeadCategory()); +} void ASTDeclReader::UpdateDecl(Decl *D, Module &Module, const RecordData &Record) { diff --git a/lib/Serialization/ASTWriter.cpp b/lib/Serialization/ASTWriter.cpp index 25bff0aba1..a57f3a1388 100644 --- a/lib/Serialization/ASTWriter.cpp +++ b/lib/Serialization/ASTWriter.cpp @@ -3188,6 +3188,7 @@ void ASTWriter::WriteASTCore(Sema &SemaRef, MemorizeStatCalls *StatCalls, WriteDeclUpdatesBlocks(); WriteDeclReplacementsBlock(); + WriteChainedObjCCategories(); // Some simple statistics Record.clear(); @@ -3236,6 +3237,26 @@ void ASTWriter::WriteDeclReplacementsBlock() { Stream.EmitRecord(DECL_REPLACEMENTS, Record); } +void ASTWriter::WriteChainedObjCCategories() { + if (LocalChainedObjCCategories.empty()) + return; + + RecordData Record; + for (SmallVector::iterator + I = LocalChainedObjCCategories.begin(), + E = LocalChainedObjCCategories.end(); I != E; ++I) { + ChainedObjCCategoriesData &Data = *I; + serialization::DeclID + HeadCatID = getDeclID(Data.Interface->getCategoryList()); + assert(HeadCatID != 0 && "Category not written ?"); + + Record.push_back(Data.InterfaceID); + Record.push_back(HeadCatID); + Record.push_back(Data.TailCatID); + } + Stream.EmitRecord(OBJC_CHAINED_CATEGORIES, Record); +} + void ASTWriter::AddSourceLocation(SourceLocation Loc, RecordDataImpl &Record) { Record.push_back(Loc.getRawEncoding()); } @@ -4037,4 +4058,17 @@ void ASTWriter::StaticDataMemberInstantiated(const VarDecl *D) { D->getMemberSpecializationInfo()->getPointOfInstantiation(), Record); } +void ASTWriter::AddedObjCCategoryToInterface(const ObjCCategoryDecl *CatD, + const ObjCInterfaceDecl *IFD) { + if (IFD->getPCHLevel() == 0) + return; // Declaration not imported from PCH. + if (CatD->getNextClassCategory() && + CatD->getNextClassCategory()->getPCHLevel() == 0) + return; // We already recorded that the tail of a category chain should be + // attached to an interface. + + ChainedObjCCategoriesData Data = { IFD, GetDeclRef(IFD), GetDeclRef(CatD) }; + LocalChainedObjCCategories.push_back(Data); +} + ASTSerializationListener::~ASTSerializationListener() { } diff --git a/test/Modules/objc-categories.m b/test/Modules/objc-categories.m new file mode 100644 index 0000000000..06f4263719 --- /dev/null +++ b/test/Modules/objc-categories.m @@ -0,0 +1,85 @@ +// RUN: mkdir -p %t +// RUN: %clang_cc1 -emit-module -o %t/diamond_top.pcm %s -D MODULE_TOP +// RUN: %clang_cc1 -I %t -emit-module -o %t/diamond_left.pcm %s -D MODULE_LEFT +// RUN: %clang_cc1 -I %t -emit-module -o %t/diamond_right.pcm %s -D MODULE_RIGHT +// RUN: %clang_cc1 -I %t -emit-module -o %t/diamond_bottom.pcm %s -D MODULE_BOTTOM +// RUN: %clang_cc1 -I %t %s -verify + +/*============================================================================*/ +#ifdef MODULE_TOP + +@interface Foo +-(void)top; +@end + +/*============================================================================*/ +#elif defined(MODULE_LEFT) + +__import_module__ diamond_top; + +@interface Foo(Left) +-(void)left; +@end + +@interface LeftFoo +-(void)left; +@end + +@interface Foo(Duplicate) // expected-note {{previous definition}} +@end + +@interface Foo(Duplicate) +@end + +/*============================================================================*/ +#elif defined(MODULE_RIGHT) + +__import_module__ diamond_top; + +@interface Foo(Right1) +-(void)right1; +@end + +@interface Foo(Right2) +-(void)right2; +@end + +@interface Foo(Duplicate) // expected-warning {{duplicate definition of category}} +@end + +/*============================================================================*/ +#elif defined(MODULE_BOTTOM) + +__import_module__ diamond_left; +__import_module__ diamond_right; + +@interface Foo(Bottom) +-(void)bottom; +@end + +@interface LeftFoo(Bottom) +-(void)bottom; +@end + +/*============================================================================*/ +#else + +__import_module__ diamond_bottom; + +@interface Foo(Source) +-(void)source; +@end + +void test(Foo *foo, LeftFoo *leftFoo) { + [foo source]; + [foo bottom]; + [foo left]; + [foo right1]; + [foo right2]; + [foo top]; + + [leftFoo left]; + [leftFoo bottom]; +} + +#endif diff --git a/test/PCH/chain-categories.m b/test/PCH/chain-categories.m new file mode 100644 index 0000000000..1b91c732b4 --- /dev/null +++ b/test/PCH/chain-categories.m @@ -0,0 +1,51 @@ +// Without PCH +// RUN: %clang_cc1 -fsyntax-only -verify -include %s -include %s %s + +// With PCH +// RUN: %clang_cc1 -fsyntax-only -verify %s -chain-include %s -chain-include %s + +#ifndef HEADER1 +#define HEADER1 +//===----------------------------------------------------------------------===// +// Primary header + +@interface NSObject +- (id)init; +- (void)finalize; +@end + +//===----------------------------------------------------------------------===// +#elif !defined(HEADER2) +#define HEADER2 +#if !defined(HEADER1) +#error Header inclusion order messed up +#endif + +//===----------------------------------------------------------------------===// +// Dependent header + +@interface MyClass : NSObject ++(void)meth; +@end + +@interface NSObject(ObjExt) +-(void)extMeth; +@end + +//===----------------------------------------------------------------------===// +#else +//===----------------------------------------------------------------------===// + +@implementation MyClass ++(void)meth {} +-(void)finalize { + [super finalize]; +} +@end + +void test(NSObject *o) { + [o extMeth]; +} + +//===----------------------------------------------------------------------===// +#endif -- 2.40.0