├── .github └── workflows │ └── test.yml ├── .gitignore ├── ClassDump.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── ClassDump.xcscheme │ └── ClassDumpTests.xcscheme ├── ClassDump ├── ClassDump.h ├── ClassDump.xctestplan ├── Models │ ├── CDGenerationOptions.h │ ├── CDGenerationOptions.m │ ├── CDSemanticString.h │ ├── CDSemanticString.m │ ├── CDVariableModel.h │ ├── CDVariableModel.m │ ├── NSArray+CDFiltering.h │ ├── NSArray+CDFiltering.m │ ├── ParseTypes │ │ ├── CDArrayType.h │ │ ├── CDArrayType.m │ │ ├── CDBitFieldType.h │ │ ├── CDBitFieldType.m │ │ ├── CDBlockType.h │ │ ├── CDBlockType.m │ │ ├── CDObjectType.h │ │ ├── CDObjectType.m │ │ ├── CDParseType.h │ │ ├── CDParseType.m │ │ ├── CDPointerType.h │ │ ├── CDPointerType.m │ │ ├── CDPrimitiveType.h │ │ ├── CDPrimitiveType.m │ │ ├── CDRecordType.h │ │ └── CDRecordType.m │ └── Reflections │ │ ├── CDClassModel.h │ │ ├── CDClassModel.m │ │ ├── CDIvarModel.h │ │ ├── CDIvarModel.m │ │ ├── CDMethodModel.h │ │ ├── CDMethodModel.m │ │ ├── CDPropertyAttribute.h │ │ ├── CDPropertyAttribute.m │ │ ├── CDPropertyModel.h │ │ ├── CDPropertyModel.m │ │ ├── CDProtocolModel+Conformance.h │ │ ├── CDProtocolModel+Conformance.m │ │ ├── CDProtocolModel.h │ │ └── CDProtocolModel.m └── Services │ ├── CDTypeParser.h │ ├── CDTypeParser.m │ ├── CDUtilities.h │ └── CDUtilities.m ├── ClassDumpTests ├── CDObjCTests.m ├── CDParseAdvancedTests.m ├── CDParseCppTests.mm ├── CDParsePrimitiveTests.m ├── CDProtocolTest.m └── Info.plist ├── LICENSE.md ├── Makefile ├── Package.swift ├── README.md ├── Sources └── ClassDumpRuntime │ ├── ClassDump │ └── include │ └── ClassDump │ ├── CDArrayType.h │ ├── CDBitFieldType.h │ ├── CDBlockType.h │ ├── CDClassModel.h │ ├── CDGenerationOptions.h │ ├── CDIvarModel.h │ ├── CDMethodModel.h │ ├── CDObjectType.h │ ├── CDParseType.h │ ├── CDPointerType.h │ ├── CDPrimitiveType.h │ ├── CDPropertyAttribute.h │ ├── CDPropertyModel.h │ ├── CDProtocolModel+Conformance.h │ ├── CDProtocolModel.h │ ├── CDRecordType.h │ ├── CDSemanticString.h │ ├── CDTypeParser.h │ ├── CDUtilities.h │ ├── CDVariableModel.h │ └── ClassDump.h └── control /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Swift Package test 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | test: 8 | runs-on: macos-14 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | 13 | - name: Test 14 | run: | 15 | swift test 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /ClassDump.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ClassDump.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ClassDump.xcodeproj/xcshareddata/xcschemes/ClassDump.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 35 | 36 | 37 | 47 | 48 | 54 | 55 | 61 | 62 | 63 | 64 | 66 | 67 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /ClassDump.xcodeproj/xcshareddata/xcschemes/ClassDumpTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 18 | 19 | 23 | 29 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /ClassDump/ClassDump.h: -------------------------------------------------------------------------------- 1 | // 2 | // ClassDump.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 3/24/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | // to support building as both an Xcode framework and a Swift Package, 10 | // all headers that are marked as "public" for the Xcode framework 11 | // should have a symlink in `Sources/ClassDumpRuntime/include/ClassDump`; 12 | // all those files should then be imported below. 13 | // 14 | // you can generate these imports using a shell script such as 15 | // `ls ClassDump/*.h | while read HEADER; do printf "#import <${HEADER}>\n"; done` 16 | // (run from `Sources/ClassDumpRuntime/include`) 17 | 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | #import 30 | #import 31 | #import 32 | #import 33 | #import 34 | #import 35 | #import 36 | #import 37 | #import 38 | -------------------------------------------------------------------------------- /ClassDump/ClassDump.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "31CD5771-2F31-4142-AFFF-9E0550C35A54", 5 | "name" : "DefaultConfig", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "addressSanitizer" : { 13 | "detectStackUseAfterReturn" : true, 14 | "enabled" : true 15 | }, 16 | "undefinedBehaviorSanitizerEnabled" : true 17 | }, 18 | "testTargets" : [ 19 | { 20 | "target" : { 21 | "containerPath" : "container:ClassDump.xcodeproj", 22 | "identifier" : "FA2A011823AEB15700B52F1D", 23 | "name" : "ClassDumpTests" 24 | } 25 | } 26 | ], 27 | "version" : 1 28 | } 29 | -------------------------------------------------------------------------------- /ClassDump/Models/CDGenerationOptions.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDGenerationOptions.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 2/25/24. 6 | // Copyright © 2024 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// Options with which a header file may be generated with 12 | @interface CDGenerationOptions : NSObject 13 | /// @c YES means hide properties and methods that are required by a protocol the type conforms to 14 | /// 15 | /// This property applies to both classes and protocols. 16 | @property (nonatomic) BOOL stripProtocolConformance; 17 | /// @c YES means hide properties and methods that are inherited from the class hierachy 18 | /// 19 | /// This property only applies to classes. Protocols can require conformances to other 20 | /// protocols, however they do not have inheritance. 21 | /// Eligible properties are only hidden if the types match between the property in the 22 | /// current class and class nearest in the hierachy. 23 | /// For example, if `AAView` has a property `AALayer *layer` 24 | /// and a subclass `BBView` has a property `BBLayer *layer`, 25 | /// the property would not be hidden since the types are different. 26 | /// @see stripProtocolConformance 27 | @property (nonatomic) BOOL stripOverrides; 28 | /// @c YES means hide duplicate occurrences of a property or method, 29 | /// @c NO means transcribe the objects reported by the runtime 30 | @property (nonatomic) BOOL stripDuplicates; 31 | /// @c YES means hide methods and ivars that are synthesized from a property 32 | /// 33 | /// This property applies to both classes and protocols. 34 | @property (nonatomic) BOOL stripSynthesized; 35 | /// @c YES means hide @c .cxx_construct method, 36 | /// @c NO means show the method if it exists 37 | /// 38 | /// This property only applies to classes. 39 | @property (nonatomic) BOOL stripCtorMethod; 40 | /// @c YES means hide @c .cxx_destruct method, 41 | /// @c NO means show the method if it exists 42 | /// 43 | /// This property only applies to classes. 44 | @property (nonatomic) BOOL stripDtorMethod; 45 | /// @c YES means add comments above each eligible declaration 46 | /// with the symbol name and image path the object is found in, 47 | /// @c NO means do not add comments for symbol or image source 48 | /// 49 | /// This property applies to both classes and protocols. 50 | @property (nonatomic) BOOL addSymbolImageComments; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /ClassDump/Models/CDGenerationOptions.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDGenerationOptions.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 2/25/24. 6 | // Copyright © 2024 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDGenerationOptions.h" 10 | 11 | @implementation CDGenerationOptions 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ClassDump/Models/CDSemanticString.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDSemanticString.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 1/1/23. 6 | // Copyright © 2023 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// The semantic types that a string may represent in an Objective-C header file 12 | typedef NS_ENUM(NSUInteger, CDSemanticType) { 13 | // whitespace, colons (':'), semicolons (';'), pointers ('*'), 14 | // braces ('(', ')'), brackets ('{','}', '[', ']', '<', '>') 15 | CDSemanticTypeStandard, 16 | // characters used to start and end a comment, and the contents of the comment 17 | CDSemanticTypeComment, 18 | // struct, union, type modifiers, language provided primitive types 19 | CDSemanticTypeKeyword, 20 | // the name of a variable- this includes both declaration and usage sites 21 | CDSemanticTypeVariable, 22 | // the name portion of a struct or union definition 23 | CDSemanticTypeRecordName, 24 | // an Obj-C class (e.g. NSString) 25 | CDSemanticTypeClass, 26 | // an Obj-C protocol (e.g. NSFastEnumeration) 27 | CDSemanticTypeProtocol, 28 | // a number literal (e.g. 2, 18, 1e5, 7.1) 29 | CDSemanticTypeNumeric, 30 | 31 | /// The number of valid cases there are in @c CDSemanticType 32 | CDSemanticTypeCount 33 | }; 34 | 35 | /// A string composed of substrings that may have different semantic meanings 36 | @interface CDSemanticString : NSObject 37 | /// The length of the string 38 | @property (readonly) NSUInteger length; 39 | /// Append another semantic string to the end of this string, 40 | /// keeping all of the semantics of both the parameter and receiver 41 | - (void)appendSemanticString:(nonnull CDSemanticString *)semanticString; 42 | /// Append a string with a semantic type to the end of this string 43 | - (void)appendString:(nullable NSString *)string semanticType:(CDSemanticType)type; 44 | /// Whether the first character in this string is equal to @c character 45 | - (BOOL)startsWithChar:(char)character; 46 | /// Whether the last character in this string is equal to @c character 47 | - (BOOL)endWithChar:(char)character; 48 | /// Enumerate the substrings and the associated semantic type that compose this string 49 | - (void)enumerateTypesUsingBlock:(void (NS_NOESCAPE ^_Nonnull)(NSString *_Nonnull string, CDSemanticType type))block; 50 | /// Enumerate the longest effective substrings and the associated semantic type that compose this string 51 | /// 52 | /// Each invocation of @c block will have the longest substring of @c type such that the next 53 | /// invocation will have a different @c type 54 | - (void)enumerateLongestEffectiveRangesUsingBlock:(void (NS_NOESCAPE ^_Nonnull)(NSString *_Nonnull string, CDSemanticType type))block; 55 | 56 | /// The string representation without semantics 57 | - (nonnull NSString *)string; 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /ClassDump/Models/CDSemanticString.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDSemanticString.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 1/1/23. 6 | // Copyright © 2023 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDSemanticString.h" 10 | 11 | @interface CDSemanticStringStaple : NSObject 12 | @property (strong, nonatomic) NSString *string; 13 | @property (nonatomic) CDSemanticType type; 14 | @end 15 | 16 | @implementation CDSemanticStringStaple 17 | @end 18 | 19 | 20 | @implementation CDSemanticString { 21 | NSMutableArray *_components; 22 | } 23 | 24 | - (instancetype)init { 25 | if (self = [super init]) { 26 | _length = 0; 27 | _components = [NSMutableArray array]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)appendSemanticString:(CDSemanticString *)semanticString { 33 | [_components addObjectsFromArray:semanticString->_components]; 34 | _length += semanticString.length; 35 | } 36 | 37 | - (void)appendString:(NSString *)string semanticType:(CDSemanticType)type { 38 | if (string.length > 0) { 39 | CDSemanticStringStaple *staple = [CDSemanticStringStaple new]; 40 | staple.string = string; 41 | staple.type = type; 42 | [_components addObject:staple]; 43 | _length += string.length; 44 | } 45 | } 46 | 47 | - (BOOL)startsWithChar:(char)character { 48 | char *bytes = &character; 49 | NSString *suffix = [[NSString alloc] initWithBytesNoCopy:bytes length:1 encoding:NSASCIIStringEncoding freeWhenDone:NO]; 50 | return [_components.firstObject.string hasPrefix:suffix]; 51 | } 52 | 53 | - (BOOL)endWithChar:(char)character { 54 | char *bytes = &character; 55 | NSString *suffix = [[NSString alloc] initWithBytesNoCopy:bytes length:1 encoding:NSASCIIStringEncoding freeWhenDone:NO]; 56 | return [_components.lastObject.string hasSuffix:suffix]; 57 | } 58 | 59 | - (void)enumerateTypesUsingBlock:(void (NS_NOESCAPE ^)(NSString *string, CDSemanticType type))block { 60 | for (CDSemanticStringStaple *staple in _components) { 61 | block(staple.string, staple.type); 62 | } 63 | } 64 | 65 | - (void)enumerateLongestEffectiveRangesUsingBlock:(void (NS_NOESCAPE ^)(NSString *string, CDSemanticType type))block { 66 | CDSemanticType activeStapleType = CDSemanticTypeStandard; 67 | NSMutableString *concatString = nil; 68 | for (CDSemanticStringStaple *staple in _components) { 69 | if ((concatString == nil) || (staple.type != activeStapleType)) { 70 | if (concatString != nil) { 71 | block([concatString copy], activeStapleType); 72 | } 73 | concatString = [NSMutableString stringWithString:staple.string]; 74 | activeStapleType = staple.type; 75 | } else { 76 | [concatString appendString:staple.string]; 77 | } 78 | } 79 | if (concatString != nil) { 80 | block([concatString copy], activeStapleType); 81 | } 82 | } 83 | 84 | - (NSString *)string { 85 | NSMutableString *build = [NSMutableString string]; 86 | for (CDSemanticStringStaple *staple in _components) { 87 | [build appendString:staple.string]; 88 | } 89 | return [build copy]; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /ClassDump/Models/CDVariableModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDVariableModel.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | @interface CDVariableModel : NSObject 14 | 15 | @property (strong, nonatomic) NSString *name; 16 | @property (strong, nonatomic) CDParseType *type; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ClassDump/Models/CDVariableModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDVariableModel.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDVariableModel.h" 10 | 11 | @implementation CDVariableModel 12 | 13 | - (BOOL)isEqual:(id)object { 14 | if ([object isKindOfClass:[self class]]) { 15 | __typeof(self) casted = (__typeof(casted))object; 16 | return (self.name == casted.name || [self.name isEqualToString:casted.name]) && 17 | (self.type == casted.type || [self.type isEqual:casted.type]); 18 | } 19 | return NO; 20 | } 21 | 22 | - (NSString *)description { 23 | return [self.type stringForVariableName:self.name]; 24 | } 25 | 26 | - (NSString *)debugDescription { 27 | return [NSString stringWithFormat:@"<%@: %p> {name: '%@', type: %@}", 28 | [self class], self, self.name, self.type.debugDescription]; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ClassDump/Models/NSArray+CDFiltering.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+CDFiltering.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 2/26/24. 6 | // Copyright © 2024 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSArray<__covariant ObjectType> (CDFiltering) 12 | 13 | - (NSArray *)cd_uniqueObjects; 14 | - (NSArray *)cd_filterObjectsIgnoring:(NSSet *)ignoreSet; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ClassDump/Models/NSArray+CDFiltering.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+CDFiltering.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 2/26/24. 6 | // Copyright © 2024 Leptos. All rights reserved. 7 | // 8 | 9 | #import "NSArray+CDFiltering.h" 10 | 11 | @implementation NSArray (CDFiltering) 12 | 13 | - (NSArray *)cd_uniqueObjects { 14 | NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.count]; 15 | 16 | for (id object in self) { 17 | if ([result containsObject:object]) { 18 | continue; 19 | } 20 | [result addObject:object]; 21 | } 22 | 23 | return result; 24 | } 25 | 26 | - (NSArray *)cd_filterObjectsIgnoring:(NSSet *)ignoreSet { 27 | NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.count]; 28 | 29 | for (id object in self) { 30 | if ([ignoreSet containsObject:object]) { 31 | continue; 32 | } 33 | [result addObject:object]; 34 | } 35 | 36 | return result; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDArrayType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDArrayType.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// Type representing a C array 12 | @interface CDArrayType : CDParseType 13 | /// Type of elements in the array 14 | @property (strong, nonatomic) CDParseType *type; 15 | /// Number of elements in the array 16 | @property (nonatomic) NSUInteger size; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDArrayType.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDArrayType.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDArrayType.h" 10 | 11 | @implementation CDArrayType 12 | 13 | - (CDSemanticString *)semanticStringForVariableName:(NSString *)varName { 14 | CDSemanticString *build = [CDSemanticString new]; 15 | CDSemanticString *modifiersString = [self modifiersSemanticString]; 16 | if (modifiersString.length > 0) { 17 | [build appendSemanticString:modifiersString]; 18 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 19 | } 20 | 21 | NSMutableArray *arrayStack = [NSMutableArray array]; 22 | 23 | CDParseType *headType = self; 24 | while ([headType isKindOfClass:[CDArrayType class]]) { 25 | CDArrayType *arrayType = (__kindof CDParseType *)headType; 26 | [arrayStack addObject:arrayType]; 27 | headType = arrayType.type; 28 | } 29 | 30 | [build appendSemanticString:[headType semanticStringForVariableName:varName]]; 31 | 32 | [arrayStack enumerateObjectsUsingBlock:^(CDArrayType *arrayType, NSUInteger idx, BOOL *stop) { 33 | [build appendString:@"[" semanticType:CDSemanticTypeStandard]; 34 | [build appendString:[NSString stringWithFormat:@"%lu", (unsigned long)arrayType.size] semanticType:CDSemanticTypeNumeric]; 35 | [build appendString:@"]" semanticType:CDSemanticTypeStandard]; 36 | }]; 37 | 38 | return build; 39 | } 40 | 41 | - (NSSet *)classReferences { 42 | return [self.type classReferences]; 43 | } 44 | 45 | - (NSSet *)protocolReferences { 46 | return [self.type protocolReferences]; 47 | } 48 | 49 | - (BOOL)isEqual:(id)object { 50 | if ([object isKindOfClass:[self class]]) { 51 | __typeof(self) casted = (__typeof(casted))object; 52 | return (self.modifiers == casted.modifiers || [self.modifiers isEqualToArray:casted.modifiers]) && 53 | (self.type == casted.type || [self.type isEqual:casted.type]) && 54 | (self.size == casted.size); 55 | } 56 | return NO; 57 | } 58 | 59 | - (NSString *)debugDescription { 60 | return [NSString stringWithFormat:@"<%@: %p> {modifiers: '%@', type: %@, size: %lu}", 61 | [self class], self, [self modifiersString], self.type.debugDescription, (unsigned long)self.size]; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDBitFieldType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDBitFieldType.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// Type representing a bit-field in a record 12 | @interface CDBitFieldType : CDParseType 13 | /// Width of the bit-fields (in bits) 14 | @property (nonatomic) NSUInteger width; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDBitFieldType.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDBitFieldType.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDBitFieldType.h" 10 | 11 | @implementation CDBitFieldType 12 | 13 | - (CDSemanticString *)semanticStringForVariableName:(NSString *)varName { 14 | CDSemanticString *build = [CDSemanticString new]; 15 | CDSemanticString *modifiersString = [self modifiersSemanticString]; 16 | if (modifiersString.length > 0) { 17 | [build appendSemanticString:modifiersString]; 18 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 19 | } 20 | 21 | NSUInteger const bitWidth = self.width; 22 | 23 | NSString *type = nil; 24 | #ifndef __CHAR_BIT__ 25 | # error __CHAR_BIT__ must be defined 26 | #endif 27 | /* all bitwidth base-types are unsigned, because that's the typical use case */ 28 | if (bitWidth <= __CHAR_BIT__) { 29 | type = @"unsigned char"; 30 | } 31 | #ifdef __SIZEOF_SHORT__ 32 | else if (bitWidth <= (__SIZEOF_SHORT__ * __CHAR_BIT__)) { 33 | type = @"unsigned short"; 34 | } 35 | #endif 36 | #ifdef __SIZEOF_INT__ 37 | else if (bitWidth <= (__SIZEOF_INT__ * __CHAR_BIT__)) { 38 | type = @"unsigned int"; 39 | } 40 | #endif 41 | #ifdef __SIZEOF_LONG__ 42 | else if (bitWidth <= (__SIZEOF_LONG__ * __CHAR_BIT__)) { 43 | type = @"unsigned long"; 44 | } 45 | #endif 46 | #ifdef __SIZEOF_LONG_LONG__ 47 | else if (bitWidth <= (__SIZEOF_LONG_LONG__ * __CHAR_BIT__)) { 48 | type = @"unsigned long long"; 49 | } 50 | #endif 51 | #ifdef __SIZEOF_INT128__ 52 | else if (bitWidth <= (__SIZEOF_INT128__ * __CHAR_BIT__)) { 53 | type = @"unsigned __int128"; 54 | } 55 | #endif 56 | else { 57 | NSAssert(NO, @"width of bit-field exceeds width of any known type"); 58 | type = @"unsigned"; 59 | } 60 | 61 | [build appendString:type semanticType:CDSemanticTypeKeyword]; 62 | 63 | if (varName != nil) { 64 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 65 | [build appendString:varName semanticType:CDSemanticTypeVariable]; 66 | } 67 | 68 | [build appendString:@" : " semanticType:CDSemanticTypeStandard]; 69 | [build appendString:[NSString stringWithFormat:@"%lu", (unsigned long)self.width] semanticType:CDSemanticTypeNumeric]; 70 | return build; 71 | } 72 | 73 | - (BOOL)isEqual:(id)object { 74 | if ([object isKindOfClass:[self class]]) { 75 | __typeof(self) casted = (__typeof(casted))object; 76 | return (self.modifiers == casted.modifiers || [self.modifiers isEqualToArray:casted.modifiers]) && 77 | (self.width == casted.width); 78 | } 79 | return NO; 80 | } 81 | 82 | - (NSString *)debugDescription { 83 | return [NSString stringWithFormat:@"<%@: %p> {modifiers: '%@', width: %lu}", 84 | [self class], self, [self modifiersString], (unsigned long)self.width]; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDBlockType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDBlockType.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/15/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// Type representing a block 12 | @interface CDBlockType : CDParseType 13 | /// The type that this block returns 14 | @property (nullable, strong, nonatomic) CDParseType *returnType; 15 | /// The types of the parameters to this block 16 | @property (nullable, strong, nonatomic) NSArray *parameterTypes; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDBlockType.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDBlockType.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/15/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDBlockType.h" 10 | 11 | @implementation CDBlockType 12 | 13 | - (CDSemanticString *)semanticStringForVariableName:(NSString *)varName { 14 | CDSemanticString *build = [CDSemanticString new]; 15 | CDSemanticString *modifiersString = [self modifiersSemanticString]; 16 | 17 | if (self.returnType != nil && self.parameterTypes != nil) { 18 | [build appendSemanticString:[self.returnType semanticStringForVariableName:nil]]; 19 | [build appendString:@" (^" semanticType:CDSemanticTypeStandard]; 20 | 21 | if (modifiersString.length > 0) { 22 | [build appendSemanticString:modifiersString]; 23 | } 24 | if (modifiersString.length > 0 && varName != nil) { 25 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 26 | } 27 | if (varName != nil) { 28 | [build appendString:varName semanticType:CDSemanticTypeVariable]; 29 | } 30 | [build appendString:@")(" semanticType:CDSemanticTypeStandard]; 31 | 32 | NSUInteger const paramCount = self.parameterTypes.count; 33 | if (paramCount == 0) { 34 | [build appendString:@"void" semanticType:CDSemanticTypeKeyword]; 35 | } else { 36 | [self.parameterTypes enumerateObjectsUsingBlock:^(CDParseType *paramType, NSUInteger idx, BOOL *stop) { 37 | [build appendSemanticString:[paramType semanticStringForVariableName:nil]]; 38 | if ((idx + 1) < paramCount) { 39 | [build appendString:@", " semanticType:CDSemanticTypeStandard]; 40 | } 41 | }]; 42 | } 43 | [build appendString:@")" semanticType:CDSemanticTypeStandard]; 44 | } else { 45 | if (modifiersString.length > 0) { 46 | [build appendSemanticString:modifiersString]; 47 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 48 | } 49 | [build appendString:@"id" semanticType:CDSemanticTypeKeyword]; 50 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 51 | [build appendString:@"/* block */" semanticType:CDSemanticTypeComment]; 52 | if (varName != nil) { 53 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 54 | [build appendString:varName semanticType:CDSemanticTypeVariable]; 55 | } 56 | } 57 | return build; 58 | } 59 | 60 | - (NSSet *)classReferences { 61 | NSMutableSet *build = [NSMutableSet set]; 62 | NSSet *returnReferences = [self.returnType classReferences]; 63 | if (returnReferences != nil) { 64 | [build unionSet:returnReferences]; 65 | } 66 | for (CDParseType *paramType in self.parameterTypes) { 67 | NSSet *paramReferences = [paramType classReferences]; 68 | if (paramReferences != nil) { 69 | [build unionSet:paramReferences]; 70 | } 71 | } 72 | return build; 73 | } 74 | 75 | - (NSSet *)protocolReferences { 76 | NSMutableSet *build = [NSMutableSet set]; 77 | NSSet *returnReferences = [self.returnType protocolReferences]; 78 | if (returnReferences != nil) { 79 | [build unionSet:returnReferences]; 80 | } 81 | for (CDParseType *paramType in self.parameterTypes) { 82 | NSSet *paramReferences = [paramType protocolReferences]; 83 | if (paramReferences != nil) { 84 | [build unionSet:paramReferences]; 85 | } 86 | } 87 | return build; 88 | } 89 | 90 | - (BOOL)isEqual:(id)object { 91 | if ([object isKindOfClass:[self class]]) { 92 | __typeof(self) casted = (__typeof(casted))object; 93 | return (self.modifiers == casted.modifiers || [self.modifiers isEqualToArray:casted.modifiers]) && 94 | (self.returnType == casted.returnType || [self.returnType isEqual:casted.returnType]) && 95 | (self.parameterTypes == casted.parameterTypes || [self.parameterTypes isEqualToArray:casted.parameterTypes]); 96 | } 97 | return NO; 98 | } 99 | 100 | - (NSString *)debugDescription { 101 | return [NSString stringWithFormat:@"<%@: %p> {modifiers: '%@', returnType: %@, parameterTypes: %@}", 102 | [self class], self, [self modifiersString], self.returnType, self.parameterTypes]; 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDObjectType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDObjectType.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// Type representing an Objective-C object 12 | @interface CDObjectType : CDParseType 13 | /// The name of the class of the object 14 | /// 15 | /// If this value is @c nil the type is @c id 16 | @property (nullable, strong, nonatomic) NSString *className; 17 | /// The names of the protocols the object conforms to 18 | @property (nullable, strong, nonatomic) NSArray *protocolNames; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDObjectType.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDObjectType.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDObjectType.h" 10 | 11 | @implementation CDObjectType 12 | 13 | - (CDSemanticString *)semanticStringForVariableName:(NSString *)varName { 14 | CDSemanticString *build = [CDSemanticString new]; 15 | CDSemanticString *modifiersString = [self modifiersSemanticString]; 16 | if (modifiersString.length > 0) { 17 | [build appendSemanticString:modifiersString]; 18 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 19 | } 20 | 21 | BOOL const hasClassName = (self.className != nil); 22 | 23 | if (hasClassName) { 24 | [build appendString:self.className semanticType:CDSemanticTypeClass]; 25 | } else { 26 | [build appendString:@"id" semanticType:CDSemanticTypeKeyword]; 27 | } 28 | 29 | NSArray *protocolNames = self.protocolNames; 30 | NSUInteger const protocolNameCount = protocolNames.count; 31 | if (protocolNames.count > 0) { 32 | [build appendString:@"<" semanticType:CDSemanticTypeStandard]; 33 | [protocolNames enumerateObjectsUsingBlock:^(NSString *protocolName, NSUInteger idx, BOOL *stop) { 34 | [build appendString:protocolName semanticType:CDSemanticTypeProtocol]; 35 | if ((idx + 1) < protocolNameCount) { 36 | [build appendString:@", " semanticType:CDSemanticTypeStandard]; 37 | } 38 | }]; 39 | [build appendString:@">" semanticType:CDSemanticTypeStandard]; 40 | } 41 | if (hasClassName) { 42 | [build appendString:@" *" semanticType:CDSemanticTypeStandard]; 43 | } 44 | 45 | if (varName != nil) { 46 | if (!hasClassName) { 47 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 48 | } 49 | [build appendString:varName semanticType:CDSemanticTypeVariable]; 50 | } 51 | return build; 52 | } 53 | 54 | - (NSSet *)classReferences { 55 | NSString *className = self.className; 56 | if (className != nil) { 57 | return [NSSet setWithObject:className]; 58 | } 59 | return nil; 60 | } 61 | 62 | - (NSSet *)protocolReferences { 63 | NSArray *protocolNames = self.protocolNames; 64 | if (protocolNames != nil) { 65 | return [NSSet setWithArray:protocolNames]; 66 | } 67 | return nil; 68 | } 69 | 70 | - (BOOL)isEqual:(id)object { 71 | if ([object isKindOfClass:[self class]]) { 72 | __typeof(self) casted = (__typeof(casted))object; 73 | return (self.modifiers == casted.modifiers || [self.modifiers isEqualToArray:casted.modifiers]) && 74 | (self.className == casted.className || [self.className isEqualToString:casted.className]) && 75 | (self.protocolNames == casted.protocolNames || [self.protocolNames isEqualToArray:casted.protocolNames]); 76 | } 77 | return NO; 78 | } 79 | 80 | - (NSString *)debugDescription { 81 | return [NSString stringWithFormat:@"<%@: %p> {modifiers: '%@', className: '%@', protocolNames: %@}", 82 | [self class], self, [self modifiersString], self.className, self.protocolNames]; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDParseType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDParseType.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSUInteger, CDTypeModifier) { 13 | CDTypeModifierConst, 14 | CDTypeModifierComplex, 15 | CDTypeModifierAtomic, 16 | 17 | CDTypeModifierIn, 18 | CDTypeModifierInOut, 19 | CDTypeModifierOut, 20 | CDTypeModifierBycopy, 21 | CDTypeModifierByref, 22 | CDTypeModifierOneway, 23 | 24 | /// The number of valid cases there are in @c CDTypeModifier 25 | CDTypeModifierCount 26 | }; 27 | 28 | OBJC_EXTERN NSString *_Nullable NSStringFromCDTypeModifier(CDTypeModifier); 29 | 30 | /// Base class to represent a type that a variable may be 31 | @interface CDParseType : NSObject 32 | 33 | @property (nullable, strong, nonatomic) NSArray *modifiers; // array of CDTypeModifier 34 | 35 | /// A string as this type would appear in code for a given variable name. 36 | /// 37 | /// @param varName The name of the variable this type is for 38 | - (nonnull NSString *)stringForVariableName:(nullable NSString *)varName; 39 | 40 | - (nonnull NSString *)modifiersString; 41 | 42 | - (nonnull CDSemanticString *)semanticStringForVariableName:(nullable NSString *)varName; 43 | 44 | - (nonnull CDSemanticString *)modifiersSemanticString; 45 | 46 | /// Classes this type references 47 | /// 48 | /// For example, `NSCache *` references the "NSCache" class 49 | - (nullable NSSet *)classReferences; 50 | /// Protocols this type references 51 | /// 52 | /// For example, `NSCache *` references the "NSFastEnumeration" protocol 53 | - (nullable NSSet *)protocolReferences; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDParseType.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDParseType.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDParseType.h" 10 | 11 | NSString *NSStringFromCDTypeModifier(CDTypeModifier modifier) { 12 | switch (modifier) { 13 | case CDTypeModifierConst: 14 | return @"const"; 15 | case CDTypeModifierComplex: 16 | return @"_Complex"; 17 | case CDTypeModifierAtomic: 18 | return @"_Atomic"; 19 | case CDTypeModifierIn: 20 | return @"in"; 21 | case CDTypeModifierInOut: 22 | return @"inout"; 23 | case CDTypeModifierOut: 24 | return @"out"; 25 | case CDTypeModifierBycopy: 26 | return @"bycopy"; 27 | case CDTypeModifierByref: 28 | return @"byref"; 29 | case CDTypeModifierOneway: 30 | return @"oneway"; 31 | default: 32 | NSCAssert(NO, @"Unknown CDTypeModifier value"); 33 | return nil; 34 | } 35 | } 36 | 37 | @implementation CDParseType 38 | 39 | - (NSString *)stringForVariableName:(NSString *)varName { 40 | return [[self semanticStringForVariableName:varName] string]; 41 | } 42 | 43 | - (CDSemanticString *)semanticStringForVariableName:(NSString *)varName { 44 | NSAssert(NO, @"Subclasses must implement %@", NSStringFromSelector(_cmd)); 45 | return [CDSemanticString new]; 46 | } 47 | 48 | - (NSString *)modifiersString { 49 | NSArray *const modifiers = self.modifiers; 50 | NSMutableArray *strings = [NSMutableArray arrayWithCapacity:modifiers.count]; 51 | [modifiers enumerateObjectsUsingBlock:^(NSNumber *value, NSUInteger idx, BOOL *stop) { 52 | strings[idx] = NSStringFromCDTypeModifier(value.unsignedIntegerValue); 53 | }]; 54 | return [strings componentsJoinedByString:@" "]; 55 | } 56 | 57 | - (CDSemanticString *)modifiersSemanticString { 58 | NSArray *const modifiers = self.modifiers; 59 | CDSemanticString *build = [CDSemanticString new]; 60 | NSUInteger const modifierCount = self.modifiers.count; 61 | [modifiers enumerateObjectsUsingBlock:^(NSNumber *value, NSUInteger idx, BOOL *stop) { 62 | NSString *string = NSStringFromCDTypeModifier(value.unsignedIntegerValue); 63 | [build appendString:string semanticType:CDSemanticTypeKeyword]; 64 | if ((idx + 1) < modifierCount) { 65 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 66 | } 67 | }]; 68 | return build; 69 | } 70 | 71 | - (NSSet *)classReferences { 72 | return nil; 73 | } 74 | 75 | - (NSSet *)protocolReferences { 76 | return nil; 77 | } 78 | 79 | - (BOOL)isEqual:(id)object { 80 | if ([object isKindOfClass:[self class]]) { 81 | __typeof(self) casted = (__typeof(casted))object; 82 | return (self.modifiers == casted.modifiers || [self.modifiers isEqualToArray:casted.modifiers]); 83 | } 84 | return NO; 85 | } 86 | 87 | - (NSString *)description { 88 | return [self stringForVariableName:nil]; 89 | } 90 | 91 | - (NSString *)debugDescription { 92 | return [NSString stringWithFormat:@"<%@: %p> {modifiers: '%@'}", 93 | [self class], self, [self modifiersString]]; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDPointerType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDPointerType.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// Type representing a pointer 12 | @interface CDPointerType : CDParseType 13 | /// The type that this pointer points to 14 | @property (nullable, strong, nonatomic) CDParseType *pointee; 15 | 16 | + (nonnull instancetype)pointerToPointee:(nonnull CDParseType *)pointee; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDPointerType.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDPointerType.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDPointerType.h" 10 | 11 | @implementation CDPointerType 12 | 13 | + (nonnull instancetype)pointerToPointee:(nonnull CDParseType *)pointee { 14 | CDPointerType *ret = [self new]; 15 | ret.pointee = pointee; 16 | return ret; 17 | } 18 | 19 | - (CDSemanticString *)semanticStringForVariableName:(NSString *)varName { 20 | CDSemanticString *build = [CDSemanticString new]; 21 | CDSemanticString *modifiersString = [self modifiersSemanticString]; 22 | if (modifiersString.length > 0) { 23 | [build appendSemanticString:modifiersString]; 24 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 25 | } 26 | [build appendSemanticString:[self.pointee semanticStringForVariableName:nil]]; 27 | if (![build endWithChar:'*']) { 28 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 29 | } 30 | [build appendString:@"*" semanticType:CDSemanticTypeStandard]; 31 | if (varName != nil) { 32 | [build appendString:varName semanticType:CDSemanticTypeVariable]; 33 | } 34 | return build; 35 | } 36 | 37 | - (NSSet *)classReferences { 38 | return [self.pointee classReferences]; 39 | } 40 | 41 | - (NSSet *)protocolReferences { 42 | return [self.pointee protocolReferences]; 43 | } 44 | 45 | - (BOOL)isEqual:(id)object { 46 | if ([object isKindOfClass:[self class]]) { 47 | __typeof(self) casted = (__typeof(casted))object; 48 | return (self.modifiers == casted.modifiers || [self.modifiers isEqualToArray:casted.modifiers]) && 49 | (self.pointee == casted.pointee || [self.pointee isEqual:casted.pointee]); 50 | } 51 | return NO; 52 | } 53 | 54 | - (NSString *)debugDescription { 55 | return [NSString stringWithFormat:@"<%@: %p> {modifiers: '%@', pointee: %@}", 56 | [self class], self, [self modifiersString], self.pointee.debugDescription]; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDPrimitiveType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDPrimitiveType.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSUInteger, CDPrimitiveRawType) { 12 | CDPrimitiveRawTypeVoid, 13 | 14 | CDPrimitiveRawTypeChar, 15 | CDPrimitiveRawTypeInt, 16 | CDPrimitiveRawTypeShort, 17 | CDPrimitiveRawTypeLong, 18 | CDPrimitiveRawTypeLongLong, 19 | CDPrimitiveRawTypeInt128, 20 | 21 | CDPrimitiveRawTypeUnsignedChar, 22 | CDPrimitiveRawTypeUnsignedInt, 23 | CDPrimitiveRawTypeUnsignedShort, 24 | CDPrimitiveRawTypeUnsignedLong, 25 | CDPrimitiveRawTypeUnsignedLongLong, 26 | CDPrimitiveRawTypeUnsignedInt128, 27 | 28 | CDPrimitiveRawTypeFloat, 29 | CDPrimitiveRawTypeDouble, 30 | CDPrimitiveRawTypeLongDouble, 31 | 32 | CDPrimitiveRawTypeBool, 33 | CDPrimitiveRawTypeClass, 34 | CDPrimitiveRawTypeSel, 35 | 36 | CDPrimitiveRawTypeFunction, 37 | 38 | /// @note This is not a real type. 39 | /// @discussion A blank type represents a type that 40 | /// is encoded to a space character. There are multiple 41 | /// types that are encoded to a space character, and it 42 | /// is not possible for us to discern the difference 43 | /// between them. 44 | CDPrimitiveRawTypeBlank, 45 | /// @note This is not a real type. 46 | /// @discussion An empty type represents a type that 47 | /// was not encoded. Usually this occurs when types 48 | /// that do not exist in Objective-C are bridged into 49 | /// Objective-C (this should only occur at runtime). 50 | CDPrimitiveRawTypeEmpty, 51 | }; 52 | 53 | OBJC_EXTERN NSString *_Nullable NSStringFromCDPrimitiveRawType(CDPrimitiveRawType); 54 | 55 | @interface CDPrimitiveType : CDParseType 56 | 57 | @property (nonatomic) CDPrimitiveRawType rawType; 58 | 59 | + (nonnull instancetype)primitiveWithRawType:(CDPrimitiveRawType)rawType; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDPrimitiveType.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDPrimitiveType.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDPrimitiveType.h" 10 | 11 | NSString *NSStringFromCDPrimitiveRawType(CDPrimitiveRawType rawType) { 12 | switch (rawType) { 13 | case CDPrimitiveRawTypeVoid: 14 | return @"void"; 15 | case CDPrimitiveRawTypeChar: 16 | return @"char"; 17 | case CDPrimitiveRawTypeInt: 18 | return @"int"; 19 | case CDPrimitiveRawTypeShort: 20 | return @"short"; 21 | case CDPrimitiveRawTypeLong: 22 | return @"long"; 23 | case CDPrimitiveRawTypeLongLong: 24 | return @"long long"; 25 | case CDPrimitiveRawTypeInt128: 26 | return @"__int128"; 27 | case CDPrimitiveRawTypeUnsignedChar: 28 | return @"unsigned char"; 29 | case CDPrimitiveRawTypeUnsignedInt: 30 | return @"unsigned int"; 31 | case CDPrimitiveRawTypeUnsignedShort: 32 | return @"unsigned short"; 33 | case CDPrimitiveRawTypeUnsignedLong: 34 | return @"unsigned long"; 35 | case CDPrimitiveRawTypeUnsignedLongLong: 36 | return @"unsigned long long"; 37 | case CDPrimitiveRawTypeUnsignedInt128: 38 | return @"unsigned __int128"; 39 | case CDPrimitiveRawTypeFloat: 40 | return @"float"; 41 | case CDPrimitiveRawTypeDouble: 42 | return @"double"; 43 | case CDPrimitiveRawTypeLongDouble: 44 | return @"long double"; 45 | case CDPrimitiveRawTypeBool: 46 | return @"BOOL"; 47 | case CDPrimitiveRawTypeClass: 48 | return @"Class"; 49 | case CDPrimitiveRawTypeSel: 50 | return @"SEL"; 51 | case CDPrimitiveRawTypeFunction: 52 | return @"void /* function */"; 53 | case CDPrimitiveRawTypeBlank: 54 | return @"void /* unknown type, blank encoding */"; 55 | case CDPrimitiveRawTypeEmpty: 56 | return @"void /* unknown type, empty encoding */"; 57 | } 58 | } 59 | 60 | @implementation CDPrimitiveType 61 | 62 | + (nonnull instancetype)primitiveWithRawType:(CDPrimitiveRawType)rawType { 63 | CDPrimitiveType *ret = [self new]; 64 | ret.rawType = rawType; 65 | return ret; 66 | } 67 | 68 | - (CDSemanticString *)semanticStringForVariableName:(NSString *)varName { 69 | CDSemanticString *build = [CDSemanticString new]; 70 | CDSemanticString *modifiersString = [self modifiersSemanticString]; 71 | if (modifiersString.length > 0) { 72 | [build appendSemanticString:modifiersString]; 73 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 74 | } 75 | switch (self.rawType) { 76 | case CDPrimitiveRawTypeVoid: 77 | [build appendString:@"void" semanticType:CDSemanticTypeKeyword]; 78 | break; 79 | case CDPrimitiveRawTypeChar: 80 | [build appendString:@"char" semanticType:CDSemanticTypeKeyword]; 81 | break; 82 | case CDPrimitiveRawTypeInt: 83 | [build appendString:@"int" semanticType:CDSemanticTypeKeyword]; 84 | break; 85 | case CDPrimitiveRawTypeShort: 86 | [build appendString:@"short" semanticType:CDSemanticTypeKeyword]; 87 | break; 88 | case CDPrimitiveRawTypeLong: 89 | [build appendString:@"long" semanticType:CDSemanticTypeKeyword]; 90 | break; 91 | case CDPrimitiveRawTypeLongLong: 92 | [build appendString:@"long long" semanticType:CDSemanticTypeKeyword]; 93 | break; 94 | case CDPrimitiveRawTypeInt128: 95 | [build appendString:@"__int128" semanticType:CDSemanticTypeKeyword]; 96 | break; 97 | case CDPrimitiveRawTypeUnsignedChar: 98 | [build appendString:@"unsigned char" semanticType:CDSemanticTypeKeyword]; 99 | break; 100 | case CDPrimitiveRawTypeUnsignedInt: 101 | [build appendString:@"unsigned int" semanticType:CDSemanticTypeKeyword]; 102 | break; 103 | case CDPrimitiveRawTypeUnsignedShort: 104 | [build appendString:@"unsigned short" semanticType:CDSemanticTypeKeyword]; 105 | break; 106 | case CDPrimitiveRawTypeUnsignedLong: 107 | [build appendString:@"unsigned long" semanticType:CDSemanticTypeKeyword]; 108 | break; 109 | case CDPrimitiveRawTypeUnsignedLongLong: 110 | [build appendString:@"unsigned long long" semanticType:CDSemanticTypeKeyword]; 111 | break; 112 | case CDPrimitiveRawTypeUnsignedInt128: 113 | [build appendString:@"unsigned __int128" semanticType:CDSemanticTypeKeyword]; 114 | break; 115 | case CDPrimitiveRawTypeFloat: 116 | [build appendString:@"float" semanticType:CDSemanticTypeKeyword]; 117 | break; 118 | case CDPrimitiveRawTypeDouble: 119 | [build appendString:@"double" semanticType:CDSemanticTypeKeyword]; 120 | break; 121 | case CDPrimitiveRawTypeLongDouble: 122 | [build appendString:@"long double" semanticType:CDSemanticTypeKeyword]; 123 | break; 124 | case CDPrimitiveRawTypeBool: 125 | [build appendString:@"BOOL" semanticType:CDSemanticTypeKeyword]; 126 | break; 127 | case CDPrimitiveRawTypeClass: 128 | [build appendString:@"Class" semanticType:CDSemanticTypeKeyword]; 129 | break; 130 | case CDPrimitiveRawTypeSel: 131 | [build appendString:@"SEL" semanticType:CDSemanticTypeKeyword]; 132 | break; 133 | case CDPrimitiveRawTypeFunction: 134 | [build appendString:@"void" semanticType:CDSemanticTypeKeyword]; 135 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 136 | [build appendString:@"/* function */" semanticType:CDSemanticTypeComment]; 137 | break; 138 | case CDPrimitiveRawTypeBlank: 139 | [build appendString:@"void" semanticType:CDSemanticTypeKeyword]; 140 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 141 | [build appendString:@"/* unknown type, blank encoding */" semanticType:CDSemanticTypeComment]; 142 | break; 143 | case CDPrimitiveRawTypeEmpty: 144 | [build appendString:@"void" semanticType:CDSemanticTypeKeyword]; 145 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 146 | [build appendString:@"/* unknown type, empty encoding */" semanticType:CDSemanticTypeComment]; 147 | break; 148 | } 149 | if (varName != nil) { 150 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 151 | [build appendString:varName semanticType:CDSemanticTypeVariable]; 152 | } 153 | return build; 154 | } 155 | 156 | - (BOOL)isEqual:(id)object { 157 | if ([object isKindOfClass:[self class]]) { 158 | __typeof(self) casted = (__typeof(casted))object; 159 | return (self.modifiers == casted.modifiers || [self.modifiers isEqualToArray:casted.modifiers]) && 160 | self.rawType == casted.rawType; 161 | } 162 | return NO; 163 | } 164 | 165 | - (NSString *)debugDescription { 166 | return [NSString stringWithFormat:@"<%@: %p> {modifiers: '%@', rawType: '%@'}", 167 | [self class], self, [self modifiersString], NSStringFromCDPrimitiveRawType(self.rawType)]; 168 | } 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDRecordType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDRecordType.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | /// Type representing a @c struct or @c union 13 | @interface CDRecordType : CDParseType 14 | /// The name of the record 15 | /// 16 | /// If the type is anonymous, this value will be @c nil 17 | @property (nullable, strong, nonatomic) NSString *name; 18 | /// @c YES if the receiver represents a @c union 19 | /// otherwise the receiver represents a @c struct 20 | @property (nonatomic) BOOL isUnion; 21 | /// The fields of the record 22 | /// 23 | /// A @c struct stores a value in each field. 24 | /// A @c union stores a single value that can be accessed 25 | /// as different types by each field. 26 | /// @note If this value is @c nil the type is incomplete. 27 | /// If the value is non-nil but empty (i.e. an empty array), 28 | /// the record is defined to have no fields. 29 | @property (nullable, strong, nonatomic) NSArray *fields; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ClassDump/Models/ParseTypes/CDRecordType.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDRecordType.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 12/8/22. 6 | // Copyright © 2022 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDRecordType.h" 10 | 11 | @implementation CDRecordType 12 | 13 | - (CDSemanticString *)semanticStringForVariableName:(NSString *)varName { 14 | CDSemanticString *build = [CDSemanticString new]; 15 | CDSemanticString *modifiersString = [self modifiersSemanticString]; 16 | if (modifiersString.length > 0) { 17 | [build appendSemanticString:modifiersString]; 18 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 19 | } 20 | [build appendString:(self.isUnion ? @"union" : @"struct") semanticType:CDSemanticTypeKeyword]; 21 | if (self.name != nil) { 22 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 23 | [build appendString:self.name semanticType:CDSemanticTypeRecordName]; 24 | } 25 | if (self.fields != nil) { 26 | [build appendString:@" { " semanticType:CDSemanticTypeStandard]; 27 | 28 | unsigned fieldName = 0; 29 | 30 | for (CDVariableModel *variableModel in self.fields) { 31 | NSString *variableName = variableModel.name; 32 | if (variableName == nil) { 33 | variableName = [NSString stringWithFormat:@"x%u", fieldName++]; 34 | } 35 | [build appendSemanticString:[variableModel.type semanticStringForVariableName:variableName]]; 36 | [build appendString:@"; " semanticType:CDSemanticTypeStandard]; 37 | } 38 | [build appendString:@"}" semanticType:CDSemanticTypeStandard]; 39 | } 40 | if (varName != nil) { 41 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 42 | [build appendString:varName semanticType:CDSemanticTypeVariable]; 43 | } 44 | return build; 45 | } 46 | 47 | - (NSSet *)classReferences { 48 | NSMutableSet *build = [NSMutableSet set]; 49 | for (CDVariableModel *variableModel in self.fields) { 50 | NSSet *paramReferences = [variableModel.type classReferences]; 51 | if (paramReferences != nil) { 52 | [build unionSet:paramReferences]; 53 | } 54 | } 55 | return build; 56 | } 57 | 58 | - (NSSet *)protocolReferences { 59 | NSMutableSet *build = [NSMutableSet set]; 60 | for (CDVariableModel *variableModel in self.fields) { 61 | NSSet *paramReferences = [variableModel.type protocolReferences]; 62 | if (paramReferences != nil) { 63 | [build unionSet:paramReferences]; 64 | } 65 | } 66 | return build; 67 | } 68 | 69 | - (BOOL)isEqual:(id)object { 70 | if ([object isKindOfClass:[self class]]) { 71 | __typeof(self) casted = (__typeof(casted))object; 72 | return (self.modifiers == casted.modifiers || [self.modifiers isEqualToArray:casted.modifiers]) && 73 | (self.name == casted.name || [self.name isEqualToString:casted.name]) && 74 | self.isUnion == casted.isUnion && 75 | (self.fields == casted.fields || [self.fields isEqualToArray:casted.fields]); 76 | } 77 | return NO; 78 | } 79 | 80 | - (NSString *)debugDescription { 81 | return [NSString stringWithFormat:@"<%@: %p> {modifiers: '%@', name: '%@', isUnion: %@, fields: %@}", 82 | [self class], self, [self modifiersString], self.name, self.isUnion ? @"YES" : @"NO", self.fields.debugDescription]; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDClassModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDClassModel.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 4/7/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | 17 | @interface CDClassModel : NSObject 18 | // the Class property must be unsafe_unretained because not all 19 | // classes can be stored with either a strong or weak reference 20 | /// The Obj-C runtime @c Class 21 | @property (unsafe_unretained, nonatomic, readonly) Class backing; 22 | /// The name of the class, e.g. @c NSObject 23 | @property (strong, nonatomic, readonly) NSString *name; 24 | /// The protocols the class conforms to 25 | @property (strong, nonatomic, readonly) NSArray *protocols; 26 | 27 | @property (strong, nonatomic, readonly) NSArray *classProperties; 28 | @property (strong, nonatomic, readonly) NSArray *instanceProperties; 29 | 30 | @property (strong, nonatomic, readonly) NSArray *classMethods; 31 | @property (strong, nonatomic, readonly) NSArray *instanceMethods; 32 | /// Instance variables, including values synthesized from properties 33 | @property (strong, nonatomic, readonly) NSArray *ivars; 34 | 35 | - (instancetype)initWithClass:(Class)cls; 36 | + (instancetype)modelWithClass:(Class)cls; 37 | 38 | /// Generate an @c interface for the class 39 | /// @param comments Generate comments with information such as the 40 | /// image or category the declaration was found in 41 | /// @param synthesizeStrip Remove methods and ivars synthesized from properties 42 | - (NSString *)linesWithComments:(BOOL)comments synthesizeStrip:(BOOL)synthesizeStrip; 43 | /// Generate an @c interface for the class 44 | /// @param comments Generate comments with information such as the 45 | /// image or category the declaration was found in 46 | /// @param synthesizeStrip Remove methods and ivars synthesized from properties 47 | - (CDSemanticString *)semanticLinesWithComments:(BOOL)comments synthesizeStrip:(BOOL)synthesizeStrip; 48 | 49 | /// Generate an @c interface for the class 50 | - (CDSemanticString *)semanticLinesWithOptions:(CDGenerationOptions *)options; 51 | 52 | /// Classes the class references in the declaration 53 | /// 54 | /// In other words, all the classes that the compiler would need to see 55 | /// for the header to pass the type checking stage of compilation. 56 | - (NSSet *)classReferences; 57 | /// Protocols the class references in the declaration 58 | /// 59 | /// In other words, all the protocols that the compiler would need to see 60 | /// for the header to pass the type checking stage of compilation. 61 | - (NSSet *)protocolReferences; 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDClassModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDClassModel.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 4/7/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDClassModel.h" 10 | #import "CDProtocolModel+Conformance.h" 11 | #import "../../Services/CDTypeParser.h" 12 | #import "../NSArray+CDFiltering.h" 13 | 14 | #import 15 | 16 | @implementation CDClassModel { 17 | NSArray *_classPropertySynthesizedMethods; 18 | NSArray *_instancePropertySynthesizedMethods; 19 | NSArray *_instancePropertySynthesizedVars; 20 | } 21 | 22 | + (instancetype)modelWithClass:(Class)cls { 23 | return [[self alloc] initWithClass:cls]; 24 | } 25 | 26 | - (instancetype)initWithClass:(Class)cls { 27 | if (self = [self init]) { 28 | _backing = cls; 29 | _name = NSStringFromClass(cls); 30 | 31 | Class const metaClass = object_getClass(cls); 32 | 33 | unsigned int count, index; 34 | 35 | Protocol *__unsafe_unretained *protocolList = class_copyProtocolList(cls, &count); 36 | if (protocolList) { 37 | NSMutableArray *protocols = [NSMutableArray arrayWithCapacity:count]; 38 | for (index = 0; index < count; index++) { 39 | Protocol *objc_protocol = protocolList[index]; 40 | [protocols addObject:[CDProtocolModel modelWithProtocol:objc_protocol]]; 41 | } 42 | free(protocolList); 43 | _protocols = [protocols copy]; 44 | } 45 | 46 | objc_property_t *classPropertyList = class_copyPropertyList(metaClass, &count); 47 | if (classPropertyList) { 48 | NSMutableArray *synthMeths = [NSMutableArray array]; 49 | NSMutableArray *properties = [NSMutableArray arrayWithCapacity:count]; 50 | for (index = 0; index < count; index++) { 51 | objc_property_t objc_property = classPropertyList[index]; 52 | CDPropertyModel *propertyRep = [CDPropertyModel modelWithProperty:objc_property isClass:YES]; 53 | [properties addObject:propertyRep]; 54 | NSString *synthMethodName; 55 | if ((synthMethodName = propertyRep.getter)) { 56 | [synthMeths addObject:synthMethodName]; 57 | } 58 | if ((synthMethodName = propertyRep.setter)) { 59 | [synthMeths addObject:synthMethodName]; 60 | } 61 | } 62 | free(classPropertyList); 63 | _classPropertySynthesizedMethods = [synthMeths copy]; 64 | _classProperties = [properties copy]; 65 | } 66 | 67 | objc_property_t *propertyList = class_copyPropertyList(cls, &count); 68 | if (propertyList) { 69 | NSMutableArray *synthMeths = [NSMutableArray array]; 70 | NSMutableArray *syntVars = [NSMutableArray array]; 71 | NSMutableArray *properties = [NSMutableArray arrayWithCapacity:count]; 72 | for (index = 0; index < count; index++) { 73 | objc_property_t objc_property = propertyList[index]; 74 | CDPropertyModel *propertyRep = [CDPropertyModel modelWithProperty:objc_property isClass:NO]; 75 | [properties addObject:propertyRep]; 76 | NSString *synthCompName; 77 | if ((synthCompName = propertyRep.getter)) { 78 | [synthMeths addObject:synthCompName]; 79 | } 80 | if ((synthCompName = propertyRep.setter)) { 81 | [synthMeths addObject:synthCompName]; 82 | } 83 | if ((synthCompName = propertyRep.iVar)) { 84 | [syntVars addObject:synthCompName]; 85 | } 86 | } 87 | free(propertyList); 88 | _instancePropertySynthesizedMethods = [synthMeths copy]; 89 | _instancePropertySynthesizedVars = [syntVars copy]; 90 | _instanceProperties = [properties copy]; 91 | } 92 | 93 | Ivar *ivarList = class_copyIvarList(cls, &count); 94 | if (ivarList) { 95 | NSMutableArray *ivars = [NSMutableArray arrayWithCapacity:count]; 96 | 97 | NSMutableArray *eligibleProperties = [NSMutableArray arrayWithArray:self.instanceProperties]; 98 | NSUInteger eligiblePropertiesCount = eligibleProperties.count; 99 | 100 | for (index = 0; index < count; index++) { 101 | CDIvarModel *model = [CDIvarModel modelWithIvar:ivarList[index]]; 102 | 103 | for (NSUInteger eligibleIndex = 0; eligibleIndex < eligiblePropertiesCount; eligibleIndex++) { 104 | CDPropertyModel *propModel = eligibleProperties[eligibleIndex]; 105 | if ([propModel.iVar isEqualToString:model.name]) { 106 | [propModel overrideType:model.type]; 107 | 108 | eligiblePropertiesCount--; 109 | // constant time operation 110 | // since we decremented eligiblePropertiesCount, this object is now unreachable 111 | [eligibleProperties exchangeObjectAtIndex:eligibleIndex withObjectAtIndex:eligiblePropertiesCount]; 112 | 113 | break; 114 | } 115 | } 116 | [ivars addObject:model]; 117 | } 118 | free(ivarList); 119 | _ivars = [ivars copy]; 120 | } 121 | 122 | Method *classMethodList = class_copyMethodList(metaClass, &count); 123 | if (classMethodList) { 124 | NSMutableArray *methods = [NSMutableArray arrayWithCapacity:count]; 125 | for (index = 0; index < count; index++) { 126 | [methods addObject:[CDMethodModel modelWithMethod:*method_getDescription(classMethodList[index]) isClass:YES]]; 127 | } 128 | _classMethods = [methods copy]; 129 | free(classMethodList); 130 | } 131 | 132 | Method *methodList = class_copyMethodList(cls, &count); 133 | if (methodList) { 134 | NSMutableArray *methods = [NSMutableArray arrayWithCapacity:count]; 135 | for (index = 0; index < count; index++) { 136 | [methods addObject:[CDMethodModel modelWithMethod:*method_getDescription(methodList[index]) isClass:NO]]; 137 | } 138 | _instanceMethods = [methods copy]; 139 | free(methodList); 140 | } 141 | 142 | } 143 | return self; 144 | } 145 | 146 | - (NSString *)linesWithComments:(BOOL)comments synthesizeStrip:(BOOL)synthesizeStrip { 147 | return [[self semanticLinesWithComments:comments synthesizeStrip:synthesizeStrip] string]; 148 | } 149 | 150 | - (CDSemanticString *)semanticLinesWithComments:(BOOL)comments synthesizeStrip:(BOOL)synthesizeStrip { 151 | CDGenerationOptions *options = [CDGenerationOptions new]; 152 | options.addSymbolImageComments = comments; 153 | options.stripSynthesized = synthesizeStrip; 154 | return [self semanticLinesWithOptions:options]; 155 | } 156 | 157 | - (CDSemanticString *)semanticLinesWithOptions:(CDGenerationOptions *)options { 158 | Dl_info info; 159 | 160 | CDSemanticString *build = [CDSemanticString new]; 161 | 162 | NSSet *forwardClasses = [self _forwardDeclarableClassReferences]; 163 | NSUInteger const forwardClassCount = forwardClasses.count; 164 | if (forwardClassCount > 0) { 165 | [build appendString:@"@class" semanticType:CDSemanticTypeKeyword]; 166 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 167 | 168 | NSUInteger classNamesRemaining = forwardClassCount; 169 | for (NSString *className in forwardClasses) { 170 | [build appendString:className semanticType:CDSemanticTypeClass]; 171 | classNamesRemaining--; 172 | if (classNamesRemaining > 0) { 173 | [build appendString:@", " semanticType:CDSemanticTypeStandard]; 174 | } 175 | } 176 | [build appendString:@";\n" semanticType:CDSemanticTypeStandard]; 177 | } 178 | 179 | NSSet *forwardProtocols = [self _forwardDeclarableProtocolReferences]; 180 | NSUInteger const forwardProtocolCount = forwardProtocols.count; 181 | if (forwardProtocolCount > 0) { 182 | [build appendString:@"@protocol" semanticType:CDSemanticTypeKeyword]; 183 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 184 | 185 | NSUInteger protocolNamesRemaining = forwardProtocolCount; 186 | for (NSString *protocolNames in forwardProtocols) { 187 | [build appendString:protocolNames semanticType:CDSemanticTypeProtocol]; 188 | protocolNamesRemaining--; 189 | if (protocolNamesRemaining > 0) { 190 | [build appendString:@", " semanticType:CDSemanticTypeStandard]; 191 | } 192 | } 193 | [build appendString:@";\n" semanticType:CDSemanticTypeStandard]; 194 | } 195 | 196 | if (forwardClassCount > 0 || forwardProtocolCount > 0) { 197 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 198 | } 199 | 200 | if (options.addSymbolImageComments) { 201 | NSString *comment = nil; 202 | if (dladdr((__bridge const void *)self.backing, &info)) { 203 | comment = [NSString stringWithFormat:@"/* %s in %s */", info.dli_sname ?: "(anonymous)", info.dli_fname]; 204 | } else { 205 | comment = @"/* no symbol found */"; 206 | } 207 | [build appendString:comment semanticType:CDSemanticTypeComment]; 208 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 209 | } 210 | [build appendString:@"@interface" semanticType:CDSemanticTypeKeyword]; 211 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 212 | [build appendString:self.name semanticType:CDSemanticTypeClass]; 213 | 214 | Class superclass = class_getSuperclass(self.backing); 215 | if (superclass) { 216 | [build appendString:@" : " semanticType:CDSemanticTypeStandard]; 217 | [build appendString:NSStringFromClass(superclass) semanticType:CDSemanticTypeClass]; 218 | } 219 | 220 | NSArray *protocols = self.protocols; 221 | NSUInteger const protocolCount = protocols.count; 222 | if (protocolCount > 0) { 223 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 224 | [build appendString:@"<" semanticType:CDSemanticTypeStandard]; 225 | [protocols enumerateObjectsUsingBlock:^(CDProtocolModel *protocol, NSUInteger idx, BOOL *stop) { 226 | [build appendString:protocol.name semanticType:CDSemanticTypeProtocol]; 227 | if ((idx + 1) < protocolCount) { 228 | [build appendString:@", " semanticType:CDSemanticTypeStandard]; 229 | } 230 | }]; 231 | [build appendString:@">" semanticType:CDSemanticTypeStandard]; 232 | } 233 | 234 | NSArray *synthedClassMethds = nil, *synthedInstcMethds = nil, *synthedVars = nil; 235 | if (options.stripSynthesized) { 236 | synthedClassMethds = _classPropertySynthesizedMethods; 237 | synthedInstcMethds = _instancePropertySynthesizedMethods; 238 | synthedVars = _instancePropertySynthesizedVars; 239 | } 240 | 241 | if (self.ivars.count - synthedVars.count) { 242 | [build appendString:@" {\n" semanticType:CDSemanticTypeStandard]; 243 | for (CDIvarModel *ivar in self.ivars) { 244 | if ([synthedVars containsObject:ivar.name]) { 245 | continue; 246 | } 247 | if (options.addSymbolImageComments) { 248 | NSString *comment = nil; 249 | if (dladdr(ivar.backing, &info)) { 250 | comment = [NSString stringWithFormat:@"/* in %s */", info.dli_fname]; 251 | } else { 252 | comment = @"/* no symbol found */"; 253 | } 254 | [build appendString:@"\n " semanticType:CDSemanticTypeStandard]; 255 | [build appendString:comment semanticType:CDSemanticTypeComment]; 256 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 257 | } 258 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 259 | [build appendSemanticString:[ivar semanticString]]; 260 | [build appendString:@";\n" semanticType:CDSemanticTypeStandard]; 261 | } 262 | [build appendString:@"}" semanticType:CDSemanticTypeStandard]; 263 | } 264 | 265 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 266 | 267 | NSMutableSet *classPropertyIgnoreSet = [NSMutableSet set]; 268 | NSMutableSet *instancePropertyIgnoreSet = [NSMutableSet set]; 269 | 270 | NSMutableSet *classMethodIgnoreSet = [NSMutableSet set]; 271 | NSMutableSet *instanceMethodIgnoreSet = [NSMutableSet set]; 272 | 273 | if (options.stripOverrides) { 274 | NSMutableSet *classPropertyIgnoreNames = [NSMutableSet set]; 275 | NSMutableSet *instancePropertyIgnoreNames = [NSMutableSet set]; 276 | 277 | NSMutableSet *classMethodIgnoreNames = [NSMutableSet set]; 278 | NSMutableSet *instanceMethodIgnoreNames = [NSMutableSet set]; 279 | 280 | Class checkClass = class_getSuperclass(self.backing); 281 | while (checkClass != NULL) { 282 | CDClassModel *superclassModel = [CDClassModel modelWithClass:checkClass]; 283 | 284 | for (CDPropertyModel *property in superclassModel.classProperties) { 285 | if ([classPropertyIgnoreNames containsObject:property.name]) { 286 | continue; 287 | } 288 | [classPropertyIgnoreNames addObject:property.name]; 289 | [classPropertyIgnoreSet addObject:property]; 290 | } 291 | 292 | for (CDPropertyModel *property in superclassModel.instanceProperties) { 293 | if ([instancePropertyIgnoreNames containsObject:property.name]) { 294 | continue; 295 | } 296 | [instancePropertyIgnoreNames addObject:property.name]; 297 | [instancePropertyIgnoreSet addObject:property]; 298 | } 299 | 300 | for (CDMethodModel *method in superclassModel.classMethods) { 301 | if ([classMethodIgnoreNames containsObject:method.name]) { 302 | continue; 303 | } 304 | [classMethodIgnoreNames addObject:method.name]; 305 | [classMethodIgnoreSet addObject:method]; 306 | } 307 | 308 | for (CDMethodModel *method in superclassModel.instanceMethods) { 309 | if ([instanceMethodIgnoreNames containsObject:method.name]) { 310 | continue; 311 | } 312 | [instanceMethodIgnoreNames addObject:method.name]; 313 | [instanceMethodIgnoreSet addObject:method]; 314 | } 315 | 316 | checkClass = class_getSuperclass(checkClass); 317 | } 318 | } 319 | 320 | if (options.stripProtocolConformance) { 321 | [classPropertyIgnoreSet addObjectsFromArray:[CDProtocolModel requiredClassPropertiesToConform:self.protocols]]; 322 | [instancePropertyIgnoreSet addObjectsFromArray:[CDProtocolModel requiredInstancePropertiesToConform:self.protocols]]; 323 | [classMethodIgnoreSet addObjectsFromArray:[CDProtocolModel requiredClassMethodsToConform:self.protocols]]; 324 | [instanceMethodIgnoreSet addObjectsFromArray:[CDProtocolModel requiredInstanceMethodsToConform:self.protocols]]; 325 | } 326 | 327 | NSArray *classProperties = self.classProperties; 328 | NSArray *instanceProperties = self.instanceProperties; 329 | 330 | NSArray *classMethods = self.classMethods; 331 | NSArray *instanceMethods = self.instanceMethods; 332 | 333 | if (options.stripDuplicates) { 334 | classProperties = [classProperties cd_uniqueObjects]; 335 | instanceProperties = [instanceProperties cd_uniqueObjects]; 336 | 337 | classMethods = [classMethods cd_uniqueObjects]; 338 | instanceMethods = [instanceMethods cd_uniqueObjects]; 339 | } 340 | 341 | classProperties = [classProperties cd_filterObjectsIgnoring:classPropertyIgnoreSet]; 342 | instanceProperties = [instanceProperties cd_filterObjectsIgnoring:instancePropertyIgnoreSet]; 343 | 344 | classMethods = [classMethods cd_filterObjectsIgnoring:classMethodIgnoreSet]; 345 | instanceMethods = [instanceMethods cd_filterObjectsIgnoring:instanceMethodIgnoreSet]; 346 | 347 | [self _appendLines:build properties:classProperties comments:options.addSymbolImageComments]; 348 | [self _appendLines:build properties:instanceProperties comments:options.addSymbolImageComments]; 349 | 350 | [self _appendLines:build methods:classMethods synthesized:synthedClassMethds comments:options.addSymbolImageComments stripCtor:NO stripDtor:NO]; 351 | [self _appendLines:build methods:instanceMethods synthesized:synthedInstcMethds comments:options.addSymbolImageComments stripCtor:options.stripCtorMethod stripDtor:options.stripDtorMethod]; 352 | 353 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 354 | [build appendString:@"@end" semanticType:CDSemanticTypeKeyword]; 355 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 356 | 357 | return build; 358 | } 359 | 360 | - (void)_appendLines:(CDSemanticString *)build properties:(NSArray *)properties comments:(BOOL)comments { 361 | if (properties.count) { 362 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 363 | 364 | Dl_info info; 365 | for (CDPropertyModel *prop in properties) { 366 | if (comments) { 367 | NSString *comment = nil; 368 | if (dladdr(prop.backing, &info)) { 369 | comment = [NSString stringWithFormat:@"/* in %s */", info.dli_fname]; 370 | } else { 371 | comment = @"/* no symbol found */"; 372 | } 373 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 374 | [build appendString:comment semanticType:CDSemanticTypeComment]; 375 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 376 | } 377 | [build appendSemanticString:[prop semanticString]]; 378 | [build appendString:@";\n" semanticType:CDSemanticTypeStandard]; 379 | } 380 | } 381 | } 382 | 383 | - (void)_appendLines:(CDSemanticString *)build methods:(NSArray *)methods synthesized:(NSArray *)synthesized comments:(BOOL)comments stripCtor:(BOOL)stripCtor stripDtor:(BOOL)stripDtor { 384 | if (methods.count - synthesized.count) { 385 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 386 | 387 | Dl_info info; 388 | NSMutableArray *synthed = [NSMutableArray arrayWithArray:synthesized]; 389 | if (stripCtor) { 390 | [synthed addObject:@".cxx_construct"]; 391 | } 392 | if (stripDtor) { 393 | [synthed addObject:@".cxx_destruct"]; 394 | } 395 | NSUInteger synthedCount = synthed.count; 396 | for (CDMethodModel *methd in methods) { 397 | // find and remove instead of just find so we don't have to search the entire 398 | // array everytime, when we know the objects that we've already filtered out won't come up again 399 | NSUInteger const searchResult = [synthed indexOfObject:methd.name inRange:NSMakeRange(0, synthedCount)]; 400 | if (searchResult != NSNotFound) { 401 | synthedCount--; 402 | // optimized version of remove since the 403 | // order of synthed doesn't matter to us. 404 | // exchange is O(1) instead of remove is O(n) 405 | [synthed exchangeObjectAtIndex:searchResult withObjectAtIndex:synthedCount]; 406 | continue; 407 | } 408 | 409 | if (comments) { 410 | Method objcMethod = NULL; 411 | if (methd.isClass) { 412 | objcMethod = class_getClassMethod(self.backing, methd.backing.name); 413 | } else { 414 | objcMethod = class_getInstanceMethod(self.backing, methd.backing.name); 415 | } 416 | IMP const methdImp = method_getImplementation(objcMethod); 417 | 418 | NSString *comment = nil; 419 | if (dladdr(methdImp, &info)) { 420 | comment = [NSString stringWithFormat:@"/* %s in %s */", info.dli_sname ?: "(anonymous)", info.dli_fname]; 421 | } else { 422 | comment = @"/* no symbol found */"; 423 | } 424 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 425 | [build appendString:comment semanticType:CDSemanticTypeComment]; 426 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 427 | } 428 | [build appendSemanticString:[methd semanticString]]; 429 | [build appendString:@";\n" semanticType:CDSemanticTypeStandard]; 430 | } 431 | } 432 | } 433 | 434 | - (NSSet *)_forwardDeclarableClassReferences { 435 | NSMutableSet *build = [NSMutableSet set]; 436 | 437 | [self _unionReferences:build sources:self.classProperties resolve:^NSSet *(CDPropertyModel *model) { 438 | return [model.type classReferences]; 439 | }]; 440 | [self _unionReferences:build sources:self.instanceProperties resolve:^NSSet *(CDPropertyModel *model) { 441 | return [model.type classReferences]; 442 | }]; 443 | 444 | [self _unionReferences:build sources:self.classMethods resolve:^NSSet *(CDMethodModel *model) { 445 | return [model classReferences]; 446 | }]; 447 | [self _unionReferences:build sources:self.instanceMethods resolve:^NSSet *(CDMethodModel *model) { 448 | return [model classReferences]; 449 | }]; 450 | 451 | [self _unionReferences:build sources:self.ivars resolve:^NSSet *(CDIvarModel *model) { 452 | return [model.type classReferences]; 453 | }]; 454 | 455 | [build removeObject:self.name]; 456 | return build; 457 | } 458 | 459 | - (NSSet *)_forwardDeclarableProtocolReferences { 460 | NSMutableSet *build = [NSMutableSet set]; 461 | 462 | [self _unionReferences:build sources:self.classProperties resolve:^NSSet *(CDPropertyModel *model) { 463 | return [model.type protocolReferences]; 464 | }]; 465 | [self _unionReferences:build sources:self.instanceProperties resolve:^NSSet *(CDPropertyModel *model) { 466 | return [model.type protocolReferences]; 467 | }]; 468 | 469 | [self _unionReferences:build sources:self.classMethods resolve:^NSSet *(CDMethodModel *model) { 470 | return [model protocolReferences]; 471 | }]; 472 | [self _unionReferences:build sources:self.instanceMethods resolve:^NSSet *(CDMethodModel *model) { 473 | return [model protocolReferences]; 474 | }]; 475 | 476 | [self _unionReferences:build sources:self.ivars resolve:^NSSet *(CDIvarModel *model) { 477 | return [model.type protocolReferences]; 478 | }]; 479 | 480 | return build; 481 | } 482 | 483 | - (NSSet *)classReferences { 484 | NSMutableSet *build = [NSMutableSet set]; 485 | 486 | NSSet *forwardDeclarable = [self _forwardDeclarableClassReferences]; 487 | if (forwardDeclarable != nil) { 488 | [build unionSet:forwardDeclarable]; 489 | } 490 | 491 | Class const superclass = class_getSuperclass(self.backing); 492 | if (superclass != NULL) { 493 | [build addObject:NSStringFromClass(superclass)]; 494 | } 495 | 496 | return build; 497 | } 498 | 499 | - (NSSet *)protocolReferences { 500 | NSMutableSet *build = [NSMutableSet set]; 501 | 502 | NSSet *forwardDeclarable = [self _forwardDeclarableProtocolReferences]; 503 | if (forwardDeclarable != nil) { 504 | [build unionSet:forwardDeclarable]; 505 | } 506 | 507 | for (CDProtocolModel *protocol in self.protocols) { 508 | [build addObject:protocol.name]; 509 | } 510 | 511 | return build; 512 | } 513 | 514 | - (void)_unionReferences:(NSMutableSet *)build sources:(NSArray *)sources resolve:(NSSet *(NS_NOESCAPE ^)(id))resolver { 515 | for (id source in sources) { 516 | NSSet *refs = resolver(source); 517 | if (refs != nil) { 518 | [build unionSet:refs]; 519 | } 520 | } 521 | } 522 | 523 | - (NSString *)description { 524 | return self.name; 525 | } 526 | 527 | - (NSString *)debugDescription { 528 | return [NSString stringWithFormat:@"<%@: %p> {name: '%@', protocols: %@, " 529 | "classProperties: %@, instanceProperties: %@, classMethods: %@, instanceMethods: %@, ivars: %@}", 530 | [self class], self, self.name, self.protocols, 531 | self.classProperties, self.instanceProperties, self.classMethods, self.instanceMethods, self.ivars]; 532 | } 533 | 534 | @end 535 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDIvarModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDIvarModel.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 4/8/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import 13 | 14 | @interface CDIvarModel : NSObject 15 | /// The Obj-C runtime @c Ivar 16 | @property (nonatomic, readonly) Ivar backing; 17 | /// The name of the ivar, e.g. @c _name 18 | @property (strong, nonatomic, readonly) NSString *name; 19 | /// The type of the ivar 20 | @property (strong, nonatomic, readonly) CDParseType *type; 21 | 22 | - (instancetype)initWithIvar:(Ivar)ivar; 23 | + (instancetype)modelWithIvar:(Ivar)ivar; 24 | 25 | - (CDSemanticString *)semanticString; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDIvarModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDIvarModel.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 4/8/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDIvarModel.h" 10 | #import "../../Services/CDTypeParser.h" 11 | 12 | @implementation CDIvarModel 13 | 14 | + (instancetype)modelWithIvar:(Ivar)ivar { 15 | return [[self alloc] initWithIvar:ivar]; 16 | } 17 | 18 | - (instancetype)initWithIvar:(Ivar)ivar { 19 | if (self = [self init]) { 20 | _backing = ivar; 21 | _name = @(ivar_getName(ivar)); 22 | _type = [CDTypeParser typeForEncoding:(ivar_getTypeEncoding(ivar) ?: "")]; 23 | } 24 | return self; 25 | } 26 | 27 | - (CDSemanticString *)semanticString { 28 | return [self.type semanticStringForVariableName:self.name]; 29 | } 30 | 31 | - (BOOL)isEqual:(id)object { 32 | if ([object isKindOfClass:[self class]]) { 33 | __typeof(self) casted = (__typeof(casted))object; 34 | Ivar const sVar = self.backing, cVar = casted.backing; 35 | return [self.name isEqual:casted.name] && 36 | (strstr(ivar_getTypeEncoding(sVar), ivar_getTypeEncoding(cVar)) == 0); 37 | } 38 | return NO; 39 | } 40 | 41 | - (NSString *)description { 42 | return [self.type stringForVariableName:self.name]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDMethodModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDMethodModel.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 4/7/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import 13 | 14 | @interface CDMethodModel : NSObject 15 | /// The Obj-C runtime @c objc_method_description 16 | @property (nonatomic, readonly) struct objc_method_description backing; 17 | /// The signature of the method, e.g. @c initWithMethod:isClass: 18 | @property (strong, nonatomic, readonly) NSString *name; 19 | /// The types of the arguments to the method, e.g. @c [id, @c BOOL] 20 | @property (strong, nonatomic, readonly) NSArray *argumentTypes; 21 | /// The return type of the method 22 | @property (strong, nonatomic, readonly) CDParseType *returnType; 23 | /// If the method is a class method, otherwise an instance method 24 | @property (nonatomic, readonly) BOOL isClass; 25 | 26 | - (instancetype)initWithMethod:(struct objc_method_description)methd isClass:(BOOL)isClass; 27 | + (instancetype)modelWithMethod:(struct objc_method_description)methd isClass:(BOOL)isClass; 28 | 29 | - (CDSemanticString *)semanticString; 30 | 31 | /// Classes the method references in the declaration 32 | /// 33 | /// In other words, all the classes that the compiler would need to see 34 | /// for the header to pass the type checking stage of compilation. 35 | - (NSSet *)classReferences; 36 | /// Protocols the method references in the declaration 37 | /// 38 | /// In other words, all the protocols that the compiler would need to see 39 | /// for the header to pass the type checking stage of compilation. 40 | - (NSSet *)protocolReferences; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDMethodModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDMethodModel.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 4/7/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDMethodModel.h" 10 | #import "../../Services/CDTypeParser.h" 11 | 12 | /* 13 | * @APPLE_LICENSE_HEADER_START@ 14 | * 15 | * This file contains Original Code and/or Modifications of Original Code 16 | * as defined in and that are subject to the Apple Public Source License 17 | * Version 2.0 (the 'License'). You may not use this file except in 18 | * compliance with the License. Please obtain a copy of the License at 19 | * http://www.opensource.apple.com/apsl/ and read it before using this 20 | * file. 21 | * 22 | * The Original Code and all software distributed under the License are 23 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 24 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 25 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 26 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 27 | * Please see the License for the specific language governing rights and 28 | * limitations under the License. 29 | * 30 | * @APPLE_LICENSE_HEADER_END@ 31 | */ 32 | 33 | /// Returns the number of times a character occurs in a null-terminated stringb 34 | static size_t characterCount(const char *str, const char c) { 35 | size_t ret = 0; 36 | while (*str) { 37 | if (*str++ == c) { 38 | ret++; 39 | } 40 | } 41 | return ret; 42 | } 43 | 44 | @implementation CDMethodModel 45 | 46 | + (instancetype)modelWithMethod:(struct objc_method_description)methd isClass:(BOOL)isClass { 47 | return [[self alloc] initWithMethod:methd isClass:isClass]; 48 | } 49 | 50 | - (instancetype)initWithMethod:(struct objc_method_description)methd isClass:(BOOL)isClass { 51 | if (self = [self init]) { 52 | _backing = methd; 53 | _isClass = isClass; 54 | _name = NSStringFromSelector(methd.name); 55 | 56 | const char *typedesc = methd.types; 57 | // this code is heavily modified from, but based on encoding_getArgumentInfo 58 | // https://github.com/apple-oss-distributions/objc4/blob/689525d556/runtime/objc-typeencoding.mm#L168-L272 59 | const char *type = typedesc; 60 | typedesc = [CDTypeParser endOfTypeEncoding:type]; 61 | _returnType = [CDTypeParser typeForEncodingStart:type end:typedesc error:NULL]; 62 | 63 | NSUInteger const expectedArguments = characterCount(sel_getName(methd.name), ':'); 64 | NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:expectedArguments + 2]; 65 | 66 | // skip stack size 67 | while (isnumber(*typedesc)) { 68 | typedesc++; 69 | } 70 | 71 | while (*typedesc) { 72 | type = typedesc; 73 | typedesc = [CDTypeParser endOfTypeEncoding:type]; 74 | [arguments addObject:[CDTypeParser typeForEncodingStart:type end:typedesc error:NULL]]; 75 | 76 | // Skip GNU runtime's register parameter hint 77 | if (*typedesc == '+') { 78 | typedesc++; 79 | } 80 | // Skip negative sign in offset 81 | if (*typedesc == '-') { 82 | typedesc++; 83 | } 84 | while (isnumber(*typedesc)) { 85 | typedesc++; 86 | } 87 | } 88 | // if there were less arguments than expected, fill in the rest with empty types 89 | for (NSUInteger argumentIndex = arguments.count; argumentIndex < expectedArguments; argumentIndex++) { 90 | [arguments addObject:[CDTypeParser typeForEncoding:""]]; // add an empty encoding 91 | } 92 | // if there were more arguments than expected, trim from the beginning. 93 | // usually `self` (type `id`) and `_cmd` (type `SEL`) are the first two parameters, 94 | // however they are not included in expectedArguments. `_cmd` may not be included 95 | // if the method is backed by a block instead of a selector. 96 | _argumentTypes = [arguments subarrayWithRange:NSMakeRange(arguments.count - expectedArguments, expectedArguments)]; 97 | } 98 | return self; 99 | } 100 | 101 | - (CDSemanticString *)semanticString { 102 | CDSemanticString *build = [CDSemanticString new]; 103 | [build appendString:(self.isClass ? @"+" : @"-") semanticType:CDSemanticTypeStandard]; 104 | [build appendString:@" (" semanticType:CDSemanticTypeStandard]; 105 | [build appendSemanticString:[self.returnType semanticStringForVariableName:nil]]; 106 | [build appendString:@")" semanticType:CDSemanticTypeStandard]; 107 | 108 | NSArray *argumentTypes = self.argumentTypes; 109 | NSUInteger const argumentTypeCount = argumentTypes.count; 110 | if (argumentTypeCount > 0) { 111 | NSArray *brokenupName = [self.name componentsSeparatedByString:@":"]; 112 | 113 | [argumentTypes enumerateObjectsUsingBlock:^(CDParseType *argumentType, NSUInteger idx, BOOL *stop) { 114 | [build appendString:brokenupName[idx] semanticType:CDSemanticTypeStandard]; 115 | [build appendString:@":" semanticType:CDSemanticTypeStandard]; 116 | [build appendString:@"(" semanticType:CDSemanticTypeStandard]; 117 | [build appendSemanticString:[argumentType semanticStringForVariableName:nil]]; 118 | [build appendString:@")" semanticType:CDSemanticTypeStandard]; 119 | [build appendString:[NSString stringWithFormat:@"a%lu", (unsigned long)idx] semanticType:CDSemanticTypeVariable]; 120 | if ((idx + 1) < argumentTypeCount) { // if there are still arguments left, add a space to separate 121 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 122 | } 123 | }]; 124 | } else { 125 | [build appendString:self.name semanticType:CDSemanticTypeStandard]; 126 | } 127 | return build; 128 | } 129 | 130 | - (NSSet *)classReferences { 131 | NSMutableSet *build = [NSMutableSet set]; 132 | NSSet *returnReferences = [self.returnType classReferences]; 133 | if (returnReferences != nil) { 134 | [build unionSet:returnReferences]; 135 | } 136 | for (CDParseType *paramType in self.argumentTypes) { 137 | NSSet *paramReferences = [paramType classReferences]; 138 | if (paramReferences != nil) { 139 | [build unionSet:paramReferences]; 140 | } 141 | } 142 | return build; 143 | } 144 | 145 | - (NSSet *)protocolReferences { 146 | NSMutableSet *build = [NSMutableSet set]; 147 | NSSet *returnReferences = [self.returnType protocolReferences]; 148 | if (returnReferences != nil) { 149 | [build unionSet:returnReferences]; 150 | } 151 | for (CDParseType *paramType in self.argumentTypes) { 152 | NSSet *paramReferences = [paramType protocolReferences]; 153 | if (paramReferences != nil) { 154 | [build unionSet:paramReferences]; 155 | } 156 | } 157 | return build; 158 | } 159 | 160 | - (BOOL)isEqual:(id)object { 161 | if ([object isKindOfClass:[self class]]) { 162 | __typeof(self) casted = (__typeof(casted))object; 163 | return [self.name isEqual:casted.name] && 164 | [self.argumentTypes isEqual:casted.argumentTypes] && 165 | [self.returnType isEqual:casted.returnType]; 166 | } 167 | return NO; 168 | } 169 | 170 | - (NSUInteger)hash { 171 | return self.name.hash; 172 | } 173 | 174 | - (NSString *)description { 175 | return [[self semanticString] string]; 176 | } 177 | 178 | - (NSString *)debugDescription { 179 | return [NSString stringWithFormat:@"<%@: %p> {signature: '%@', argumentTypes: %@, " 180 | "returnType: '%@', isClass: %@}", 181 | [self class], self, self.name, self.argumentTypes, 182 | self.returnType, self.isClass ? @"YES" : @"NO"]; 183 | } 184 | 185 | @end 186 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDPropertyAttribute.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDPropertyAttribute.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 1/6/23. 6 | // Copyright © 2023 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CDPropertyAttribute : NSObject 12 | /// The name of a property attribute, e.g. @c strong, @c nonatomic, @c getter 13 | @property (strong, nonatomic, readonly) NSString *name; 14 | /// The value of a property attribute, e.g. the method name for @c getter= or @c setter= 15 | @property (strong, nonatomic, readonly) NSString *value; 16 | 17 | - (instancetype)initWithName:(NSString *)name value:(NSString *)value; 18 | + (instancetype)attributeWithName:(NSString *)name value:(NSString *)value; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDPropertyAttribute.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDPropertyAttribute.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 1/6/23. 6 | // Copyright © 2023 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDPropertyAttribute.h" 10 | 11 | @implementation CDPropertyAttribute 12 | 13 | + (instancetype)attributeWithName:(NSString *)name value:(NSString *)value { 14 | return [[self alloc] initWithName:name value:value]; 15 | } 16 | 17 | - (instancetype)initWithName:(NSString *)name value:(NSString *)value { 18 | if (self = [self init]) { 19 | _name = name; 20 | _value = value; 21 | } 22 | return self; 23 | } 24 | 25 | - (BOOL)isEqual:(id)object { 26 | if ([object isKindOfClass:[self class]]) { 27 | __typeof(self) casted = (__typeof(casted))object; 28 | return (self.name == casted.name || [self.name isEqual:casted.name]) && 29 | (self.value == casted.value || [self.value isEqual:casted.value]); 30 | } 31 | return NO; 32 | } 33 | 34 | - (NSString *)description { 35 | NSString *name = self.name; 36 | NSString *value = self.value; 37 | if (value != nil) { 38 | return [[name stringByAppendingString:@"="] stringByAppendingString:value]; 39 | } 40 | return name; 41 | } 42 | 43 | - (NSString *)debugDescription { 44 | return [NSString stringWithFormat:@"<%@: %p> {name: '%@', value: '%@'}", 45 | [self class], self, self.name, self.value]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDPropertyModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDPropertyModel.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 3/24/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import 13 | #import 14 | 15 | @interface CDPropertyModel : NSObject 16 | /// The Obj-C runtime @c objc_property_t 17 | @property (nonatomic, readonly) objc_property_t backing; 18 | /// The name of the property, e.g. @c name 19 | @property (strong, nonatomic, readonly) NSString *name; 20 | /// The type of the property 21 | @property (strong, nonatomic, readonly) CDParseType *type; 22 | /// The attributes of the property 23 | @property (strong, nonatomic, readonly) NSArray *attributes; 24 | /// The name of the backing instance variable 25 | @property (strong, nonatomic, readonly) NSString *iVar; 26 | /// The signature of the getter method, e.g. @c count 27 | @property (strong, nonatomic, readonly) NSString *getter; 28 | /// The signature of the setter method, e.g. @c setName: 29 | @property (strong, nonatomic, readonly) NSString *setter; 30 | 31 | - (instancetype)initWithProperty:(objc_property_t)property isClass:(BOOL)isClass; 32 | + (instancetype)modelWithProperty:(objc_property_t)property isClass:(BOOL)isClass; 33 | 34 | /// Override the @c type of the property. 35 | /// Used when the corresponding ivar is found with more type information; 36 | /// e.g. An ivar may know the type is @c NSString @c * 37 | /// however the property only has @c id as the type 38 | - (void)overrideType:(CDParseType *)type; 39 | 40 | - (CDSemanticString *)semanticString; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDPropertyModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDPropertyModel.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 3/24/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDPropertyModel.h" 10 | #import "../../Services/CDTypeParser.h" 11 | 12 | @implementation CDPropertyModel 13 | 14 | + (instancetype)modelWithProperty:(objc_property_t)property isClass:(BOOL)isClass { 15 | return [[self alloc] initWithProperty:property isClass:isClass]; 16 | } 17 | 18 | - (instancetype)initWithProperty:(objc_property_t)property isClass:(BOOL)isClass { 19 | if (self = [self init]) { 20 | _backing = property; 21 | _name = @(property_getName(property)); 22 | 23 | BOOL isReadOnly = NO, isDynamic = NO; 24 | 25 | const char *const propAttribs = property_getAttributes(property); 26 | NSMutableArray *attributes = [NSMutableArray array]; 27 | if (isClass) { 28 | [attributes addObject:[CDPropertyAttribute attributeWithName:@"class" value:nil]]; 29 | } 30 | 31 | for (const char *propSeek = propAttribs; propSeek < (propAttribs + strlen(propAttribs)); propSeek++) { 32 | const char switchOnMe = *propSeek++; 33 | 34 | NSString *attributeName = nil; 35 | NSString *attributeValue = nil; 36 | 37 | const char *const attribHead = propSeek; 38 | while (*propSeek && *propSeek != ',') { 39 | switch (*propSeek) { 40 | case '"': { 41 | propSeek = strchr(++propSeek, '"'); 42 | } break; 43 | case '{': { 44 | unsigned openTokens = 1; 45 | while (openTokens) { 46 | switch (*++propSeek) { 47 | case '{': 48 | openTokens++; 49 | break; 50 | case '}': 51 | openTokens--; 52 | break; 53 | } 54 | } 55 | } break; 56 | case '(': { 57 | unsigned openTokens = 1; 58 | while (openTokens) { 59 | switch (*++propSeek) { 60 | case '(': 61 | openTokens++; 62 | break; 63 | case ')': 64 | openTokens--; 65 | break; 66 | } 67 | } 68 | } break; 69 | } 70 | propSeek++; 71 | } 72 | 73 | NSUInteger const valueLen = propSeek - attribHead; 74 | if (valueLen > 0) { 75 | attributeValue = [[NSString alloc] initWithBytes:attribHead length:valueLen encoding:NSUTF8StringEncoding]; 76 | } 77 | 78 | /* per https://github.com/llvm/llvm-project/blob/b7f97d3661/clang/lib/AST/ASTContext.cpp#L7878-L7973 79 | * 80 | * enum PropertyAttributes { 81 | * kPropertyReadOnly = 'R', // property is read-only. 82 | * kPropertyBycopy = 'C', // property is a copy of the value last assigned 83 | * kPropertyByref = '&', // property is a reference to the value last assigned 84 | * kPropertyDynamic = 'D', // property is dynamic 85 | * kPropertyGetter = 'G', // followed by getter selector name 86 | * kPropertySetter = 'S', // followed by setter selector name 87 | * kPropertyInstanceVariable = 'V', // followed by instance variable name 88 | * kPropertyType = 'T', // followed by old-style type encoding. 89 | * kPropertyWeak = 'W', // 'weak' property 90 | * kPropertyStrong = 'P', // property GC'able 91 | * kPropertyNonAtomic = 'N', // property non-atomic 92 | * kPropertyOptional = '?', // property optional 93 | * }; 94 | */ 95 | switch (switchOnMe) { 96 | case 'R': 97 | attributeName = @"readonly"; 98 | isReadOnly = YES; 99 | break; 100 | case 'C': 101 | attributeName = @"copy"; 102 | break; 103 | case '&': 104 | attributeName = @"retain"; 105 | break; 106 | case 'D': 107 | isDynamic = YES; 108 | break; 109 | case 'G': 110 | attributeName = @"getter"; 111 | _getter = attributeValue; 112 | break; 113 | case 'S': 114 | attributeName = @"setter"; 115 | _setter = attributeValue; 116 | break; 117 | case 'V': 118 | _iVar = attributeValue; 119 | break; 120 | case 'T': 121 | _type = [CDTypeParser typeForEncodingStart:attribHead end:propSeek error:NULL]; 122 | break; 123 | case 'W': 124 | attributeName = @"weak"; 125 | break; 126 | case 'P': 127 | // eligible for garbage collection, no notation 128 | break; 129 | case 'N': 130 | attributeName = @"nonatomic"; 131 | break; 132 | case '?': 133 | // @optional in a protocol 134 | break; 135 | default: 136 | NSAssert(NO, @"Unknown attribute code: %c", switchOnMe); 137 | break; 138 | } 139 | 140 | if (attributeName) { 141 | [attributes addObject:[CDPropertyAttribute attributeWithName:attributeName value:attributeValue]]; 142 | } 143 | } 144 | 145 | _attributes = [attributes copy]; 146 | if (!isDynamic) { 147 | if (!self.getter) { 148 | _getter = [self.name copy]; 149 | } 150 | if (!self.setter && !isReadOnly) { 151 | // per https://github.com/llvm/llvm-project/blob/86616443bf/clang/lib/Basic/IdentifierTable.cpp#L756-L762 152 | unichar const realFirstChar = [self.name characterAtIndex:0]; 153 | NSString *firstChar = [NSString stringWithCharacters:&realFirstChar length:1]; 154 | _setter = [NSString stringWithFormat:@"set%@%@:", firstChar.uppercaseString, [self.name substringFromIndex:1]]; 155 | } 156 | } 157 | } 158 | return self; 159 | } 160 | 161 | - (void)overrideType:(CDParseType *)type { 162 | _type = type; 163 | } 164 | 165 | - (CDSemanticString *)semanticString { 166 | CDSemanticString *build = [CDSemanticString new]; 167 | [build appendString:@"@property" semanticType:CDSemanticTypeKeyword]; 168 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 169 | 170 | NSArray *attributes = self.attributes; 171 | NSUInteger const attributeCount = attributes.count; 172 | if (attributeCount > 0) { 173 | [build appendString:@"(" semanticType:CDSemanticTypeStandard]; 174 | [attributes enumerateObjectsUsingBlock:^(CDPropertyAttribute *attribute, NSUInteger idx, BOOL *stop) { 175 | [build appendString:attribute.name semanticType:CDSemanticTypeKeyword]; 176 | 177 | NSString *attributeValue = attribute.value; 178 | if (attributeValue != nil) { 179 | [build appendString:@"=" semanticType:CDSemanticTypeStandard]; 180 | [build appendString:attributeValue semanticType:CDSemanticTypeVariable]; 181 | } 182 | if ((idx + 1) < attributeCount) { 183 | [build appendString:@", " semanticType:CDSemanticTypeStandard]; 184 | } 185 | }]; 186 | [build appendString:@") " semanticType:CDSemanticTypeStandard]; 187 | } 188 | [build appendSemanticString:[self.type semanticStringForVariableName:self.name]]; 189 | return build; 190 | } 191 | 192 | static BOOL _NSStringNullableEqual(NSString *a, NSString *b) { 193 | return (!a && !b) || [a isEqual:b]; 194 | } 195 | - (BOOL)isEqual:(id)object { 196 | if ([object isKindOfClass:[self class]]) { 197 | __typeof(self) casted = (__typeof(casted))object; 198 | return [self.name isEqual:casted.name] && [self.attributes isEqual:casted.attributes] && 199 | _NSStringNullableEqual(self.iVar, casted.iVar) && 200 | _NSStringNullableEqual(self.getter, casted.getter) && 201 | _NSStringNullableEqual(self.setter, casted.setter); 202 | } 203 | return NO; 204 | } 205 | 206 | - (NSUInteger)hash { 207 | return self.name.hash; 208 | } 209 | 210 | - (NSString *)description { 211 | return [[self semanticString] string]; 212 | } 213 | 214 | - (NSString *)debugDescription { 215 | return [NSString stringWithFormat:@"<%@: %p> {type: %@, attributes: %@, " 216 | "ivar: '%@', getter: '%@', setter: '%@'}", 217 | [self class], self, self.type, self.attributes, 218 | self.iVar, self.getter, self.setter]; 219 | } 220 | 221 | @end 222 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDProtocolModel+Conformance.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDProtocolModel+Conformance.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 3/3/24. 6 | // Copyright © 2024 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CDProtocolModel (Conformance) 12 | 13 | /// The class properties required for a type conforming to the given protocols to provide 14 | /// 15 | /// A property with a given name will only appear once in this collection. 16 | + (NSArray *)requiredClassPropertiesToConform:(NSArray *)protocols; 17 | /// The instance properties required for a type conforming to the given protocols to provide 18 | /// 19 | /// A property with a given name will only appear once in this collection. 20 | + (NSArray *)requiredInstancePropertiesToConform:(NSArray *)protocols; 21 | 22 | /// The class methods required for a type conforming to the given protocols to provide 23 | /// 24 | /// A method with a given selector will only appear once in this collection. 25 | + (NSArray *)requiredClassMethodsToConform:(NSArray *)protocols; 26 | /// The instance methods required for a type conforming to the given protocols to provide 27 | /// 28 | /// A method with a given selector will only appear once in this collection. 29 | + (NSArray *)requiredInstanceMethodsToConform:(NSArray *)protocols; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDProtocolModel+Conformance.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDProtocolModel+Conformance.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 3/3/24. 6 | // Copyright © 2024 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDProtocolModel+Conformance.h" 10 | 11 | #import 12 | 13 | @implementation CDProtocolModel (Conformance) 14 | 15 | + (NSArray *)requiredClassPropertiesToConform:(NSArray *)protocols { 16 | return [self _genericRequiredForConformance:protocols property:@selector(requiredClassProperties) identifiable:^NSString *(CDPropertyModel *model) { 17 | return model.name; 18 | }]; 19 | } 20 | + (NSArray *)requiredInstancePropertiesToConform:(NSArray *)protocols { 21 | return [self _genericRequiredForConformance:protocols property:@selector(requiredInstanceProperties) identifiable:^NSString *(CDPropertyModel *model) { 22 | return model.name; 23 | }]; 24 | } 25 | 26 | + (NSArray *)requiredClassMethodsToConform:(NSArray *)protocols { 27 | return [self _genericRequiredForConformance:protocols property:@selector(requiredClassMethods) identifiable:^NSString *(CDMethodModel *model) { 28 | return model.name; 29 | }]; 30 | } 31 | + (NSArray *)requiredInstanceMethodsToConform:(NSArray *)protocols { 32 | return [self _genericRequiredForConformance:protocols property:@selector(requiredInstanceMethods) identifiable:^NSString *(CDMethodModel *model) { 33 | return model.name; 34 | }]; 35 | } 36 | 37 | // returns NSArray 38 | // mode: KeyPath> 39 | // identifiable: (T) -> T.ID 40 | + (NSArray *)_genericRequiredForConformance:(NSArray *)protocols property:(SEL)mode identifiable:(id(NS_NOESCAPE ^)(id))identifiableResolver { 41 | NSMutableArray *build = [NSMutableArray array]; 42 | NSMutableSet *trackingSet = [NSMutableSet set]; 43 | [self _genericRequiredForConformance:protocols build:build tracking:trackingSet property:mode identifiable:identifiableResolver]; 44 | return build; 45 | } 46 | 47 | + (void)_genericRequiredForConformance:(NSArray *)protocols build:(NSMutableArray *)build tracking:(NSMutableSet *)trackingSet property:(SEL)mode identifiable:(id(NS_NOESCAPE ^)(id))identifiableResolver { 48 | // the order here is very important for protocols that have 49 | // confliciting declarations for a given identifier 50 | for (CDProtocolModel *protocol in protocols) { 51 | NSArray *recursiveResolve = ((id (*)(id, SEL))objc_msgSend)(protocol, mode); 52 | for (id object in recursiveResolve) { 53 | id const identifier = identifiableResolver(object); 54 | 55 | if ([trackingSet containsObject:identifier]) { 56 | continue; 57 | } 58 | [trackingSet addObject:identifier]; 59 | [build addObject:object]; 60 | } 61 | [self _genericRequiredForConformance:protocol.protocols build:build tracking:trackingSet property:mode identifiable:identifiableResolver]; 62 | } 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDProtocolModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDProtocolModel.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 4/7/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | #import 13 | #import 14 | 15 | @interface CDProtocolModel : NSObject 16 | /// The Obj-C runtime @c Protocol 17 | @property (strong, nonatomic, readonly) Protocol *backing; 18 | /// The name of the protocol, e.g. @c NSObject 19 | @property (strong, nonatomic, readonly) NSString *name; 20 | /// The protocols the protocol conforms to 21 | @property (strong, nonatomic, readonly) NSArray *protocols; 22 | 23 | @property (strong, nonatomic, readonly) NSArray *requiredClassProperties; 24 | @property (strong, nonatomic, readonly) NSArray *requiredInstanceProperties; 25 | 26 | @property (strong, nonatomic, readonly) NSArray *requiredClassMethods; 27 | @property (strong, nonatomic, readonly) NSArray *requiredInstanceMethods; 28 | 29 | @property (strong, nonatomic, readonly) NSArray *optionalClassProperties; 30 | @property (strong, nonatomic, readonly) NSArray *optionalInstanceProperties; 31 | 32 | @property (strong, nonatomic, readonly) NSArray *optionalClassMethods; 33 | @property (strong, nonatomic, readonly) NSArray *optionalInstanceMethods; 34 | 35 | - (instancetype)initWithProtocol:(Protocol *)prcl; 36 | + (instancetype)modelWithProtocol:(Protocol *)prcl; 37 | 38 | /// Generate an @c interface for the protocol 39 | /// @param comments Generate comments with information such as the 40 | /// image the declaration was found in 41 | /// @param synthesizeStrip Remove methods and ivars synthesized from properties 42 | - (NSString *)linesWithComments:(BOOL)comments synthesizeStrip:(BOOL)synthesizeStrip; 43 | /// Generate an @c interface for the protocol 44 | /// @param comments Generate comments with information such as the 45 | /// image the declaration was found in 46 | /// @param synthesizeStrip Remove methods and ivars synthesized from properties 47 | - (CDSemanticString *)semanticLinesWithComments:(BOOL)comments synthesizeStrip:(BOOL)synthesizeStrip; 48 | 49 | /// Generate an @c interface for the protocol 50 | - (CDSemanticString *)semanticLinesWithOptions:(CDGenerationOptions *)options; 51 | 52 | /// Classes the protocol references in the declaration 53 | /// 54 | /// In other words, all the classes that the compiler would need to see 55 | /// for the header to pass the type checking stage of compilation. 56 | - (NSSet *)classReferences; 57 | /// Protocols the protocol references in the declaration 58 | /// 59 | /// In other words, all the protocols that the compiler would need to see 60 | /// for the header to pass the type checking stage of compilation. 61 | - (NSSet *)protocolReferences; 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /ClassDump/Models/Reflections/CDProtocolModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDProtocolModel.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 4/7/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDProtocolModel.h" 10 | 11 | #import 12 | 13 | @implementation CDProtocolModel { 14 | NSArray *_classPropertySynthesizedMethods; 15 | NSArray *_instancePropertySynthesizedMethods; 16 | } 17 | 18 | + (instancetype)modelWithProtocol:(Protocol *)prcl { 19 | return [[self alloc] initWithProtocol:prcl]; 20 | } 21 | 22 | - (instancetype)initWithProtocol:(Protocol *)prcl { 23 | if (self = [self init]) { 24 | _backing = prcl; 25 | _name = NSStringFromProtocol(prcl); 26 | 27 | unsigned int count, index; 28 | 29 | Protocol *__unsafe_unretained *protocolList = protocol_copyProtocolList(prcl, &count); 30 | if (protocolList) { 31 | NSMutableArray *protocols = [NSMutableArray arrayWithCapacity:count]; 32 | for (index = 0; index < count; index++) { 33 | /* circular dependecies are illegal */ 34 | Protocol *objc_protocol = protocolList[index]; 35 | [protocols addObject:[CDProtocolModel modelWithProtocol:objc_protocol]]; 36 | } 37 | free(protocolList); 38 | _protocols = [protocols copy]; 39 | } 40 | 41 | NSMutableArray *classSynthMeths = [NSMutableArray array]; 42 | NSMutableArray *instcSynthMeths = [NSMutableArray array]; 43 | 44 | #if 0 /* this appears to not be working properly, depending on version combinations between runtime enviorment and target image */ 45 | objc_property_t *reqClassProps = protocol_copyPropertyList2(prcl, &count, YES, NO); 46 | if (reqClassProps) { 47 | NSMutableArray *properties = [NSMutableArray arrayWithCapacity:count]; 48 | for (index = 0; index < count; index++) { 49 | CDPropertyModel *propertyRep = [CDPropertyModel modelWithProperty:reqClassProps[index] isClass:YES]; 50 | [properties addObject:propertyRep]; 51 | NSString *synthMethodName; 52 | if ((synthMethodName = propertyRep.getter)) { 53 | [classSynthMeths addObject:synthMethodName]; 54 | } 55 | if ((synthMethodName = propertyRep.setter)) { 56 | [classSynthMeths addObject:synthMethodName]; 57 | } 58 | } 59 | free(reqClassProps); 60 | _requiredClassProperties = [properties copy]; 61 | } 62 | #endif 63 | struct objc_method_description *reqClassMeths = protocol_copyMethodDescriptionList(prcl, YES, NO, &count); 64 | if (reqClassMeths) { 65 | NSMutableArray *methods = [NSMutableArray arrayWithCapacity:count]; 66 | for (index = 0; index < count; index++) { 67 | [methods addObject:[CDMethodModel modelWithMethod:reqClassMeths[index] isClass:YES]]; 68 | } 69 | free(reqClassMeths); 70 | _requiredClassMethods = [methods copy]; 71 | } 72 | 73 | objc_property_t *reqInstProps = protocol_copyPropertyList2(prcl, &count, YES, YES); 74 | if (reqInstProps) { 75 | NSMutableArray *properties = [NSMutableArray arrayWithCapacity:count]; 76 | for (index = 0; index < count; index++) { 77 | CDPropertyModel *propertyRep = [CDPropertyModel modelWithProperty:reqInstProps[index] isClass:NO]; 78 | [properties addObject:propertyRep]; 79 | NSString *synthMethodName; 80 | if ((synthMethodName = propertyRep.getter)) { 81 | [instcSynthMeths addObject:synthMethodName]; 82 | } 83 | if ((synthMethodName = propertyRep.setter)) { 84 | [instcSynthMeths addObject:synthMethodName]; 85 | } 86 | } 87 | free(reqInstProps); 88 | _requiredInstanceProperties = [properties copy]; 89 | } 90 | struct objc_method_description *reqInstMeths = protocol_copyMethodDescriptionList(prcl, YES, YES, &count); 91 | if (reqInstMeths) { 92 | NSMutableArray *methods = [NSMutableArray arrayWithCapacity:count]; 93 | for (index = 0; index < count; index++) { 94 | [methods addObject:[CDMethodModel modelWithMethod:reqInstMeths[index] isClass:NO]]; 95 | } 96 | free(reqInstMeths); 97 | _requiredInstanceMethods = [methods copy]; 98 | } 99 | 100 | objc_property_t *optClassProps = protocol_copyPropertyList2(prcl, &count, NO, NO); 101 | if (optClassProps) { 102 | NSMutableArray *properties = [NSMutableArray arrayWithCapacity:count]; 103 | for (index = 0; index < count; index++) { 104 | CDPropertyModel *propertyRep = [CDPropertyModel modelWithProperty:optClassProps[index] isClass:YES]; 105 | [properties addObject:propertyRep]; 106 | NSString *synthMethodName; 107 | if ((synthMethodName = propertyRep.getter)) { 108 | [classSynthMeths addObject:synthMethodName]; 109 | } 110 | if ((synthMethodName = propertyRep.setter)) { 111 | [classSynthMeths addObject:synthMethodName]; 112 | } 113 | } 114 | free(optClassProps); 115 | _optionalClassProperties = [properties copy]; 116 | } 117 | struct objc_method_description *optClassMeths = protocol_copyMethodDescriptionList(prcl, NO, NO, &count); 118 | if (optClassMeths) { 119 | NSMutableArray *methods = [NSMutableArray arrayWithCapacity:count]; 120 | for (index = 0; index < count; index++) { 121 | [methods addObject:[CDMethodModel modelWithMethod:(optClassMeths[index]) isClass:YES]]; 122 | } 123 | free(optClassMeths); 124 | _requiredClassMethods = [methods copy]; 125 | } 126 | 127 | objc_property_t *optInstProps = protocol_copyPropertyList2(prcl, &count, NO, YES); 128 | if (optInstProps) { 129 | NSMutableArray *properties = [NSMutableArray arrayWithCapacity:count]; 130 | for (index = 0; index < count; index++) { 131 | CDPropertyModel *propertyRep = [CDPropertyModel modelWithProperty:optInstProps[index] isClass:NO]; 132 | [properties addObject:propertyRep]; 133 | NSString *synthMethodName; 134 | if ((synthMethodName = propertyRep.getter)) { 135 | [instcSynthMeths addObject:synthMethodName]; 136 | } 137 | if ((synthMethodName = propertyRep.setter)) { 138 | [instcSynthMeths addObject:synthMethodName]; 139 | } 140 | } 141 | free(optInstProps); 142 | _optionalInstanceProperties = [properties copy]; 143 | } 144 | struct objc_method_description *optInstMeths = protocol_copyMethodDescriptionList(prcl, NO, YES, &count); 145 | if (optInstMeths) { 146 | NSMutableArray *methods = [NSMutableArray arrayWithCapacity:count]; 147 | for (index = 0; index < count; index++) { 148 | [methods addObject:[CDMethodModel modelWithMethod:optInstMeths[index] isClass:NO]]; 149 | } 150 | free(optInstMeths); 151 | _optionalInstanceMethods = [methods copy]; 152 | } 153 | 154 | _classPropertySynthesizedMethods = [classSynthMeths copy]; 155 | _instancePropertySynthesizedMethods = [instcSynthMeths copy]; 156 | } 157 | return self; 158 | } 159 | 160 | - (NSString *)linesWithComments:(BOOL)comments synthesizeStrip:(BOOL)synthesizeStrip { 161 | return [[self semanticLinesWithComments:comments synthesizeStrip:synthesizeStrip] string]; 162 | } 163 | 164 | - (CDSemanticString *)semanticLinesWithComments:(BOOL)comments synthesizeStrip:(BOOL)synthesizeStrip { 165 | CDGenerationOptions *options = [CDGenerationOptions new]; 166 | options.addSymbolImageComments = comments; 167 | options.stripSynthesized = synthesizeStrip; 168 | return [self semanticLinesWithOptions:options]; 169 | } 170 | 171 | - (CDSemanticString *)semanticLinesWithOptions:(CDGenerationOptions *)options { 172 | CDSemanticString *build = [CDSemanticString new]; 173 | 174 | NSSet *forwardClasses = [self _forwardDeclarableClassReferences]; 175 | NSUInteger const forwardClassCount = forwardClasses.count; 176 | if (forwardClassCount > 0) { 177 | [build appendString:@"@class" semanticType:CDSemanticTypeKeyword]; 178 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 179 | 180 | NSUInteger classNamesRemaining = forwardClassCount; 181 | for (NSString *className in forwardClasses) { 182 | [build appendString:className semanticType:CDSemanticTypeClass]; 183 | classNamesRemaining--; 184 | if (classNamesRemaining > 0) { 185 | [build appendString:@", " semanticType:CDSemanticTypeStandard]; 186 | } 187 | } 188 | [build appendString:@";\n" semanticType:CDSemanticTypeStandard]; 189 | } 190 | 191 | NSSet *forwardProtocols = [self _forwardDeclarableProtocolReferences]; 192 | NSUInteger const forwardProtocolCount = forwardProtocols.count; 193 | if (forwardProtocolCount > 0) { 194 | [build appendString:@"@protocol" semanticType:CDSemanticTypeKeyword]; 195 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 196 | 197 | NSUInteger protocolNamesRemaining = forwardProtocolCount; 198 | for (NSString *protocolNames in forwardProtocols) { 199 | [build appendString:protocolNames semanticType:CDSemanticTypeProtocol]; 200 | protocolNamesRemaining--; 201 | if (protocolNamesRemaining > 0) { 202 | [build appendString:@", " semanticType:CDSemanticTypeStandard]; 203 | } 204 | } 205 | [build appendString:@";\n" semanticType:CDSemanticTypeStandard]; 206 | } 207 | 208 | if (forwardClassCount > 0 || forwardProtocolCount > 0) { 209 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 210 | } 211 | 212 | if (options.addSymbolImageComments) { 213 | NSString *comment = nil; 214 | Dl_info info; 215 | if (dladdr((__bridge void *)self.backing, &info)) { 216 | comment = [NSString stringWithFormat:@"/* %s in %s */", info.dli_sname ?: "(anonymous)", info.dli_fname]; 217 | } else { 218 | comment = @"/* no symbol found */"; 219 | } 220 | [build appendString:comment semanticType:CDSemanticTypeComment]; 221 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 222 | } 223 | [build appendString:@"@protocol" semanticType:CDSemanticTypeKeyword]; 224 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 225 | [build appendString:self.name semanticType:CDSemanticTypeProtocol]; 226 | 227 | NSArray *protocols = self.protocols; 228 | NSUInteger const protocolCount = protocols.count; 229 | if (protocolCount > 0) { 230 | [build appendString:@" " semanticType:CDSemanticTypeStandard]; 231 | [build appendString:@"<" semanticType:CDSemanticTypeStandard]; 232 | [protocols enumerateObjectsUsingBlock:^(CDProtocolModel *protocol, NSUInteger idx, BOOL *stop) { 233 | [build appendString:protocol.name semanticType:CDSemanticTypeProtocol]; 234 | if ((idx + 1) < protocolCount) { 235 | [build appendString:@", " semanticType:CDSemanticTypeStandard]; 236 | } 237 | }]; 238 | [build appendString:@">" semanticType:CDSemanticTypeStandard]; 239 | } 240 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 241 | 242 | [self _appendLines:build properties:self.requiredClassProperties comments:options.addSymbolImageComments]; 243 | [self _appendLines:build methods:self.requiredClassMethods synthesized:(options.stripSynthesized ? _classPropertySynthesizedMethods : nil) comments:options.addSymbolImageComments]; 244 | 245 | [self _appendLines:build properties:self.requiredInstanceProperties comments:options.addSymbolImageComments]; 246 | [self _appendLines:build methods:self.requiredInstanceMethods synthesized:(options.stripSynthesized ? _instancePropertySynthesizedMethods : nil) comments:options.addSymbolImageComments]; 247 | 248 | if (self.optionalClassProperties.count || self.optionalClassMethods.count || 249 | self.optionalInstanceProperties.count || self.optionalInstanceMethods.count) { 250 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 251 | [build appendString:@"@optional" semanticType:CDSemanticTypeKeyword]; 252 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 253 | } 254 | 255 | [self _appendLines:build properties:self.optionalClassProperties comments:options.addSymbolImageComments]; 256 | [self _appendLines:build methods:self.optionalClassMethods synthesized:(options.stripSynthesized ? _classPropertySynthesizedMethods : nil) comments:options.addSymbolImageComments]; 257 | 258 | [self _appendLines:build properties:self.optionalInstanceProperties comments:options.addSymbolImageComments]; 259 | [self _appendLines:build methods:self.optionalInstanceMethods synthesized:(options.stripSynthesized ? _instancePropertySynthesizedMethods : nil) comments:options.addSymbolImageComments]; 260 | 261 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 262 | [build appendString:@"@end" semanticType:CDSemanticTypeKeyword]; 263 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 264 | 265 | return build; 266 | } 267 | 268 | - (void)_appendLines:(CDSemanticString *)build properties:(NSArray *)properties comments:(BOOL)comments { 269 | if (properties.count) { 270 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 271 | 272 | Dl_info info; 273 | for (CDPropertyModel *prop in properties) { 274 | if (comments) { 275 | NSString *comment = nil; 276 | if (dladdr(prop.backing, &info)) { 277 | comment = [NSString stringWithFormat:@"/* in %s */", info.dli_fname]; 278 | } else { 279 | comment = @"/* no symbol found */"; 280 | } 281 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 282 | [build appendString:comment semanticType:CDSemanticTypeComment]; 283 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 284 | } 285 | [build appendSemanticString:[prop semanticString]]; 286 | [build appendString:@";\n" semanticType:CDSemanticTypeStandard]; 287 | } 288 | } 289 | } 290 | 291 | - (void)_appendLines:(CDSemanticString *)build methods:(NSArray *)methods synthesized:(NSArray *)synthesized comments:(BOOL)comments { 292 | if (methods.count) { 293 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 294 | 295 | Dl_info info; 296 | NSMutableArray *synthed = [NSMutableArray arrayWithArray:synthesized]; 297 | NSUInteger synthedCount = synthed.count; 298 | for (CDMethodModel *methd in methods) { 299 | // find and remove instead of just find so we don't have to search the entire 300 | // array everytime, when we know the objects that we've already filtered out won't come up again 301 | NSUInteger const searchResult = [synthed indexOfObject:methd.name inRange:NSMakeRange(0, synthedCount)]; 302 | if (searchResult != NSNotFound) { 303 | synthedCount--; 304 | // optimized version of remove since the 305 | // order of synthed doesn't matter to us. 306 | // exchange is O(1) instead of remove is O(n) 307 | [synthed exchangeObjectAtIndex:searchResult withObjectAtIndex:synthedCount]; 308 | continue; 309 | } 310 | 311 | if (comments) { 312 | NSString *comment = nil; 313 | if (dladdr(methd.backing.types, &info)) { 314 | comment = [NSString stringWithFormat:@"/* in %s */", info.dli_fname]; 315 | } else { 316 | comment = @"/* no symbol found */"; 317 | } 318 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 319 | [build appendString:comment semanticType:CDSemanticTypeComment]; 320 | [build appendString:@"\n" semanticType:CDSemanticTypeStandard]; 321 | } 322 | [build appendSemanticString:[methd semanticString]]; 323 | [build appendString:@";\n" semanticType:CDSemanticTypeStandard]; 324 | } 325 | } 326 | } 327 | 328 | - (NSSet *)_forwardDeclarableClassReferences { 329 | NSMutableSet *build = [NSMutableSet set]; 330 | 331 | [self _unionReferences:build sources:self.requiredClassProperties resolve:^NSSet *(CDPropertyModel *model) { 332 | return [model.type classReferences]; 333 | }]; 334 | [self _unionReferences:build sources:self.requiredInstanceProperties resolve:^NSSet *(CDPropertyModel *model) { 335 | return [model.type classReferences]; 336 | }]; 337 | 338 | [self _unionReferences:build sources:self.requiredClassMethods resolve:^NSSet *(CDMethodModel *model) { 339 | return [model classReferences]; 340 | }]; 341 | [self _unionReferences:build sources:self.requiredInstanceMethods resolve:^NSSet *(CDMethodModel *model) { 342 | return [model classReferences]; 343 | }]; 344 | 345 | [self _unionReferences:build sources:self.optionalClassProperties resolve:^NSSet *(CDPropertyModel *model) { 346 | return [model.type classReferences]; 347 | }]; 348 | [self _unionReferences:build sources:self.optionalInstanceProperties resolve:^NSSet *(CDPropertyModel *model) { 349 | return [model.type classReferences]; 350 | }]; 351 | 352 | [self _unionReferences:build sources:self.optionalClassMethods resolve:^NSSet *(CDMethodModel *model) { 353 | return [model classReferences]; 354 | }]; 355 | [self _unionReferences:build sources:self.optionalInstanceMethods resolve:^NSSet *(CDMethodModel *model) { 356 | return [model classReferences]; 357 | }]; 358 | 359 | return build; 360 | } 361 | 362 | - (NSSet *)_forwardDeclarableProtocolReferences { 363 | NSMutableSet *build = [NSMutableSet set]; 364 | 365 | [self _unionReferences:build sources:self.requiredClassProperties resolve:^NSSet *(CDPropertyModel *model) { 366 | return [model.type protocolReferences]; 367 | }]; 368 | [self _unionReferences:build sources:self.requiredInstanceProperties resolve:^NSSet *(CDPropertyModel *model) { 369 | return [model.type protocolReferences]; 370 | }]; 371 | 372 | [self _unionReferences:build sources:self.requiredClassMethods resolve:^NSSet *(CDMethodModel *model) { 373 | return [model protocolReferences]; 374 | }]; 375 | [self _unionReferences:build sources:self.requiredInstanceMethods resolve:^NSSet *(CDMethodModel *model) { 376 | return [model protocolReferences]; 377 | }]; 378 | 379 | [self _unionReferences:build sources:self.optionalClassProperties resolve:^NSSet *(CDPropertyModel *model) { 380 | return [model.type protocolReferences]; 381 | }]; 382 | [self _unionReferences:build sources:self.optionalInstanceProperties resolve:^NSSet *(CDPropertyModel *model) { 383 | return [model.type protocolReferences]; 384 | }]; 385 | 386 | [self _unionReferences:build sources:self.optionalClassMethods resolve:^NSSet *(CDMethodModel *model) { 387 | return [model protocolReferences]; 388 | }]; 389 | [self _unionReferences:build sources:self.optionalInstanceMethods resolve:^NSSet *(CDMethodModel *model) { 390 | return [model protocolReferences]; 391 | }]; 392 | 393 | [build removeObject:self.name]; 394 | return build; 395 | } 396 | 397 | - (NSSet *)classReferences { 398 | return [self _forwardDeclarableClassReferences]; 399 | } 400 | 401 | - (NSSet *)protocolReferences { 402 | NSMutableSet *build = [NSMutableSet set]; 403 | 404 | NSSet *forwardDeclarable = [self _forwardDeclarableProtocolReferences]; 405 | if (forwardDeclarable != nil) { 406 | [build unionSet:forwardDeclarable]; 407 | } 408 | 409 | for (CDProtocolModel *protocol in self.protocols) { 410 | [build addObject:protocol.name]; 411 | } 412 | 413 | return build; 414 | } 415 | 416 | - (void)_unionReferences:(NSMutableSet *)build sources:(NSArray *)sources resolve:(NSSet *(NS_NOESCAPE ^)(id))resolver { 417 | for (id source in sources) { 418 | NSSet *refs = resolver(source); 419 | if (refs != nil) { 420 | [build unionSet:refs]; 421 | } 422 | } 423 | } 424 | 425 | - (NSString *)description { 426 | return self.name; 427 | } 428 | 429 | - (NSString *)debugDescription { 430 | return [NSString stringWithFormat:@"<%@: %p> {name: '%@', protocols: %@, " 431 | "requiredClassProperties: %@, requiredInstanceProperties: %@, " 432 | "requiredClassMethods: %@, requiredInstanceMethods: %@, " 433 | "optionalClassProperties: %@, optionalInstanceProperties: %@, " 434 | "optionalClassMethods: %@, optionalInstanceMethods: %@}", 435 | [self class], self, self.name, self.protocols, 436 | self.requiredClassProperties, self.requiredInstanceProperties, 437 | self.requiredClassMethods, self.requiredInstanceMethods, 438 | self.optionalClassProperties, self.optionalInstanceProperties, 439 | self.optionalClassMethods, self.optionalInstanceMethods]; 440 | } 441 | 442 | @end 443 | -------------------------------------------------------------------------------- /ClassDump/Services/CDTypeParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDTypeParser.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 3/24/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CDTypeParser : NSObject 13 | 14 | /// Find the end of an Objective-C type encoding 15 | /// @param encoding An Objective-C type encoding 16 | /// @returns A pointer to the first character that is not part of the type encoding. 17 | /// If @c encoding is not a type encoding, @c encoding will be returned. 18 | + (const char *)endOfTypeEncoding:(const char *)encoding; 19 | /// Get an object representing the type encoded in @c encoding 20 | /// @param encoding A null-terminated C-string as returned by @c \@encode 21 | + (CDParseType *)typeForEncoding:(const char *)encoding; 22 | /// Get an object representing the type encoded 23 | /// @param start A pointer to the start of an encoded value as returned by @c \@encode 24 | /// @param end A pointer to the first byte out-of-bounds from @c start 25 | /// @param error Set to @c YES if an error occurs during proccessing 26 | + (CDParseType *)typeForEncodingStart:(const char *const)start end:(const char *const)end error:(inout BOOL *)error; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /ClassDump/Services/CDTypeParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDTypeParser.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 3/24/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDTypeParser.h" 10 | 11 | #import "../Models/ParseTypes/CDPrimitiveType.h" 12 | #import "../Models/ParseTypes/CDObjectType.h" 13 | #import "../Models/ParseTypes/CDBlockType.h" 14 | #import "../Models/ParseTypes/CDRecordType.h" 15 | #import "../Models/ParseTypes/CDPointerType.h" 16 | #import "../Models/ParseTypes/CDArrayType.h" 17 | #import "../Models/ParseTypes/CDBitFieldType.h" 18 | 19 | /* 20 | * @APPLE_LICENSE_HEADER_START@ 21 | * 22 | * This file contains Original Code and/or Modifications of Original Code 23 | * as defined in and that are subject to the Apple Public Source License 24 | * Version 2.0 (the 'License'). You may not use this file except in 25 | * compliance with the License. Please obtain a copy of the License at 26 | * http://www.opensource.apple.com/apsl/ and read it before using this 27 | * file. 28 | * 29 | * The Original Code and all software distributed under the License are 30 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 31 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 32 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 33 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 34 | * Please see the License for the specific language governing rights and 35 | * limitations under the License. 36 | * 37 | * @APPLE_LICENSE_HEADER_END@ 38 | */ 39 | 40 | @implementation CDTypeParser 41 | 42 | // References: 43 | // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html 44 | // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html 45 | // https://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Type-encoding.html 46 | 47 | // originally based off of `SkipFirstType` 48 | // https://github.com/apple-oss-distributions/objc4/blob/689525d556/runtime/objc-typeencoding.mm#L64-L105 49 | + (const char *)endOfTypeEncoding:(const char *)encoding { 50 | while (*encoding) { 51 | switch (*encoding) { 52 | /* known primitive types */ 53 | case '*': 54 | case 'c': 55 | case 'i': 56 | case 's': 57 | case 'l': 58 | case 'q': 59 | case 't': 60 | case 'C': 61 | case 'I': 62 | case 'S': 63 | case 'L': 64 | case 'Q': 65 | case 'T': 66 | case ' ': 67 | case 'f': 68 | case 'd': 69 | case 'D': 70 | case 'B': 71 | case 'v': 72 | case '#': 73 | case ':': 74 | case '?': 75 | // once we see an underlying type, there's nothing else in the encoding 76 | encoding++; 77 | return encoding; 78 | 79 | /* type modifiers */ 80 | case '^': 81 | case 'r': 82 | case 'n': 83 | case 'N': 84 | case 'o': 85 | case 'O': 86 | case 'R': 87 | case 'V': 88 | case 'A': 89 | case 'j': 90 | encoding++; 91 | break; 92 | 93 | case '@': { 94 | encoding++; 95 | if (*encoding == '"') { 96 | encoding++; 97 | while (*encoding != '"') { 98 | encoding++; 99 | } 100 | encoding++; 101 | } else if (*encoding == '?') { 102 | encoding++; 103 | if (*encoding == '<') { 104 | unsigned openTokens = 1; 105 | while (openTokens) { 106 | switch (*++encoding) { 107 | case '<': 108 | openTokens++; 109 | break; 110 | case '>': 111 | openTokens--; 112 | break; 113 | } 114 | } 115 | encoding++; 116 | } 117 | } 118 | return encoding; 119 | } break; 120 | 121 | case 'b': { 122 | encoding++; 123 | while (isnumber(*encoding)) { 124 | encoding++; 125 | } 126 | return encoding; 127 | } break; 128 | 129 | case '[': { 130 | unsigned openTokens = 1; 131 | while (openTokens) { 132 | switch (*++encoding) { 133 | case '[': 134 | openTokens++; 135 | break; 136 | case ']': 137 | openTokens--; 138 | break; 139 | } 140 | } 141 | encoding++; 142 | return encoding; 143 | } break; 144 | 145 | case '{': { 146 | unsigned openTokens = 1; 147 | while (openTokens) { 148 | switch (*++encoding) { 149 | case '{': 150 | openTokens++; 151 | break; 152 | case '}': 153 | openTokens--; 154 | break; 155 | } 156 | } 157 | encoding++; 158 | return encoding; 159 | } break; 160 | 161 | case '(': { 162 | unsigned openTokens = 1; 163 | while (openTokens) { 164 | switch (*++encoding) { 165 | case '(': 166 | openTokens++; 167 | break; 168 | case ')': 169 | openTokens--; 170 | break; 171 | } 172 | } 173 | encoding++; 174 | return encoding; 175 | } break; 176 | 177 | default: 178 | // don't modify, this isn't actually a type 179 | return encoding; 180 | } 181 | } 182 | return encoding; 183 | } 184 | 185 | + (CDParseType *)typeForEncoding:(const char *)encoding { 186 | return [self typeForEncodingStart:encoding end:encoding + strlen(encoding) error:NULL]; 187 | } 188 | 189 | + (CDParseType *)typeForEncodingStart:(const char *const)start end:(const char *const)end error:(inout BOOL *)error { 190 | __kindof CDParseType *type = nil; 191 | NSMutableArray *modifiers = [NSMutableArray array]; 192 | 193 | // clang encoding: 194 | // https://github.com/llvm/llvm-project/blob/1ce8e3543b/clang/lib/AST/ASTContext.cpp#L8202 195 | // gcc encoding: 196 | // https://github.com/gcc-mirror/gcc/blob/c6b12b802c/gcc/objc/objc-encoding.cc 197 | 198 | for (const char *chr = start; chr < end; chr++) { 199 | switch (*chr) { 200 | case '^': { 201 | chr++; 202 | 203 | CDParseType *pointee = nil; 204 | if (chr == end) { 205 | pointee = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeVoid]; 206 | } else { 207 | BOOL pointeeError = NO; 208 | pointee = [self typeForEncodingStart:chr end:end error:&pointeeError]; 209 | chr = end; // we've consumed the rest of the token 210 | 211 | if (pointeeError) { 212 | if (error) { 213 | *error = pointeeError; 214 | } 215 | return nil; 216 | } 217 | } 218 | NSAssert(type == nil, @"Overwriting type"); 219 | type = [CDPointerType pointerToPointee:pointee]; 220 | } break; 221 | case '*': { 222 | CDParseType *pointee = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeChar]; 223 | 224 | NSAssert(type == nil, @"Overwriting type"); 225 | type = [CDPointerType pointerToPointee:pointee]; 226 | } break; 227 | case 'c': 228 | NSAssert(type == nil, @"Overwriting type"); 229 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeChar]; 230 | break; 231 | case 'i': 232 | NSAssert(type == nil, @"Overwriting type"); 233 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeInt]; 234 | break; 235 | case 's': 236 | NSAssert(type == nil, @"Overwriting type"); 237 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeShort]; 238 | break; 239 | case 'l': 240 | NSAssert(type == nil, @"Overwriting type"); 241 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeLong]; 242 | break; 243 | case 'q': 244 | NSAssert(type == nil, @"Overwriting type"); 245 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeLongLong]; 246 | break; 247 | case 't': 248 | NSAssert(type == nil, @"Overwriting type"); 249 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeInt128]; 250 | break; 251 | case 'C': 252 | NSAssert(type == nil, @"Overwriting type"); 253 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeUnsignedChar]; 254 | break; 255 | case 'I': 256 | NSAssert(type == nil, @"Overwriting type"); 257 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeUnsignedInt]; 258 | break; 259 | case 'S': 260 | NSAssert(type == nil, @"Overwriting type"); 261 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeUnsignedShort]; 262 | break; 263 | case 'L': 264 | NSAssert(type == nil, @"Overwriting type"); 265 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeUnsignedLong]; 266 | break; 267 | case 'Q': 268 | NSAssert(type == nil, @"Overwriting type"); 269 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeUnsignedLongLong]; 270 | break; 271 | case 'T': 272 | NSAssert(type == nil, @"Overwriting type"); 273 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeUnsignedInt128]; 274 | break; 275 | case ' ': 276 | NSAssert(type == nil, @"Overwriting type"); 277 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeBlank]; 278 | break; 279 | case 'f': 280 | NSAssert(type == nil, @"Overwriting type"); 281 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeFloat]; 282 | break; 283 | case 'd': 284 | NSAssert(type == nil, @"Overwriting type"); 285 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeDouble]; 286 | break; 287 | case 'D': 288 | NSAssert(type == nil, @"Overwriting type"); 289 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeLongDouble]; 290 | break; 291 | case 'B': 292 | NSAssert(type == nil, @"Overwriting type"); 293 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeBool]; 294 | break; 295 | case 'v': 296 | NSAssert(type == nil, @"Overwriting type"); 297 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeVoid]; 298 | break; 299 | case '#': 300 | NSAssert(type == nil, @"Overwriting type"); 301 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeClass]; 302 | break; 303 | case ':': 304 | NSAssert(type == nil, @"Overwriting type"); 305 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeSel]; 306 | break; 307 | case 'r': 308 | [modifiers addObject:@(CDTypeModifierConst)]; 309 | break; 310 | case 'n': 311 | [modifiers addObject:@(CDTypeModifierIn)]; 312 | break; 313 | case 'N': 314 | [modifiers addObject:@(CDTypeModifierInOut)]; 315 | break; 316 | case 'o': 317 | [modifiers addObject:@(CDTypeModifierOut)]; 318 | break; 319 | case 'O': 320 | [modifiers addObject:@(CDTypeModifierBycopy)]; 321 | break; 322 | case 'R': 323 | [modifiers addObject:@(CDTypeModifierByref)]; 324 | break; 325 | case 'V': 326 | [modifiers addObject:@(CDTypeModifierOneway)]; 327 | break; 328 | case 'A': 329 | [modifiers addObject:@(CDTypeModifierAtomic)]; 330 | break; 331 | case 'j': 332 | [modifiers addObject:@(CDTypeModifierComplex)]; 333 | break; 334 | case '@': { 335 | if (chr[1] == '"') { 336 | CDObjectType *objType = [CDObjectType new]; 337 | 338 | chr += 2; 339 | const char *const chrcpy = chr; 340 | const char *protocolHead = NULL; 341 | while (*chr != '"') { 342 | if (*chr == '<' && !protocolHead) { 343 | protocolHead = chr; 344 | } 345 | chr++; 346 | } 347 | 348 | if (!protocolHead) { 349 | objType.className = [[NSString alloc] initWithBytes:chrcpy length:(chr - chrcpy) encoding:NSUTF8StringEncoding]; 350 | } else { 351 | ptrdiff_t const baseTypeLength = (protocolHead - chrcpy); 352 | 353 | if (baseTypeLength) { 354 | objType.className = [[NSString alloc] initWithBytes:chrcpy length:baseTypeLength encoding:NSUTF8StringEncoding]; 355 | } 356 | 357 | NSMutableArray *protocolNames = [NSMutableArray array]; 358 | const char *protocolSearch = protocolHead; 359 | while (chr > protocolSearch) { 360 | while (*protocolSearch != '>') { 361 | protocolSearch++; 362 | } 363 | protocolHead++; // skip the leading '<' 364 | NSString *protocolName = [[NSString alloc] initWithBytes:protocolHead length:(protocolSearch - protocolHead) encoding:NSUTF8StringEncoding]; 365 | [protocolNames addObject:protocolName]; 366 | 367 | protocolSearch++; // move over the trailing '>' 368 | if (protocolSearch == chr) { 369 | break; 370 | } 371 | assert(protocolSearch[0] == '<'); 372 | protocolHead = protocolSearch; 373 | } 374 | 375 | objType.protocolNames = protocolNames; 376 | } 377 | NSAssert(type == nil, @"Overwriting type"); 378 | type = objType; 379 | } else if (chr[1] == '?') { 380 | CDBlockType *blockType = [CDBlockType new]; 381 | chr++; 382 | 383 | if (chr[1] == '<') { 384 | // chr is pointing to '?' 385 | // jump over '?' and '<' 386 | chr += 2; 387 | 388 | const char *parameterEncoding = [self endOfTypeEncoding:chr]; 389 | blockType.returnType = [self typeForEncodingStart:chr end:parameterEncoding error:NULL]; 390 | chr = parameterEncoding; 391 | 392 | NSAssert(chr[0] == '@' && chr[1] == '?', @"First block parameter should be itself"); 393 | chr += 2; // skip first block parameter 394 | 395 | // what we expect is that the input string looks something like: 396 | // "@?<@?____>" 397 | // ^ 398 | // chr is currently pointing here. Initialize `paramEnd` on the 399 | // character before, since `paramEnd` is incremented before being read, 400 | // which would be an issue if the input string is 401 | // "@?<@?>" 402 | // ^ 403 | // because chr points here, and we'd end up walking past the end of the type. 404 | const char *paramEnd = chr - 1; 405 | unsigned openTokens = 1; 406 | while (openTokens) { 407 | switch (*++paramEnd) { 408 | case '<': 409 | openTokens++; 410 | break; 411 | case '>': 412 | openTokens--; 413 | break; 414 | } 415 | } 416 | 417 | NSMutableArray *parameterTypes = [NSMutableArray array]; 418 | while (chr < paramEnd) { 419 | const char *tokenEnd = [self endOfTypeEncoding:chr]; 420 | [parameterTypes addObject:[self typeForEncodingStart:chr end:tokenEnd error:NULL]]; 421 | chr = tokenEnd; 422 | } 423 | chr = paramEnd; 424 | 425 | blockType.parameterTypes = parameterTypes; 426 | } 427 | 428 | NSAssert(type == nil, @"Overwriting type"); 429 | type = blockType; 430 | } else { 431 | NSAssert(type == nil, @"Overwriting type"); 432 | type = [CDObjectType new]; 433 | } 434 | } break; 435 | case 'b': { 436 | chr++; // fastforward over 'b' 437 | 438 | CDBitFieldType *bitField = [CDBitFieldType new]; 439 | bitField.width = strtoul(chr, (char **)&chr, 10); 440 | 441 | NSAssert(type == nil, @"Overwriting type"); 442 | type = bitField; 443 | } break; 444 | case '[': { 445 | const char *const chrcpy = chr; 446 | unsigned openTokens = 1; 447 | while (openTokens) { 448 | switch (*++chr) { 449 | case '[': 450 | openTokens++; 451 | break; 452 | case ']': 453 | openTokens--; 454 | break; 455 | } 456 | } 457 | 458 | CDArrayType *arrayType = [CDArrayType new]; 459 | 460 | char *tokenStart = NULL; 461 | arrayType.size = strtoul(chrcpy + 1, &tokenStart, 10); 462 | 463 | BOOL typeError = NO; 464 | arrayType.type = [self typeForEncodingStart:tokenStart end:chr error:&typeError]; 465 | 466 | if (typeError) { 467 | if (error) { 468 | *error = typeError; 469 | } 470 | return nil; 471 | } 472 | NSAssert(type == nil, @"Overwriting type"); 473 | type = arrayType; 474 | } break; 475 | case '{': { 476 | const char *const chrcpy = chr; 477 | unsigned openTokens = 1; 478 | while (openTokens) { 479 | switch (*++chr) { 480 | case '{': 481 | openTokens++; 482 | break; 483 | case '}': 484 | openTokens--; 485 | break; 486 | } 487 | } 488 | NSAssert(type == nil, @"Overwriting type"); 489 | type = [self recordTypeForEncodingStart:chrcpy end:chr + 1]; 490 | } break; 491 | case '(': { 492 | const char *const chrcpy = chr; 493 | unsigned openTokens = 1; 494 | while (openTokens) { 495 | switch (*++chr) { 496 | case '(': 497 | openTokens++; 498 | break; 499 | case ')': 500 | openTokens--; 501 | break; 502 | } 503 | } 504 | NSAssert(type == nil, @"Overwriting type"); 505 | type = [self recordTypeForEncodingStart:chrcpy end:chr + 1]; 506 | } break; 507 | case '?': 508 | NSAssert(type == nil, @"Overwriting type"); 509 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeFunction]; 510 | break; 511 | default: { 512 | NSAssert(NO, @"Unknown encoding"); 513 | if (error) { 514 | *error = YES; 515 | } 516 | return nil; 517 | } break; 518 | } 519 | } 520 | 521 | if (type == nil) { 522 | type = [CDPrimitiveType primitiveWithRawType:CDPrimitiveRawTypeEmpty]; 523 | } 524 | type.modifiers = modifiers; 525 | return type; 526 | } 527 | 528 | + (CDRecordType *)recordTypeForEncodingStart:(const char *const)start end:(const char *const)end { 529 | const char *const endToken = end - 1; 530 | const char firstChar = *start; 531 | const char lastChar = *endToken; 532 | 533 | BOOL const isStruct = (firstChar == '{' && lastChar == '}'); 534 | BOOL const isUnion = (firstChar == '(' && lastChar == ')'); 535 | NSAssert(isStruct || isUnion, @"Expected either a struct or union"); 536 | NSAssert(isStruct != isUnion, @"Record cannot be both a struct and union"); 537 | 538 | CDRecordType *record = [CDRecordType new]; 539 | record.isUnion = isUnion; 540 | 541 | size_t nameOffset = 1; 542 | while (start[nameOffset] != '=' && start[nameOffset] != '}') { 543 | nameOffset++; 544 | } 545 | nameOffset++; 546 | 547 | // anonymous indicator 548 | if (nameOffset != 3 && start[1] != '?') { 549 | record.name = [[NSString alloc] initWithBytes:(start + 1) length:(nameOffset - 2) encoding:NSUTF8StringEncoding]; 550 | } 551 | // no content, usually caused by multiple levels of indirection 552 | if (nameOffset == (end - start)) { 553 | return record; 554 | } 555 | 556 | NSMutableArray *fields = [NSMutableArray array]; 557 | 558 | for (const char *chr = start + nameOffset; chr < endToken;) { 559 | CDVariableModel *variableModel = [CDVariableModel new]; 560 | 561 | if (*chr == '"') { 562 | const char *const chrcpy = ++chr; 563 | while (*chr != '"') { 564 | chr++; 565 | } 566 | variableModel.name = [[NSString alloc] initWithBytes:chrcpy length:(chr - chrcpy) encoding:NSUTF8StringEncoding]; 567 | chr++; 568 | } 569 | 570 | const char *const chrcpy = chr; 571 | chr = [self endOfTypeEncoding:chrcpy]; 572 | 573 | BOOL subError = NO; 574 | variableModel.type = [self typeForEncodingStart:chrcpy end:chr error:&subError]; 575 | if (subError) { 576 | return nil; 577 | } 578 | [fields addObject:variableModel]; 579 | } 580 | record.fields = fields; 581 | return record; 582 | } 583 | 584 | @end 585 | -------------------------------------------------------------------------------- /ClassDump/Services/CDUtilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDUtilities.h 3 | // ClassDump 4 | // 5 | // Created by Leptos on 5/10/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CDUtilities : NSObject 12 | 13 | /// The paths of the images in the loaded dyld shared cache 14 | + (nonnull NSArray *)dyldSharedCacheImagePaths; 15 | 16 | /// Names of all registered Obj-C classes 17 | + (nonnull NSArray *)classNames; 18 | /// Determines if the Obj-C class with the given name is safe to reference 19 | + (BOOL)isClassSafeToInspect:(nonnull NSString *)className; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ClassDump/Services/CDUtilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDUtilities.m 3 | // ClassDump 4 | // 5 | // Created by Leptos on 5/10/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import "CDUtilities.h" 10 | 11 | #import 12 | #import 13 | 14 | 15 | /* Portions of code in this file have been explicitly copied from Apple's dyld 16 | * 17 | * @APPLE_LICENSE_HEADER_START@ 18 | * 19 | * This file contains Original Code and/or Modifications of Original Code 20 | * as defined in and that are subject to the Apple Public Source License 21 | * Version 2.0 (the 'License'). You may not use this file except in 22 | * compliance with the License. Please obtain a copy of the License at 23 | * http://www.opensource.apple.com/apsl/ and read it before using this 24 | * file. 25 | * 26 | * The Original Code and all software distributed under the License are 27 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 28 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 29 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 30 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 31 | * Please see the License for the specific language governing rights and 32 | * limitations under the License. 33 | * 34 | * @APPLE_LICENSE_HEADER_END@ 35 | */ 36 | 37 | // https://github.com/apple-oss-distributions/dyld/blob/c8a445f88f/cache-builder/dyld_cache_format.h#L31 38 | struct dyld_cache_header 39 | { 40 | char magic[16]; // e.g. "dyld_v0 i386" 41 | uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info 42 | uint32_t mappingCount; // number of dyld_cache_mapping_info entries 43 | uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing 44 | uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing 45 | uint64_t dyldBaseAddress; // base address of dyld when cache was built 46 | uint64_t codeSignatureOffset; // file offset of code signature blob 47 | uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) 48 | uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info 49 | uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info 50 | uint64_t localSymbolsOffset; // file offset of where local symbols are stored 51 | uint64_t localSymbolsSize; // size of local symbols information 52 | uint8_t uuid[16]; // unique value for each shared cache file 53 | uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache 54 | uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses 55 | uint32_t branchPoolsCount; // number of uint64_t entries 56 | uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache 57 | uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache 58 | uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info 59 | uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries 60 | uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info 61 | uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info 62 | uint64_t otherImageGroupAddrUnused; // unused 63 | uint64_t otherImageGroupSizeUnused; // unused 64 | uint64_t progClosuresAddr; // (unslid) address of list of program launch closures 65 | uint64_t progClosuresSize; // size of list of program launch closures 66 | uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures 67 | uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures 68 | uint32_t platform; // platform number (macOS=1, etc) 69 | uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion 70 | dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid 71 | simulator : 1, // for simulator of specified platform 72 | locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache 73 | builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used for overrides 74 | padding : 20; // TBD 75 | uint64_t sharedRegionStart; // base load address of cache if not slid 76 | uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any 77 | uint64_t maxSlide; // runtime slide of cache can be between zero and this value 78 | uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache 79 | uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache 80 | uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs 81 | uint64_t dylibsTrieSize; // size of trie of cached dylib paths 82 | uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures 83 | uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures 84 | uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures 85 | uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures 86 | uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info 87 | uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries 88 | uint64_t dylibsPBLStateArrayAddrUnused; // unused 89 | uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs 90 | uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program 91 | uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program 92 | uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet 93 | uint32_t programTrieSize; 94 | uint32_t osVersion; // OS Version of dylibs in this cache for the main platform 95 | uint32_t altPlatform; // e.g. iOSMac on macOS 96 | uint32_t altOsVersion; // e.g. 14.0 for iOSMac 97 | uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header 98 | uint64_t swiftOptsSize; // size of Swift optimizations header 99 | uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry 100 | uint32_t subCacheArrayCount; // number of subCache entries 101 | uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols 102 | uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data 103 | uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region 104 | uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data 105 | uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region 106 | uint32_t imagesOffset; // file offset to first dyld_cache_image_info 107 | uint32_t imagesCount; // number of dyld_cache_image_info entries 108 | uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2) 109 | uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header 110 | uint64_t objcOptsSize; // size of ObjC optimizations header 111 | uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection 112 | uint64_t cacheAtlasSize; // size of embedded cache atlas 113 | uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header 114 | uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data 115 | }; 116 | 117 | // https://github.com/apple-oss-distributions/dyld/blob/c8a445f88f/cache-builder/dyld_cache_format.h#L142 118 | struct dyld_cache_image_info 119 | { 120 | uint64_t address; 121 | uint64_t modTime; 122 | uint64_t inode; 123 | uint32_t pathFileOffset; 124 | uint32_t pad; 125 | }; 126 | 127 | 128 | @implementation CDUtilities 129 | 130 | + (NSArray *)dyldSharedCacheImagePaths { 131 | // thanks to https://github.com/EthanArbuckle/dlopen_handle_info/blob/054ea7019a/dlopen_handle.m#L84 132 | struct task_dyld_info dyld_info; 133 | mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; 134 | task_info(mach_task_self_, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); 135 | 136 | struct dyld_all_image_infos *dyld_runtime_infos = (struct dyld_all_image_infos *)dyld_info.all_image_info_addr; 137 | const void *const shared_cache_base = (const void *)dyld_runtime_infos->sharedCacheBaseAddress; 138 | 139 | const struct dyld_cache_header *const cache_header = shared_cache_base; 140 | 141 | // I believe this changed with iOS 14 142 | BOOL usesOld = (cache_header->imagesCountOld != 0); 143 | const uint32_t images_offset = usesOld ? cache_header->imagesOffsetOld : cache_header->imagesOffset; 144 | const uint32_t image_count = usesOld ? cache_header->imagesCountOld : cache_header->imagesCount; 145 | 146 | const struct dyld_cache_image_info *const image_info = shared_cache_base + images_offset; 147 | 148 | NSMutableArray *ret = [NSMutableArray arrayWithCapacity:image_count]; 149 | for (uint32_t imageIndex = 0; imageIndex < image_count; imageIndex++) { 150 | const char *dylibPath = shared_cache_base + image_info[imageIndex].pathFileOffset; 151 | [ret addObject:@(dylibPath)]; 152 | } 153 | return ret; 154 | } 155 | 156 | + (NSArray *)classNames { 157 | unsigned int classCount = 0; 158 | Class *const classList = objc_copyClassList(&classCount); 159 | 160 | NSMutableArray *ret = [NSMutableArray arrayWithCapacity:classCount]; 161 | 162 | for (unsigned int classIndex = 0; classIndex < classCount; classIndex++) { 163 | Class __unsafe_unretained const cls = classList[classIndex]; 164 | [ret addObject:NSStringFromClass(cls)]; 165 | } 166 | free(classList); 167 | 168 | return ret; 169 | } 170 | 171 | + (BOOL)isClassSafeToInspect:(NSString *)className { 172 | Class __unsafe_unretained const cls = NSClassFromString(className); 173 | if (cls == NULL) { 174 | return NO; 175 | } 176 | return class_respondsToSelector(cls, @selector(respondsToSelector:)); 177 | } 178 | 179 | @end 180 | -------------------------------------------------------------------------------- /ClassDumpTests/CDObjCTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDObjCTests.m 3 | // ClassDumpTests 4 | // 5 | // Created by Leptos on 1/23/20. 6 | // Copyright © 2020 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CDObjCTests : XCTestCase 13 | 14 | @end 15 | 16 | @protocol CDDemoProtocol 17 | @optional 18 | - (void)happening; 19 | @required 20 | - (void)happenedWithError:(NSError *)error; 21 | @end 22 | 23 | @interface CDDemoClass : NSObject 24 | 25 | @property (class, getter=isFun) BOOL fun; 26 | 27 | @property (nonatomic) NSString *charlie; 28 | 29 | @property (weak, nonatomic) id cacheDelegate; 30 | @property (nonatomic) id progressReportingEnumeration; 31 | @property (nonatomic) NSUUID *mutableUUID; 32 | @property (weak, nonatomic) NSURL *discardableURL; 33 | 34 | - (id)alfa:(int *const *)alfa error:(inout id *)err; 35 | 36 | @end 37 | 38 | @implementation CDDemoClass 39 | 40 | + (BOOL)isFun { 41 | return YES; 42 | } 43 | + (void)setFun:(BOOL)fun { 44 | NSParameterAssert(fun); 45 | } 46 | - (id)alfa:(int *const *)alfa error:(inout id *)err { 47 | return nil; 48 | } 49 | 50 | - (void)happenedWithError:(NSError *)error { 51 | } 52 | 53 | @end 54 | 55 | @implementation CDObjCTests { 56 | CDClassModel *_model; 57 | } 58 | 59 | - (void)setUp { 60 | _model = [CDClassModel modelWithClass:[CDDemoClass class]]; 61 | } 62 | 63 | - (void)testClass { 64 | XCTAssert([_model.name isEqualToString:@"CDDemoClass"]); 65 | XCTAssert(_model.protocols.count == 1); 66 | XCTAssert(_model.classProperties.count == 1); 67 | XCTAssert(_model.classMethods.count == 2); 68 | XCTAssert(_model.ivars.count == 5); 69 | } 70 | 71 | - (void)testProtocol { 72 | CDProtocolModel *protocol = _model.protocols.firstObject; 73 | XCTAssert([protocol.name isEqualToString:@"CDDemoProtocol"]); 74 | 75 | NSArray *requiredInstanceMethods = protocol.requiredInstanceMethods; 76 | NSArray *optionalInstanceMethods = protocol.optionalInstanceMethods; 77 | 78 | XCTAssert(protocol.requiredClassMethods.count == 0); 79 | XCTAssert(requiredInstanceMethods.count == 1); 80 | XCTAssert(protocol.optionalClassMethods.count == 0); 81 | XCTAssert(optionalInstanceMethods.count == 1); 82 | 83 | CDMethodModel *requiredInstanceMethod = requiredInstanceMethods.firstObject; 84 | XCTAssert([requiredInstanceMethod.name isEqualToString:@"happenedWithError:"]); 85 | XCTAssert(requiredInstanceMethod.argumentTypes.count == 1); 86 | XCTAssert([[requiredInstanceMethod.argumentTypes[0] stringForVariableName:nil] isEqualToString:@"id"]); 87 | XCTAssert(!requiredInstanceMethod.isClass); 88 | 89 | CDMethodModel *optionalInstanceMethod = optionalInstanceMethods.firstObject; 90 | XCTAssert([optionalInstanceMethod.name isEqualToString:@"happening"]); 91 | XCTAssert(optionalInstanceMethod.argumentTypes.count == 0); 92 | XCTAssert(!optionalInstanceMethod.isClass); 93 | } 94 | 95 | - (void)testClassProperty { 96 | CDPropertyModel *clsProp = _model.classProperties.firstObject; 97 | XCTAssert([clsProp.name isEqualToString:@"fun"]); 98 | XCTAssert(clsProp.iVar == nil); 99 | XCTAssert([clsProp.getter isEqualToString:@"isFun"]); 100 | XCTAssert([clsProp.setter isEqualToString:@"setFun:"]); 101 | } 102 | 103 | - (void)testInstanceProperty { 104 | BOOL found = NO; 105 | for (CDPropertyModel *prop in _model.instanceProperties) { 106 | if ([prop.name isEqualToString:@"charlie"]) { 107 | XCTAssert(!found, @"Found property multiple times"); 108 | found = YES; 109 | 110 | XCTAssert([prop.iVar isEqualToString:@"_charlie"]); 111 | XCTAssert([prop.getter isEqualToString:@"charlie"]); 112 | XCTAssert([prop.setter isEqualToString:@"setCharlie:"]); 113 | XCTAssert([[prop.type stringForVariableName:prop.name] isEqualToString:@"NSString *charlie"]); 114 | } 115 | } 116 | XCTAssert(found, @"Failed to find property"); 117 | } 118 | 119 | - (void)testPropertyProtocols { 120 | for (CDPropertyModel *prop in _model.instanceProperties) { 121 | if ([prop.name isEqualToString:@"charlie"]) { 122 | XCTAssert([[prop.type stringForVariableName:prop.name] isEqualToString:@"NSString *charlie"]); 123 | } else if ([prop.name isEqualToString:@"cacheDelegate"]) { 124 | XCTAssert([[prop.type stringForVariableName:prop.name] isEqualToString:@"id cacheDelegate"]); 125 | } else if ([prop.name isEqualToString:@"progressReportingEnumeration"]) { 126 | XCTAssert([[prop.type stringForVariableName:prop.name] isEqualToString:@"id progressReportingEnumeration"]); 127 | } else if ([prop.name isEqualToString:@"mutableUUID"]) { 128 | XCTAssert([[prop.type stringForVariableName:prop.name] isEqualToString:@"NSUUID *mutableUUID"]); 129 | } else if ([prop.name isEqualToString:@"discardableURL"]) { 130 | XCTAssert([[prop.type stringForVariableName:prop.name] isEqualToString:@"NSURL *discardableURL"]); 131 | } 132 | } 133 | } 134 | 135 | - (void)testMethod { 136 | BOOL found = NO; 137 | for (CDMethodModel *mthd in _model.instanceMethods) { 138 | if ([mthd.name isEqualToString:@"alfa:error:"]) { 139 | XCTAssert(!found, @"Found method multiple times"); 140 | found = YES; 141 | 142 | XCTAssert(mthd.argumentTypes.count == 2); 143 | XCTAssert([[mthd.argumentTypes[0] stringForVariableName:nil] isEqualToString:@"int **"]); 144 | XCTAssert([[mthd.argumentTypes[1] stringForVariableName:nil] isEqualToString:@"inout id *"]); 145 | XCTAssert([[mthd.returnType stringForVariableName:nil] isEqualToString:@"id"]); 146 | XCTAssert(!mthd.isClass); 147 | } 148 | } 149 | XCTAssert(found, @"Failed to find method"); 150 | } 151 | 152 | - (void)testIvar { 153 | BOOL found = NO; 154 | for (CDIvarModel *ivar in _model.ivars) { 155 | if ([ivar.name isEqualToString:@"_charlie"]) { 156 | XCTAssert(!found, @"Found ivar multiple times"); 157 | found = YES; 158 | 159 | XCTAssert([[ivar.type stringForVariableName:ivar.name] isEqualToString:@"NSString *_charlie"]); 160 | } 161 | } 162 | XCTAssert(found, @"Failed to find ivar"); 163 | } 164 | 165 | @end 166 | -------------------------------------------------------------------------------- /ClassDumpTests/CDParseAdvancedTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDParseAdvancedTests.m 3 | // ClassDumpTests 4 | // 5 | // Created by Leptos on 1/1/20. 6 | // Copyright © 2020 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CDParseAdvancedTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation CDParseAdvancedTests 17 | 18 | - (void)testComplex { 19 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(_Complex float)]; 20 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"_Complex float var"]); 21 | } 22 | 23 | - (void)testAtomic { 24 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(_Atomic int)]; 25 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"_Atomic int var"]); 26 | } 27 | 28 | - (void)testFunction { 29 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(int (*)(char))]; 30 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"void /* function */ *var"]); 31 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"void /* function */ *"]); 32 | } 33 | 34 | - (void)testConstAttribute { 35 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(const char *)]; 36 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"const char *var"]); 37 | } 38 | 39 | - (void)testInlineArray { 40 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(char[8])]; 41 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"char var[8]"]); 42 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"char[8]"]); 43 | } 44 | 45 | - (void)testMutliDemensionalArray { 46 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(int[8][2][4])]; 47 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"int var[8][2][4]"]); 48 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"int[8][2][4]"]); 49 | } 50 | 51 | - (void)testPointerArray { 52 | // this is an array of 4 pointers to long long 53 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(long long *[4])]; 54 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"long long *var[4]"]); 55 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"long long *[4]"]); 56 | } 57 | 58 | - (void)testArrayPointer { 59 | // this is a pointer to array of 2 int elements 60 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(int (*)[2])]; 61 | XCTExpectFailure(@"Multiple levels of pointers/ arrays known to decode in the reverse order"); 62 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"int (*var)[2]"]); 63 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"int (*)[2]"]); 64 | } 65 | 66 | - (void)testPointerArrayPointers { 67 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(int *(*)[2])]; 68 | XCTExpectFailure(@"Multiple levels of pointers/ arrays known to decode in the reverse order"); 69 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"int *(*var)[2]"]); 70 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"int *(*)[2]"]); 71 | } 72 | 73 | - (void)testPointerArrayPointersPointer { 74 | /* pointer to an array of pointers to pointers 75 | * 76 | * int **pp; 77 | * int **ppa[2] = { pp, pp }; 78 | * int **(*ppap)[2] = &ppa; 79 | */ 80 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(int **(*)[2])]; 81 | XCTExpectFailure(@"Multiple levels of pointers/ arrays known to decode in the reverse order"); 82 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"int **(*var)[2]"]); 83 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"int **(*)[2]"]); 84 | } 85 | 86 | - (void)testPointerArrayPointersArray { 87 | /* array of pointers to an array of pointers 88 | * 89 | * int *ip; 90 | * int *ipa[2] = { ip, ip }; 91 | * int *(*ipap)[2] = &ipa; 92 | * int *(*ipapa[4])[2] = { ipap, ipap, ipap, ipap }; 93 | */ 94 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(int *(*[4])[2])]; 95 | XCTExpectFailure(@"Multiple levels of pointers/ arrays known to decode in the reverse order"); 96 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"int *(*var[4])[2]"]); 97 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"int *(*[4])[2]"]); 98 | } 99 | 100 | - (void)testPointerArrayPointersArrayPointer { 101 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(int *(*(*)[4])[2])]; 102 | XCTExpectFailure(@"Multiple levels of pointers/ arrays known to decode in the reverse order"); 103 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"int *(*(*var)[4])[2]"]); 104 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"int *(*(*)[4])[2]"]); 105 | } 106 | 107 | - (void)testArrayPointerArray { 108 | /* array of pointers to an array 109 | * 110 | * int i; 111 | * int ia[2] = { i, i }; 112 | * int (*iap)[2] = &ia; 113 | * int (*iapa[4])[2] = { iap, iap, iap, iap }; 114 | */ 115 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(int (*[4])[2])]; 116 | XCTExpectFailure(@"Multiple levels of pointers/ arrays known to decode in the reverse order"); 117 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"int (*var[4])[2]"]); 118 | XCTAssert([[type stringForVariableName:nil] isEqualToString:@"int (*[4])[2]"]); 119 | } 120 | 121 | - (void)testMutliDemensionalArrayStruct { 122 | struct TestStruct { 123 | int a[1]; 124 | float b[2][3]; 125 | long long *c[4]; 126 | }; 127 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(struct TestStruct [5])]; 128 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"struct TestStruct { " 129 | "int x0[1]; " 130 | "float x1[2][3]; " 131 | "long long *x2[4]; " 132 | "} var[5]"]); 133 | } 134 | 135 | - (void)testStruct { 136 | struct TestStruct { 137 | int a; 138 | float b; 139 | long long c; 140 | }; 141 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(struct TestStruct)]; 142 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"struct TestStruct { " 143 | "int x0; float x1; long long x2; " 144 | "} var"]); 145 | } 146 | 147 | - (void)testUnion { 148 | union TestUnion { 149 | int fixed; 150 | float floating; 151 | }; 152 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(union TestUnion)]; 153 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"union TestUnion { " 154 | "int x0; float x1; " 155 | "} var"]); 156 | } 157 | 158 | - (void)testNestedStructsUnions { 159 | union TestUnion { 160 | char bytes[16]; 161 | struct demo_sockaddr_in { 162 | unsigned char sin_len; 163 | unsigned char sin_family; 164 | unsigned short sin_port; 165 | union { 166 | char bytes[4]; 167 | int addr; 168 | } sin_addr; 169 | char sin_zero[8]; 170 | } socket_addr; 171 | int words[4]; 172 | }; 173 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(union TestUnion)]; 174 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"union TestUnion { " 175 | "char x0[16]; " 176 | "struct demo_sockaddr_in { " 177 | "unsigned char x0; " 178 | "unsigned char x1; " 179 | "unsigned short x2; " 180 | "union { char x0[4]; int x1; } x3; " 181 | "char x4[8]; " 182 | "} x1; " 183 | "int x2[4]; " 184 | "} var"]); 185 | } 186 | 187 | - (void)testBitfields { 188 | struct BitfieldTest { 189 | unsigned a : 18; 190 | unsigned b : 2; 191 | unsigned c : 30; 192 | unsigned long d : 34; 193 | unsigned e : 1; 194 | unsigned __int128 f : 100; 195 | unsigned g : 10; 196 | unsigned h : 15; 197 | }; 198 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(struct BitfieldTest)]; 199 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"struct BitfieldTest { " 200 | "unsigned int x0 : 18; unsigned char x1 : 2; " 201 | "unsigned int x2 : 30; unsigned long x3 : 34; " 202 | "unsigned char x4 : 1; unsigned __int128 x5 : 100; " 203 | "unsigned short x6 : 10; unsigned short x7 : 15; " 204 | "} var"]); 205 | } 206 | 207 | - (void)testModifiedFields { 208 | struct ModifiersTest { 209 | _Atomic BOOL a; 210 | _Complex float b; 211 | _Atomic _Complex int c; 212 | }; 213 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(struct ModifiersTest)]; 214 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"struct ModifiersTest { " 215 | "_Atomic BOOL x0; _Complex float x1; _Atomic _Complex int x2; " 216 | "} var"]); 217 | } 218 | 219 | - (void)testPointers { 220 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(void **)]; 221 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"void **var"]); 222 | 223 | type = [CDTypeParser typeForEncoding:@encode(int *)]; 224 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"int *var"]); 225 | 226 | type = [CDTypeParser typeForEncoding:@encode(char **)]; 227 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"char **var"]); 228 | } 229 | 230 | @end 231 | -------------------------------------------------------------------------------- /ClassDumpTests/CDParseCppTests.mm: -------------------------------------------------------------------------------- 1 | // 2 | // CDParseCppTests.mm 3 | // ClassDumpTests 4 | // 5 | // Created by Leptos on 1/1/20. 6 | // Copyright © 2020 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "../ClassDump/ClassDump.h" 11 | #import "../ClassDump/Services/CDTypeParser.h" 12 | 13 | @interface CDParseCppTests : XCTestCase 14 | 15 | @end 16 | 17 | @implementation CDParseCppTests 18 | 19 | class User { 20 | char *name; 21 | unsigned level; 22 | }; 23 | class SuperUser : User { 24 | unsigned permissions; 25 | }; 26 | 27 | template 28 | class BinaryArray { 29 | _T inlineArray[2]; 30 | }; 31 | 32 | namespace ClassDump { 33 | class Chocolate { 34 | float cocoaPercent; 35 | }; 36 | } 37 | 38 | - (void)testClass { 39 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(SuperUser)]; 40 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"struct SuperUser { " 41 | "char *x0; unsigned int x1; unsigned int x2; " 42 | "} var"]); 43 | } 44 | 45 | - (void)testGenerics { 46 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(BinaryArray)]; 47 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"struct BinaryArray { " 48 | "struct User { char *x0; unsigned int x1; } x0[2]; " 49 | "} var"]); 50 | } 51 | 52 | - (void)testNamespace { 53 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(ClassDump::Chocolate)]; 54 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"struct Chocolate { float x0; } var"]); 55 | 56 | type = [CDTypeParser typeForEncoding:@encode(BinaryArray)]; 57 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"struct BinaryArray { " 58 | "struct Chocolate { float x0; } x0[2]; " 59 | "} var"]); 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /ClassDumpTests/CDParsePrimitiveTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDParsePrimitiveTests.m 3 | // ClassDumpTests 4 | // 5 | // Created by Leptos on 12/21/19. 6 | // Copyright © 2019 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CDParsePrimitiveTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation CDParsePrimitiveTests 17 | 18 | - (void)testVoid { 19 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(void)]; 20 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"void var"]); 21 | } 22 | 23 | - (void)testChar { 24 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(char)]; 25 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"char var"]); 26 | type = [CDTypeParser typeForEncoding:@encode(unsigned char)]; 27 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"unsigned char var"]); 28 | } 29 | 30 | - (void)testShort { 31 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(short)]; 32 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"short var"]); 33 | type = [CDTypeParser typeForEncoding:@encode(unsigned short)]; 34 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"unsigned short var"]); 35 | } 36 | 37 | - (void)testInt { 38 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(int)]; 39 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"int var"]); 40 | type = [CDTypeParser typeForEncoding:@encode(unsigned int)]; 41 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"unsigned int var"]); 42 | } 43 | 44 | #if __SIZEOF_LONG__ != __SIZEOF_LONG_LONG__ 45 | - (void)testLong { 46 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(long)]; 47 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"long var"]); 48 | 49 | type = [CDTypeParser typeForEncoding:@encode(unsigned long)]; 50 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"unsigned long var"]); 51 | } 52 | #endif 53 | 54 | - (void)testLongLong { 55 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(long long)]; 56 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"long long var"]); 57 | type = [CDTypeParser typeForEncoding:@encode(unsigned long long)]; 58 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"unsigned long long var"]); 59 | } 60 | 61 | #ifdef __SIZEOF_INT128__ 62 | - (void)testInt128 { 63 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(__int128)]; 64 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"__int128 var"]); 65 | type = [CDTypeParser typeForEncoding:@encode(unsigned __int128)]; 66 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"unsigned __int128 var"]); 67 | } 68 | #endif 69 | 70 | - (void)testFloating { 71 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(float)]; 72 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"float var"]); 73 | type = [CDTypeParser typeForEncoding:@encode(double)]; 74 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"double var"]); 75 | 76 | #ifdef __SIZEOF_LONG_DOUBLE__ 77 | type = [CDTypeParser typeForEncoding:@encode(long double)]; 78 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"long double var"]); 79 | #endif 80 | } 81 | 82 | - (void)testObjcTypes { 83 | CDParseType *type = [CDTypeParser typeForEncoding:@encode(Class)]; 84 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"Class var"]); 85 | type = [CDTypeParser typeForEncoding:@encode(SEL)]; 86 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"SEL var"]); 87 | #if __OBJC_BOOL_IS_BOOL 88 | type = [CDTypeParser typeForEncoding:@encode(BOOL)]; 89 | XCTAssert([[type stringForVariableName:@"var"] isEqualToString:@"BOOL var"]); 90 | #endif 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /ClassDumpTests/CDProtocolTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDProtocolTest.m 3 | // ClassDumpTests 4 | // 5 | // Created by Leptos on 3/3/24. 6 | // Copyright © 2024 Leptos. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CDProtocolTest : XCTestCase 13 | 14 | @end 15 | 16 | @protocol CDRandomProvider 17 | - (id)randomValue; 18 | @end 19 | 20 | @protocol CDRollProvider 21 | - (int)randomValue; 22 | @end 23 | 24 | @protocol CDRandomProbability 25 | - (float)randomValue; 26 | @end 27 | 28 | // this class only exists so that we can see how 29 | // the compiler handles these protocol conformances 30 | @interface CDRollProbability : NSObject 31 | @end 32 | 33 | @implementation CDRollProbability 34 | - (int)randomValue { 35 | return 0; 36 | } 37 | @end 38 | 39 | // this class only exists so that we can see how 40 | // the compiler handles these protocol conformances 41 | @interface CDRandomRoll : NSObject 42 | @end 43 | 44 | @implementation CDRandomRoll 45 | - (float)randomValue { 46 | return 0; 47 | } 48 | @end 49 | 50 | 51 | @implementation CDProtocolTest 52 | 53 | - (void)testRollProbability { 54 | CDClassModel *classModel = [CDClassModel modelWithClass:[CDRollProbability class]]; 55 | NSArray *requiredMethods = [CDProtocolModel requiredInstanceMethodsToConform:classModel.protocols]; 56 | XCTAssert(requiredMethods.count > 1); 57 | CDMethodModel *requiredMethod = requiredMethods[0]; 58 | XCTAssert([requiredMethod.name isEqualToString:@"randomValue"]); 59 | 60 | CDParseType *returnType = requiredMethod.returnType; 61 | XCTAssert([returnType isMemberOfClass:[CDPrimitiveType class]]); 62 | CDPrimitiveType *primitiveReturnType = (__kindof CDParseType *)returnType; 63 | XCTAssert(primitiveReturnType.rawType == CDPrimitiveRawTypeInt); 64 | } 65 | 66 | - (void)testRandomRoll { 67 | CDClassModel *classModel = [CDClassModel modelWithClass:[CDRandomRoll class]]; 68 | NSArray *requiredMethods = [CDProtocolModel requiredInstanceMethodsToConform:classModel.protocols]; 69 | XCTAssert(requiredMethods.count > 1); 70 | CDMethodModel *requiredMethod = requiredMethods[0]; 71 | XCTAssert([requiredMethod.name isEqualToString:@"randomValue"]); 72 | 73 | CDParseType *returnType = requiredMethod.returnType; 74 | XCTAssert([returnType isMemberOfClass:[CDPrimitiveType class]]); 75 | CDPrimitiveType *primitiveReturnType = (__kindof CDParseType *)returnType; 76 | XCTAssert(primitiveReturnType.rawType == CDPrimitiveRawTypeFloat); 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /ClassDumpTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2019 Leptos 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include $(THEOS)/makefiles/common.mk 2 | 3 | LIBRARY_NAME = ClassDumpRuntime 4 | ClassDumpRuntime_CFLAGS = -fobjc-arc -I Sources/ClassDumpRuntime/include 5 | ClassDumpRuntime_FILES = $(wildcard ClassDump/*/*.m) $(wildcard ClassDump/*/*/*.m) 6 | 7 | include $(THEOS_MAKE_PATH)/library.mk 8 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "ClassDumpRuntime", 8 | platforms: [ 9 | .iOS(.v12), 10 | .macOS(.v10_13), 11 | .watchOS(.v4), 12 | .tvOS(.v12), 13 | .macCatalyst(.v13), 14 | .visionOS(.v1), 15 | ], 16 | products: [ 17 | .library( 18 | name: "ClassDumpRuntime", 19 | targets: ["ClassDumpRuntime"] 20 | ), 21 | ], 22 | targets: [ 23 | .target( 24 | name: "ClassDumpRuntime" 25 | ), 26 | .testTarget( 27 | name: "ClassDumpRuntimeTests", 28 | dependencies: ["ClassDumpRuntime"], 29 | path: "ClassDumpTests" 30 | ), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ClassDumpRuntime 2 | 3 | Create human readable interfaces from runtime Objective-C environments. 4 | 5 | This library is intended to be compiled with Xcode or [Theos](https://github.com/theos/theos). 6 | The library can be loaded into an Objective-C runtime to view class or protocol headers. 7 | 8 | Example: 9 | 10 | ```objc 11 | CDClassModel *ortho = [CDClassModel modelWithClass:[NSOrthography class]]; 12 | 13 | [ortho linesWithComments:NO synthesizeStrip:YES]; 14 | /* 15 | @interface NSOrthography : NSObject 16 | 17 | @property (class, readonly) BOOL supportsSecureCoding; 18 | 19 | @property (readonly, copy) NSString *dominantScript; 20 | @property (readonly, copy) NSDictionary *languageMap; 21 | 22 | + (id)orthographyWithDominantScript:(id)a0 languageMap:(id)a1; 23 | + (id)_scriptNameForScriptIndex:(unsigned long long)a0; 24 | + (void)initialize; 25 | + (id)allocWithZone:(struct _NSZone { } *)a0; 26 | 27 | - (id)initWithDominantScript:(id)a0 languageMap:(id)a1; 28 | - (unsigned int)orthographyFlags; 29 | - (id)dominantLanguage; 30 | - (id)allScripts; 31 | - (id)languagesForScript:(id)a0; 32 | - (id)replacementObjectForPortCoder:(id)a0; 33 | - (id)dominantLanguageForScript:(id)a0; 34 | - (id)allLanguages; 35 | - (id)initWithCoder:(id)a0; 36 | - (void)encodeWithCoder:(id)a0; 37 | - (BOOL)isEqual:(id)a0; 38 | - (unsigned long long)hash; 39 | - (id)description; 40 | - (id)copyWithZone:(struct _NSZone { } *)a0; 41 | - (Class)classForCoder; 42 | 43 | @end 44 | */ 45 | 46 | [ortho linesWithComments:NO synthesizeStrip:NO]; 47 | /* 48 | @interface NSOrthography : NSObject 49 | 50 | @property (class, readonly) BOOL supportsSecureCoding; 51 | 52 | @property (readonly, copy) NSString *dominantScript; 53 | @property (readonly, copy) NSDictionary *languageMap; 54 | 55 | + (id)orthographyWithDominantScript:(id)a0 languageMap:(id)a1; 56 | + (id)_scriptNameForScriptIndex:(unsigned long long)a0; 57 | + (void)initialize; 58 | + (id)allocWithZone:(struct _NSZone { } *)a0; 59 | + (BOOL)supportsSecureCoding; 60 | 61 | - (id)initWithDominantScript:(id)a0 languageMap:(id)a1; 62 | - (unsigned int)orthographyFlags; 63 | - (id)dominantScript; 64 | - (id)languageMap; 65 | - (id)dominantLanguage; 66 | - (id)allScripts; 67 | - (id)languagesForScript:(id)a0; 68 | - (id)replacementObjectForPortCoder:(id)a0; 69 | - (id)dominantLanguageForScript:(id)a0; 70 | - (id)allLanguages; 71 | - (id)initWithCoder:(id)a0; 72 | - (void)encodeWithCoder:(id)a0; 73 | - (BOOL)isEqual:(id)a0; 74 | - (unsigned long long)hash; 75 | - (id)description; 76 | - (id)copyWithZone:(struct _NSZone { } *)a0; 77 | - (Class)classForCoder; 78 | 79 | @end 80 | */ 81 | 82 | [ortho.protocols[1] linesWithComments:NO synthesizeStrip:YES]; 83 | /* 84 | @protocol NSSecureCoding 85 | 86 | + (BOOL)supportsSecureCoding; 87 | 88 | @end 89 | */ 90 | ``` 91 | -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/ClassDump: -------------------------------------------------------------------------------- 1 | ../../ClassDump -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDArrayType.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/ParseTypes/CDArrayType.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDBitFieldType.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/ParseTypes/CDBitFieldType.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDBlockType.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/ParseTypes/CDBlockType.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDClassModel.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/Reflections/CDClassModel.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDGenerationOptions.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/CDGenerationOptions.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDIvarModel.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/Reflections/CDIvarModel.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDMethodModel.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/Reflections/CDMethodModel.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDObjectType.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/ParseTypes/CDObjectType.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDParseType.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/ParseTypes/CDParseType.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDPointerType.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/ParseTypes/CDPointerType.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDPrimitiveType.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/ParseTypes/CDPrimitiveType.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDPropertyAttribute.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/Reflections/CDPropertyAttribute.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDPropertyModel.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/Reflections/CDPropertyModel.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDProtocolModel+Conformance.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/Reflections/CDProtocolModel+Conformance.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDProtocolModel.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/Reflections/CDProtocolModel.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDRecordType.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/ParseTypes/CDRecordType.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDSemanticString.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/CDSemanticString.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDTypeParser.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Services/CDTypeParser.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDUtilities.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Services/CDUtilities.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/CDVariableModel.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/Models/CDVariableModel.h -------------------------------------------------------------------------------- /Sources/ClassDumpRuntime/include/ClassDump/ClassDump.h: -------------------------------------------------------------------------------- 1 | ../../../ClassDumpRuntime/ClassDump/ClassDump.h -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: null.leptos.classdumpruntime 2 | Name: ClassDumpRuntime 3 | Version: 0.0.1 4 | Architecture: iphoneos-arm 5 | Description: ClassDump utility intended to be used with Cycript 6 | Maintainer: Leptos 7 | Author: Leptos 8 | Section: System 9 | Tag: role::developer 10 | --------------------------------------------------------------------------------