| // -*- mode: ObjC -*- |
| |
| // This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files. |
| // Copyright (C) 1997-2019 Steve Nygard. |
| |
| #import "CDTypeController.h" |
| |
| #import "CDStructureTable.h" |
| #import "CDClassDump.h" |
| #import "CDTypeFormatter.h" |
| #import "CDType.h" |
| |
| static BOOL debug = NO; |
| |
| @interface CDTypeController () |
| @property (weak, readonly) CDClassDump *classDump; |
| @property (readonly) CDStructureTable *structureTable; |
| @property (readonly) CDStructureTable *unionTable; |
| @end |
| |
| #pragma mark - |
| |
| @implementation CDTypeController |
| { |
| __weak CDClassDump *_classDump; // passed during formatting, to get at options. |
| __weak id <CDTypeControllerDelegate> _delegate; |
| |
| CDTypeFormatter *_ivarTypeFormatter; |
| CDTypeFormatter *_methodTypeFormatter; |
| CDTypeFormatter *_propertyTypeFormatter; |
| CDTypeFormatter *_structDeclarationTypeFormatter; |
| |
| CDStructureTable *_structureTable; |
| CDStructureTable *_unionTable; |
| } |
| |
| - (id)initWithClassDump:(CDClassDump *)classDump; |
| { |
| if ((self = [super init])) { |
| _classDump = classDump; |
| |
| _ivarTypeFormatter = [[CDTypeFormatter alloc] init]; |
| _ivarTypeFormatter.shouldExpand = NO; |
| _ivarTypeFormatter.shouldAutoExpand = YES; |
| _ivarTypeFormatter.baseLevel = 1; |
| _ivarTypeFormatter.typeController = self; |
| |
| _methodTypeFormatter = [[CDTypeFormatter alloc] init]; |
| _methodTypeFormatter.shouldExpand = NO; |
| _methodTypeFormatter.shouldAutoExpand = NO; |
| _methodTypeFormatter.baseLevel = 0; |
| _methodTypeFormatter.typeController = self; |
| |
| _propertyTypeFormatter = [[CDTypeFormatter alloc] init]; |
| _propertyTypeFormatter.shouldExpand = NO; |
| _propertyTypeFormatter.shouldAutoExpand = NO; |
| _propertyTypeFormatter.baseLevel = 0; |
| _propertyTypeFormatter.typeController = self; |
| |
| _structDeclarationTypeFormatter = [[CDTypeFormatter alloc] init]; |
| _structDeclarationTypeFormatter.shouldExpand = YES; // But don't expand named struct members... |
| _structDeclarationTypeFormatter.shouldAutoExpand = YES; |
| _structDeclarationTypeFormatter.baseLevel = 0; |
| _structDeclarationTypeFormatter.typeController = self; // But need to ignore some things? |
| |
| _structureTable = [[CDStructureTable alloc] init]; |
| _structureTable.anonymousBaseName = @"CDStruct_"; |
| _structureTable.identifier = @"Structs"; |
| _structureTable.typeController = self; |
| |
| _unionTable = [[CDStructureTable alloc] init]; |
| _unionTable.anonymousBaseName = @"CDUnion_"; |
| _unionTable.identifier = @"Unions"; |
| _unionTable.typeController = self; |
| |
| //[structureTable debugName:@"_xmlSAXHandler"]; |
| //[structureTable debugName:@"UCKeyboardTypeHeader"]; |
| //[structureTable debugName:@"UCKeyboardLayout"]; |
| //[structureTable debugName:@"ppd_group_s"]; |
| //[structureTable debugName:@"stat"]; |
| //[structureTable debugName:@"timespec"]; |
| //[structureTable debugName:@"AudioUnitEvent"]; |
| //[structureTable debugAnon:@"{?=II}"]; |
| //[structureTable debugName:@"_CommandStackEntry"]; |
| //[structureTable debugName:@"_flags"]; |
| } |
| |
| return self; |
| } |
| |
| #pragma mark - |
| |
| - (BOOL)shouldShowIvarOffsets; |
| { |
| return self.classDump.shouldShowIvarOffsets; |
| } |
| |
| - (BOOL)shouldShowMethodAddresses; |
| { |
| return self.classDump.shouldShowMethodAddresses; |
| } |
| |
| - (BOOL)targetArchUses64BitABI; |
| { |
| return CDArchUses64BitABI(self.classDump.targetArch); |
| } |
| |
| #pragma mark - |
| |
| - (CDType *)typeFormatter:(CDTypeFormatter *)typeFormatter replacementForType:(CDType *)type; |
| { |
| #if 0 |
| if (type.type == '{') return [structureTable replacementForType:type]; |
| if (type.type == '(') return [unionTable replacementForType:type]; |
| #endif |
| return nil; |
| } |
| |
| - (NSString *)typeFormatter:(CDTypeFormatter *)typeFormatter typedefNameForStructure:(CDType *)structureType level:(NSUInteger)level; |
| { |
| if (level == 0 && typeFormatter == self.structDeclarationTypeFormatter) |
| return nil; |
| |
| if ([self shouldExpandType:structureType] == NO) |
| return [self typedefNameForType:structureType]; |
| |
| return nil; |
| } |
| |
| - (void)typeFormatter:(CDTypeFormatter *)typeFormatter didReferenceClassName:(NSString *)name; |
| { |
| if ([self.delegate respondsToSelector:@selector(typeController:didReferenceClassName:)]) |
| [self.delegate typeController:self didReferenceClassName:name]; |
| } |
| |
| - (void)typeFormatter:(CDTypeFormatter *)typeFormatter didReferenceProtocolNames:(NSArray *)names; |
| { |
| if ([self.delegate respondsToSelector:@selector(typeController:didReferenceProtocolNames:)]) |
| [self.delegate typeController:self didReferenceProtocolNames:names]; |
| } |
| |
| #pragma mark - |
| |
| - (void)appendStructuresToString:(NSMutableString *)resultString; |
| { |
| if (self.hasUnknownFunctionPointers && self.hasUnknownBlocks) { |
| [resultString appendString:@"#pragma mark Function Pointers and Blocks\n\n"]; |
| } else if (self.hasUnknownFunctionPointers) { |
| [resultString appendString:@"#pragma mark Function Pointers\n\n"]; |
| } else if (self.hasUnknownBlocks) { |
| [resultString appendString:@"#pragma mark Blocks\n\n"]; |
| } |
| |
| if (self.hasUnknownFunctionPointers) { |
| [resultString appendFormat:@"typedef void (*CDUnknownFunctionPointerType)(void); // return type and parameters are unknown\n\n"]; |
| } |
| |
| if (self.hasUnknownBlocks) { |
| [resultString appendFormat:@"typedef void (^CDUnknownBlockType)(void); // return type and parameters are unknown\n\n"]; |
| } |
| |
| [self.structureTable appendNamedStructuresToString:resultString formatter:self.structDeclarationTypeFormatter markName:@"Named Structures"]; |
| [self.structureTable appendTypedefsToString:resultString formatter:self.structDeclarationTypeFormatter markName:@"Typedef'd Structures"]; |
| |
| [self.unionTable appendNamedStructuresToString:resultString formatter:self.structDeclarationTypeFormatter markName:@"Named Unions"]; |
| [self.unionTable appendTypedefsToString:resultString formatter:self.structDeclarationTypeFormatter markName:@"Typedef'd Unions"]; |
| } |
| |
| // Call this before calling generateMemberNames. |
| - (void)generateTypedefNames; |
| { |
| [self.structureTable generateTypedefNames]; |
| [self.unionTable generateTypedefNames]; |
| } |
| |
| - (void)generateMemberNames; |
| { |
| [self.structureTable generateMemberNames]; |
| [self.unionTable generateMemberNames]; |
| } |
| |
| #pragma mark - Run phase 1+ |
| |
| - (void)workSomeMagic; |
| { |
| [self startPhase1]; |
| [self startPhase2]; |
| [self startPhase3]; |
| |
| [self generateTypedefNames]; |
| [self generateMemberNames]; |
| |
| if (debug) { |
| NSMutableString *str = [NSMutableString string]; |
| [self.structureTable appendNamedStructuresToString:str formatter:self.structDeclarationTypeFormatter markName:@"Named Structures"]; |
| [self.unionTable appendNamedStructuresToString:str formatter:self.structDeclarationTypeFormatter markName:@"Named Unions"]; |
| [str writeToFile:@"/tmp/out.struct" atomically:NO encoding:NSUTF8StringEncoding error:NULL]; |
| |
| str = [NSMutableString string]; |
| [self.structureTable appendTypedefsToString:str formatter:self.structDeclarationTypeFormatter markName:@"Typedef'd Structures"]; |
| [self.unionTable appendTypedefsToString:str formatter:self.structDeclarationTypeFormatter markName:@"Typedef'd Unions"]; |
| [str writeToFile:@"/tmp/out.typedef" atomically:NO encoding:NSUTF8StringEncoding error:NULL]; |
| //NSLog(@"str =\n%@", str); |
| } |
| } |
| |
| #pragma mark - Phase 0 |
| |
| - (void)phase0RegisterStructure:(CDType *)structure usedInMethod:(BOOL)isUsedInMethod; |
| { |
| if (structure.primitiveType == '{') { |
| [self.structureTable phase0RegisterStructure:structure usedInMethod:isUsedInMethod]; |
| } else if (structure.primitiveType == '(') { |
| [self.unionTable phase0RegisterStructure:structure usedInMethod:isUsedInMethod]; |
| } else { |
| NSLog(@"%s, unknown structure type: %d", __cmd, structure.primitiveType); |
| } |
| } |
| |
| - (void)endPhase:(NSUInteger)phase; |
| { |
| if (phase == 0) { |
| [self.structureTable finishPhase0]; |
| [self.unionTable finishPhase0]; |
| } |
| } |
| |
| #pragma mark - Phase 1 |
| |
| // Phase one builds a list of all of the named and unnamed structures. |
| // It does this by going through all the top level structures we found in phase 0. |
| - (void)startPhase1; |
| { |
| //NSLog(@" > %s", __cmd); |
| // Structures and unions can be nested, so do phase 1 on each table before finishing the phase. |
| [self.structureTable runPhase1]; |
| [self.unionTable runPhase1]; |
| |
| [self.structureTable finishPhase1]; |
| [self.unionTable finishPhase1]; |
| //NSLog(@"< %s", __cmd); |
| } |
| |
| - (void)phase1RegisterStructure:(CDType *)structure; |
| { |
| if (structure.primitiveType == '{') { |
| [self.structureTable phase1RegisterStructure:structure]; |
| } else if (structure.primitiveType == '(') { |
| [self.unionTable phase1RegisterStructure:structure]; |
| } else { |
| NSLog(@"%s, unknown structure type: %d", __cmd, structure.primitiveType); |
| } |
| } |
| |
| #pragma mark - Phase 2 |
| |
| - (void)startPhase2; |
| { |
| NSUInteger maxDepth = self.structureTable.phase1_maxDepth; |
| if (maxDepth < self.unionTable.phase1_maxDepth) |
| maxDepth = self.unionTable.phase1_maxDepth; |
| |
| if (debug) NSLog(@"max structure/union depth is: %lu", maxDepth); |
| |
| for (NSUInteger depth = 1; depth <= maxDepth; depth++) { |
| [self.structureTable runPhase2AtDepth:depth]; |
| [self.unionTable runPhase2AtDepth:depth]; |
| } |
| |
| //[self.structureTable logPhase2Info]; |
| [self.structureTable finishPhase2]; |
| [self.unionTable finishPhase2]; |
| } |
| |
| - (void)startPhase3; |
| { |
| // do phase2 merge on all the types from phase 0 |
| [self.structureTable phase2ReplacementOnPhase0]; |
| [self.unionTable phase2ReplacementOnPhase0]; |
| |
| // Any info referenced by a method, or with >1 reference, gets typedef'd. |
| // - Generate name hash based on full type string at this point |
| // - Then fill in unnamed fields |
| |
| // Print method/>1 ref names and typedefs |
| // Go through all updated phase0_structureInfo types |
| // - start merging these into a new table |
| // - If this is the first time a structure has been added: |
| // - add one reference for each subtype |
| // - otherwise just merge them. |
| // - end result should be CDStructureInfos with counts and method reference flags |
| [self.structureTable buildPhase3Exceptions]; |
| [self.unionTable buildPhase3Exceptions]; |
| |
| [self.structureTable runPhase3]; |
| [self.unionTable runPhase3]; |
| |
| [self.structureTable finishPhase3]; |
| [self.unionTable finishPhase3]; |
| //[structureTable logPhase3Info]; |
| |
| // - All named structures (minus exceptions like struct _flags) get declared at the top level |
| // - All anonymous structures (minus exceptions) referenced by a method |
| // OR references >1 time gets typedef'd at the top and referenced by typedef subsequently |
| // Celebrate! |
| |
| // Then... what do we do when printing ivars/method types? |
| // CDTypeController - (BOOL)shouldExpandType:(CDType *)type; |
| // CDTypeController - (NSString *)typedefNameForType:(CDType *)type; |
| |
| //NSLog(@"< %s", __cmd); |
| } |
| |
| - (CDType *)phase2ReplacementForType:(CDType *)type; |
| { |
| if (type.primitiveType == '{') return [self.structureTable phase2ReplacementForType:type]; |
| if (type.primitiveType == '(') return [self.unionTable phase2ReplacementForType:type]; |
| |
| return nil; |
| } |
| |
| - (void)phase3RegisterStructure:(CDType *)structure; |
| { |
| //NSLog(@"%s, type= %@", __cmd, [aStructure typeString]); |
| if (structure.primitiveType == '{') [self.structureTable phase3RegisterStructure:structure count:1 usedInMethod:NO]; |
| if (structure.primitiveType == '(') [self.unionTable phase3RegisterStructure:structure count:1 usedInMethod:NO]; |
| } |
| |
| - (CDType *)phase3ReplacementForType:(CDType *)type; |
| { |
| if (type.primitiveType == '{') return [self.structureTable phase3ReplacementForType:type]; |
| if (type.primitiveType == '(') return [self.unionTable phase3ReplacementForType:type]; |
| |
| return nil; |
| } |
| |
| #pragma mark - |
| |
| - (BOOL)shouldShowName:(NSString *)name; |
| { |
| return [self.classDump shouldShowName:name]; |
| } |
| |
| - (BOOL)shouldExpandType:(CDType *)type; |
| { |
| if (type.primitiveType == '{') return [self.structureTable shouldExpandType:type]; |
| if (type.primitiveType == '(') return [self.unionTable shouldExpandType:type]; |
| |
| return NO; |
| } |
| |
| - (NSString *)typedefNameForType:(CDType *)type; |
| { |
| if (type.primitiveType == '{') return [self.structureTable typedefNameForType:type]; |
| if (type.primitiveType == '(') return [self.unionTable typedefNameForType:type]; |
| |
| return nil; |
| } |
| |
| @end |