| // -*- mode: ObjC -*- |
| |
| // This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files. |
| // Copyright (C) 1997-1998, 2000-2001, 2004-2012 Steve Nygard. |
| |
| #import "CDObjectiveCProcessor.h" |
| |
| #import "CDClassDump.h" |
| #import "CDMachOFile.h" |
| #import "CDVisitor.h" |
| #import "CDLCSegment.h" |
| #import "CDLCDynamicSymbolTable.h" |
| #import "CDLCSymbolTable.h" |
| #import "CDOCProtocol.h" |
| #import "CDTypeController.h" |
| #import "CDOCClass.h" |
| #import "CDOCCategory.h" |
| #import "CDSection.h" |
| |
| // Note: sizeof(long long) == 8 on both 32-bit and 64-bit. sizeof(uint64_t) == 8. So use [NSNumber numberWithUnsignedLongLong:]. |
| |
| @implementation CDObjectiveCProcessor |
| { |
| CDMachOFile *_machOFile; |
| |
| NSMutableArray *_classes; |
| NSMutableDictionary *_classesByAddress; |
| |
| NSMutableArray *_categories; |
| |
| NSMutableDictionary *_protocolsByName; // uniqued |
| NSMutableDictionary *_protocolsByAddress; // non-uniqued |
| } |
| |
| - (id)initWithMachOFile:(CDMachOFile *)machOFile; |
| { |
| if ((self = [super init])) { |
| _machOFile = machOFile; |
| _classes = [[NSMutableArray alloc] init]; |
| _classesByAddress = [[NSMutableDictionary alloc] init]; |
| _categories = [[NSMutableArray alloc] init]; |
| _protocolsByName = [[NSMutableDictionary alloc] init]; |
| _protocolsByAddress = [[NSMutableDictionary alloc] init]; |
| } |
| |
| return self; |
| } |
| |
| #pragma mark - Debugging |
| |
| - (NSString *)description; |
| { |
| return [NSString stringWithFormat:@"<%@:%p> machOFile: %@", |
| NSStringFromClass([self class]), self, |
| self.machOFile.filename]; |
| } |
| |
| #pragma mark - |
| |
| - (BOOL)hasObjectiveCData; |
| { |
| return self.machOFile.hasObjectiveC1Data || self.machOFile.hasObjectiveC2Data; |
| } |
| |
| - (CDSection *)objcImageInfoSection; |
| { |
| // Implement in subclasses. |
| return nil; |
| } |
| |
| - (NSString *)garbageCollectionStatus; |
| { |
| if (self.objcImageInfoSection != nil) { |
| // The SDK frameworks (i.e. within Xcode, not in /System) have empty sections. |
| if (self.objcImageInfoSection.size < 8) |
| return @"Unknown"; |
| |
| CDMachOFileDataCursor *cursor = [[CDMachOFileDataCursor alloc] initWithSection:self.objcImageInfoSection]; |
| |
| [cursor readInt32]; |
| uint32_t v2 = [cursor readInt32]; |
| //NSLog(@"%s: %08x %08x", __cmd, v1, v2); |
| // v2 == 0 -> Objective-C Garbage Collection: Unsupported |
| // v2 == 2 -> Supported |
| // v2 == 6 -> Required |
| //NSParameterAssert(v2 == 0 || v2 == 2 || v2 == 6); |
| |
| // See markgc.c in the objc4 project |
| switch (v2 & 0x06) { |
| case 0: return @"Unsupported"; |
| case 2: return @"Supported"; |
| case 6: return @"Required"; |
| } |
| |
| return [NSString stringWithFormat:@"Unknown (0x%08x)", v2]; |
| } |
| |
| return nil; |
| } |
| |
| #pragma mark - |
| |
| - (void)addClass:(CDOCClass *)aClass withAddress:(uint64_t)address; |
| { |
| [_classes addObject:aClass]; |
| [_classesByAddress setObject:aClass forKey:[NSNumber numberWithUnsignedLongLong:address]]; |
| } |
| |
| - (CDOCClass *)classWithAddress:(uint64_t)address; |
| { |
| return [_classesByAddress objectForKey:[NSNumber numberWithUnsignedLongLong:address]]; |
| } |
| |
| - (void)addClassesFromArray:(NSArray *)array; |
| { |
| if (array != nil) |
| [_classes addObjectsFromArray:array]; |
| } |
| |
| - (void)addCategoriesFromArray:(NSArray *)array; |
| { |
| if (array != nil) |
| [_categories addObjectsFromArray:array]; |
| } |
| |
| - (CDOCProtocol *)protocolWithAddress:(uint64_t)address; |
| { |
| NSNumber *key = [NSNumber numberWithUnsignedLongLong:address]; |
| return _protocolsByAddress[key]; |
| } |
| |
| - (void)setProtocol:(CDOCProtocol *)protocol withAddress:(uint64_t)address; |
| { |
| NSNumber *key = [NSNumber numberWithUnsignedLongLong:address]; |
| _protocolsByAddress[key] = protocol; |
| } |
| |
| - (CDOCProtocol *)protocolForName:(NSString *)name; |
| { |
| return _protocolsByName[name]; |
| } |
| |
| - (void)addCategory:(CDOCCategory *)category; |
| { |
| if (category != nil) |
| [_categories addObject:category]; |
| } |
| |
| #pragma mark - Processing |
| |
| - (void)process; |
| { |
| if (self.machOFile.isEncrypted == NO && self.machOFile.canDecryptAllSegments) { |
| [self.machOFile.symbolTable loadSymbols]; |
| [self.machOFile.dynamicSymbolTable loadSymbols]; |
| |
| [self loadProtocols]; |
| |
| // Load classes before categories, so we can get a dictionary of classes by address. |
| [self loadClasses]; |
| [self loadCategories]; |
| } |
| } |
| |
| - (void)loadProtocols; |
| { |
| // Implement in subclasses. |
| } |
| |
| - (void)loadClasses; |
| { |
| // Implement in subclasses. |
| } |
| |
| - (void)loadCategories; |
| { |
| // Implement in subclasses. |
| } |
| |
| |
| - (void)registerTypesWithObject:(CDTypeController *)typeController phase:(NSUInteger)phase; |
| { |
| for (CDOCClass *aClass in _classes) |
| [aClass registerTypesWithObject:typeController phase:phase]; |
| |
| for (CDOCCategory *category in _categories) |
| [category registerTypesWithObject:typeController phase:phase]; |
| |
| for (NSString *name in [[_protocolsByName allKeys] sortedArrayUsingSelector:@selector(compare:)]) |
| [_protocolsByName[name] registerTypesWithObject:typeController phase:phase]; |
| } |
| |
| - (void)recursivelyVisit:(CDVisitor *)visitor; |
| { |
| NSMutableArray *classesAndCategories = [[NSMutableArray alloc] init]; |
| [classesAndCategories addObjectsFromArray:_classes]; |
| [classesAndCategories addObjectsFromArray:_categories]; |
| |
| // TODO: Sort protocols by dependency |
| // TODO (2004-01-30): It looks like protocols might be defined in more than one file. i.e. NSObject. |
| // TODO (2004-02-02): Looks like we need to record the order the protocols were encountered, or just always sort protocols |
| NSArray *protocolNames = [[_protocolsByName allKeys] sortedArrayUsingSelector:@selector(compare:)]; |
| |
| [visitor willVisitObjectiveCProcessor:self]; |
| [visitor visitObjectiveCProcessor:self]; |
| |
| for (NSString *protocolName in protocolNames) { |
| [_protocolsByName[protocolName] recursivelyVisit:visitor]; |
| } |
| |
| if ([[visitor classDump] shouldSortClassesByInheritance]) { |
| [classesAndCategories sortTopologically]; |
| } else if ([[visitor classDump] shouldSortClasses]) |
| [classesAndCategories sortUsingSelector:@selector(ascendingCompareByName:)]; |
| |
| for (id aClassOrCategory in classesAndCategories) |
| [aClassOrCategory recursivelyVisit:visitor]; |
| |
| [visitor didVisitObjectiveCProcessor:self]; |
| } |
| |
| - (void)createUniquedProtocols; |
| { |
| // Now unique the protocols by name and store in protocolsByName |
| |
| for (NSNumber *key in [[_protocolsByAddress allKeys] sortedArrayUsingSelector:@selector(compare:)]) { |
| CDOCProtocol *p1 = _protocolsByAddress[key]; |
| CDOCProtocol *p2 = _protocolsByName[p1.name]; |
| if (p2 == nil) { |
| p2 = [[CDOCProtocol alloc] init]; |
| [p2 setName:[p1 name]]; |
| _protocolsByName[p2.name] = p2; |
| // adopted protocols still not set, will want uniqued instances |
| } else { |
| } |
| } |
| |
| //NSLog(@"uniqued protocol names: %@", [[[protocolsByName allKeys] sortedArrayUsingSelector:@selector(compare:)] componentsJoinedByString:@", "]); |
| |
| // And finally fill in adopted protocols, instance and class methods. And properties. |
| for (NSNumber *key in [[_protocolsByAddress allKeys] sortedArrayUsingSelector:@selector(compare:)]) { |
| CDOCProtocol *p1 = _protocolsByAddress[key]; |
| CDOCProtocol *uniqueProtocol = _protocolsByName[p1.name]; |
| for (CDOCProtocol *p2 in [p1 protocols]) |
| [uniqueProtocol addProtocol:_protocolsByName[p2.name]]; |
| |
| if ([[uniqueProtocol classMethods] count] == 0) { |
| for (CDOCMethod *method in [p1 classMethods]) |
| [uniqueProtocol addClassMethod:method]; |
| } else { |
| NSParameterAssert([[p1 classMethods] count] == 0 || [[uniqueProtocol classMethods] count] == [[p1 classMethods] count]); |
| } |
| |
| if ([[uniqueProtocol instanceMethods] count] == 0) { |
| for (CDOCMethod *method in [p1 instanceMethods]) |
| [uniqueProtocol addInstanceMethod:method]; |
| } else { |
| if (!([[p1 instanceMethods] count] == 0 || [[uniqueProtocol instanceMethods] count] == [[p1 instanceMethods] count])) { |
| //NSLog(@"p1 name: %@, uniqueProtocol name: %@", [p1 name], [uniqueProtocol name]); |
| //NSLog(@"p1 instanceMethods: %@", [p1 instanceMethods]); |
| //NSLog(@"uniqueProtocol instanceMethods: %@", [uniqueProtocol instanceMethods]); |
| } |
| NSParameterAssert([[p1 instanceMethods] count] == 0 || [[uniqueProtocol instanceMethods] count] == [[p1 instanceMethods] count]); |
| } |
| |
| if ([[uniqueProtocol optionalClassMethods] count] == 0) { |
| for (CDOCMethod *method in [p1 optionalClassMethods]) |
| [uniqueProtocol addOptionalClassMethod:method]; |
| } else { |
| NSParameterAssert([[p1 optionalClassMethods] count] == 0 || [[uniqueProtocol optionalClassMethods] count] == [[p1 optionalClassMethods] count]); |
| } |
| |
| if ([[uniqueProtocol optionalInstanceMethods] count] == 0) { |
| for (CDOCMethod *method in [p1 optionalInstanceMethods]) |
| [uniqueProtocol addOptionalInstanceMethod:method]; |
| } else { |
| NSParameterAssert([[p1 optionalInstanceMethods] count] == 0 || [[uniqueProtocol optionalInstanceMethods] count] == [[p1 optionalInstanceMethods] count]); |
| } |
| |
| if ([[uniqueProtocol properties] count] == 0) { |
| for (CDOCProperty *property in [p1 properties]) |
| [uniqueProtocol addProperty:property]; |
| } else { |
| NSParameterAssert([[p1 properties] count] == 0 || [[uniqueProtocol properties] count] == [[p1 properties] count]); |
| } |
| } |
| |
| //NSLog(@"protocolsByName: %@", protocolsByName); |
| } |
| |
| @end |