mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 20:34:04 -04:00 
			
		
		
		
	added files via upload
This commit is contained in:
		
							
								
								
									
										108
									
								
								Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| // | ||||
| //  FLEXRuntimeUtility.h | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/8/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeConstants.h" | ||||
|  | ||||
| #define PropertyKey(suffix) kFLEXPropertyAttributeKey##suffix : @"" | ||||
| #define PropertyKeyGetter(getter) kFLEXPropertyAttributeKeyCustomGetter : NSStringFromSelector(@selector(getter)) | ||||
| #define PropertyKeySetter(setter) kFLEXPropertyAttributeKeyCustomSetter : NSStringFromSelector(@selector(setter)) | ||||
|  | ||||
| /// Takes: min iOS version, property name, target class, property type, and a list of attributes | ||||
| #define FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, type, ...) ({ \ | ||||
|     if (@available(iOS iOS_atLeast, *)) { \ | ||||
|         NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithDictionary:@{ \ | ||||
|             kFLEXPropertyAttributeKeyTypeEncoding : @(type), \ | ||||
|             __VA_ARGS__ \ | ||||
|         }]; \ | ||||
|         [FLEXRuntimeUtility \ | ||||
|             tryAddPropertyWithName:#name \ | ||||
|             attributes:attrs \ | ||||
|             toClass:cls \ | ||||
|         ]; \ | ||||
|     } \ | ||||
| }) | ||||
|  | ||||
| /// Takes: min iOS version, property name, target class, property type, and a list of attributes | ||||
| #define FLEXRuntimeUtilityTryAddNonatomicProperty(iOS_atLeast, name, cls, type, ...) \ | ||||
|     FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, @encode(type), PropertyKey(NonAtomic), __VA_ARGS__); | ||||
| /// Takes: min iOS version, property name, target class, property type (class name), and a list of attributes | ||||
| #define FLEXRuntimeUtilityTryAddObjectProperty(iOS_atLeast, name, cls, type, ...) \ | ||||
|     FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, FLEXEncodeClass(type), PropertyKey(NonAtomic), __VA_ARGS__); | ||||
|  | ||||
| extern NSString * const FLEXRuntimeUtilityErrorDomain; | ||||
|  | ||||
| typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) { | ||||
|     // Start at a random value instead of 0 to avoid confusion with an absent code | ||||
|     FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0xbabe, | ||||
|     FLEXRuntimeUtilityErrorCodeInvocationFailed, | ||||
|     FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch | ||||
| }; | ||||
|  | ||||
| @interface FLEXRuntimeUtility : NSObject | ||||
|  | ||||
| // General Helpers | ||||
| + (BOOL)pointerIsValidObjcObject:(const void *)pointer; | ||||
| /// Unwraps raw pointers to objects stored in NSValue, and re-boxes C strings into NSStrings. | ||||
| + (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType; | ||||
| /// Some fields have a name in their encoded string (e.g. \"width\"d) | ||||
| /// @return the offset to skip the field name, 0 if there is no name | ||||
| + (NSUInteger)fieldNameOffsetForTypeEncoding:(const FLEXTypeEncoding *)typeEncoding; | ||||
| /// Given name "foo" and type "int" this would return "int foo", but | ||||
| /// given name "foo" and type "T *" it would return "T *foo" | ||||
| + (NSString *)appendName:(NSString *)name toType:(NSString *)typeEncoding; | ||||
|  | ||||
| /// @return The class hierarchy for the given object or class, | ||||
| /// from the current class to the root-most class. | ||||
| + (NSArray<Class> *)classHierarchyOfObject:(id)objectOrClass; | ||||
|  | ||||
| /// Used to describe an object in brief within an explorer row | ||||
| + (NSString *)summaryForObject:(id)value; | ||||
| + (NSString *)safeClassNameForObject:(id)object; | ||||
| + (NSString *)safeDescriptionForObject:(id)object; | ||||
| + (NSString *)safeDebugDescriptionForObject:(id)object; | ||||
|  | ||||
| + (BOOL)safeObject:(id)object isKindOfClass:(Class)cls; | ||||
| + (BOOL)safeObject:(id)object respondsToSelector:(SEL)sel; | ||||
|  | ||||
| // Property Helpers | ||||
| + (BOOL)tryAddPropertyWithName:(const char *)name | ||||
|                     attributes:(NSDictionary<NSString *, NSString *> *)attributePairs | ||||
|                        toClass:(__unsafe_unretained Class)theClass; | ||||
| + (NSArray<NSString *> *)allPropertyAttributeKeys; | ||||
|  | ||||
| // Method Helpers | ||||
| + (NSArray *)prettyArgumentComponentsForMethod:(Method)method; | ||||
|  | ||||
| // Method Calling/Field Editing | ||||
| + (id)performSelector:(SEL)selector onObject:(id)object; | ||||
| + (id)performSelector:(SEL)selector | ||||
|              onObject:(id)object | ||||
|         withArguments:(NSArray *)arguments | ||||
|                 error:(NSError * __autoreleasing *)error; | ||||
| + (id)performSelector:(SEL)selector | ||||
|              onObject:(id)object | ||||
|         withArguments:(NSArray *)arguments | ||||
|       allowForwarding:(BOOL)mightForwardMsgSend | ||||
|                 error:(NSError * __autoreleasing *)error; | ||||
|  | ||||
| + (NSString *)editableJSONStringForObject:(id)object; | ||||
| + (id)objectValueFromEditableJSONString:(NSString *)string; | ||||
| + (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString; | ||||
| + (void)enumerateTypesInStructEncoding:(const char *)structEncoding | ||||
|                             usingBlock:(void (^)(NSString *structName, | ||||
|                                                  const char *fieldTypeEncoding, | ||||
|                                                  NSString *prettyTypeEncoding, | ||||
|                                                  NSUInteger fieldIndex, | ||||
|                                                  NSUInteger fieldOffset))typeBlock; | ||||
| + (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type; | ||||
|  | ||||
| #pragma mark - Metadata Helpers | ||||
|  | ||||
| + (NSString *)readableTypeForEncoding:(NSString *)encodingString; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										881
									
								
								Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										881
									
								
								Tweaks/FLEX/Utility/Runtime/FLEXRuntimeUtility.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,881 @@ | ||||
| // | ||||
| //  FLEXRuntimeUtility.m | ||||
| //  Flipboard | ||||
| // | ||||
| //  Created by Ryan Olson on 6/8/14. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <UIKit/UIKit.h> | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "FLEXObjcInternal.h" | ||||
| #import "FLEXTypeEncodingParser.h" | ||||
| #import "FLEXMethod.h" | ||||
|  | ||||
| NSString * const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain"; | ||||
|  | ||||
| @implementation FLEXRuntimeUtility | ||||
|  | ||||
| #pragma mark - General Helpers (Public) | ||||
|  | ||||
| + (BOOL)pointerIsValidObjcObject:(const void *)pointer { | ||||
|     return FLEXPointerIsValidObjcObject(pointer); | ||||
| } | ||||
|  | ||||
| + (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType { | ||||
|     if (!returnedObjectOrNil) { | ||||
|         return nil; | ||||
|     } | ||||
|  | ||||
|     NSInteger i = 0; | ||||
|     if (returnType[i] == FLEXTypeEncodingConst) { | ||||
|         i++; | ||||
|     } | ||||
|  | ||||
|     BOOL returnsObjectOrClass = returnType[i] == FLEXTypeEncodingObjcObject || | ||||
|                                 returnType[i] == FLEXTypeEncodingObjcClass; | ||||
|     BOOL returnsVoidPointer   = returnType[i] == FLEXTypeEncodingPointer && | ||||
|                                 returnType[i+1] == FLEXTypeEncodingVoid; | ||||
|     BOOL returnsCString       = returnType[i] == FLEXTypeEncodingCString; | ||||
|  | ||||
|     // If we got back an NSValue and the return type is not an object, | ||||
|     // we check to see if the pointer is of a valid object. If not, | ||||
|     // we just display the NSValue. | ||||
|     if (!returnsObjectOrClass) { | ||||
|         // Skip NSNumber instances | ||||
|         if ([returnedObjectOrNil isKindOfClass:[NSNumber class]]) { | ||||
|             return returnedObjectOrNil; | ||||
|         } | ||||
|          | ||||
|         // Can only be NSValue since return type is not an object, | ||||
|         // so we bail if this doesn't add up | ||||
|         if (![returnedObjectOrNil isKindOfClass:[NSValue class]]) { | ||||
|             return returnedObjectOrNil; | ||||
|         } | ||||
|  | ||||
|         NSValue *value = (NSValue *)returnedObjectOrNil; | ||||
|  | ||||
|         if (returnsCString) { | ||||
|             // Wrap char * in NSString | ||||
|             const char *string = (const char *)value.pointerValue; | ||||
|             returnedObjectOrNil = string ? [NSString stringWithCString:string encoding:NSUTF8StringEncoding] : NULL; | ||||
|         } else if (returnsVoidPointer) { | ||||
|             // Cast valid objects disguised as void * to id | ||||
|             if ([FLEXRuntimeUtility pointerIsValidObjcObject:value.pointerValue]) { | ||||
|                 returnedObjectOrNil = (__bridge id)value.pointerValue; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return returnedObjectOrNil; | ||||
| } | ||||
|  | ||||
| + (NSUInteger)fieldNameOffsetForTypeEncoding:(const FLEXTypeEncoding *)typeEncoding { | ||||
|     NSUInteger beginIndex = 0; | ||||
|     while (typeEncoding[beginIndex] == FLEXTypeEncodingQuote) { | ||||
|         NSUInteger endIndex = beginIndex + 1; | ||||
|         while (typeEncoding[endIndex] != FLEXTypeEncodingQuote) { | ||||
|             ++endIndex; | ||||
|         } | ||||
|         beginIndex = endIndex + 1; | ||||
|     } | ||||
|     return beginIndex; | ||||
| } | ||||
|  | ||||
| + (NSArray<Class> *)classHierarchyOfObject:(id)objectOrClass { | ||||
|     NSMutableArray<Class> *superClasses = [NSMutableArray new]; | ||||
|     id cls = [objectOrClass class]; | ||||
|     do { | ||||
|         [superClasses addObject:cls]; | ||||
|     } while ((cls = [cls superclass])); | ||||
|  | ||||
|     return superClasses; | ||||
| } | ||||
|  | ||||
| + (NSString *)safeClassNameForObject:(id)object { | ||||
|     // Don't assume that we have an NSObject subclass | ||||
|     if ([self safeObject:object respondsToSelector:@selector(class)]) { | ||||
|         return NSStringFromClass([object class]); | ||||
|     } | ||||
|  | ||||
|     return NSStringFromClass(object_getClass(object)); | ||||
| } | ||||
|  | ||||
| /// Could be nil | ||||
| + (NSString *)safeDescriptionForObject:(id)object { | ||||
|     // Don't assume that we have an NSObject subclass; not all objects respond to -description | ||||
|     if ([self safeObject:object respondsToSelector:@selector(description)]) { | ||||
|         @try { | ||||
|             return [object description]; | ||||
|         } @catch (NSException *exception) { | ||||
|             return nil; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| /// Never nil | ||||
| + (NSString *)safeDebugDescriptionForObject:(id)object { | ||||
|     NSString *description = nil; | ||||
|  | ||||
|     if ([self safeObject:object respondsToSelector:@selector(debugDescription)]) { | ||||
|         @try { | ||||
|             description = [object debugDescription]; | ||||
|         } @catch (NSException *exception) { } | ||||
|     } else { | ||||
|         description = [self safeDescriptionForObject:object]; | ||||
|     } | ||||
|  | ||||
|     if (!description.length) { | ||||
|         NSString *cls = NSStringFromClass(object_getClass(object)); | ||||
|         if (object_isClass(object)) { | ||||
|             description = [cls stringByAppendingString:@" class (no description)"]; | ||||
|         } else { | ||||
|             description = [cls stringByAppendingString:@" instance (no description)"]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return description; | ||||
| } | ||||
|  | ||||
| + (NSString *)summaryForObject:(id)value { | ||||
|     NSString *description = nil; | ||||
|  | ||||
|     // Special case BOOL for better readability. | ||||
|     if ([self safeObject:value isKindOfClass:[NSValue class]]) { | ||||
|         const char *type = [value objCType]; | ||||
|         if (strcmp(type, @encode(BOOL)) == 0) { | ||||
|             BOOL boolValue = NO; | ||||
|             [value getValue:&boolValue]; | ||||
|             return boolValue ? @"YES" : @"NO"; | ||||
|         } else if (strcmp(type, @encode(SEL)) == 0) { | ||||
|             SEL selector = NULL; | ||||
|             [value getValue:&selector]; | ||||
|             return NSStringFromSelector(selector); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @try { | ||||
|         // Single line display - replace newlines and tabs with spaces. | ||||
|         description = [[self safeDescriptionForObject:value] stringByReplacingOccurrencesOfString:@"\n" withString:@" "]; | ||||
|         description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "]; | ||||
|     } @catch (NSException *e) { | ||||
|         description = [@"Thrown: " stringByAppendingString:e.reason ?: @"(nil exception reason)"]; | ||||
|     } | ||||
|  | ||||
|     if (!description) { | ||||
|         description = @"nil"; | ||||
|     } | ||||
|  | ||||
|     return description; | ||||
| } | ||||
|  | ||||
| + (BOOL)safeObject:(id)object isKindOfClass:(Class)cls { | ||||
|     static BOOL (*isKindOfClass)(id, SEL, Class) = nil; | ||||
|     static BOOL (*isKindOfClass_meta)(id, SEL, Class) = nil; | ||||
|     static dispatch_once_t onceToken; | ||||
|     dispatch_once(&onceToken, ^{ | ||||
|         isKindOfClass = (BOOL(*)(id, SEL, Class))[NSObject instanceMethodForSelector:@selector(isKindOfClass:)]; | ||||
|         isKindOfClass_meta = (BOOL(*)(id, SEL, Class))[NSObject methodForSelector:@selector(isKindOfClass:)]; | ||||
|     }); | ||||
|      | ||||
|     BOOL isClass = object_isClass(object); | ||||
|     return (isClass ? isKindOfClass_meta : isKindOfClass)(object, @selector(isKindOfClass:), cls); | ||||
| } | ||||
|  | ||||
| + (BOOL)safeObject:(id)object respondsToSelector:(SEL)sel { | ||||
|     // If we're given a class, we want to know if classes respond to this selector. | ||||
|     // Similarly, if we're given an instance, we want to know if instances respond.  | ||||
|     BOOL isClass = object_isClass(object); | ||||
|     Class cls = isClass ? object : object_getClass(object); | ||||
|     // BOOL isMetaclass = class_isMetaClass(cls); | ||||
|      | ||||
|     if (isClass) { | ||||
|         // In theory, this should also work for metaclasses... | ||||
|         return class_getClassMethod(cls, sel) != nil; | ||||
|     } else { | ||||
|         return class_getInstanceMethod(cls, sel) != nil; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Property Helpers (Public) | ||||
|  | ||||
| + (BOOL)tryAddPropertyWithName:(const char *)name | ||||
|                     attributes:(NSDictionary<NSString *, NSString *> *)attributePairs | ||||
|                        toClass:(__unsafe_unretained Class)theClass { | ||||
|     objc_property_t property = class_getProperty(theClass, name); | ||||
|     if (!property) { | ||||
|         unsigned int totalAttributesCount = (unsigned int)attributePairs.count; | ||||
|         objc_property_attribute_t *attributes = malloc(sizeof(objc_property_attribute_t) * totalAttributesCount); | ||||
|         if (attributes) { | ||||
|             unsigned int attributeIndex = 0; | ||||
|             for (NSString *attributeName in attributePairs.allKeys) { | ||||
|                 objc_property_attribute_t attribute; | ||||
|                 attribute.name = attributeName.UTF8String; | ||||
|                 attribute.value = attributePairs[attributeName].UTF8String; | ||||
|                 attributes[attributeIndex++] = attribute; | ||||
|             } | ||||
|  | ||||
|             BOOL success = class_addProperty(theClass, name, attributes, totalAttributesCount); | ||||
|             free(attributes); | ||||
|             return success; | ||||
|         } else { | ||||
|             return NO; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| + (NSArray<NSString *> *)allPropertyAttributeKeys { | ||||
|     static NSArray<NSString *> *allPropertyAttributeKeys = nil; | ||||
|     static dispatch_once_t onceToken; | ||||
|     dispatch_once(&onceToken, ^{ | ||||
|         allPropertyAttributeKeys = @[ | ||||
|             kFLEXPropertyAttributeKeyTypeEncoding, | ||||
|             kFLEXPropertyAttributeKeyBackingIvarName, | ||||
|             kFLEXPropertyAttributeKeyReadOnly, | ||||
|             kFLEXPropertyAttributeKeyCopy, | ||||
|             kFLEXPropertyAttributeKeyRetain, | ||||
|             kFLEXPropertyAttributeKeyNonAtomic, | ||||
|             kFLEXPropertyAttributeKeyCustomGetter, | ||||
|             kFLEXPropertyAttributeKeyCustomSetter, | ||||
|             kFLEXPropertyAttributeKeyDynamic, | ||||
|             kFLEXPropertyAttributeKeyWeak, | ||||
|             kFLEXPropertyAttributeKeyGarbageCollectable, | ||||
|             kFLEXPropertyAttributeKeyOldStyleTypeEncoding, | ||||
|         ]; | ||||
|     }); | ||||
|  | ||||
|     return allPropertyAttributeKeys; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Method Helpers (Public) | ||||
|  | ||||
| + (NSArray<NSString *> *)prettyArgumentComponentsForMethod:(Method)method { | ||||
|     NSMutableArray<NSString *> *components = [NSMutableArray new]; | ||||
|  | ||||
|     NSString *selectorName = NSStringFromSelector(method_getName(method)); | ||||
|     NSMutableArray<NSString *> *selectorComponents = [selectorName componentsSeparatedByString:@":"].mutableCopy; | ||||
|  | ||||
|     // this is a workaround cause method_getNumberOfArguments() returns wrong number for some methods | ||||
|     if (selectorComponents.count == 1) { | ||||
|         return @[]; | ||||
|     } | ||||
|  | ||||
|     if ([selectorComponents.lastObject isEqualToString:@""]) { | ||||
|         [selectorComponents removeLastObject]; | ||||
|     } | ||||
|  | ||||
|     for (unsigned int argIndex = 0; argIndex < selectorComponents.count; argIndex++) { | ||||
|         char *argType = method_copyArgumentType(method, argIndex + kFLEXNumberOfImplicitArgs); | ||||
|         NSString *readableArgType = (argType != NULL) ? [self readableTypeForEncoding:@(argType)] : nil; | ||||
|         free(argType); | ||||
|         NSString *prettyComponent = [NSString | ||||
|             stringWithFormat:@"%@:(%@) ", | ||||
|             selectorComponents[argIndex], | ||||
|             readableArgType | ||||
|         ]; | ||||
|         [components addObject:prettyComponent]; | ||||
|     } | ||||
|  | ||||
|     return components; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Method Calling/Field Editing (Public) | ||||
|  | ||||
| + (id)performSelector:(SEL)selector onObject:(id)object { | ||||
|     return [self performSelector:selector onObject:object withArguments:@[] error:nil]; | ||||
| } | ||||
|  | ||||
| + (id)performSelector:(SEL)selector | ||||
|              onObject:(id)object | ||||
|         withArguments:(NSArray *)arguments | ||||
|                 error:(NSError * __autoreleasing *)error { | ||||
|     return [self performSelector:selector | ||||
|         onObject:object | ||||
|         withArguments:arguments | ||||
|         allowForwarding:NO | ||||
|         error:error | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| + (id)performSelector:(SEL)selector | ||||
|              onObject:(id)object | ||||
|         withArguments:(NSArray *)arguments | ||||
|       allowForwarding:(BOOL)mightForwardMsgSend | ||||
|                 error:(NSError * __autoreleasing *)error { | ||||
|     static dispatch_once_t onceToken; | ||||
|     static SEL stdStringExclusion = nil; | ||||
|     dispatch_once(&onceToken, ^{ | ||||
|         stdStringExclusion = NSSelectorFromString(@"stdString"); | ||||
|     }); | ||||
|  | ||||
|     // Bail if the object won't respond to this selector | ||||
|     if (mightForwardMsgSend || ![self safeObject:object respondsToSelector:selector]) { | ||||
|         if (error) { | ||||
|             NSString *msg = [NSString | ||||
|                 stringWithFormat:@"This object does not respond to the selector %@", | ||||
|                 NSStringFromSelector(selector) | ||||
|             ]; | ||||
|             NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg }; | ||||
|             *error = [NSError | ||||
|                 errorWithDomain:FLEXRuntimeUtilityErrorDomain | ||||
|                 code:FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector | ||||
|                 userInfo:userInfo | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         return nil; | ||||
|     } | ||||
|  | ||||
|     // It is important to use object_getClass and not -class here, as | ||||
|     // object_getClass will return a different result for class objects | ||||
|     Class cls = object_getClass(object); | ||||
|     NSMethodSignature *methodSignature = [FLEXMethod selector:selector class:cls].signature; | ||||
|     if (!methodSignature) { | ||||
|         // Unsupported type encoding | ||||
|         return nil; | ||||
|     } | ||||
|      | ||||
|     // Probably an unsupported type encoding, like bitfields. | ||||
|     // In the future, we could calculate the return length | ||||
|     // on our own. For now, we abort. | ||||
|     // | ||||
|     // For future reference, the code here will get the true type encoding. | ||||
|     // NSMethodSignature will convert {?=b8b4b1b1b18[8S]} to {?} | ||||
|     // | ||||
|     // returnType = method_getTypeEncoding(class_getInstanceMethod([object class], selector)); | ||||
|     if (!methodSignature.methodReturnLength && | ||||
|         methodSignature.methodReturnType[0] != FLEXTypeEncodingVoid) { | ||||
|         return nil; | ||||
|     } | ||||
|  | ||||
|     // Build the invocation | ||||
|     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; | ||||
|     [invocation setSelector:selector]; | ||||
|     [invocation setTarget:object]; | ||||
|     [invocation retainArguments]; | ||||
|  | ||||
|     // Always self and _cmd | ||||
|     NSUInteger numberOfArguments = methodSignature.numberOfArguments; | ||||
|     for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) { | ||||
|         NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs; | ||||
|         id argumentObject = arguments.count > argumentsArrayIndex ? arguments[argumentsArrayIndex] : nil; | ||||
|  | ||||
|         // NSNull in the arguments array can be passed as a placeholder to indicate nil. | ||||
|         // We only need to set the argument if it will be non-nil. | ||||
|         if (argumentObject && ![argumentObject isKindOfClass:[NSNull class]]) { | ||||
|             const char *typeEncodingCString = [methodSignature getArgumentTypeAtIndex:argumentIndex]; | ||||
|             if (typeEncodingCString[0] == FLEXTypeEncodingObjcObject || | ||||
|               typeEncodingCString[0] == FLEXTypeEncodingObjcClass || | ||||
|               [self isTollFreeBridgedValue:argumentObject forCFType:typeEncodingCString]) { | ||||
|                 // Object | ||||
|                 [invocation setArgument:&argumentObject atIndex:argumentIndex]; | ||||
|             } else if (strcmp(typeEncodingCString, @encode(CGColorRef)) == 0 && | ||||
|                     [argumentObject isKindOfClass:[UIColor class]]) { | ||||
|                 // Bridging UIColor to CGColorRef | ||||
|                 CGColorRef colorRef = [argumentObject CGColor]; | ||||
|                 [invocation setArgument:&colorRef atIndex:argumentIndex]; | ||||
|             } else if ([argumentObject isKindOfClass:[NSValue class]]) { | ||||
|                 // Primitive boxed in NSValue | ||||
|                 NSValue *argumentValue = (NSValue *)argumentObject; | ||||
|  | ||||
|                 // Ensure that the type encoding on the NSValue matches the type encoding of the argument in the method signature | ||||
|                 if (strcmp([argumentValue objCType], typeEncodingCString) != 0) { | ||||
|                     if (error) { | ||||
|                         NSString *msg =  [NSString | ||||
|                             stringWithFormat:@"Type encoding mismatch for argument at index %lu. " | ||||
|                             "Value type: %s; Method argument type: %s.", | ||||
|                             (unsigned long)argumentsArrayIndex, argumentValue.objCType, typeEncodingCString | ||||
|                         ]; | ||||
|                         NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg }; | ||||
|                         *error = [NSError | ||||
|                             errorWithDomain:FLEXRuntimeUtilityErrorDomain | ||||
|                             code:FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch | ||||
|                             userInfo:userInfo | ||||
|                         ]; | ||||
|                     } | ||||
|                     return nil; | ||||
|                 } | ||||
|  | ||||
|                 @try { | ||||
|                     NSUInteger bufferSize = 0; | ||||
|                     FLEXGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL); | ||||
|  | ||||
|                     if (bufferSize > 0) { | ||||
|                         void *buffer = alloca(bufferSize); | ||||
|                         [argumentValue getValue:buffer]; | ||||
|                         [invocation setArgument:buffer atIndex:argumentIndex]; | ||||
|                     } | ||||
|                 } @catch (NSException *exception) { } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Try to invoke the invocation but guard against an exception being thrown. | ||||
|     id returnObject = nil; | ||||
|     @try { | ||||
|         [invocation invoke]; | ||||
|  | ||||
|         // Retrieve the return value and box if necessary. | ||||
|         const char *returnType = methodSignature.methodReturnType; | ||||
|  | ||||
|         if (returnType[0] == FLEXTypeEncodingObjcObject || returnType[0] == FLEXTypeEncodingObjcClass) { | ||||
|             // Return value is an object. | ||||
|             __unsafe_unretained id objectReturnedFromMethod = nil; | ||||
|             [invocation getReturnValue:&objectReturnedFromMethod]; | ||||
|             returnObject = objectReturnedFromMethod; | ||||
|         } else if (returnType[0] != FLEXTypeEncodingVoid) { | ||||
|             NSAssert(methodSignature.methodReturnLength, @"Memory corruption lies ahead"); | ||||
|  | ||||
|             if (returnType[0] == FLEXTypeEncodingStructBegin) { | ||||
|                 if (selector == stdStringExclusion && [object isKindOfClass:[NSString class]]) { | ||||
|                     // stdString is a C++ object and we will crash if we try to access it | ||||
|                     if (error) { | ||||
|                         *error = [NSError | ||||
|                             errorWithDomain:FLEXRuntimeUtilityErrorDomain | ||||
|                             code:FLEXRuntimeUtilityErrorCodeInvocationFailed | ||||
|                             userInfo:@{ NSLocalizedDescriptionKey : @"Skipping -[NSString stdString]" } | ||||
|                         ]; | ||||
|                     } | ||||
|  | ||||
|                     return nil; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Will use arbitrary buffer for return value and box it. | ||||
|             void *returnValue = malloc(methodSignature.methodReturnLength); | ||||
|             [invocation getReturnValue:returnValue]; | ||||
|             returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType]; | ||||
|             free(returnValue); | ||||
|         } | ||||
|     } @catch (NSException *exception) { | ||||
|         // Bummer... | ||||
|         if (error) { | ||||
|             // "… on <class>" / "… on instance of <class>" | ||||
|             NSString *class = NSStringFromClass([object class]); | ||||
|             NSString *calledOn = object == [object class] ? class : [@"an instance of " stringByAppendingString:class]; | ||||
|  | ||||
|             NSString *message = [NSString | ||||
|                 stringWithFormat:@"Exception '%@' thrown while performing selector '%@' on %@.\nReason:\n\n%@", | ||||
|                 exception.name, NSStringFromSelector(selector), calledOn, exception.reason | ||||
|             ]; | ||||
|  | ||||
|             *error = [NSError | ||||
|                 errorWithDomain:FLEXRuntimeUtilityErrorDomain | ||||
|                 code:FLEXRuntimeUtilityErrorCodeInvocationFailed | ||||
|                 userInfo:@{ NSLocalizedDescriptionKey : message } | ||||
|             ]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return returnObject; | ||||
| } | ||||
|  | ||||
| + (BOOL)isTollFreeBridgedValue:(id)value forCFType:(const char *)typeEncoding { | ||||
|     // See https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Toll-FreeBridgin/Toll-FreeBridgin.html | ||||
| #define CASE(cftype, foundationClass) \ | ||||
|     if (strcmp(typeEncoding, @encode(cftype)) == 0) { \ | ||||
|         return [value isKindOfClass:[foundationClass class]]; \ | ||||
|     } | ||||
|  | ||||
|     CASE(CFArrayRef, NSArray); | ||||
|     CASE(CFAttributedStringRef, NSAttributedString); | ||||
|     CASE(CFCalendarRef, NSCalendar); | ||||
|     CASE(CFCharacterSetRef, NSCharacterSet); | ||||
|     CASE(CFDataRef, NSData); | ||||
|     CASE(CFDateRef, NSDate); | ||||
|     CASE(CFDictionaryRef, NSDictionary); | ||||
|     CASE(CFErrorRef, NSError); | ||||
|     CASE(CFLocaleRef, NSLocale); | ||||
|     CASE(CFMutableArrayRef, NSMutableArray); | ||||
|     CASE(CFMutableAttributedStringRef, NSMutableAttributedString); | ||||
|     CASE(CFMutableCharacterSetRef, NSMutableCharacterSet); | ||||
|     CASE(CFMutableDataRef, NSMutableData); | ||||
|     CASE(CFMutableDictionaryRef, NSMutableDictionary); | ||||
|     CASE(CFMutableSetRef, NSMutableSet); | ||||
|     CASE(CFMutableStringRef, NSMutableString); | ||||
|     CASE(CFNumberRef, NSNumber); | ||||
|     CASE(CFReadStreamRef, NSInputStream); | ||||
|     CASE(CFRunLoopTimerRef, NSTimer); | ||||
|     CASE(CFSetRef, NSSet); | ||||
|     CASE(CFStringRef, NSString); | ||||
|     CASE(CFTimeZoneRef, NSTimeZone); | ||||
|     CASE(CFURLRef, NSURL); | ||||
|     CASE(CFWriteStreamRef, NSOutputStream); | ||||
|  | ||||
| #undef CASE | ||||
|  | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| + (NSString *)editableJSONStringForObject:(id)object { | ||||
|     NSString *editableDescription = nil; | ||||
|  | ||||
|     if (object) { | ||||
|         // This is a hack to use JSON serialization for our editable objects. | ||||
|         // NSJSONSerialization doesn't allow writing fragments - the top level object must be an array or dictionary. | ||||
|         // We always wrap the object inside an array and then strip the outer square braces off the final string. | ||||
|         NSArray *wrappedObject = @[object]; | ||||
|         if ([NSJSONSerialization isValidJSONObject:wrappedObject]) { | ||||
|             NSData *jsonData = [NSJSONSerialization dataWithJSONObject:wrappedObject options:0 error:NULL]; | ||||
|             NSString *wrappedDescription = [NSString stringWithUTF8String:jsonData.bytes]; | ||||
|             editableDescription = [wrappedDescription substringWithRange:NSMakeRange(1, wrappedDescription.length - 2)]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return editableDescription; | ||||
| } | ||||
|  | ||||
| + (id)objectValueFromEditableJSONString:(NSString *)string { | ||||
|     id value = nil; | ||||
|     // nil for empty string/whitespace | ||||
|     if ([string stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet].length) { | ||||
|         value = [NSJSONSerialization | ||||
|             JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding] | ||||
|             options:NSJSONReadingAllowFragments | ||||
|             error:NULL | ||||
|         ]; | ||||
|     } | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| + (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString { | ||||
|     NSNumberFormatter *formatter = [NSNumberFormatter new]; | ||||
|     [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; | ||||
|     NSNumber *number = [formatter numberFromString:inputString]; | ||||
|      | ||||
|     // Is the type encoding longer than one character? | ||||
|     if (strlen(typeEncoding) > 1) { | ||||
|         NSString *type = @(typeEncoding); | ||||
|          | ||||
|         // Is it NSDecimalNumber or NSNumber? | ||||
|         if ([type isEqualToString:@FLEXEncodeClass(NSDecimalNumber)]) { | ||||
|             return [NSDecimalNumber decimalNumberWithString:inputString]; | ||||
|         } else if ([type isEqualToString:@FLEXEncodeClass(NSNumber)]) { | ||||
|             return number; | ||||
|         } | ||||
|          | ||||
|         return nil; | ||||
|     } | ||||
|      | ||||
|     // Type encoding is one character, switch on the type | ||||
|     FLEXTypeEncoding type = typeEncoding[0]; | ||||
|     uint8_t value[32]; | ||||
|     void *bufferStart = &value[0]; | ||||
|      | ||||
|     // Make sure we box the number with the correct type encoding | ||||
|     // so it can be properly unboxed later via getValue: | ||||
|     switch (type) { | ||||
|         case FLEXTypeEncodingChar: | ||||
|             *(char *)bufferStart = number.charValue; break; | ||||
|         case FLEXTypeEncodingInt: | ||||
|             *(int *)bufferStart = number.intValue; break; | ||||
|         case FLEXTypeEncodingShort: | ||||
|             *(short *)bufferStart = number.shortValue; break; | ||||
|         case FLEXTypeEncodingLong: | ||||
|             *(long *)bufferStart = number.longValue; break; | ||||
|         case FLEXTypeEncodingLongLong: | ||||
|             *(long long *)bufferStart = number.longLongValue; break; | ||||
|         case FLEXTypeEncodingUnsignedChar: | ||||
|             *(unsigned char *)bufferStart = number.unsignedCharValue; break; | ||||
|         case FLEXTypeEncodingUnsignedInt: | ||||
|             *(unsigned int *)bufferStart = number.unsignedIntValue; break; | ||||
|         case FLEXTypeEncodingUnsignedShort: | ||||
|             *(unsigned short *)bufferStart = number.unsignedShortValue; break; | ||||
|         case FLEXTypeEncodingUnsignedLong: | ||||
|             *(unsigned long *)bufferStart = number.unsignedLongValue; break; | ||||
|         case FLEXTypeEncodingUnsignedLongLong: | ||||
|             *(unsigned long long *)bufferStart = number.unsignedLongLongValue; break; | ||||
|         case FLEXTypeEncodingFloat: | ||||
|             *(float *)bufferStart = number.floatValue; break; | ||||
|         case FLEXTypeEncodingDouble: | ||||
|             *(double *)bufferStart = number.doubleValue; break; | ||||
|              | ||||
|         case FLEXTypeEncodingLongDouble: | ||||
|             // NSNumber does not support long double | ||||
|         default: | ||||
|             return nil; | ||||
|     } | ||||
|      | ||||
|     return [NSValue value:value withObjCType:typeEncoding]; | ||||
| } | ||||
|  | ||||
| + (void)enumerateTypesInStructEncoding:(const char *)structEncoding | ||||
|                             usingBlock:(void (^)(NSString *structName, | ||||
|                                                  const char *fieldTypeEncoding, | ||||
|                                                  NSString *prettyTypeEncoding, | ||||
|                                                  NSUInteger fieldIndex, | ||||
|                                                  NSUInteger fieldOffset))typeBlock { | ||||
|     if (structEncoding && structEncoding[0] == FLEXTypeEncodingStructBegin) { | ||||
|         const char *equals = strchr(structEncoding, '='); | ||||
|         if (equals) { | ||||
|             const char *nameStart = structEncoding + 1; | ||||
|             NSString *structName = [@(structEncoding) | ||||
|                 substringWithRange:NSMakeRange(nameStart - structEncoding, equals - nameStart) | ||||
|             ]; | ||||
|  | ||||
|             NSUInteger fieldAlignment = 0, structSize = 0; | ||||
|             if (FLEXGetSizeAndAlignment(structEncoding, &structSize, &fieldAlignment)) { | ||||
|                 NSUInteger runningFieldIndex = 0; | ||||
|                 NSUInteger runningFieldOffset = 0; | ||||
|                 const char *typeStart = equals + 1; | ||||
|                  | ||||
|                 while (*typeStart != FLEXTypeEncodingStructEnd) { | ||||
|                     NSUInteger fieldSize = 0; | ||||
|                     // If the struct type encoding was successfully handled by | ||||
|                     // FLEXGetSizeAndAlignment above, we *should* be ok with the field here. | ||||
|                     const char *nextTypeStart = NSGetSizeAndAlignment(typeStart, &fieldSize, NULL); | ||||
|                     NSString *typeEncoding = [@(structEncoding) | ||||
|                         substringWithRange:NSMakeRange(typeStart - structEncoding, nextTypeStart - typeStart) | ||||
|                     ]; | ||||
|                      | ||||
|                     // Padding to keep proper alignment. __attribute((packed)) structs | ||||
|                     // will break here. The type encoding is no different for packed structs, | ||||
|                     // so it's not clear there's anything we can do for those. | ||||
|                     const NSUInteger currentSizeSum = runningFieldOffset % fieldAlignment; | ||||
|                     if (currentSizeSum != 0 && currentSizeSum + fieldSize > fieldAlignment) { | ||||
|                         runningFieldOffset += fieldAlignment - currentSizeSum; | ||||
|                     } | ||||
|                      | ||||
|                     typeBlock( | ||||
|                         structName, | ||||
|                         typeEncoding.UTF8String, | ||||
|                         [self readableTypeForEncoding:typeEncoding], | ||||
|                         runningFieldIndex, | ||||
|                         runningFieldOffset | ||||
|                     ); | ||||
|                     runningFieldOffset += fieldSize; | ||||
|                     runningFieldIndex++; | ||||
|                     typeStart = nextTypeStart; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Metadata Helpers | ||||
|  | ||||
| + (NSDictionary<NSString *, NSString *> *)attributesForProperty:(objc_property_t)property { | ||||
|     NSString *attributes = @(property_getAttributes(property) ?: ""); | ||||
|     // Thanks to MAObjcRuntime for inspiration here. | ||||
|     NSArray<NSString *> *attributePairs = [attributes componentsSeparatedByString:@","]; | ||||
|     NSMutableDictionary<NSString *, NSString *> *attributesDictionary = [NSMutableDictionary new]; | ||||
|     for (NSString *attributePair in attributePairs) { | ||||
|         attributesDictionary[[attributePair substringToIndex:1]] = [attributePair substringFromIndex:1]; | ||||
|     } | ||||
|     return attributesDictionary; | ||||
| } | ||||
|  | ||||
| + (NSString *)appendName:(NSString *)name toType:(NSString *)type { | ||||
|     if (!type.length) { | ||||
|         type = @"(?)"; | ||||
|     } | ||||
|      | ||||
|     NSString *combined = nil; | ||||
|     if ([type characterAtIndex:type.length - 1] == FLEXTypeEncodingCString) { | ||||
|         combined = [type stringByAppendingString:name]; | ||||
|     } else { | ||||
|         combined = [type stringByAppendingFormat:@" %@", name]; | ||||
|     } | ||||
|     return combined; | ||||
| } | ||||
|  | ||||
| + (NSString *)readableTypeForEncoding:(NSString *)encodingString { | ||||
|     if (!encodingString.length) { | ||||
|         return @"?"; | ||||
|     } | ||||
|  | ||||
|     // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html | ||||
|     // class-dump has a much nicer and much more complete implementation for this task, but it is distributed under GPLv2 :/ | ||||
|     // See https://github.com/nygard/class-dump/blob/master/Source/CDType.m | ||||
|     // Warning: this method uses multiple middle returns and macros to cut down on boilerplate. | ||||
|     // The use of macros here was inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html | ||||
|     const char *encodingCString = encodingString.UTF8String; | ||||
|  | ||||
|     // Some fields have a name, such as {Size=\"width\"d\"height\"d}, we need to extract the name out and recursive | ||||
|     const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:encodingCString]; | ||||
|     if (fieldNameOffset > 0) { | ||||
|         // According to https://github.com/nygard/class-dump/commit/33fb5ed221810685f57c192e1ce8ab6054949a7c, | ||||
|         // there are some consecutive quoted strings, so use `_` to concatenate the names. | ||||
|         NSString *const fieldNamesString = [encodingString substringWithRange:NSMakeRange(0, fieldNameOffset)]; | ||||
|         NSArray<NSString *> *const fieldNames = [fieldNamesString | ||||
|             componentsSeparatedByString:[NSString stringWithFormat:@"%c", FLEXTypeEncodingQuote] | ||||
|         ]; | ||||
|         NSMutableString *finalFieldNamesString = [NSMutableString new]; | ||||
|         for (NSString *const fieldName in fieldNames) { | ||||
|             if (fieldName.length > 0) { | ||||
|                 if (finalFieldNamesString.length > 0) { | ||||
|                     [finalFieldNamesString appendString:@"_"]; | ||||
|                 } | ||||
|                 [finalFieldNamesString appendString:fieldName]; | ||||
|             } | ||||
|         } | ||||
|         NSString *const recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:fieldNameOffset]]; | ||||
|         return [NSString stringWithFormat:@"%@ %@", recursiveType, finalFieldNamesString]; | ||||
|     } | ||||
|  | ||||
|     // Objects | ||||
|     if (encodingCString[0] == FLEXTypeEncodingObjcObject) { | ||||
|         NSString *class = [encodingString substringFromIndex:1]; | ||||
|         class = [class stringByReplacingOccurrencesOfString:@"\"" withString:@""]; | ||||
|         if (class.length == 0 || (class.length == 1 && [class characterAtIndex:0] == FLEXTypeEncodingUnknown)) { | ||||
|             class = @"id"; | ||||
|         } else { | ||||
|             class = [class stringByAppendingString:@" *"]; | ||||
|         } | ||||
|         return class; | ||||
|     } | ||||
|  | ||||
|     // Qualifier Prefixes | ||||
|     // Do this first since some of the direct translations (i.e. Method) contain a prefix. | ||||
| #define RECURSIVE_TRANSLATE(prefix, formatString) \ | ||||
|     if (encodingCString[0] == prefix) { \ | ||||
|         NSString *recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:1]]; \ | ||||
|         return [NSString stringWithFormat:formatString, recursiveType]; \ | ||||
|     } | ||||
|  | ||||
|     // If there's a qualifier prefix on the encoding, translate it and then | ||||
|     // recursively call this method with the rest of the encoding string. | ||||
|     RECURSIVE_TRANSLATE('^', @"%@ *"); | ||||
|     RECURSIVE_TRANSLATE('r', @"const %@"); | ||||
|     RECURSIVE_TRANSLATE('n', @"in %@"); | ||||
|     RECURSIVE_TRANSLATE('N', @"inout %@"); | ||||
|     RECURSIVE_TRANSLATE('o', @"out %@"); | ||||
|     RECURSIVE_TRANSLATE('O', @"bycopy %@"); | ||||
|     RECURSIVE_TRANSLATE('R', @"byref %@"); | ||||
|     RECURSIVE_TRANSLATE('V', @"oneway %@"); | ||||
|     RECURSIVE_TRANSLATE('b', @"bitfield(%@)"); | ||||
|  | ||||
| #undef RECURSIVE_TRANSLATE | ||||
|  | ||||
|   // C Types | ||||
| #define TRANSLATE(ctype) \ | ||||
|     if (strcmp(encodingCString, @encode(ctype)) == 0) { \ | ||||
|         return (NSString *)CFSTR(#ctype); \ | ||||
|     } | ||||
|  | ||||
|     // Order matters here since some of the cocoa types are typedefed to c types. | ||||
|     // We can't recover the exact mapping, but we choose to prefer the cocoa types. | ||||
|     // This is not an exhaustive list, but it covers the most common types | ||||
|     TRANSLATE(CGRect); | ||||
|     TRANSLATE(CGPoint); | ||||
|     TRANSLATE(CGSize); | ||||
|     TRANSLATE(CGVector); | ||||
|     TRANSLATE(UIEdgeInsets); | ||||
|     if (@available(iOS 11.0, *)) { | ||||
|       TRANSLATE(NSDirectionalEdgeInsets); | ||||
|     } | ||||
|     TRANSLATE(UIOffset); | ||||
|     TRANSLATE(NSRange); | ||||
|     TRANSLATE(CGAffineTransform); | ||||
|     TRANSLATE(CATransform3D); | ||||
|     TRANSLATE(CGColorRef); | ||||
|     TRANSLATE(CGPathRef); | ||||
|     TRANSLATE(CGContextRef); | ||||
|     TRANSLATE(NSInteger); | ||||
|     TRANSLATE(NSUInteger); | ||||
|     TRANSLATE(CGFloat); | ||||
|     TRANSLATE(BOOL); | ||||
|     TRANSLATE(int); | ||||
|     TRANSLATE(short); | ||||
|     TRANSLATE(long); | ||||
|     TRANSLATE(long long); | ||||
|     TRANSLATE(unsigned char); | ||||
|     TRANSLATE(unsigned int); | ||||
|     TRANSLATE(unsigned short); | ||||
|     TRANSLATE(unsigned long); | ||||
|     TRANSLATE(unsigned long long); | ||||
|     TRANSLATE(float); | ||||
|     TRANSLATE(double); | ||||
|     TRANSLATE(long double); | ||||
|     TRANSLATE(char *); | ||||
|     TRANSLATE(Class); | ||||
|     TRANSLATE(objc_property_t); | ||||
|     TRANSLATE(Ivar); | ||||
|     TRANSLATE(Method); | ||||
|     TRANSLATE(Category); | ||||
|     TRANSLATE(NSZone *); | ||||
|     TRANSLATE(SEL); | ||||
|     TRANSLATE(void); | ||||
|  | ||||
| #undef TRANSLATE | ||||
|  | ||||
|     // For structs, we only use the name of the structs | ||||
|     if (encodingCString[0] == FLEXTypeEncodingStructBegin) { | ||||
|         // Special case: std::string | ||||
|         if ([encodingString hasPrefix:@"{basic_string<char"]) { | ||||
|             return @"std::string"; | ||||
|         } | ||||
|  | ||||
|         const char *equals = strchr(encodingCString, '='); | ||||
|         if (equals) { | ||||
|             const char *nameStart = encodingCString + 1; | ||||
|             // For anonymous structs | ||||
|             if (nameStart[0] == FLEXTypeEncodingUnknown) { | ||||
|                 return @"anonymous struct"; | ||||
|             } else { | ||||
|                 NSString *const structName = [encodingString | ||||
|                     substringWithRange:NSMakeRange(nameStart - encodingCString, equals - nameStart) | ||||
|                 ]; | ||||
|                 return structName; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // If we couldn't translate, just return the original encoding string | ||||
|     return encodingString; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark - Internal Helpers | ||||
|  | ||||
| + (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type { | ||||
|     // Remove the field name if there is any (e.g. \"width\"d -> d) | ||||
|     const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:type]; | ||||
|     if (fieldNameOffset > 0) { | ||||
|         return [self valueForPrimitivePointer:pointer objCType:type + fieldNameOffset]; | ||||
|     } | ||||
|  | ||||
|     // CASE macro inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html | ||||
| #define CASE(ctype, selectorpart) \ | ||||
|     if (strcmp(type, @encode(ctype)) == 0) { \ | ||||
|         return [NSNumber numberWith ## selectorpart: *(ctype *)pointer]; \ | ||||
|     } | ||||
|  | ||||
|     CASE(BOOL, Bool); | ||||
|     CASE(unsigned char, UnsignedChar); | ||||
|     CASE(short, Short); | ||||
|     CASE(unsigned short, UnsignedShort); | ||||
|     CASE(int, Int); | ||||
|     CASE(unsigned int, UnsignedInt); | ||||
|     CASE(long, Long); | ||||
|     CASE(unsigned long, UnsignedLong); | ||||
|     CASE(long long, LongLong); | ||||
|     CASE(unsigned long long, UnsignedLongLong); | ||||
|     CASE(float, Float); | ||||
|     CASE(double, Double); | ||||
|     CASE(long double, Double); | ||||
|  | ||||
| #undef CASE | ||||
|  | ||||
|     NSValue *value = nil; | ||||
|     if (FLEXGetSizeAndAlignment(type, nil, nil)) { | ||||
|         @try { | ||||
|             value = [NSValue valueWithBytes:pointer objCType:type]; | ||||
|         } @catch (NSException *exception) { | ||||
|             // Certain type encodings are not supported by valueWithBytes:objCType:. | ||||
|             // Just fail silently if an exception is thrown. | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										73
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| // | ||||
| //  FLEXObjcInternal.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 11/1/18. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| // The macros below are copied straight from | ||||
| // objc-internal.h, objc-private.h, objc-object.h, and objc-config.h with | ||||
| // as few modifications as possible. Changes are noted in boxed comments. | ||||
| // https://opensource.apple.com/source/objc4/objc4-723/ | ||||
| // https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-internal.h.auto.html | ||||
| // https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-object.h.auto.html | ||||
|  | ||||
| ///////////////////// | ||||
| // objc-internal.h // | ||||
| ///////////////////// | ||||
|  | ||||
| #if __LP64__ | ||||
| #define OBJC_HAVE_TAGGED_POINTERS 1 | ||||
| #endif | ||||
|  | ||||
| #if OBJC_HAVE_TAGGED_POINTERS | ||||
|  | ||||
| #if TARGET_OS_OSX && __x86_64__ | ||||
| // 64-bit Mac - tag bit is LSB | ||||
| #   define OBJC_MSB_TAGGED_POINTERS 0 | ||||
| #else | ||||
| // Everything else - tag bit is MSB | ||||
| #   define OBJC_MSB_TAGGED_POINTERS 1 | ||||
| #endif | ||||
|  | ||||
| #if OBJC_MSB_TAGGED_POINTERS | ||||
| #   define _OBJC_TAG_MASK (1UL<<63) | ||||
| #   define _OBJC_TAG_EXT_MASK (0xfUL<<60) | ||||
| #else | ||||
| #   define _OBJC_TAG_MASK 1UL | ||||
| #   define _OBJC_TAG_EXT_MASK 0xfUL | ||||
| #endif | ||||
|  | ||||
| #endif // OBJC_HAVE_TAGGED_POINTERS | ||||
|  | ||||
| ////////////////////////////////////// | ||||
| // originally _objc_isTaggedPointer // | ||||
| ////////////////////////////////////// | ||||
| NS_INLINE BOOL flex_isTaggedPointer(const void *ptr)  { | ||||
|     #if OBJC_HAVE_TAGGED_POINTERS | ||||
|         return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; | ||||
|     #else | ||||
|         return NO; | ||||
|     #endif | ||||
| } | ||||
|  | ||||
| #define FLEXPointerIsTaggedPointer(obj) flex_isTaggedPointer((__bridge void *)obj) | ||||
|  | ||||
| BOOL FLEXPointerIsReadable(const void * ptr); | ||||
|  | ||||
| /// @brief Assumes memory is valid and readable. | ||||
| /// @discussion objc-internal.h, objc-private.h, and objc-config.h | ||||
| /// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/ | ||||
| /// https://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py | ||||
| /// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/ | ||||
| BOOL FLEXPointerIsValidObjcObject(const void * ptr); | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
							
								
								
									
										196
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.mm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.mm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | ||||
| // | ||||
| //  FLEXObjcInternal.mm | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 11/1/18. | ||||
| // | ||||
|  | ||||
| /* | ||||
|  * Copyright (c) 2005-2007 Apple Inc.  All Rights Reserved. | ||||
|  * | ||||
|  * @APPLE_LICENSE_HEADER_START@ | ||||
|  * | ||||
|  * This file contains Original Code and/or Modifications of Original Code | ||||
|  * as defined in and that are subject to the Apple Public Source License | ||||
|  * Version 2.0 (the 'License'). You may not use this file except in | ||||
|  * compliance with the License. Please obtain a copy of the License at | ||||
|  * http://www.opensource.apple.com/apsl/ and read it before using this | ||||
|  * file. | ||||
|  * | ||||
|  * The Original Code and all software distributed under the License are | ||||
|  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | ||||
|  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | ||||
|  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | ||||
|  * Please see the License for the specific language governing rights and | ||||
|  * limitations under the License. | ||||
|  * | ||||
|  * @APPLE_LICENSE_HEADER_END@ | ||||
|  */ | ||||
|  | ||||
| #import "FLEXObjcInternal.h" | ||||
| #import <objc/runtime.h> | ||||
| // For malloc_size | ||||
| #import <malloc/malloc.h> | ||||
| // For vm_region_64 | ||||
| #include <mach/mach.h> | ||||
|  | ||||
| #if __arm64e__ | ||||
| #include <ptrauth.h> | ||||
| #endif | ||||
|  | ||||
| #define ALWAYS_INLINE inline __attribute__((always_inline)) | ||||
| #define NEVER_INLINE inline __attribute__((noinline)) | ||||
|  | ||||
| // The macros below are copied straight from | ||||
| // objc-internal.h, objc-private.h, objc-object.h, and objc-config.h with | ||||
| // as few modifications as possible. Changes are noted in boxed comments. | ||||
| // https://opensource.apple.com/source/objc4/objc4-723/ | ||||
| // https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-internal.h.auto.html | ||||
| // https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-object.h.auto.html | ||||
|  | ||||
| ///////////////////// | ||||
| // objc-internal.h // | ||||
| ///////////////////// | ||||
|  | ||||
| #if OBJC_HAVE_TAGGED_POINTERS | ||||
|  | ||||
| /////////////////// | ||||
| // objc-object.h // | ||||
| /////////////////// | ||||
|  | ||||
| //////////////////////////////////////////////// | ||||
| // originally objc_object::isExtTaggedPointer // | ||||
| //////////////////////////////////////////////// | ||||
| NS_INLINE BOOL flex_isExtTaggedPointer(const void *ptr)  { | ||||
|     return ((uintptr_t)ptr & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK; | ||||
| } | ||||
|  | ||||
| #endif // OBJC_HAVE_TAGGED_POINTERS | ||||
|  | ||||
| ///////////////////////////////////// | ||||
| // FLEXObjectInternal              // | ||||
| // No Apple code beyond this point // | ||||
| ///////////////////////////////////// | ||||
|  | ||||
| extern "C" { | ||||
|  | ||||
| BOOL FLEXPointerIsReadable(const void *inPtr) { | ||||
|     kern_return_t error = KERN_SUCCESS; | ||||
|  | ||||
|     vm_size_t vmsize; | ||||
| #if __arm64e__ | ||||
|     // On arm64e, we need to strip the PAC from the pointer so the adress is readable | ||||
|     vm_address_t address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer); | ||||
| #else | ||||
|     vm_address_t address = (vm_address_t)inPtr; | ||||
| #endif | ||||
|     vm_region_basic_info_data_t info; | ||||
|     mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; | ||||
|     memory_object_name_t object; | ||||
|  | ||||
|     error = vm_region_64( | ||||
|         mach_task_self(), | ||||
|         &address, | ||||
|         &vmsize, | ||||
|         VM_REGION_BASIC_INFO, | ||||
|         (vm_region_info_t)&info, | ||||
|         &info_count, | ||||
|         &object | ||||
|     ); | ||||
|  | ||||
|     if (error != KERN_SUCCESS) { | ||||
|         // vm_region/vm_region_64 returned an error | ||||
|         return NO; | ||||
|     } else if (!(BOOL)(info.protection & VM_PROT_READ)) { | ||||
|         return NO; | ||||
|     } | ||||
|  | ||||
| #if __arm64e__ | ||||
|     address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer); | ||||
| #else | ||||
|     address = (vm_address_t)inPtr; | ||||
| #endif | ||||
|      | ||||
|     // Read the memory | ||||
|     vm_size_t size = 0; | ||||
|     char buf[sizeof(uintptr_t)]; | ||||
|     error = vm_read_overwrite(mach_task_self(), address, sizeof(uintptr_t), (vm_address_t)buf, &size); | ||||
|     if (error != KERN_SUCCESS) { | ||||
|         // vm_read_overwrite returned an error | ||||
|         return NO; | ||||
|     } | ||||
|  | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| /// Accepts addresses that may or may not be readable. | ||||
| /// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/ | ||||
| BOOL FLEXPointerIsValidObjcObject(const void *ptr) { | ||||
|     uintptr_t pointer = (uintptr_t)ptr; | ||||
|  | ||||
|     if (!ptr) { | ||||
|         return NO; | ||||
|     } | ||||
|  | ||||
| #if OBJC_HAVE_TAGGED_POINTERS | ||||
|     // Tagged pointers have 0x1 set, no other valid pointers do | ||||
|     // objc-internal.h -> _objc_isTaggedPointer() | ||||
|     if (flex_isTaggedPointer(ptr) || flex_isExtTaggedPointer(ptr)) { | ||||
|         return YES; | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     // Check pointer alignment | ||||
|     if ((pointer % sizeof(uintptr_t)) != 0) { | ||||
|         return NO; | ||||
|     } | ||||
|  | ||||
|     // From LLDB: | ||||
|     // Pointers in a class_t will only have bits 0 through 46 set, | ||||
|     // so if any pointer has bits 47 through 63 high, we know that this is not a valid isa | ||||
|     // https://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py | ||||
|     if ((pointer & 0xFFFF800000000000) != 0) { | ||||
|         return NO; | ||||
|     } | ||||
|  | ||||
|     // Make sure dereferencing this address won't crash | ||||
|     if (!FLEXPointerIsReadable(ptr)) { | ||||
|         return NO; | ||||
|     } | ||||
|  | ||||
|     // http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html | ||||
|     // We check if the returned class is readable because object_getClass | ||||
|     // can return a garbage value when given a non-nil pointer to a non-object | ||||
|     Class cls = object_getClass((__bridge id)ptr); | ||||
|     if (!cls || !FLEXPointerIsReadable((__bridge void *)cls)) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     // Just because this pointer is readable doesn't mean whatever is at | ||||
|     // it's ISA offset is readable. We need to do the same checks on it's ISA. | ||||
|     // Even this isn't perfect, because once we call object_isClass, we're | ||||
|     // going to dereference a member of the metaclass, which may or may not | ||||
|     // be readable itself. For the time being there is no way to access it | ||||
|     // to check here, and I have yet to hard-code a solution. | ||||
|     Class metaclass = object_getClass(cls); | ||||
|     if (!metaclass || !FLEXPointerIsReadable((__bridge void *)metaclass)) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     // Does the class pointer we got appear as a class to the runtime? | ||||
|     if (!object_isClass(cls)) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     // Is the allocation size at least as large as the expected instance size? | ||||
|     ssize_t instanceSize = class_getInstanceSize(cls); | ||||
|     if (malloc_size(ptr) < instanceSize) { | ||||
|         return NO; | ||||
|     } | ||||
|  | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
|  | ||||
| } // End extern "C" | ||||
							
								
								
									
										79
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| // | ||||
| //  FLEXRuntimeConstants.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 3/11/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
| #import <objc/runtime.h> | ||||
|  | ||||
| #define FLEXEncodeClass(class) ("@\"" #class "\"") | ||||
| #define FLEXEncodeObject(obj) (obj ? [NSString stringWithFormat:@"@\"%@\"", [obj class]].UTF8String : @encode(id)) | ||||
|  | ||||
| // Arguments 0 and 1 are self and _cmd always | ||||
| extern const unsigned int kFLEXNumberOfImplicitArgs; | ||||
|  | ||||
| // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6 | ||||
| extern NSString *const kFLEXPropertyAttributeKeyTypeEncoding; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyBackingIvarName; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyReadOnly; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyCopy; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyRetain; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyNonAtomic; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyCustomGetter; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyCustomSetter; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyDynamic; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyWeak; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyGarbageCollectable; | ||||
| extern NSString *const kFLEXPropertyAttributeKeyOldStyleTypeEncoding; | ||||
|  | ||||
| typedef NS_ENUM(NSUInteger, FLEXPropertyAttribute) { | ||||
|     FLEXPropertyAttributeTypeEncoding       = 'T', | ||||
|     FLEXPropertyAttributeBackingIvarName    = 'V', | ||||
|     FLEXPropertyAttributeCopy               = 'C', | ||||
|     FLEXPropertyAttributeCustomGetter       = 'G', | ||||
|     FLEXPropertyAttributeCustomSetter       = 'S', | ||||
|     FLEXPropertyAttributeDynamic            = 'D', | ||||
|     FLEXPropertyAttributeGarbageCollectible = 'P', | ||||
|     FLEXPropertyAttributeNonAtomic          = 'N', | ||||
|     FLEXPropertyAttributeOldTypeEncoding    = 't', | ||||
|     FLEXPropertyAttributeReadOnly           = 'R', | ||||
|     FLEXPropertyAttributeRetain             = '&', | ||||
|     FLEXPropertyAttributeWeak               = 'W' | ||||
| }; //NS_SWIFT_NAME(FLEX.PropertyAttribute); | ||||
|  | ||||
| typedef NS_ENUM(char, FLEXTypeEncoding) { | ||||
|     FLEXTypeEncodingNull             = '\0', | ||||
|     FLEXTypeEncodingUnknown          = '?', | ||||
|     FLEXTypeEncodingChar             = 'c', | ||||
|     FLEXTypeEncodingInt              = 'i', | ||||
|     FLEXTypeEncodingShort            = 's', | ||||
|     FLEXTypeEncodingLong             = 'l', | ||||
|     FLEXTypeEncodingLongLong         = 'q', | ||||
|     FLEXTypeEncodingUnsignedChar     = 'C', | ||||
|     FLEXTypeEncodingUnsignedInt      = 'I', | ||||
|     FLEXTypeEncodingUnsignedShort    = 'S', | ||||
|     FLEXTypeEncodingUnsignedLong     = 'L', | ||||
|     FLEXTypeEncodingUnsignedLongLong = 'Q', | ||||
|     FLEXTypeEncodingFloat            = 'f', | ||||
|     FLEXTypeEncodingDouble           = 'd', | ||||
|     FLEXTypeEncodingLongDouble       = 'D', | ||||
|     FLEXTypeEncodingCBool            = 'B', | ||||
|     FLEXTypeEncodingVoid             = 'v', | ||||
|     FLEXTypeEncodingCString          = '*', | ||||
|     FLEXTypeEncodingObjcObject       = '@', | ||||
|     FLEXTypeEncodingObjcClass        = '#', | ||||
|     FLEXTypeEncodingSelector         = ':', | ||||
|     FLEXTypeEncodingArrayBegin       = '[', | ||||
|     FLEXTypeEncodingArrayEnd         = ']', | ||||
|     FLEXTypeEncodingStructBegin      = '{', | ||||
|     FLEXTypeEncodingStructEnd        = '}', | ||||
|     FLEXTypeEncodingUnionBegin       = '(', | ||||
|     FLEXTypeEncodingUnionEnd         = ')', | ||||
|     FLEXTypeEncodingQuote            = '\"', | ||||
|     FLEXTypeEncodingBitField         = 'b', | ||||
|     FLEXTypeEncodingPointer          = '^', | ||||
|     FLEXTypeEncodingConst            = 'r' | ||||
| }; //NS_SWIFT_NAME(FLEX.TypeEncoding); | ||||
							
								
								
									
										24
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| // | ||||
| //  FLEXRuntimeConstants.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 3/11/20. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeConstants.h" | ||||
|  | ||||
| const unsigned int kFLEXNumberOfImplicitArgs = 2; | ||||
|  | ||||
| NSString *const kFLEXPropertyAttributeKeyTypeEncoding = @"T"; | ||||
| NSString *const kFLEXPropertyAttributeKeyBackingIvarName = @"V"; | ||||
| NSString *const kFLEXPropertyAttributeKeyReadOnly = @"R"; | ||||
| NSString *const kFLEXPropertyAttributeKeyCopy = @"C"; | ||||
| NSString *const kFLEXPropertyAttributeKeyRetain = @"&"; | ||||
| NSString *const kFLEXPropertyAttributeKeyNonAtomic = @"N"; | ||||
| NSString *const kFLEXPropertyAttributeKeyCustomGetter = @"G"; | ||||
| NSString *const kFLEXPropertyAttributeKeyCustomSetter = @"S"; | ||||
| NSString *const kFLEXPropertyAttributeKeyDynamic = @"D"; | ||||
| NSString *const kFLEXPropertyAttributeKeyWeak = @"W"; | ||||
| NSString *const kFLEXPropertyAttributeKeyGarbageCollectable = @"P"; | ||||
| NSString *const kFLEXPropertyAttributeKeyOldStyleTypeEncoding = @"t"; | ||||
							
								
								
									
										56
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| // | ||||
| //  FLEXRuntimeSafety.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 3/25/17. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
| #import <objc/runtime.h> | ||||
|  | ||||
| #pragma mark - Classes | ||||
|  | ||||
| extern NSUInteger const kFLEXKnownUnsafeClassCount; | ||||
| extern const Class * FLEXKnownUnsafeClassList(void); | ||||
| extern NSSet * FLEXKnownUnsafeClassNames(void); | ||||
| extern CFSetRef FLEXKnownUnsafeClasses; | ||||
|  | ||||
| static Class cNSObject = nil, cNSProxy = nil; | ||||
|  | ||||
| __attribute__((constructor)) | ||||
| static void FLEXInitKnownRootClasses(void) { | ||||
|     cNSObject = [NSObject class]; | ||||
|     cNSProxy = [NSProxy class]; | ||||
| } | ||||
|  | ||||
| static inline BOOL FLEXClassIsSafe(Class cls) { | ||||
|     // Is it nil or known to be unsafe? | ||||
|     if (!cls || CFSetContainsValue(FLEXKnownUnsafeClasses, (__bridge void *)cls)) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     // Is it a known root class? | ||||
|     if (!class_getSuperclass(cls)) { | ||||
|         return cls == cNSObject || cls == cNSProxy; | ||||
|     } | ||||
|      | ||||
|     // Probably safe | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| static inline BOOL FLEXClassNameIsSafe(NSString *cls) { | ||||
|     if (!cls) return NO; | ||||
|      | ||||
|     NSSet *ignored = FLEXKnownUnsafeClassNames(); | ||||
|     return ![ignored containsObject:cls]; | ||||
| } | ||||
|  | ||||
| #pragma mark - Ivars | ||||
|  | ||||
| extern CFSetRef FLEXKnownUnsafeIvars; | ||||
|  | ||||
| static inline BOOL FLEXIvarIsSafe(Ivar ivar) { | ||||
|     if (!ivar) return NO; | ||||
|  | ||||
|     return !CFSetContainsValue(FLEXKnownUnsafeIvars, ivar); | ||||
| } | ||||
							
								
								
									
										107
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| // | ||||
| //  FLEXRuntimeSafety.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner on 3/25/17. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeSafety.h" | ||||
|  | ||||
| NSUInteger const kFLEXKnownUnsafeClassCount = 19; | ||||
| Class * _UnsafeClasses = NULL; | ||||
| CFSetRef FLEXKnownUnsafeClasses = nil; | ||||
| CFSetRef FLEXKnownUnsafeIvars = nil; | ||||
|  | ||||
| #define FLEXClassPointerOrCFNull(name) \ | ||||
|     (NSClassFromString(name) ?: (__bridge id)kCFNull) | ||||
|  | ||||
| #define FLEXIvarOrCFNull(cls, name) \ | ||||
|     (class_getInstanceVariable([cls class], name) ?: (void *)kCFNull) | ||||
|  | ||||
| __attribute__((constructor)) | ||||
| static void FLEXRuntimeSafteyInit() { | ||||
|     FLEXKnownUnsafeClasses = CFSetCreate( | ||||
|         kCFAllocatorDefault, | ||||
|         (const void **)(uintptr_t)FLEXKnownUnsafeClassList(), | ||||
|         kFLEXKnownUnsafeClassCount, | ||||
|         nil | ||||
|     ); | ||||
|  | ||||
|     Ivar unsafeIvars[] = { | ||||
|         FLEXIvarOrCFNull(NSURL, "_urlString"), | ||||
|         FLEXIvarOrCFNull(NSURL, "_baseURL"), | ||||
|     }; | ||||
|     FLEXKnownUnsafeIvars = CFSetCreate( | ||||
|         kCFAllocatorDefault, | ||||
|         (const void **)unsafeIvars, | ||||
|         sizeof(unsafeIvars), | ||||
|         nil | ||||
|     ); | ||||
| } | ||||
|  | ||||
| const Class * FLEXKnownUnsafeClassList() { | ||||
|     if (!_UnsafeClasses) { | ||||
|         const Class ignored[] = { | ||||
|             FLEXClassPointerOrCFNull(@"__ARCLite__"), | ||||
|             FLEXClassPointerOrCFNull(@"__NSCFCalendar"), | ||||
|             FLEXClassPointerOrCFNull(@"__NSCFTimer"), | ||||
|             FLEXClassPointerOrCFNull(@"NSCFTimer"), | ||||
|             FLEXClassPointerOrCFNull(@"__NSGenericDeallocHandler"), | ||||
|             FLEXClassPointerOrCFNull(@"NSAutoreleasePool"), | ||||
|             FLEXClassPointerOrCFNull(@"NSPlaceholderNumber"), | ||||
|             FLEXClassPointerOrCFNull(@"NSPlaceholderString"), | ||||
|             FLEXClassPointerOrCFNull(@"NSPlaceholderValue"), | ||||
|             FLEXClassPointerOrCFNull(@"Object"), | ||||
|             FLEXClassPointerOrCFNull(@"VMUArchitecture"), | ||||
|             FLEXClassPointerOrCFNull(@"JSExport"), | ||||
|             FLEXClassPointerOrCFNull(@"__NSAtom"), | ||||
|             FLEXClassPointerOrCFNull(@"_NSZombie_"), | ||||
|             FLEXClassPointerOrCFNull(@"_CNZombie_"), | ||||
|             FLEXClassPointerOrCFNull(@"__NSMessage"), | ||||
|             FLEXClassPointerOrCFNull(@"__NSMessageBuilder"), | ||||
|             FLEXClassPointerOrCFNull(@"FigIrisAutoTrimmerMotionSampleExport"), | ||||
|             // Temporary until we have our own type encoding parser; | ||||
|             // setVectors: has an invalid type encoding and crashes NSMethodSignature | ||||
|             FLEXClassPointerOrCFNull(@"_UIPointVector"), | ||||
|         }; | ||||
|          | ||||
|         assert((sizeof(ignored) / sizeof(Class)) == kFLEXKnownUnsafeClassCount); | ||||
|  | ||||
|         _UnsafeClasses = (Class *)malloc(sizeof(ignored)); | ||||
|         memcpy(_UnsafeClasses, ignored, sizeof(ignored)); | ||||
|     } | ||||
|  | ||||
|     return _UnsafeClasses; | ||||
| } | ||||
|  | ||||
| NSSet * FLEXKnownUnsafeClassNames() { | ||||
|     static NSSet *set = nil; | ||||
|     if (!set) { | ||||
|         NSArray *ignored = @[ | ||||
|             @"__ARCLite__", | ||||
|             @"__NSCFCalendar", | ||||
|             @"__NSCFTimer", | ||||
|             @"NSCFTimer", | ||||
|             @"__NSGenericDeallocHandler", | ||||
|             @"NSAutoreleasePool", | ||||
|             @"NSPlaceholderNumber", | ||||
|             @"NSPlaceholderString", | ||||
|             @"NSPlaceholderValue", | ||||
|             @"Object", | ||||
|             @"VMUArchitecture", | ||||
|             @"JSExport", | ||||
|             @"__NSAtom", | ||||
|             @"_NSZombie_", | ||||
|             @"_CNZombie_", | ||||
|             @"__NSMessage", | ||||
|             @"__NSMessageBuilder", | ||||
|             @"FigIrisAutoTrimmerMotionSampleExport", | ||||
|             @"_UIPointVector", | ||||
|         ]; | ||||
|  | ||||
|         set = [NSSet setWithArray:ignored]; | ||||
|         assert(set.count == kFLEXKnownUnsafeClassCount); | ||||
|     } | ||||
|  | ||||
|     return set; | ||||
| } | ||||
							
								
								
									
										46
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| // | ||||
| //  FLEXTypeEncodingParser.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 8/22/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| /// @return \c YES if the type is supported, \c NO otherwise | ||||
| BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger * _Nullable sizep, NSUInteger * _Nullable alignp); | ||||
|  | ||||
| @interface FLEXTypeEncodingParser : NSObject | ||||
|  | ||||
| /// \c cleanedEncoding is necessary because a type encoding may contain a pointer | ||||
| /// to an unsupported type. \c NSMethodSignature will pass each type to \c NSGetSizeAndAlignment | ||||
| /// which will throw an exception on unsupported struct pointers, and this exception is caught | ||||
| /// by \c NSMethodSignature, but it still bothers anyone debugging with \c objc_exception_throw | ||||
| /// | ||||
| /// @param cleanedEncoding the "safe" type encoding you can pass to \c NSMethodSignature | ||||
| /// @return whether the given type encoding can be passed to | ||||
| /// \c NSMethodSignature without it throwing an exception. | ||||
| + (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding cleaned:(NSString *_Nonnull*_Nullable)cleanedEncoding; | ||||
|  | ||||
| /// @return The type encoding of an individual argument in a method's type encoding string. | ||||
| /// Pass 0 to get the type of the return value. 1 and 2 are `self` and `_cmd` respectively. | ||||
| + (NSString *)type:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx; | ||||
|  | ||||
| /// @return The size in bytes of the typeof an individual argument in a method's type encoding string. | ||||
| /// Pass 0 to get the size of the return value. 1 and 2 are `self` and `_cmd` respectively. | ||||
| + (ssize_t)size:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx; | ||||
|  | ||||
| /// @param unaligned whether to compute the aligned or unaligned size. | ||||
| /// @return The size in bytes, or \c -1 if the type encoding is unsupported. | ||||
| /// Do not pass in the result of \c method_getTypeEncoding | ||||
| + (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(nullable ssize_t *)alignOut unaligned:(BOOL)unaligned; | ||||
|  | ||||
| /// Defaults to \C unaligned:NO | ||||
| + (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(nullable ssize_t *)alignOut; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										900
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										900
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,900 @@ | ||||
| // | ||||
| //  FLEXTypeEncodingParser.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Tanner Bennett on 8/22/19. | ||||
| //  Copyright © 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXTypeEncodingParser.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
|  | ||||
| #define S(__ch) ({ \ | ||||
|     unichar __c = __ch; \ | ||||
|     [[NSString alloc] initWithCharacters:&__c length:1]; \ | ||||
| }) | ||||
|  | ||||
| typedef struct FLEXTypeInfo { | ||||
|     /// The size is unaligned. -1 if not supported at all. | ||||
|     ssize_t size; | ||||
|     ssize_t align; | ||||
|     /// NO if the type cannot be supported at all | ||||
|     /// YES if the type is either fully or partially supported. | ||||
|     BOOL supported; | ||||
|     /// YES if the type was only partially supported, such as in | ||||
|     /// the case of unions in pointer types, or named structure | ||||
|     /// types without member info. These can be corrected manually | ||||
|     /// since they can be fixed or replaced with less info. | ||||
|     BOOL fixesApplied; | ||||
|     /// Whether this type is a union or one of its members | ||||
|     /// recursively contains a union, exlcuding pointers. | ||||
|     /// | ||||
|     /// Unions are tricky because they're supported by | ||||
|     /// \c NSGetSizeAndAlignment but not by \c NSMethodSignature | ||||
|     /// so we need to track whenever a type contains a union | ||||
|     /// so that we can clean it out of pointer types. | ||||
|     BOOL containsUnion; | ||||
|     /// size can only be 0 if not void | ||||
|     BOOL isVoid; | ||||
| } FLEXTypeInfo; | ||||
|  | ||||
| /// Type info for a completely unsupported type. | ||||
| static FLEXTypeInfo FLEXTypeInfoUnsupported = (FLEXTypeInfo){ -1, 0, NO, NO, NO, NO }; | ||||
| /// Type info for the void return type. | ||||
| static FLEXTypeInfo FLEXTypeInfoVoid = (FLEXTypeInfo){ 0, 0, YES, NO, NO, YES }; | ||||
|  | ||||
| /// Builds type info for a fully or partially supported type. | ||||
| static inline FLEXTypeInfo FLEXTypeInfoMake(ssize_t size, ssize_t align, BOOL fixed) { | ||||
|     return (FLEXTypeInfo){ size, align, YES, fixed, NO, NO }; | ||||
| } | ||||
|  | ||||
| /// Builds type info for a fully or partially supported type. | ||||
| static inline FLEXTypeInfo FLEXTypeInfoMakeU(ssize_t size, ssize_t align, BOOL fixed, BOOL hasUnion) { | ||||
|     return (FLEXTypeInfo){ size, align, YES, fixed, hasUnion, NO }; | ||||
| } | ||||
|  | ||||
| BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *alignp) { | ||||
|     NSInteger size = 0; | ||||
|     ssize_t align = 0; | ||||
|     size = [FLEXTypeEncodingParser sizeForTypeEncoding:@(type) alignment:&align]; | ||||
|      | ||||
|     if (size == -1) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     if (sizep) { | ||||
|         *sizep = (NSUInteger)size; | ||||
|     } | ||||
|      | ||||
|     if (alignp) { | ||||
|         *alignp = (NSUInteger)size; | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| @interface FLEXTypeEncodingParser () | ||||
| @property (nonatomic, readonly) NSScanner *scan; | ||||
| @property (nonatomic, readonly) NSString *scanned; | ||||
| @property (nonatomic, readonly) NSString *unscanned; | ||||
| @property (nonatomic, readonly) char nextChar; | ||||
|  | ||||
| /// Replacements are made to this string as we scan as needed | ||||
| @property (nonatomic) NSMutableString *cleaned; | ||||
| /// Offset for \e further replacements to be made within \c cleaned | ||||
| @property (nonatomic, readonly) NSUInteger cleanedReplacingOffset; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXTypeEncodingParser | ||||
|  | ||||
| - (NSString *)scanned { | ||||
|     return [self.scan.string substringToIndex:self.scan.scanLocation]; | ||||
| } | ||||
|  | ||||
| - (NSString *)unscanned { | ||||
|     return [self.scan.string substringFromIndex:self.scan.scanLocation]; | ||||
| } | ||||
|  | ||||
| #pragma mark Initialization | ||||
|  | ||||
| - (id)initWithObjCTypes:(NSString *)typeEncoding { | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _scan = [NSScanner scannerWithString:typeEncoding]; | ||||
|         _scan.caseSensitive = YES; | ||||
|         _cleaned = typeEncoding.mutableCopy; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark Public | ||||
|  | ||||
| + (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding cleaned:(NSString * __autoreleasing *)cleanedEncoding { | ||||
|     if (!typeEncoding.length) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding]; | ||||
|      | ||||
|     while (!parser.scan.isAtEnd) { | ||||
|         FLEXTypeInfo info = [parser parseNextType]; | ||||
|          | ||||
|         if (!info.supported || info.containsUnion || (info.size == 0 && !info.isVoid)) { | ||||
|             return NO; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     if (cleanedEncoding) { | ||||
|         *cleanedEncoding = parser.cleaned.copy; | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| + (NSString *)type:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx { | ||||
|     FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding]; | ||||
|  | ||||
|     // Scan up to the argument we want | ||||
|     for (NSUInteger i = 0; i < idx; i++) { | ||||
|         if (![parser scanPastArg]) { | ||||
|             [NSException raise:NSRangeException | ||||
|                 format:@"Index %@ out of bounds for type encoding '%@'",  | ||||
|                 @(idx), typeEncoding | ||||
|             ]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return [parser scanArg]; | ||||
| } | ||||
|  | ||||
| + (ssize_t)size:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx { | ||||
|     return [self sizeForTypeEncoding:[self type:typeEncoding forMethodArgumentAtIndex:idx] alignment:nil]; | ||||
| } | ||||
|  | ||||
| + (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut { | ||||
|     return [self sizeForTypeEncoding:type alignment:alignOut unaligned:NO]; | ||||
| } | ||||
|  | ||||
| + (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut unaligned:(BOOL)unaligned { | ||||
|     FLEXTypeInfo info = [self parseType:type]; | ||||
|      | ||||
|     ssize_t size = info.size; | ||||
|     ssize_t align = info.align; | ||||
|      | ||||
|     if (info.supported) { | ||||
|         if (alignOut) { | ||||
|             *alignOut = align; | ||||
|         } | ||||
|  | ||||
|         if (!unaligned) { | ||||
|             size += size % align; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     // size is -1 if not supported | ||||
|     return size; | ||||
| } | ||||
|  | ||||
| + (FLEXTypeInfo)parseType:(NSString *)type cleaned:(NSString * __autoreleasing *)cleanedEncoding { | ||||
|     FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:type]; | ||||
|     FLEXTypeInfo info = [parser parseNextType]; | ||||
|     if (cleanedEncoding) { | ||||
|         *cleanedEncoding = parser.cleaned; | ||||
|     } | ||||
|      | ||||
|     return info; | ||||
| } | ||||
|  | ||||
| + (FLEXTypeInfo)parseType:(NSString *)type { | ||||
|     return [self parseType:type cleaned:nil]; | ||||
| } | ||||
|  | ||||
| #pragma mark Private | ||||
|  | ||||
| - (NSCharacterSet *)identifierFirstCharCharacterSet { | ||||
|     static NSCharacterSet *identifierFirstSet = nil; | ||||
|     static dispatch_once_t onceToken; | ||||
|     dispatch_once(&onceToken, ^{ | ||||
|         NSString *allowed = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$"; | ||||
|         identifierFirstSet = [NSCharacterSet characterSetWithCharactersInString:allowed]; | ||||
|     }); | ||||
|      | ||||
|     return identifierFirstSet; | ||||
| } | ||||
|  | ||||
| - (NSCharacterSet *)identifierCharacterSet { | ||||
|     static NSCharacterSet *identifierSet = nil; | ||||
|     static dispatch_once_t onceToken; | ||||
|     dispatch_once(&onceToken, ^{ | ||||
|         NSString *allowed = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$1234567890"; | ||||
|         identifierSet = [NSCharacterSet characterSetWithCharactersInString:allowed]; | ||||
|     }); | ||||
|      | ||||
|     return identifierSet; | ||||
| } | ||||
|  | ||||
| - (char)nextChar { | ||||
|     NSScanner *scan = self.scan; | ||||
|     return [scan.string characterAtIndex:scan.scanLocation]; | ||||
| } | ||||
|  | ||||
| /// For scanning struct/class names | ||||
| - (NSString *)scanIdentifier { | ||||
|     NSString *prefix = nil, *suffix = nil; | ||||
|      | ||||
|     // Identifiers cannot start with a number | ||||
|     if (![self.scan scanCharactersFromSet:self.identifierFirstCharCharacterSet intoString:&prefix]) { | ||||
|         return nil; | ||||
|     } | ||||
|      | ||||
|     // Optional because identifier may just be one character | ||||
|     [self.scan scanCharactersFromSet:self.identifierCharacterSet intoString:&suffix]; | ||||
|      | ||||
|     if (suffix) { | ||||
|         return [prefix stringByAppendingString:suffix]; | ||||
|     } | ||||
|      | ||||
|     return prefix; | ||||
| } | ||||
|  | ||||
| /// @return the size in bytes | ||||
| - (ssize_t)sizeForType:(FLEXTypeEncoding)type { | ||||
|     switch (type) { | ||||
|         case FLEXTypeEncodingChar: return sizeof(char); | ||||
|         case FLEXTypeEncodingInt: return sizeof(int); | ||||
|         case FLEXTypeEncodingShort: return sizeof(short); | ||||
|         case FLEXTypeEncodingLong: return sizeof(long); | ||||
|         case FLEXTypeEncodingLongLong: return sizeof(long long); | ||||
|         case FLEXTypeEncodingUnsignedChar: return sizeof(unsigned char); | ||||
|         case FLEXTypeEncodingUnsignedInt: return sizeof(unsigned int); | ||||
|         case FLEXTypeEncodingUnsignedShort: return sizeof(unsigned short); | ||||
|         case FLEXTypeEncodingUnsignedLong: return sizeof(unsigned long); | ||||
|         case FLEXTypeEncodingUnsignedLongLong: return sizeof(unsigned long long); | ||||
|         case FLEXTypeEncodingFloat: return sizeof(float); | ||||
|         case FLEXTypeEncodingDouble: return sizeof(double); | ||||
|         case FLEXTypeEncodingLongDouble: return sizeof(long double); | ||||
|         case FLEXTypeEncodingCBool: return sizeof(_Bool); | ||||
|         case FLEXTypeEncodingVoid: return 0; | ||||
|         case FLEXTypeEncodingCString: return sizeof(char *); | ||||
|         case FLEXTypeEncodingObjcObject:  return sizeof(id); | ||||
|         case FLEXTypeEncodingObjcClass:  return sizeof(Class); | ||||
|         case FLEXTypeEncodingSelector: return sizeof(SEL); | ||||
|         // Unknown / '?' is typically a pointer. In the rare case | ||||
|         // it isn't, such as in '{?=...}', it is never passed here. | ||||
|         case FLEXTypeEncodingUnknown: | ||||
|         case FLEXTypeEncodingPointer: return sizeof(uintptr_t); | ||||
|  | ||||
|         default: return -1; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (FLEXTypeInfo)parseNextType { | ||||
|     NSUInteger start = self.scan.scanLocation; | ||||
|  | ||||
|     // Check for void first | ||||
|     if ([self scanChar:FLEXTypeEncodingVoid]) { | ||||
|         // Skip argument frame for method signatures | ||||
|         [self scanSize]; | ||||
|         return FLEXTypeInfoVoid; | ||||
|     } | ||||
|  | ||||
|     // Scan optional const | ||||
|     [self scanChar:FLEXTypeEncodingConst]; | ||||
|  | ||||
|     // Check for pointer, then scan next | ||||
|     if ([self scanChar:FLEXTypeEncodingPointer]) { | ||||
|         // Recurse to scan something else | ||||
|         NSUInteger pointerTypeStart = self.scan.scanLocation; | ||||
|         if ([self scanPastArg]) { | ||||
|             // Make sure the pointer type is supported, and clean it if not | ||||
|             NSUInteger pointerTypeLength = self.scan.scanLocation - pointerTypeStart; | ||||
|             NSString *pointerType = [self.scan.string | ||||
|                 substringWithRange:NSMakeRange(pointerTypeStart, pointerTypeLength) | ||||
|             ]; | ||||
|              | ||||
|             // Deeeep nested cleaning info gets lost here | ||||
|             NSString *cleaned = nil; | ||||
|             FLEXTypeInfo info = [self.class parseType:pointerType cleaned:&cleaned]; | ||||
|             BOOL needsCleaning = !info.supported || info.containsUnion || info.fixesApplied; | ||||
|              | ||||
|             // Clean the type if it is unsupported, malformed, or contains a union. | ||||
|             // (Unions are supported by NSGetSizeAndAlignment but not | ||||
|             // supported by NSMethodSignature for some reason) | ||||
|             if (needsCleaning) { | ||||
|                 // If unsupported, no cleaning occurred in parseType:cleaned: above. | ||||
|                 // Otherwise, the type is partially supported and we did clean it, | ||||
|                 // and we will replace this type with the cleaned type from above. | ||||
|                 if (!info.supported || info.containsUnion) { | ||||
|                     cleaned = [self cleanPointeeTypeAtLocation:pointerTypeStart]; | ||||
|                 } | ||||
|                  | ||||
|                 NSInteger offset = self.cleanedReplacingOffset; | ||||
|                 NSInteger location = pointerTypeStart - offset; | ||||
|                 [self.cleaned replaceCharactersInRange:NSMakeRange( | ||||
|                     location, pointerTypeLength | ||||
|                 ) withString:cleaned]; | ||||
|             } | ||||
|              | ||||
|             // Skip optional frame offset | ||||
|             [self scanSize]; | ||||
|              | ||||
|             ssize_t size = [self sizeForType:FLEXTypeEncodingPointer]; | ||||
|             return FLEXTypeInfoMake(size, size, !info.supported || info.fixesApplied); | ||||
|         } else { | ||||
|             // Scan failed, abort | ||||
|             self.scan.scanLocation = start; | ||||
|             return FLEXTypeInfoUnsupported; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Check for struct/union/array | ||||
|     char next = self.nextChar; | ||||
|     BOOL didScanSUA = YES, structOrUnion = NO, isUnion = NO; | ||||
|     FLEXTypeEncoding opening = FLEXTypeEncodingNull, closing = FLEXTypeEncodingNull; | ||||
|     switch (next) { | ||||
|         case FLEXTypeEncodingStructBegin: | ||||
|             structOrUnion = YES; | ||||
|             opening = FLEXTypeEncodingStructBegin; | ||||
|             closing = FLEXTypeEncodingStructEnd; | ||||
|             break; | ||||
|         case FLEXTypeEncodingUnionBegin: | ||||
|             structOrUnion = isUnion = YES; | ||||
|             opening = FLEXTypeEncodingUnionBegin; | ||||
|             closing = FLEXTypeEncodingUnionEnd; | ||||
|             break; | ||||
|         case FLEXTypeEncodingArrayBegin: | ||||
|             opening = FLEXTypeEncodingArrayBegin; | ||||
|             closing = FLEXTypeEncodingArrayEnd; | ||||
|             break; | ||||
|              | ||||
|         default: | ||||
|             didScanSUA = NO; | ||||
|             break; | ||||
|     } | ||||
|      | ||||
|     if (didScanSUA) { | ||||
|         BOOL containsUnion = isUnion; | ||||
|         BOOL fixesApplied = NO; | ||||
|          | ||||
|         NSUInteger backup = self.scan.scanLocation; | ||||
|  | ||||
|         // Ensure we have a closing tag | ||||
|         if (![self scanPair:opening close:closing]) { | ||||
|             // Scan failed, abort | ||||
|             self.scan.scanLocation = start; | ||||
|             return FLEXTypeInfoUnsupported; | ||||
|         } | ||||
|  | ||||
|         // Move cursor just after opening tag (struct/union/array) | ||||
|         NSInteger arrayCount = -1; | ||||
|         self.scan.scanLocation = backup + 1; | ||||
|          | ||||
|         if (!structOrUnion) { | ||||
|             arrayCount = [self scanSize]; | ||||
|             if (!arrayCount || self.nextChar == FLEXTypeEncodingArrayEnd) { | ||||
|                 // Malformed array type: | ||||
|                 // 1. Arrays must have a count after the opening brace | ||||
|                 // 2. Arrays must have an element type after the count | ||||
|                 self.scan.scanLocation = start; | ||||
|                 return FLEXTypeInfoUnsupported; | ||||
|             } | ||||
|         } else { | ||||
|             // If we encounter the ?= portion of something like {?=b8b4b1b1b18[8S]} | ||||
|             // then we skip over it, since it means nothing to us in this context. | ||||
|             // It is completely optional, and if it fails, we go right back where we were. | ||||
|             if (![self scanTypeName] && self.nextChar == FLEXTypeEncodingUnknown) { | ||||
|                 // Exception: we are trying to parse {?} which is invalid | ||||
|                 self.scan.scanLocation = start; | ||||
|                 return FLEXTypeInfoUnsupported; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Sum sizes of members together: | ||||
|         // Scan for bitfields before checking for other members | ||||
|         // | ||||
|         // Arrays will only have one "member," but | ||||
|         // this logic still works for them | ||||
|         ssize_t sizeSoFar = 0; | ||||
|         ssize_t maxAlign = 0; | ||||
|         NSMutableString *cleanedBackup = self.cleaned.mutableCopy; | ||||
|          | ||||
|         while (![self scanChar:closing]) { | ||||
|             next = self.nextChar; | ||||
|             // Check for bitfields, which we cannot support because | ||||
|             // type encodings for bitfields do not include alignment info | ||||
|             if (next == FLEXTypeEncodingBitField) { | ||||
|                 self.scan.scanLocation = start; | ||||
|                 return FLEXTypeInfoUnsupported; | ||||
|             } | ||||
|  | ||||
|             // Structure fields could be named | ||||
|             if (next == FLEXTypeEncodingQuote) { | ||||
|                 [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote]; | ||||
|             } | ||||
|  | ||||
|             FLEXTypeInfo info = [self parseNextType]; | ||||
|             if (!info.supported || info.containsUnion) { | ||||
|                 // The above call is the only time in this method where | ||||
|                 // `cleaned` might be mutated recursively, so this is the | ||||
|                 // only place where we need to keep and restore a backup | ||||
|                 // | ||||
|                 // For instance, if we've been iterating over the members | ||||
|                 // of a struct and we've encountered a few pointers so far | ||||
|                 // that we needed to clean, and suddenly we come across an | ||||
|                 // unsupported member, we need to be able to "rewind" and | ||||
|                 // undo any changes to `self.cleaned` so that the parent | ||||
|                 // call in the call stack can wipe the current structure | ||||
|                 // clean entirely if needed. Example below: | ||||
|                 // | ||||
|                 //      Initial: ^{foo=^{pair<d,d>}{^pair<i,i>}{invalid_type<d>}} | ||||
|                 //                       v-- here | ||||
|                 //    1st clean: ^{foo=^{?=}{^pair<i,i>}{invalid_type<d>} | ||||
|                 //                           v-- here | ||||
|                 //    2nd clean: ^{foo=^{?=}{?=}{invalid_type<d>} | ||||
|                 //                               v-- here | ||||
|                 //  Can't clean: ^{foo=^{?=}{?=}{invalid_type<d>} | ||||
|                 //                 v-- to here | ||||
|                 //       Rewind: ^{foo=^{pair<d,d>}{^pair<i,i>}{invalid_type<d>}} | ||||
|                 //  Final clean: ^{foo=} | ||||
|                 self.cleaned = cleanedBackup; | ||||
|                 self.scan.scanLocation = start; | ||||
|                 return FLEXTypeInfoUnsupported; | ||||
|             } | ||||
|              | ||||
|             // Unions are the size of their largest member, | ||||
|             // arrays are element.size x length, and | ||||
|             // structs are the sum of their members | ||||
|             if (structOrUnion) { | ||||
|                 if (isUnion) { // Union | ||||
|                     sizeSoFar = MAX(sizeSoFar, info.size); | ||||
|                 } else { // Struct | ||||
|                     sizeSoFar += info.size; | ||||
|                 } | ||||
|             } else { // Array | ||||
|                 sizeSoFar = info.size * arrayCount; | ||||
|             } | ||||
|              | ||||
|             // Propogate the max alignment and other metadata | ||||
|             maxAlign = MAX(maxAlign, info.align); | ||||
|             containsUnion = containsUnion || info.containsUnion; | ||||
|             fixesApplied = fixesApplied || info.fixesApplied; | ||||
|         } | ||||
|          | ||||
|         // Skip optional frame offset | ||||
|         [self scanSize]; | ||||
|  | ||||
|         return FLEXTypeInfoMakeU(sizeSoFar, maxAlign, fixesApplied, containsUnion); | ||||
|     } | ||||
|      | ||||
|     // Scan single thing and possible size and return | ||||
|     ssize_t size = -1; | ||||
|     char t = self.nextChar; | ||||
|     switch (t) { | ||||
|         case FLEXTypeEncodingUnknown: | ||||
|         case FLEXTypeEncodingChar: | ||||
|         case FLEXTypeEncodingInt: | ||||
|         case FLEXTypeEncodingShort: | ||||
|         case FLEXTypeEncodingLong: | ||||
|         case FLEXTypeEncodingLongLong: | ||||
|         case FLEXTypeEncodingUnsignedChar: | ||||
|         case FLEXTypeEncodingUnsignedInt: | ||||
|         case FLEXTypeEncodingUnsignedShort: | ||||
|         case FLEXTypeEncodingUnsignedLong: | ||||
|         case FLEXTypeEncodingUnsignedLongLong: | ||||
|         case FLEXTypeEncodingFloat: | ||||
|         case FLEXTypeEncodingDouble: | ||||
|         case FLEXTypeEncodingLongDouble: | ||||
|         case FLEXTypeEncodingCBool: | ||||
|         case FLEXTypeEncodingCString: | ||||
|         case FLEXTypeEncodingSelector: | ||||
|         case FLEXTypeEncodingBitField: { | ||||
|             self.scan.scanLocation++; | ||||
|             // Skip optional frame offset | ||||
|             [self scanSize]; | ||||
|              | ||||
|             if (t == FLEXTypeEncodingBitField) { | ||||
|                 self.scan.scanLocation = start; | ||||
|                 return FLEXTypeInfoUnsupported; | ||||
|             } else { | ||||
|                 // Compute size | ||||
|                 size = [self sizeForType:t]; | ||||
|             } | ||||
|         } | ||||
|             break; | ||||
|          | ||||
|         case FLEXTypeEncodingObjcObject: | ||||
|         case FLEXTypeEncodingObjcClass: { | ||||
|             self.scan.scanLocation++; | ||||
|             // These might have numbers OR quotes after them | ||||
|             // Skip optional frame offset | ||||
|             [self scanSize]; | ||||
|             [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote]; | ||||
|             size = sizeof(id); | ||||
|         } | ||||
|             break; | ||||
|              | ||||
|         default: break; | ||||
|     } | ||||
|  | ||||
|     if (size > 0) { | ||||
|         // Alignment of scalar types is its size | ||||
|         return FLEXTypeInfoMake(size, size, NO); | ||||
|     } | ||||
|  | ||||
|     self.scan.scanLocation = start; | ||||
|     return FLEXTypeInfoUnsupported; | ||||
| } | ||||
|  | ||||
| - (BOOL)scanString:(NSString *)str { | ||||
|     return [self.scan scanString:str intoString:nil]; | ||||
| } | ||||
|  | ||||
| - (BOOL)canScanString:(NSString *)str { | ||||
|     NSScanner *scan = self.scan; | ||||
|     NSUInteger len = str.length; | ||||
|     unichar buff1[len], buff2[len]; | ||||
|      | ||||
|     [str getCharacters:buff1]; | ||||
|     [scan.string getCharacters:buff2 range:NSMakeRange(scan.scanLocation, len)]; | ||||
|     if (memcmp(buff1, buff2, len) == 0) { | ||||
|         return YES; | ||||
|     } | ||||
|  | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| - (BOOL)canScanChar:(char)c { | ||||
|     // By avoiding any ARC calls on these two objects which we know won't be | ||||
|     // free'd out from under us, we're making HUGE performance savings in this | ||||
|     // parser, because this method is one of the most-used methods of the parser. | ||||
|     // This is probably the most performance-critical method in this class. | ||||
|     __unsafe_unretained NSScanner *scan = self.scan; | ||||
|     __unsafe_unretained NSString *string = scan.string; | ||||
|     if (scan.scanLocation >= string.length) return NO; | ||||
|      | ||||
|     return [string characterAtIndex:scan.scanLocation] == c; | ||||
| } | ||||
|  | ||||
| - (BOOL)scanChar:(char)c { | ||||
|     if ([self canScanChar:c]) { | ||||
|         self.scan.scanLocation++; | ||||
|         return YES; | ||||
|     } | ||||
|      | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| - (BOOL)scanChar:(char)c into:(char *)ref { | ||||
|     if ([self scanChar:c]) { | ||||
|         *ref = c; | ||||
|         return YES; | ||||
|     } | ||||
|  | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| - (ssize_t)scanSize { | ||||
|     NSInteger size = 0; | ||||
|     if ([self.scan scanInteger:&size]) { | ||||
|         return size; | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| - (NSString *)scanPair:(char)c1 close:(char)c2 { | ||||
|     // Starting position and string variables | ||||
|     NSUInteger start = self.scan.scanLocation; | ||||
|     NSString *s1 = S(c1); | ||||
|  | ||||
|     // Scan opening tag | ||||
|     if (![self scanChar:c1]) { | ||||
|         self.scan.scanLocation = start; | ||||
|         return nil; | ||||
|     } | ||||
|  | ||||
|     // Character set for scanning up to either symbol | ||||
|     NSCharacterSet *bothChars = ({ | ||||
|         unichar buff[2] = { c1, c2 }; | ||||
|         NSString *bothCharsStr = [[NSString alloc] initWithCharacters:buff length:2]; | ||||
|         [NSCharacterSet characterSetWithCharactersInString:bothCharsStr]; | ||||
|     }); | ||||
|  | ||||
|     // Stack for finding pairs, starting with the opening symbol | ||||
|     NSMutableArray *stack = [NSMutableArray arrayWithObject:s1]; | ||||
|  | ||||
|     // Algorithm for scanning to the closing end of a pair of opening/closing symbols | ||||
|     // scanUpToCharactersFromSet:intoString: returns NO if you're already at one of the chars, | ||||
|     // so we need to check if we can actually scan one if it returns NO | ||||
|     while ([self.scan scanUpToCharactersFromSet:bothChars intoString:nil] || | ||||
|            [self canScanChar:c1] || [self canScanChar:c2]) { | ||||
|         // Closing symbol found | ||||
|         if ([self scanChar:c2]) { | ||||
|             if (!stack.count) { | ||||
|                 // Abort, no matching opening symbol | ||||
|                 self.scan.scanLocation = start; | ||||
|                 return nil; | ||||
|             } | ||||
|  | ||||
|             // Pair found, pop opening symbol | ||||
|             [stack removeLastObject]; | ||||
|             // Exit loop if we reached the closing brace we needed | ||||
|             if (!stack.count) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         // Opening symbol found | ||||
|         if ([self scanChar:c1]) { | ||||
|             // Begin pair | ||||
|             [stack addObject:s1]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (stack.count) { | ||||
|         // Abort, no matching closing symbol | ||||
|         self.scan.scanLocation = start; | ||||
|         return nil; | ||||
|     } | ||||
|  | ||||
|     // Slice out the string we just scanned | ||||
|     return [self.scan.string | ||||
|         substringWithRange:NSMakeRange(start, self.scan.scanLocation - start) | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| - (BOOL)scanPastArg { | ||||
|     NSUInteger start = self.scan.scanLocation; | ||||
|  | ||||
|     // Check for void first | ||||
|     if ([self scanChar:FLEXTypeEncodingVoid]) { | ||||
|         return YES; | ||||
|     } | ||||
|  | ||||
|     // Scan optional const | ||||
|     [self scanChar:FLEXTypeEncodingConst]; | ||||
|  | ||||
|     // Check for pointer, then scan next | ||||
|     if ([self scanChar:FLEXTypeEncodingPointer]) { | ||||
|         // Recurse to scan something else | ||||
|         if ([self scanPastArg]) { | ||||
|             return YES; | ||||
|         } else { | ||||
|             // Scan failed, abort | ||||
|             self.scan.scanLocation = start; | ||||
|             return NO; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     char next = self.nextChar; | ||||
|  | ||||
|     // Check for struct/union/array, scan past it | ||||
|     FLEXTypeEncoding opening = FLEXTypeEncodingNull, closing = FLEXTypeEncodingNull; | ||||
|     BOOL checkPair = YES; | ||||
|     switch (next) { | ||||
|         case FLEXTypeEncodingStructBegin: | ||||
|             opening = FLEXTypeEncodingStructBegin; | ||||
|             closing = FLEXTypeEncodingStructEnd; | ||||
|             break; | ||||
|         case FLEXTypeEncodingUnionBegin: | ||||
|             opening = FLEXTypeEncodingUnionBegin; | ||||
|             closing = FLEXTypeEncodingUnionEnd; | ||||
|             break; | ||||
|         case FLEXTypeEncodingArrayBegin: | ||||
|             opening = FLEXTypeEncodingArrayBegin; | ||||
|             closing = FLEXTypeEncodingArrayEnd; | ||||
|             break; | ||||
|              | ||||
|         default: | ||||
|             checkPair = NO; | ||||
|             break; | ||||
|     } | ||||
|      | ||||
|     if (checkPair && [self scanPair:opening close:closing]) { | ||||
|         return YES; | ||||
|     } | ||||
|  | ||||
|     // Scan single thing and possible size and return | ||||
|     switch (next) { | ||||
|         case FLEXTypeEncodingUnknown: | ||||
|         case FLEXTypeEncodingChar: | ||||
|         case FLEXTypeEncodingInt: | ||||
|         case FLEXTypeEncodingShort: | ||||
|         case FLEXTypeEncodingLong: | ||||
|         case FLEXTypeEncodingLongLong: | ||||
|         case FLEXTypeEncodingUnsignedChar: | ||||
|         case FLEXTypeEncodingUnsignedInt: | ||||
|         case FLEXTypeEncodingUnsignedShort: | ||||
|         case FLEXTypeEncodingUnsignedLong: | ||||
|         case FLEXTypeEncodingUnsignedLongLong: | ||||
|         case FLEXTypeEncodingFloat: | ||||
|         case FLEXTypeEncodingDouble: | ||||
|         case FLEXTypeEncodingLongDouble: | ||||
|         case FLEXTypeEncodingCBool: | ||||
|         case FLEXTypeEncodingCString: | ||||
|         case FLEXTypeEncodingSelector: | ||||
|         case FLEXTypeEncodingBitField: { | ||||
|             self.scan.scanLocation++; | ||||
|             // Size is optional | ||||
|             [self scanSize]; | ||||
|             return YES; | ||||
|         } | ||||
|          | ||||
|         case FLEXTypeEncodingObjcObject: | ||||
|         case FLEXTypeEncodingObjcClass: { | ||||
|             self.scan.scanLocation++; | ||||
|             // These might have numbers OR quotes after them | ||||
|             [self scanSize] || [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote]; | ||||
|             return YES; | ||||
|         } | ||||
|              | ||||
|         default: break; | ||||
|     } | ||||
|  | ||||
|     self.scan.scanLocation = start; | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| - (NSString *)scanArg { | ||||
|     NSUInteger start = self.scan.scanLocation; | ||||
|     if (![self scanPastArg]) { | ||||
|         return nil; | ||||
|     } | ||||
|  | ||||
|     return [self.scan.string | ||||
|         substringWithRange:NSMakeRange(start, self.scan.scanLocation - start) | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| - (BOOL)scanTypeName { | ||||
|     NSUInteger start = self.scan.scanLocation; | ||||
|  | ||||
|     // The ?= portion of something like {?=b8b4b1b1b18[8S]} | ||||
|     if ([self scanChar:FLEXTypeEncodingUnknown]) { | ||||
|         if (![self scanString:@"="]) { | ||||
|             // No size information available for strings like {?=} | ||||
|             self.scan.scanLocation = start; | ||||
|             return NO; | ||||
|         } | ||||
|     } else { | ||||
|         if (![self scanIdentifier] || ![self scanString:@"="]) { | ||||
|             // 1. Not a valid identifier | ||||
|             // 2. No size information available for strings like {CGPoint} | ||||
|             self.scan.scanLocation = start; | ||||
|             return NO; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (NSString *)extractTypeNameFromScanLocation:(BOOL)allowMissingTypeInfo closing:(FLEXTypeEncoding)closeTag { | ||||
|     NSUInteger start = self.scan.scanLocation; | ||||
|  | ||||
|     // The ?= portion of something like {?=b8b4b1b1b18[8S]} | ||||
|     if ([self scanChar:FLEXTypeEncodingUnknown]) { | ||||
|         return @"?"; | ||||
|     } else { | ||||
|         NSString *typeName = [self scanIdentifier]; | ||||
|         char next = self.nextChar; | ||||
|          | ||||
|         if (!typeName) { | ||||
|             // Did not scan an identifier | ||||
|             self.scan.scanLocation = start; | ||||
|             return nil; | ||||
|         } | ||||
|          | ||||
|         switch (next) { | ||||
|             case '=': | ||||
|                 return typeName; | ||||
|                  | ||||
|             default: { | ||||
|                 // = is non-optional unless we allowMissingTypeInfo, in whcih | ||||
|                 // case the next character needs to be a closing brace | ||||
|                 if (allowMissingTypeInfo && next == closeTag) { | ||||
|                     return typeName; | ||||
|                 } else { | ||||
|                     // Not a valid identifier; possibly a generic C++ type | ||||
|                     // i.e. {pair<T, U>} where `name` was found as `pair` | ||||
|                     self.scan.scanLocation = start; | ||||
|                     return nil; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (NSString *)cleanPointeeTypeAtLocation:(NSUInteger)scanLocation { | ||||
|     NSUInteger start = self.scan.scanLocation; | ||||
|     self.scan.scanLocation = scanLocation; | ||||
|      | ||||
|     // The return / cleanup code for when the scanned type is already clean | ||||
|     NSString * (^typeIsClean)(void) = ^NSString * { | ||||
|         NSString *clean = [self.scan.string | ||||
|             substringWithRange:NSMakeRange(scanLocation, self.scan.scanLocation - scanLocation) | ||||
|         ]; | ||||
|         // Reset scan location even on success, because this method is not supposed to change it | ||||
|         self.scan.scanLocation = start; | ||||
|         return clean; | ||||
|     }; | ||||
|  | ||||
|     // No void, this is not a return type | ||||
|  | ||||
|     // Scan optional const | ||||
|     [self scanChar:FLEXTypeEncodingConst]; | ||||
|      | ||||
|     char next = self.nextChar; | ||||
|     switch (next) { | ||||
|         case FLEXTypeEncodingPointer: | ||||
|             // Recurse to scan something else | ||||
|             [self scanChar:next]; | ||||
|             return [self cleanPointeeTypeAtLocation:self.scan.scanLocation]; | ||||
|              | ||||
|         case FLEXTypeEncodingArrayBegin: | ||||
|             // All arrays are supported, scan past them | ||||
|             if ([self scanPair:FLEXTypeEncodingArrayBegin close:FLEXTypeEncodingArrayEnd]) { | ||||
|                 return typeIsClean(); | ||||
|             } | ||||
|             break; | ||||
|              | ||||
|         case FLEXTypeEncodingUnionBegin: | ||||
|             // Unions are not supported at all in NSMethodSignature | ||||
|             // We could check for the closing token to be safe, but eh | ||||
|             self.scan.scanLocation = start; | ||||
|             return @"?"; | ||||
|              | ||||
|         case FLEXTypeEncodingStructBegin: { | ||||
|             FLEXTypeInfo info = [self.class parseType:self.unscanned]; | ||||
|             if (info.supported && !info.fixesApplied) { | ||||
|                 [self scanPastArg]; | ||||
|                 return typeIsClean(); | ||||
|             } | ||||
|              | ||||
|             // The structure we just tried to scan is unsupported, so just return its name | ||||
|             // if it has one. If not, just return a question mark. | ||||
|             self.scan.scanLocation++; // Skip past { | ||||
|             NSString *name = [self extractTypeNameFromScanLocation:YES closing:FLEXTypeEncodingStructEnd]; | ||||
|             if (name) { | ||||
|                 // Got the name, scan past the closing token | ||||
|                 [self.scan scanUpToString:@"}" intoString:nil]; | ||||
|                 if (![self scanChar:FLEXTypeEncodingStructEnd]) { | ||||
|                     // Missing struct close token | ||||
|                     self.scan.scanLocation = start; | ||||
|                     return nil; | ||||
|                 } | ||||
|             } else { | ||||
|                 // Did not scan valid identifier, possibly a C++ type | ||||
|                 self.scan.scanLocation = start; | ||||
|                 return @"{?=}"; | ||||
|             } | ||||
|              | ||||
|             // Reset scan location even on success, because this method is not supposed to change it | ||||
|             self.scan.scanLocation = start; | ||||
|             return ({ // "{name=}" | ||||
|                 NSMutableString *format = @"{".mutableCopy; | ||||
|                 [format appendString:name]; | ||||
|                 [format appendString:@"=}"]; | ||||
|                 format; | ||||
|             }); | ||||
|         } | ||||
|          | ||||
|         default: | ||||
|             break; | ||||
|     } | ||||
|      | ||||
|     // Check for other types, which in theory are all valid but whatever | ||||
|     FLEXTypeInfo info = [self parseNextType]; | ||||
|     if (info.supported && !info.fixesApplied) { | ||||
|         return typeIsClean(); | ||||
|     } | ||||
|      | ||||
|     self.scan.scanLocation = start; | ||||
|     return @"?"; | ||||
| } | ||||
|  | ||||
| - (NSUInteger)cleanedReplacingOffset { | ||||
|     return self.scan.string.length - self.cleaned.length; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,58 @@ | ||||
| // | ||||
| //  FLEXBlockDescription.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Oliver Letterer on 2012-09-01 | ||||
| //  Forked from CTObjectiveCRuntimeAdditions (MIT License) | ||||
| //  https://github.com/ebf/CTObjectiveCRuntimeAdditions | ||||
| // | ||||
| //  Copyright (c) 2020 FLEX Team-EDV Beratung Föllmer GmbH | ||||
| //  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, subject to the following conditions: | ||||
| //  The above copyright notice and this permission notice shall be included in all copies or | ||||
| //  substantial portions of the Software. | ||||
| // | ||||
| //  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. | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
|  | ||||
| typedef NS_OPTIONS(NSUInteger, FLEXBlockOptions) { | ||||
|    FLEXBlockOptionHasCopyDispose = (1 << 25), | ||||
|    FLEXBlockOptionHasCtor        = (1 << 26), // helpers have C++ code | ||||
|    FLEXBlockOptionIsGlobal       = (1 << 28), | ||||
|    FLEXBlockOptionHasStret       = (1 << 29), // IFF BLOCK_HAS_SIGNATURE | ||||
|    FLEXBlockOptionHasSignature   = (1 << 30), | ||||
| }; | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| #pragma mark - | ||||
| @interface FLEXBlockDescription : NSObject | ||||
|  | ||||
| + (instancetype)describing:(id)block; | ||||
|  | ||||
| @property (nonatomic, readonly, nullable) NSMethodSignature *signature; | ||||
| @property (nonatomic, readonly, nullable) NSString *signatureString; | ||||
| @property (nonatomic, readonly, nullable) NSString *sourceDeclaration; | ||||
| @property (nonatomic, readonly) FLEXBlockOptions flags; | ||||
| @property (nonatomic, readonly) NSUInteger size; | ||||
| @property (nonatomic, readonly) NSString *summary; | ||||
| @property (nonatomic, readonly) id block; | ||||
|  | ||||
| - (BOOL)isCompatibleForBlockSwizzlingWithMethodSignature:(NSMethodSignature *)methodSignature; | ||||
|  | ||||
| @end | ||||
|  | ||||
| #pragma mark - | ||||
| @interface NSBlock : NSObject | ||||
| - (void)invoke; | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
| @@ -0,0 +1,157 @@ | ||||
| // | ||||
| //  FLEXBlockDescription.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Created by Oliver Letterer on 2012-09-01 | ||||
| //  Forked from CTObjectiveCRuntimeAdditions (MIT License) | ||||
| //  https://github.com/ebf/CTObjectiveCRuntimeAdditions | ||||
| // | ||||
| //  Copyright (c) 2020 FLEX Team-EDV Beratung Föllmer GmbH | ||||
| //  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, subject to the following conditions: | ||||
| //  The above copyright notice and this permission notice shall be included in all copies or | ||||
| //  substantial portions of the Software. | ||||
| // | ||||
| //  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. | ||||
|  | ||||
| #import "FLEXBlockDescription.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
|  | ||||
| struct block_object { | ||||
|     void *isa; | ||||
|     int flags; | ||||
|     int reserved; | ||||
|     void (*invoke)(void *, ...); | ||||
|     struct block_descriptor { | ||||
|         unsigned long int reserved;    // NULL | ||||
|         unsigned long int size;     // sizeof(struct Block_literal_1) | ||||
|         // optional helper functions | ||||
|         void (*copy_helper)(void *dst, void *src);     // IFF (1<<25) | ||||
|         void (*dispose_helper)(void *src);             // IFF (1<<25) | ||||
|         // required ABI.2010.3.16 | ||||
|         const char *signature;                         // IFF (1<<30) | ||||
|     } *descriptor; | ||||
|     // imported variables | ||||
| }; | ||||
|  | ||||
| @implementation FLEXBlockDescription | ||||
|  | ||||
| + (instancetype)describing:(id)block { | ||||
|     return [[self alloc] initWithObjcBlock:block]; | ||||
| } | ||||
|  | ||||
| - (id)initWithObjcBlock:(id)block { | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _block = block; | ||||
|          | ||||
|         struct block_object *blockRef = (__bridge struct block_object *)block; | ||||
|         _flags = blockRef->flags; | ||||
|         _size = blockRef->descriptor->size; | ||||
|          | ||||
|         if (_flags & FLEXBlockOptionHasSignature) { | ||||
|             void *signatureLocation = blockRef->descriptor; | ||||
|             signatureLocation += sizeof(unsigned long int); | ||||
|             signatureLocation += sizeof(unsigned long int); | ||||
|              | ||||
|             if (_flags & FLEXBlockOptionHasCopyDispose) { | ||||
|                 signatureLocation += sizeof(void(*)(void *dst, void *src)); | ||||
|                 signatureLocation += sizeof(void (*)(void *src)); | ||||
|             } | ||||
|              | ||||
|             const char *signature = (*(const char **)signatureLocation); | ||||
|             _signatureString = @(signature); | ||||
|              | ||||
|             @try { | ||||
|                 _signature = [NSMethodSignature signatureWithObjCTypes:signature]; | ||||
|             } @catch (NSException *exception) { } | ||||
|         } | ||||
|          | ||||
|         NSMutableString *summary = [NSMutableString stringWithFormat: | ||||
|             @"Type signature: %@\nSize: %@\nIs global: %@\nHas constructor: %@\nIs stret: %@", | ||||
|             self.signatureString ?: @"nil", @(self.size), | ||||
|             @((BOOL)(_flags & FLEXBlockOptionIsGlobal)), | ||||
|             @((BOOL)(_flags & FLEXBlockOptionHasCtor)), | ||||
|             @((BOOL)(_flags & FLEXBlockOptionHasStret)) | ||||
|         ]; | ||||
|          | ||||
|         if (!self.signature) { | ||||
|             [summary appendFormat:@"\nNumber of arguments: %@", @(self.signature.numberOfArguments)]; | ||||
|         } | ||||
|          | ||||
|         _summary = summary.copy; | ||||
|         _sourceDeclaration = [self buildLikelyDeclaration]; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (BOOL)isCompatibleForBlockSwizzlingWithMethodSignature:(NSMethodSignature *)methodSignature { | ||||
|     if (!self.signature) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     if (self.signature.numberOfArguments != methodSignature.numberOfArguments + 1) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     if (strcmp(self.signature.methodReturnType, methodSignature.methodReturnType) != 0) { | ||||
|         return NO; | ||||
|     } | ||||
|      | ||||
|     for (int i = 0; i < methodSignature.numberOfArguments; i++) { | ||||
|         if (i == 1) { | ||||
|             // SEL in method, IMP in block | ||||
|             if (strcmp([methodSignature getArgumentTypeAtIndex:i], ":") != 0) { | ||||
|                 return NO; | ||||
|             } | ||||
|              | ||||
|             if (strcmp([self.signature getArgumentTypeAtIndex:i + 1], "^?") != 0) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } else { | ||||
|             if (strcmp([self.signature getArgumentTypeAtIndex:i], [self.signature getArgumentTypeAtIndex:i + 1]) != 0) { | ||||
|                 return NO; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return YES; | ||||
| } | ||||
|  | ||||
| - (NSString *)buildLikelyDeclaration { | ||||
|     NSMethodSignature *signature = self.signature; | ||||
|     NSUInteger numberOfArguments = signature.numberOfArguments; | ||||
|     const char *returnType       = signature.methodReturnType; | ||||
|      | ||||
|     // Return type | ||||
|     NSMutableString *decl = [NSMutableString stringWithString:@"^"]; | ||||
|     if (returnType[0] != FLEXTypeEncodingVoid) { | ||||
|         [decl appendString:[FLEXRuntimeUtility readableTypeForEncoding:@(returnType)]]; | ||||
|         [decl appendString:@" "]; | ||||
|     } | ||||
|      | ||||
|     // Arguments | ||||
|     if (numberOfArguments) { | ||||
|         [decl appendString:@"("]; | ||||
|         for (NSUInteger i = 1; i < numberOfArguments; i++) { | ||||
|             const char *argType = [self.signature getArgumentTypeAtIndex:i] ?: "?"; | ||||
|             NSString *readableArgType = [FLEXRuntimeUtility readableTypeForEncoding:@(argType)]; | ||||
|             [decl appendFormat:@"%@ arg%@, ", readableArgType, @(i)]; | ||||
|         } | ||||
|          | ||||
|         [decl deleteCharactersInRange:NSMakeRange(decl.length-2, 2)]; | ||||
|         [decl appendString:@")"]; | ||||
|     } | ||||
|      | ||||
|     return decl.copy; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,80 @@ | ||||
| // | ||||
| //  FLEXClassBuilder.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 7/3/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
| @class FLEXIvarBuilder, FLEXMethodBase, FLEXProperty, FLEXProtocol; | ||||
|  | ||||
|  | ||||
| #pragma mark FLEXClassBuilder | ||||
| @interface FLEXClassBuilder : NSObject | ||||
|  | ||||
| @property (nonatomic, readonly) Class workingClass; | ||||
|  | ||||
| /// Begins constructing a class with the given name. | ||||
| /// | ||||
| /// This new class will implicitly inherits from \c NSObject with \c 0 extra bytes. | ||||
| /// Classes created this way must be registered with \c -registerClass before being used. | ||||
| + (instancetype)allocateClass:(NSString *)name; | ||||
| /// Begins constructing a class with the given name and superclass. | ||||
| /// @discussion Calls \c -allocateClass:superclass:extraBytes: with \c 0 extra bytes. | ||||
| /// Classes created this way must be registered with \c -registerClass before being used. | ||||
| + (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass; | ||||
| /// Begins constructing a new class object with the given name and superclass. | ||||
| /// @discussion Pass \c nil to \e superclass to create a new root class. | ||||
| /// Classes created this way must be registered with \c -registerClass before being used. | ||||
| + (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass extraBytes:(size_t)bytes; | ||||
| /// Begins constructing a new root class object with the given name and \c 0 extra bytes. | ||||
| /// @discussion Classes created this way must be registered with \c -registerClass before being used. | ||||
| + (instancetype)allocateRootClass:(NSString *)name; | ||||
| /// Use this to modify existing classes. @warning You cannot add instance variables to existing classes. | ||||
| + (instancetype)builderForClass:(Class)cls; | ||||
|  | ||||
| /// @return Any methods that failed to be added. | ||||
| - (NSArray<FLEXMethodBase *> *)addMethods:(NSArray<FLEXMethodBase *> *)methods; | ||||
| /// @return Any properties that failed to be added. | ||||
| - (NSArray<FLEXProperty *> *)addProperties:(NSArray<FLEXProperty *> *)properties; | ||||
| /// @return Any protocols that failed to be added. | ||||
| - (NSArray<FLEXProtocol *> *)addProtocols:(NSArray<FLEXProtocol *> *)protocols; | ||||
| /// @warning Adding Ivars to existing classes is not supported and will always fail. | ||||
| - (NSArray<FLEXIvarBuilder *> *)addIvars:(NSArray<FLEXIvarBuilder *> *)ivars; | ||||
|  | ||||
| /// Finalizes construction of a new class. | ||||
| /// @discussion Once a class is registered, instance variables cannot be added. | ||||
| /// @note Raises an exception if called on a previously registered class. | ||||
| - (Class)registerClass; | ||||
| /// Uses \c objc_lookupClass to determine if the working class is registered. | ||||
| @property (nonatomic, readonly) BOOL isRegistered; | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| #pragma mark FLEXIvarBuilder | ||||
| @interface FLEXIvarBuilder : NSObject | ||||
|  | ||||
| /// Consider using the \c FLEXIvarBuilderWithNameAndType() macro below.  | ||||
| /// @param name The name of the Ivar, such as \c \@"_value". | ||||
| /// @param size The size of the Ivar. Usually \c sizeof(type). For objects, this is \c sizeof(id). | ||||
| /// @param alignment The alignment of the Ivar. Usually \c log2(sizeof(type)). | ||||
| /// @param encoding The type encoding of the Ivar. For objects, this is \c \@(\@encode(id)), and for others it is \c \@(\@encode(type)). | ||||
| + (instancetype)name:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding; | ||||
|  | ||||
| @property (nonatomic, readonly) NSString *name; | ||||
| @property (nonatomic, readonly) NSString *encoding; | ||||
| @property (nonatomic, readonly) size_t   size; | ||||
| @property (nonatomic, readonly) uint8_t  alignment; | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| #define FLEXIvarBuilderWithNameAndType(nameString, type) [FLEXIvarBuilder \ | ||||
|     name:nameString \ | ||||
|     size:sizeof(type) \ | ||||
|     alignment:log2(sizeof(type)) \ | ||||
|     typeEncoding:@(@encode(type)) \ | ||||
| ] | ||||
							
								
								
									
										168
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXClassBuilder.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXClassBuilder.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | ||||
| // | ||||
| //  FLEXClassBuilder.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 7/3/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXClassBuilder.h" | ||||
| #import "FLEXProperty.h" | ||||
| #import "FLEXMethodBase.h" | ||||
| #import "FLEXProtocol.h" | ||||
| #import <objc/runtime.h> | ||||
|  | ||||
|  | ||||
| #pragma mark FLEXClassBuilder | ||||
|  | ||||
| @interface FLEXClassBuilder () | ||||
| @property (nonatomic) NSString *name; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXClassBuilder | ||||
|  | ||||
| - (id)init { | ||||
|     [NSException | ||||
|         raise:NSInternalInconsistencyException | ||||
|         format:@"Class instance should not be created with -init" | ||||
|     ]; | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| #pragma mark Initializers | ||||
| + (instancetype)allocateClass:(NSString *)name { | ||||
|     return [self allocateClass:name superclass:NSObject.class]; | ||||
| } | ||||
|  | ||||
| + (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass { | ||||
|     return [self allocateClass:name superclass:superclass extraBytes:0]; | ||||
| } | ||||
|  | ||||
| + (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass extraBytes:(size_t)bytes { | ||||
|     NSParameterAssert(name); | ||||
|     return [[self alloc] initWithClass:objc_allocateClassPair(superclass, name.UTF8String, bytes)]; | ||||
| } | ||||
|  | ||||
| + (instancetype)allocateRootClass:(NSString *)name { | ||||
|     NSParameterAssert(name); | ||||
|     return [[self alloc] initWithClass:objc_allocateClassPair(Nil, name.UTF8String, 0)]; | ||||
| } | ||||
|  | ||||
| + (instancetype)builderForClass:(Class)cls { | ||||
|     return [[self alloc] initWithClass:cls]; | ||||
| } | ||||
|  | ||||
| - (id)initWithClass:(Class)cls { | ||||
|     NSParameterAssert(cls); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _workingClass = cls; | ||||
|         _name = NSStringFromClass(_workingClass); | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (NSString *)description { | ||||
|     return [NSString stringWithFormat:@"<%@ name=%@, registered=%d>", | ||||
|             NSStringFromClass(self.class), self.name, self.isRegistered]; | ||||
| } | ||||
|  | ||||
| #pragma mark Building | ||||
| - (NSArray *)addMethods:(NSArray *)methods { | ||||
|     NSParameterAssert(methods.count); | ||||
|      | ||||
|     NSMutableArray *failed = [NSMutableArray new]; | ||||
|     for (FLEXMethodBase *m in methods) { | ||||
|         if (!class_addMethod(self.workingClass, m.selector, m.implementation, m.typeEncoding.UTF8String)) { | ||||
|             [failed addObject:m]; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return failed; | ||||
| } | ||||
|  | ||||
| - (NSArray *)addProperties:(NSArray *)properties { | ||||
|     NSParameterAssert(properties.count); | ||||
|      | ||||
|     NSMutableArray *failed = [NSMutableArray new]; | ||||
|     for (FLEXProperty *p in properties) { | ||||
|         unsigned int pcount; | ||||
|         objc_property_attribute_t *attributes = [p copyAttributesList:&pcount]; | ||||
|         if (!class_addProperty(self.workingClass, p.name.UTF8String, attributes, pcount)) { | ||||
|             [failed addObject:p]; | ||||
|         } | ||||
|         free(attributes); | ||||
|     } | ||||
|      | ||||
|     return failed; | ||||
| } | ||||
|  | ||||
| - (NSArray *)addProtocols:(NSArray *)protocols { | ||||
|     NSParameterAssert(protocols.count); | ||||
|      | ||||
|     NSMutableArray *failed = [NSMutableArray new]; | ||||
|     for (FLEXProtocol *p in protocols) { | ||||
|         if (!class_addProtocol(self.workingClass, p.objc_protocol)) { | ||||
|             [failed addObject:p]; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return failed; | ||||
| } | ||||
|  | ||||
| - (NSArray *)addIvars:(NSArray *)ivars { | ||||
|     NSParameterAssert(ivars.count); | ||||
|      | ||||
|     NSMutableArray *failed = [NSMutableArray new]; | ||||
|     for (FLEXIvarBuilder *ivar in ivars) { | ||||
|         if (!class_addIvar(self.workingClass, ivar.name.UTF8String, ivar.size, ivar.alignment, ivar.encoding.UTF8String)) { | ||||
|             [failed addObject:ivar]; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return failed; | ||||
| } | ||||
|  | ||||
| - (Class)registerClass { | ||||
|     if (self.isRegistered) { | ||||
|         [NSException raise:NSInternalInconsistencyException format:@"Class is already registered"]; | ||||
|     } | ||||
|      | ||||
|     objc_registerClassPair(self.workingClass); | ||||
|     return self.workingClass; | ||||
| } | ||||
|  | ||||
| - (BOOL)isRegistered { | ||||
|     return objc_lookUpClass(self.name.UTF8String) != nil; | ||||
| } | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| #pragma mark FLEXIvarBuilder | ||||
|  | ||||
| @implementation FLEXIvarBuilder | ||||
|  | ||||
| + (instancetype)name:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding { | ||||
|     return [[self alloc] initWithName:name size:size alignment:alignment typeEncoding:encoding]; | ||||
| } | ||||
|  | ||||
| - (id)initWithName:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding { | ||||
|     NSParameterAssert(name); NSParameterAssert(encoding); | ||||
|     NSParameterAssert(size > 0); NSParameterAssert(alignment > 0); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _name      = name; | ||||
|         _encoding  = encoding; | ||||
|         _size      = size; | ||||
|         _alignment = alignment; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										51
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| // | ||||
| //  FLEXIvar.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/30/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeConstants.h" | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| @interface FLEXIvar : NSObject | ||||
|  | ||||
| + (instancetype)ivar:(Ivar)ivar; | ||||
| + (instancetype)named:(NSString *)name onClass:(Class)cls; | ||||
|  | ||||
| /// The underlying \c Ivar data structure. | ||||
| @property (nonatomic, readonly) Ivar             objc_ivar; | ||||
|  | ||||
| /// The name of the instance variable. | ||||
| @property (nonatomic, readonly) NSString         *name; | ||||
| /// The type of the instance variable. | ||||
| @property (nonatomic, readonly) FLEXTypeEncoding type; | ||||
| /// The type encoding string of the instance variable. | ||||
| @property (nonatomic, readonly) NSString         *typeEncoding; | ||||
| /// The offset of the instance variable. | ||||
| @property (nonatomic, readonly) NSInteger        offset; | ||||
| /// The size of the instance variable. 0 if unknown. | ||||
| @property (nonatomic, readonly) NSUInteger       size; | ||||
| /// Describes the type encoding, size, offset, and objc_ivar | ||||
| @property (nonatomic, readonly) NSString        *details; | ||||
| /// The full path of the image that contains this ivar definition, | ||||
| /// or \c nil if this ivar was probably defined at runtime. | ||||
| @property (nonatomic, readonly, nullable) NSString *imagePath; | ||||
|  | ||||
| /// For internal use | ||||
| @property (nonatomic) id tag; | ||||
|  | ||||
| - (nullable id)getValue:(id)target; | ||||
| - (void)setValue:(nullable id)value onObject:(id)target; | ||||
|  | ||||
| /// Calls into -getValue: and passes that value into | ||||
| /// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:] | ||||
| /// and returns the result | ||||
| - (nullable id)getPotentiallyUnboxedValue:(id)target; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										158
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | ||||
| // | ||||
| //  FLEXIvar.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/30/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXIvar.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "FLEXRuntimeSafety.h" | ||||
| #import "FLEXTypeEncodingParser.h" | ||||
| #import "NSString+FLEX.h" | ||||
| #include "FLEXObjcInternal.h" | ||||
| #include <dlfcn.h> | ||||
|  | ||||
| @interface FLEXIvar () { | ||||
|     NSString *_flex_description; | ||||
| } | ||||
| @end | ||||
|  | ||||
| @implementation FLEXIvar | ||||
|  | ||||
| #pragma mark Initializers | ||||
|  | ||||
| + (instancetype)ivar:(Ivar)ivar { | ||||
|     return [[self alloc] initWithIvar:ivar]; | ||||
| } | ||||
|  | ||||
| + (instancetype)named:(NSString *)name onClass:(Class)cls { | ||||
|     Ivar _Nullable ivar = class_getInstanceVariable(cls, name.UTF8String); | ||||
|     NSAssert(ivar, @"Cannot find ivar with name %@ on class %@", name, cls); | ||||
|     return [self ivar:ivar]; | ||||
| } | ||||
|  | ||||
| - (id)initWithIvar:(Ivar)ivar { | ||||
|     NSParameterAssert(ivar); | ||||
|  | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _objc_ivar = ivar; | ||||
|         [self examine]; | ||||
|     } | ||||
|  | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| #pragma mark Other | ||||
|  | ||||
| - (NSString *)description { | ||||
|     if (!_flex_description) { | ||||
|         NSString *readableType = [FLEXRuntimeUtility readableTypeForEncoding:self.typeEncoding]; | ||||
|         _flex_description = [FLEXRuntimeUtility appendName:self.name toType:readableType]; | ||||
|     } | ||||
|  | ||||
|     return _flex_description; | ||||
| } | ||||
|  | ||||
| - (NSString *)debugDescription { | ||||
|     return [NSString stringWithFormat:@"<%@ name=%@, encoding=%@, offset=%ld>", | ||||
|             NSStringFromClass(self.class), self.name, self.typeEncoding, (long)self.offset]; | ||||
| } | ||||
|  | ||||
| - (void)examine { | ||||
|     _name         = @(ivar_getName(self.objc_ivar) ?: "(nil)"); | ||||
|     _offset       = ivar_getOffset(self.objc_ivar); | ||||
|     _typeEncoding = @(ivar_getTypeEncoding(self.objc_ivar) ?: ""); | ||||
|  | ||||
|     NSString *typeForDetails = _typeEncoding; | ||||
|     NSString *sizeForDetails = nil; | ||||
|     if (_typeEncoding.length) { | ||||
|         _type = (FLEXTypeEncoding)[_typeEncoding characterAtIndex:0]; | ||||
|         FLEXGetSizeAndAlignment(_typeEncoding.UTF8String, &_size, nil); | ||||
|         sizeForDetails = [@(_size).stringValue stringByAppendingString:@" bytes"]; | ||||
|     } else { | ||||
|         _type = FLEXTypeEncodingNull; | ||||
|         typeForDetails = @"no type info"; | ||||
|         sizeForDetails = @"unknown size"; | ||||
|     } | ||||
|  | ||||
|     Dl_info exeInfo; | ||||
|     if (dladdr(_objc_ivar, &exeInfo)) { | ||||
|         _imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil; | ||||
|     } | ||||
|  | ||||
|     _details = [NSString stringWithFormat: | ||||
|         @"%@, offset %@  —  %@", | ||||
|         sizeForDetails, @(_offset), typeForDetails | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| - (id)getValue:(id)target { | ||||
|     id value = nil; | ||||
|     if (!FLEXIvarIsSafe(_objc_ivar) || | ||||
|         _type == FLEXTypeEncodingNull || | ||||
|         FLEXPointerIsTaggedPointer(target)) { | ||||
|         return nil; | ||||
|     } | ||||
|  | ||||
| #ifdef __arm64__ | ||||
|     // See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html | ||||
|     if (self.type == FLEXTypeEncodingObjcClass && [self.name isEqualToString:@"isa"]) { | ||||
|         value = object_getClass(target); | ||||
|     } else | ||||
| #endif | ||||
|     if (self.type == FLEXTypeEncodingObjcObject || self.type == FLEXTypeEncodingObjcClass) { | ||||
|         value = object_getIvar(target, self.objc_ivar); | ||||
|     } else { | ||||
|         void *pointer = (__bridge void *)target + self.offset; | ||||
|         value = [FLEXRuntimeUtility | ||||
|             valueForPrimitivePointer:pointer | ||||
|             objCType:self.typeEncoding.UTF8String | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     return value; | ||||
| } | ||||
|  | ||||
| - (void)setValue:(id)value onObject:(id)target { | ||||
|     const char *typeEncodingCString = self.typeEncoding.UTF8String; | ||||
|     if (self.type == FLEXTypeEncodingObjcObject) { | ||||
|         object_setIvar(target, self.objc_ivar, value); | ||||
|     } else if ([value isKindOfClass:[NSValue class]]) { | ||||
|         // Primitive - unbox the NSValue. | ||||
|         NSValue *valueValue = (NSValue *)value; | ||||
|  | ||||
|         // Make sure that the box contained the correct type. | ||||
|         NSAssert( | ||||
|             strcmp(valueValue.objCType, typeEncodingCString) == 0, | ||||
|             @"Type encoding mismatch (value: %s; ivar: %s) in setting ivar named: %@ on object: %@", | ||||
|             valueValue.objCType, typeEncodingCString, self.name, target | ||||
|         ); | ||||
|  | ||||
|         NSUInteger bufferSize = 0; | ||||
|         if (FLEXGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL)) { | ||||
|             void *buffer = calloc(bufferSize, 1); | ||||
|             [valueValue getValue:buffer]; | ||||
|             void *pointer = (__bridge void *)target + self.offset; | ||||
|             memcpy(pointer, buffer, bufferSize); | ||||
|             free(buffer); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (id)getPotentiallyUnboxedValue:(id)target { | ||||
|     NSString *type = self.typeEncoding; | ||||
|     if (type.flex_typeIsNonObjcPointer && type.flex_pointeeType != FLEXTypeEncodingVoid) { | ||||
|         return [self getValue:target]; | ||||
|     } | ||||
|  | ||||
|     return [FLEXRuntimeUtility | ||||
|         potentiallyUnwrapBoxedPointer:[self getValue:target] | ||||
|         type:type.UTF8String | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										96
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| // | ||||
| //  FLEXMethod.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/30/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeConstants.h" | ||||
| #import "FLEXMethodBase.h" | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| /// A class representing a concrete method which already exists in a class. | ||||
| /// This class contains helper methods for swizzling or invoking the method. | ||||
| /// | ||||
| /// Any of the initializers will return nil if the type encoding | ||||
| /// of the method is unsupported by `NSMethodSignature`. In general, | ||||
| /// any method whose return type or parameters involve a struct with | ||||
| /// bitfields or arrays is unsupported. | ||||
| /// | ||||
| /// I do not remember why I didn't include \c signature in the base class | ||||
| /// when I originally wrote this, but I probably had a good reason. We can | ||||
| /// always go back and move it to \c FLEXMethodBase if we find we need to. | ||||
| @interface FLEXMethod : FLEXMethodBase | ||||
|  | ||||
| /// Defaults to instance method | ||||
| + (nullable instancetype)method:(Method)method; | ||||
| + (nullable instancetype)method:(Method)method isInstanceMethod:(BOOL)isInstanceMethod; | ||||
|  | ||||
| /// Constructs an \c FLEXMethod for the given method on the given class. | ||||
| /// @param cls the class, or metaclass if this is a class method | ||||
| /// @return The newly constructed \c FLEXMethod object, or \c nil if the | ||||
| /// specified class or its superclasses do not contain a method with the specified selector. | ||||
| + (nullable instancetype)selector:(SEL)selector class:(Class)cls; | ||||
| /// Constructs an \c FLEXMethod for the given method on the given class, | ||||
| /// only if the given class itself defines or overrides the desired method. | ||||
| /// @param cls the class, or metaclass if this is a class method | ||||
| /// @return The newly constructed \c FLEXMethod object, or \c nil \e if the | ||||
| /// specified class does not define or override, or if the specified class | ||||
| /// or its superclasses do not contain, a method with the specified selector. | ||||
| + (nullable instancetype)selector:(SEL)selector implementedInClass:(Class)cls; | ||||
|  | ||||
| @property (nonatomic, readonly) Method            objc_method; | ||||
| /// The implementation of the method. | ||||
| /// @discussion Setting \c implementation will change the implementation of this method | ||||
| /// for the entire class which implements said method. It will also not modify the selector of said method. | ||||
| @property (nonatomic          ) IMP               implementation; | ||||
| /// Whether the method is an instance method or not. | ||||
| @property (nonatomic, readonly) BOOL              isInstanceMethod; | ||||
| /// The number of arguments to the method. | ||||
| @property (nonatomic, readonly) NSUInteger        numberOfArguments; | ||||
| /// The \c NSMethodSignature object corresponding to the method's type encoding. | ||||
| @property (nonatomic, readonly) NSMethodSignature *signature; | ||||
| /// Same as \e typeEncoding but with parameter sizes up front and offsets after the types. | ||||
| @property (nonatomic, readonly) NSString          *signatureString; | ||||
| /// The return type of the method. | ||||
| @property (nonatomic, readonly) FLEXTypeEncoding  *returnType; | ||||
| /// The return size of the method. | ||||
| @property (nonatomic, readonly) NSUInteger        returnSize; | ||||
| /// The full path of the image that contains this method definition, | ||||
| /// or \c nil if this ivar was probably defined at runtime. | ||||
| @property (nonatomic, readonly) NSString          *imagePath; | ||||
|  | ||||
| /// Like @code - (void)foo:(int)bar @endcode | ||||
| @property (nonatomic, readonly) NSString *description; | ||||
| /// Like @code -[Class foo:] @endcode | ||||
| - (NSString *)debugNameGivenClassName:(NSString *)name; | ||||
|  | ||||
| /// Swizzles the recieving method with the given method. | ||||
| - (void)swapImplementations:(FLEXMethod *)method; | ||||
|  | ||||
| #define FLEXMagicNumber 0xdeadbeef | ||||
| #define FLEXArg(expr) FLEXMagicNumber,/// @encode(__typeof__(expr)), (__typeof__(expr) []){ expr } | ||||
|  | ||||
| /// Sends a message to \e target, and returns it's value, or \c nil if not applicable. | ||||
| /// @discussion You may send any message with this method. Primitive return values will be wrapped | ||||
| /// in instances of \c NSNumber and \c NSValue. \c void and bitfield returning methods return \c nil. | ||||
| /// \c SEL return types are converted to strings using \c NSStringFromSelector. | ||||
| /// @return The object returned by this method, or an instance of \c NSValue or \c NSNumber containing | ||||
| /// the primitive return type, or a string for \c SEL return types. | ||||
| - (id)sendMessage:(id)target, ...; | ||||
| /// Used internally by \c sendMessage:target,. Pass \c NULL to the first parameter for void methods. | ||||
| - (void)getReturnValue:(void *)retPtr forMessageSend:(id)target, ...; | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| @interface FLEXMethod (Comparison) | ||||
|  | ||||
| - (NSComparisonResult)compare:(FLEXMethod *)method; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										430
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										430
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,430 @@ | ||||
| // | ||||
| //  FLEXMethod.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/30/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXMethod.h" | ||||
| #import "FLEXMirror.h" | ||||
| #import "FLEXTypeEncodingParser.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #include <dlfcn.h> | ||||
|  | ||||
| @implementation FLEXMethod | ||||
| @synthesize imagePath = _imagePath; | ||||
| @dynamic implementation; | ||||
|  | ||||
| + (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation { | ||||
|     [NSException raise:NSInternalInconsistencyException format:@"Class instance should not be created with +buildMethodNamed:withTypes:implementation"]; return nil; | ||||
| } | ||||
|  | ||||
| - (id)init { | ||||
|     [NSException | ||||
|         raise:NSInternalInconsistencyException | ||||
|         format:@"Class instance should not be created with -init" | ||||
|     ]; | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| #pragma mark Initializers | ||||
|  | ||||
| + (instancetype)method:(Method)method { | ||||
|     return [[self alloc] initWithMethod:method isInstanceMethod:YES]; | ||||
| } | ||||
|  | ||||
| + (instancetype)method:(Method)method isInstanceMethod:(BOOL)isInstanceMethod { | ||||
|     return [[self alloc] initWithMethod:method isInstanceMethod:isInstanceMethod]; | ||||
| } | ||||
|  | ||||
| + (instancetype)selector:(SEL)selector class:(Class)cls { | ||||
|     BOOL instance = !class_isMetaClass(cls); | ||||
|     // class_getInstanceMethod will return an instance method if not given | ||||
|     // not given a metaclass, or a class method if given a metaclass, but | ||||
|     // this isn't documented so we just want to be safe here. | ||||
|     Method m = instance ? class_getInstanceMethod(cls, selector) : class_getClassMethod(cls, selector); | ||||
|     if (m == NULL) return nil; | ||||
|      | ||||
|     return [self method:m isInstanceMethod:instance]; | ||||
| } | ||||
|  | ||||
| + (instancetype)selector:(SEL)selector implementedInClass:(Class)cls { | ||||
|     if (![cls superclass]) { return [self selector:selector class:cls]; } | ||||
|      | ||||
|     BOOL unique = [cls methodForSelector:selector] != [[cls superclass] methodForSelector:selector]; | ||||
|      | ||||
|     if (unique) { | ||||
|         return [self selector:selector class:cls]; | ||||
|     } | ||||
|      | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| - (id)initWithMethod:(Method)method isInstanceMethod:(BOOL)isInstanceMethod { | ||||
|     NSParameterAssert(method); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _objc_method = method; | ||||
|         _isInstanceMethod = isInstanceMethod; | ||||
|         _signatureString = @(method_getTypeEncoding(method) ?: "?@:"); | ||||
|          | ||||
|         NSString *cleanSig = nil; | ||||
|         if ([FLEXTypeEncodingParser methodTypeEncodingSupported:_signatureString cleaned:&cleanSig]) { | ||||
|             _signature = [NSMethodSignature signatureWithObjCTypes:cleanSig.UTF8String]; | ||||
|         } | ||||
|  | ||||
|         [self examine]; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
|  | ||||
| #pragma mark Other | ||||
|  | ||||
| - (NSString *)description { | ||||
|     if (!_flex_description) { | ||||
|         _flex_description = [self prettyName]; | ||||
|     } | ||||
|      | ||||
|     return _flex_description; | ||||
| } | ||||
|  | ||||
| - (NSString *)debugNameGivenClassName:(NSString *)name { | ||||
|     NSMutableString *string = [NSMutableString stringWithString:_isInstanceMethod ? @"-[" : @"+["]; | ||||
|     [string appendString:name]; | ||||
|     [string appendString:@" "]; | ||||
|     [string appendString:self.selectorString]; | ||||
|     [string appendString:@"]"]; | ||||
|     return string; | ||||
| } | ||||
|  | ||||
| - (NSString *)prettyName { | ||||
|     NSString *methodTypeString = self.isInstanceMethod ? @"-" : @"+"; | ||||
|     NSString *readableReturnType = [FLEXRuntimeUtility readableTypeForEncoding:@(self.signature.methodReturnType ?: "")]; | ||||
|      | ||||
|     NSString *prettyName = [NSString stringWithFormat:@"%@ (%@)", methodTypeString, readableReturnType]; | ||||
|     NSArray *components = [self prettyArgumentComponents]; | ||||
|  | ||||
|     if (components.count) { | ||||
|         return [prettyName stringByAppendingString:[components componentsJoinedByString:@" "]]; | ||||
|     } else { | ||||
|         return [prettyName stringByAppendingString:self.selectorString]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (NSArray *)prettyArgumentComponents { | ||||
|     // NSMethodSignature can't handle some type encodings | ||||
|     // like ^AI@:ir* which happen to very much exist | ||||
|     if (self.signature.numberOfArguments < self.numberOfArguments) { | ||||
|         return nil; | ||||
|     } | ||||
|      | ||||
|     NSMutableArray *components = [NSMutableArray new]; | ||||
|  | ||||
|     NSArray *selectorComponents = [self.selectorString componentsSeparatedByString:@":"]; | ||||
|     NSUInteger numberOfArguments = self.numberOfArguments; | ||||
|      | ||||
|     for (NSUInteger argIndex = 2; argIndex < numberOfArguments; argIndex++) { | ||||
|         assert(argIndex < self.signature.numberOfArguments); | ||||
|          | ||||
|         const char *argType = [self.signature getArgumentTypeAtIndex:argIndex] ?: "?"; | ||||
|         NSString *readableArgType = [FLEXRuntimeUtility readableTypeForEncoding:@(argType)]; | ||||
|         NSString *prettyComponent = [NSString | ||||
|             stringWithFormat:@"%@:(%@) ", | ||||
|             selectorComponents[argIndex - 2], | ||||
|             readableArgType | ||||
|         ]; | ||||
|  | ||||
|         [components addObject:prettyComponent]; | ||||
|     } | ||||
|      | ||||
|     return components; | ||||
| } | ||||
|  | ||||
| - (NSString *)debugDescription { | ||||
|     return [NSString stringWithFormat:@"<%@ selector=%@, signature=%@>", | ||||
|             NSStringFromClass(self.class), self.selectorString, self.signatureString]; | ||||
| } | ||||
|  | ||||
| - (void)examine { | ||||
|     _implementation    = method_getImplementation(_objc_method); | ||||
|     _selector          = method_getName(_objc_method); | ||||
|     _numberOfArguments = method_getNumberOfArguments(_objc_method); | ||||
|     _name              = NSStringFromSelector(_selector); | ||||
|     _returnType        = (FLEXTypeEncoding *)_signature.methodReturnType ?: ""; | ||||
|     _returnSize        = _signature.methodReturnLength; | ||||
| } | ||||
|  | ||||
| #pragma mark Public | ||||
|  | ||||
| - (void)setImplementation:(IMP)implementation { | ||||
|     NSParameterAssert(implementation); | ||||
|     method_setImplementation(self.objc_method, implementation); | ||||
|     [self examine]; | ||||
| } | ||||
|  | ||||
| - (NSString *)typeEncoding { | ||||
|     if (!_typeEncoding) { | ||||
|         _typeEncoding = [_signatureString | ||||
|             stringByReplacingOccurrencesOfString:@"[0-9]" | ||||
|             withString:@"" | ||||
|             options:NSRegularExpressionSearch | ||||
|             range:NSMakeRange(0, _signatureString.length) | ||||
|         ]; | ||||
|     } | ||||
|      | ||||
|     return _typeEncoding; | ||||
| } | ||||
|  | ||||
| - (NSString *)imagePath { | ||||
|     if (!_imagePath) { | ||||
|         Dl_info exeInfo; | ||||
|         if (dladdr(_implementation, &exeInfo)) { | ||||
|             _imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : @""; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return _imagePath; | ||||
| } | ||||
|  | ||||
| #pragma mark Misc | ||||
|  | ||||
| - (void)swapImplementations:(FLEXMethod *)method { | ||||
|     method_exchangeImplementations(self.objc_method, method.objc_method); | ||||
|     [self examine]; | ||||
|     [method examine]; | ||||
| } | ||||
|  | ||||
| // Some code borrowed from MAObjcRuntime, by Mike Ash. | ||||
| - (id)sendMessage:(id)target, ... { | ||||
|     id ret = nil; | ||||
|     va_list args; | ||||
|     va_start(args, target); | ||||
|      | ||||
|     switch (self.returnType[0]) { | ||||
|         case FLEXTypeEncodingUnknown: { | ||||
|             [self getReturnValue:NULL forMessageSend:target arguments:args]; | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingChar: { | ||||
|             char val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingInt: { | ||||
|             int val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingShort: { | ||||
|             short val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingLong: { | ||||
|             long val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingLongLong: { | ||||
|             long long val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingUnsignedChar: { | ||||
|             unsigned char val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingUnsignedInt: { | ||||
|             unsigned int val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingUnsignedShort: { | ||||
|             unsigned short val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingUnsignedLong: { | ||||
|             unsigned long val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingUnsignedLongLong: { | ||||
|             unsigned long long val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingFloat: { | ||||
|             float val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingDouble: { | ||||
|             double val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingLongDouble: { | ||||
|             long double val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = [NSValue value:&val withObjCType:self.returnType]; | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingCBool: { | ||||
|             bool val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingVoid: { | ||||
|             [self getReturnValue:NULL forMessageSend:target arguments:args]; | ||||
|             return nil; | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingCString: { | ||||
|             char *val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = @(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingObjcObject: { | ||||
|             id val = nil; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = val; | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingObjcClass: { | ||||
|             Class val = Nil; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = val; | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingSelector: { | ||||
|             SEL val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = NSStringFromSelector(val); | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingArrayBegin: { | ||||
|             void *val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = [NSValue valueWithBytes:val objCType:self.signature.methodReturnType]; | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingUnionBegin: | ||||
|         case FLEXTypeEncodingStructBegin: { | ||||
|             if (self.signature.methodReturnLength) { | ||||
|                 void * val = malloc(self.signature.methodReturnLength); | ||||
|                 [self getReturnValue:val forMessageSend:target arguments:args]; | ||||
|                 ret = [NSValue valueWithBytes:val objCType:self.signature.methodReturnType]; | ||||
|             } else { | ||||
|                 [self getReturnValue:NULL forMessageSend:target arguments:args]; | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingBitField: { | ||||
|             [self getReturnValue:NULL forMessageSend:target arguments:args]; | ||||
|             break; | ||||
|         } | ||||
|         case FLEXTypeEncodingPointer: { | ||||
|             void * val = 0; | ||||
|             [self getReturnValue:&val forMessageSend:target arguments:args]; | ||||
|             ret = [NSValue valueWithPointer:val]; | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         default: { | ||||
|             [NSException raise:NSInvalidArgumentException | ||||
|                         format:@"Unsupported type encoding: %s", (char *)self.returnType]; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     va_end(args); | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| // Code borrowed from MAObjcRuntime, by Mike Ash. | ||||
| - (void)getReturnValue:(void *)retPtr forMessageSend:(id)target, ... { | ||||
|     va_list args; | ||||
|     va_start(args, target); | ||||
|     [self getReturnValue:retPtr forMessageSend:target arguments:args]; | ||||
|     va_end(args); | ||||
| } | ||||
|  | ||||
| // Code borrowed from MAObjcRuntime, by Mike Ash. | ||||
| - (void)getReturnValue:(void *)retPtr forMessageSend:(id)target arguments:(va_list)args { | ||||
|     if (!_signature) { | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_signature]; | ||||
|     NSUInteger argumentCount = _signature.numberOfArguments; | ||||
|      | ||||
|     invocation.target = target; | ||||
|      | ||||
|     for (NSUInteger i = 2; i < argumentCount; i++) { | ||||
|         int cookie = va_arg(args, int); | ||||
|         if (cookie != FLEXMagicNumber) { | ||||
|             [NSException | ||||
|                 raise:NSInternalInconsistencyException | ||||
|                 format:@"%s: incorrect magic cookie %08x; make sure you didn't forget " | ||||
|                 "any arguments and that all arguments are wrapped in FLEXArg().", __func__, cookie | ||||
|             ]; | ||||
|         } | ||||
|         const char *typeString = va_arg(args, char *); | ||||
|         void *argPointer       = va_arg(args, void *); | ||||
|          | ||||
|         NSUInteger inSize, sigSize; | ||||
|         NSGetSizeAndAlignment(typeString, &inSize, NULL); | ||||
|         NSGetSizeAndAlignment([_signature getArgumentTypeAtIndex:i], &sigSize, NULL); | ||||
|          | ||||
|         if (inSize != sigSize) { | ||||
|             [NSException | ||||
|                 raise:NSInternalInconsistencyException | ||||
|                 format:@"%s:size mismatch between passed-in argument and " | ||||
|                 "required argument; in type:%s (%lu) requested:%s (%lu)", | ||||
|                 __func__, typeString, (long)inSize, [_signature getArgumentTypeAtIndex:i], (long)sigSize | ||||
|             ]; | ||||
|         } | ||||
|          | ||||
|         [invocation setArgument:argPointer atIndex:i]; | ||||
|     } | ||||
|      | ||||
|     // Hack to make NSInvocation invoke the desired implementation | ||||
|     IMP imp = [invocation methodForSelector:NSSelectorFromString(@"invokeUsingIMP:")]; | ||||
|     void (*invokeWithIMP)(id, SEL, IMP) = (void *)imp; | ||||
|     invokeWithIMP(invocation, 0, _implementation); | ||||
|      | ||||
|     if (_signature.methodReturnLength && retPtr) { | ||||
|         [invocation getReturnValue:retPtr]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| @implementation FLEXMethod (Comparison) | ||||
|  | ||||
| - (NSComparisonResult)compare:(FLEXMethod *)method { | ||||
|     return [self.selectorString compare:method.selectorString]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										43
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| // | ||||
| //  FLEXMethodBase.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 7/5/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
|  | ||||
|  | ||||
| /// A base class for methods which encompasses those that may not | ||||
| /// have been added to a class yet. Useful on it's own for adding | ||||
| /// methods to a class, or building a new class from the ground up. | ||||
| @interface FLEXMethodBase : NSObject { | ||||
| @protected | ||||
|     SEL      _selector; | ||||
|     NSString *_name; | ||||
|     NSString *_typeEncoding; | ||||
|     IMP      _implementation; | ||||
|      | ||||
|     NSString *_flex_description; | ||||
| } | ||||
|  | ||||
| /// Constructs and returns an \c FLEXSimpleMethod instance with the given name, type encoding, and implementation. | ||||
| + (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation; | ||||
|  | ||||
| /// The selector of the method. | ||||
| @property (nonatomic, readonly) SEL      selector; | ||||
| /// The selector string of the method. | ||||
| @property (nonatomic, readonly) NSString *selectorString; | ||||
| /// Same as selectorString. | ||||
| @property (nonatomic, readonly) NSString *name; | ||||
| /// The type encoding of the method. | ||||
| @property (nonatomic, readonly) NSString *typeEncoding; | ||||
| /// The implementation of the method. | ||||
| @property (nonatomic, readonly) IMP      implementation; | ||||
|  | ||||
| /// For internal use | ||||
| @property (nonatomic) id tag; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										49
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| // | ||||
| //  FLEXMethodBase.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 7/5/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXMethodBase.h" | ||||
|  | ||||
|  | ||||
| @implementation FLEXMethodBase | ||||
|  | ||||
| #pragma mark Initializers | ||||
|  | ||||
| + (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation { | ||||
|     return [[self alloc] initWithSelector:sel_registerName(name.UTF8String) types:typeEncoding imp:implementation]; | ||||
| } | ||||
|  | ||||
| - (id)initWithSelector:(SEL)selector types:(NSString *)types imp:(IMP)imp { | ||||
|     NSParameterAssert(selector); NSParameterAssert(types); NSParameterAssert(imp); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _selector = selector; | ||||
|         _typeEncoding = types; | ||||
|         _implementation = imp; | ||||
|         _name = NSStringFromSelector(self.selector); | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (NSString *)selectorString { | ||||
|     return _name; | ||||
| } | ||||
|  | ||||
| #pragma mark Overrides | ||||
|  | ||||
| - (NSString *)description { | ||||
|     if (!_flex_description) { | ||||
|         _flex_description = [NSString stringWithFormat:@"%@ '%@'", _name, _typeEncoding]; | ||||
|     } | ||||
|  | ||||
|     return _flex_description; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										97
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| // | ||||
| //  FLEXMirror.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/29/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
| #import <objc/runtime.h> | ||||
| @class FLEXMethod, FLEXProperty, FLEXIvar, FLEXProtocol; | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| #pragma mark FLEXMirror Protocol | ||||
| NS_SWIFT_NAME(FLEXMirrorProtocol) | ||||
| @protocol FLEXMirror <NSObject> | ||||
|  | ||||
| /// Swift initializer | ||||
| /// @throws If a metaclass object is passed in. | ||||
| - (instancetype)initWithSubject:(id)objectOrClass NS_SWIFT_NAME(init(reflecting:)); | ||||
|  | ||||
| /// The underlying object or \c Class used to create this \c FLEXMirror. | ||||
| @property (nonatomic, readonly) id   value; | ||||
| /// Whether \c value was a class or a class instance. | ||||
| @property (nonatomic, readonly) BOOL isClass; | ||||
| /// The name of the \c Class of the \c value property. | ||||
| @property (nonatomic, readonly) NSString *className; | ||||
|  | ||||
| @property (nonatomic, readonly) NSArray<FLEXProperty *> *properties; | ||||
| @property (nonatomic, readonly) NSArray<FLEXProperty *> *classProperties; | ||||
| @property (nonatomic, readonly) NSArray<FLEXIvar *>     *ivars; | ||||
| @property (nonatomic, readonly) NSArray<FLEXMethod *>   *methods; | ||||
| @property (nonatomic, readonly) NSArray<FLEXMethod *>   *classMethods; | ||||
| @property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols; | ||||
|  | ||||
| /// Super mirrors are initialized with the class that corresponds to the value passed in. | ||||
| /// If you passed in an instance of a class, it's superclass is used to create this mirror. | ||||
| /// If you passed in a class, then that class's superclass is used. | ||||
| /// | ||||
| /// @note This property should be computed, not cached. | ||||
| @property (nonatomic, readonly, nullable) id<FLEXMirror> superMirror NS_SWIFT_NAME(superMirror); | ||||
|  | ||||
| @end | ||||
|  | ||||
| #pragma mark FLEXMirror Class | ||||
| @interface FLEXMirror : NSObject <FLEXMirror> | ||||
|  | ||||
| /// Reflects an instance of an object or \c Class. | ||||
| /// @discussion \c FLEXMirror will immediately gather all useful information. Consider using the | ||||
| /// \c NSObject categories provided if your code will only use a few pieces of information, | ||||
| /// or if your code needs to run faster. | ||||
| /// | ||||
| /// Regardless of whether you reflect an instance or a class object, \c methods and \c properties | ||||
| /// will be populated with instance methods and properties, and \c classMethods and \c classProperties | ||||
| /// will be populated with class methods and properties. | ||||
| /// | ||||
| /// @param objectOrClass An instance of an objct or a \c Class object. | ||||
| /// @throws If a metaclass object is passed in. | ||||
| /// @return An instance of \c FLEXMirror. | ||||
| + (instancetype)reflect:(id)objectOrClass; | ||||
|  | ||||
| @property (nonatomic, readonly) id   value; | ||||
| @property (nonatomic, readonly) BOOL isClass; | ||||
| @property (nonatomic, readonly) NSString *className; | ||||
|  | ||||
| @property (nonatomic, readonly) NSArray<FLEXProperty *> *properties; | ||||
| @property (nonatomic, readonly) NSArray<FLEXProperty *> *classProperties; | ||||
| @property (nonatomic, readonly) NSArray<FLEXIvar *>     *ivars; | ||||
| @property (nonatomic, readonly) NSArray<FLEXMethod *>   *methods; | ||||
| @property (nonatomic, readonly) NSArray<FLEXMethod *>   *classMethods; | ||||
| @property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols; | ||||
|  | ||||
| @property (nonatomic, readonly, nullable) FLEXMirror *superMirror NS_SWIFT_NAME(superMirror); | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| @interface FLEXMirror (ExtendedMirror) | ||||
|  | ||||
| /// @return The instance method with the given name, or \c nil if one does not exist. | ||||
| - (nullable FLEXMethod *)methodNamed:(nullable NSString *)name; | ||||
| /// @return The class method with the given name, or \c nil if one does not exist. | ||||
| - (nullable FLEXMethod *)classMethodNamed:(nullable NSString *)name; | ||||
| /// @return The instance property with the given name, or \c nil if one does not exist. | ||||
| - (nullable FLEXProperty *)propertyNamed:(nullable NSString *)name; | ||||
| /// @return The class property with the given name, or \c nil if one does not exist. | ||||
| - (nullable FLEXProperty *)classPropertyNamed:(nullable NSString *)name; | ||||
| /// @return The instance variable with the given name, or \c nil if one does not exist. | ||||
| - (nullable FLEXIvar *)ivarNamed:(nullable NSString *)name; | ||||
| /// @return The protocol with the given name, or \c nil if one does not exist. | ||||
| - (nullable FLEXProtocol *)protocolNamed:(nullable NSString *)name; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										145
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,145 @@ | ||||
| // | ||||
| //  FLEXMirror.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/29/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXMirror.h" | ||||
| #import "FLEXProperty.h" | ||||
| #import "FLEXMethod.h" | ||||
| #import "FLEXIvar.h" | ||||
| #import "FLEXProtocol.h" | ||||
| #import "FLEXUtility.h" | ||||
|  | ||||
|  | ||||
| #pragma mark FLEXMirror | ||||
|  | ||||
| @implementation FLEXMirror | ||||
|  | ||||
| - (id)init { | ||||
|     [NSException | ||||
|         raise:NSInternalInconsistencyException | ||||
|         format:@"Class instance should not be created with -init" | ||||
|     ]; | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| #pragma mark Initialization | ||||
| + (instancetype)reflect:(id)objectOrClass { | ||||
|     return [[self alloc] initWithSubject:objectOrClass]; | ||||
| } | ||||
|  | ||||
| - (id)initWithSubject:(id)objectOrClass { | ||||
|     NSParameterAssert(objectOrClass); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _value = objectOrClass; | ||||
|         [self examine]; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (NSString *)description { | ||||
|     return [NSString stringWithFormat:@"<%@ %@=%@>", | ||||
|         NSStringFromClass(self.class), | ||||
|         self.isClass ? @"metaclass" : @"class", | ||||
|         self.className | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| - (void)examine { | ||||
|     BOOL isClass = object_isClass(self.value); | ||||
|     Class cls  = isClass ? self.value : object_getClass(self.value); | ||||
|     Class meta = object_getClass(cls); | ||||
|     _className = NSStringFromClass(cls); | ||||
|     _isClass   = isClass; | ||||
|      | ||||
|     unsigned int pcount, cpcount, mcount, cmcount, ivcount, pccount; | ||||
|     Ivar *objcIvars                       = class_copyIvarList(cls, &ivcount); | ||||
|     Method *objcMethods                   = class_copyMethodList(cls, &mcount); | ||||
|     Method *objcClsMethods                = class_copyMethodList(meta, &cmcount); | ||||
|     objc_property_t *objcProperties       = class_copyPropertyList(cls, &pcount); | ||||
|     objc_property_t *objcClsProperties    = class_copyPropertyList(meta, &cpcount); | ||||
|     Protocol *__unsafe_unretained *protos = class_copyProtocolList(cls, &pccount); | ||||
|      | ||||
|     _ivars = [NSArray flex_forEachUpTo:ivcount map:^id(NSUInteger i) { | ||||
|         return [FLEXIvar ivar:objcIvars[i]]; | ||||
|     }]; | ||||
|      | ||||
|     _methods = [NSArray flex_forEachUpTo:mcount map:^id(NSUInteger i) { | ||||
|         return [FLEXMethod method:objcMethods[i] isInstanceMethod:YES]; | ||||
|     }]; | ||||
|     _classMethods = [NSArray flex_forEachUpTo:cmcount map:^id(NSUInteger i) { | ||||
|         return [FLEXMethod method:objcClsMethods[i] isInstanceMethod:NO]; | ||||
|     }]; | ||||
|      | ||||
|     _properties = [NSArray flex_forEachUpTo:pcount map:^id(NSUInteger i) { | ||||
|         return [FLEXProperty property:objcProperties[i] onClass:cls]; | ||||
|     }]; | ||||
|     _classProperties = [NSArray flex_forEachUpTo:cpcount map:^id(NSUInteger i) { | ||||
|         return [FLEXProperty property:objcClsProperties[i] onClass:meta]; | ||||
|     }]; | ||||
|      | ||||
|     _protocols = [NSArray flex_forEachUpTo:pccount map:^id(NSUInteger i) { | ||||
|         return [FLEXProtocol protocol:protos[i]]; | ||||
|     }]; | ||||
|      | ||||
|     // Cleanup | ||||
|     free(objcClsProperties); | ||||
|     free(objcProperties); | ||||
|     free(objcClsMethods); | ||||
|     free(objcMethods); | ||||
|     free(objcIvars); | ||||
|     free(protos); | ||||
|     protos = NULL; | ||||
| } | ||||
|  | ||||
| #pragma mark Misc | ||||
|  | ||||
| - (FLEXMirror *)superMirror { | ||||
|     Class cls = _isClass ? _value : object_getClass(_value); | ||||
|     return [FLEXMirror reflect:class_getSuperclass(cls)]; | ||||
| } | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| #pragma mark ExtendedMirror | ||||
|  | ||||
| @implementation FLEXMirror (ExtendedMirror) | ||||
|  | ||||
| - (id)filter:(NSArray *)array forName:(NSString *)name { | ||||
|     NSPredicate *filter = [NSPredicate predicateWithFormat:@"%K = %@", @"name", name]; | ||||
|     return [array filteredArrayUsingPredicate:filter].firstObject; | ||||
| } | ||||
|  | ||||
| - (FLEXMethod *)methodNamed:(NSString *)name { | ||||
|     return [self filter:self.methods forName:name]; | ||||
| } | ||||
|  | ||||
| - (FLEXMethod *)classMethodNamed:(NSString *)name { | ||||
|     return [self filter:self.classMethods forName:name]; | ||||
| } | ||||
|  | ||||
| - (FLEXProperty *)propertyNamed:(NSString *)name { | ||||
|     return [self filter:self.properties forName:name]; | ||||
| } | ||||
|  | ||||
| - (FLEXProperty *)classPropertyNamed:(NSString *)name { | ||||
|     return [self filter:self.classProperties forName:name]; | ||||
| } | ||||
|  | ||||
| - (FLEXIvar *)ivarNamed:(NSString *)name { | ||||
|     return [self filter:self.ivars forName:name]; | ||||
| } | ||||
|  | ||||
| - (FLEXProtocol *)protocolNamed:(NSString *)name { | ||||
|     return [self filter:self.protocols forName:name]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										138
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| // | ||||
| //  FLEXProperty.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/30/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeConstants.h" | ||||
| @class FLEXPropertyAttributes, FLEXMethodBase; | ||||
|  | ||||
|  | ||||
| #pragma mark FLEXProperty | ||||
| @interface FLEXProperty : NSObject | ||||
|  | ||||
| /// You may use this initializer instead of \c property:onClass: if you don't need | ||||
| /// to know anything about the uniqueness of this property or where it comes from. | ||||
| + (instancetype)property:(objc_property_t)property; | ||||
| /// This initializer can be used to access additional information | ||||
| /// in an efficient manner. That information being whether this property | ||||
| /// is certainly not unique and the name of the binary image which declares it. | ||||
| /// @param cls the class, or metaclass if this is a class property. | ||||
| + (instancetype)property:(objc_property_t)property onClass:(Class)cls; | ||||
| /// @param cls the class, or metaclass if this is a class property | ||||
| + (instancetype)named:(NSString *)name onClass:(Class)cls; | ||||
| /// Constructs a new property with the given name and attributes. | ||||
| + (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes; | ||||
|  | ||||
| /// \c 0 if the instance was created via \c +propertyWithName:attributes, | ||||
| /// otherwise this is the first property in \c objc_properties | ||||
| @property (nonatomic, readonly) objc_property_t  objc_property; | ||||
| @property (nonatomic, readonly) objc_property_t  *objc_properties; | ||||
| @property (nonatomic, readonly) NSInteger        objc_propertyCount; | ||||
| @property (nonatomic, readonly) BOOL             isClassProperty; | ||||
|  | ||||
| /// The name of the property. | ||||
| @property (nonatomic, readonly) NSString         *name; | ||||
| /// The type of the property. Get the full type from the attributes. | ||||
| @property (nonatomic, readonly) FLEXTypeEncoding type; | ||||
| /// The property's attributes. | ||||
| @property (nonatomic          ) FLEXPropertyAttributes *attributes; | ||||
| /// The (likely) setter, regardless of whether the property is readonly. | ||||
| /// For example, this might be the custom setter. | ||||
| @property (nonatomic, readonly) SEL likelySetter; | ||||
| @property (nonatomic, readonly) NSString *likelySetterString; | ||||
| /// Not valid unless initialized with the owning class. | ||||
| @property (nonatomic, readonly) BOOL likelySetterExists; | ||||
| /// The (likely) getter. For example, this might be the custom getter. | ||||
| @property (nonatomic, readonly) SEL likelyGetter; | ||||
| @property (nonatomic, readonly) NSString *likelyGetterString; | ||||
| /// Not valid unless initialized with the owning class. | ||||
| @property (nonatomic, readonly) BOOL likelyGetterExists; | ||||
| /// Always \c nil for class properties. | ||||
| @property (nonatomic, readonly) NSString *likelyIvarName; | ||||
| /// Not valid unless initialized with the owning class. | ||||
| @property (nonatomic, readonly) BOOL likelyIvarExists; | ||||
|  | ||||
| /// Whether there are certainly multiple definitions of this property, | ||||
| /// such as in categories in other binary images or something. | ||||
| /// @return Whether \c objc_property matches the return value of \c class_getProperty, | ||||
| /// or \c NO if this property was not created with \c property:onClass | ||||
| @property (nonatomic, readonly) BOOL multiple; | ||||
| /// @return The bundle of the image that contains this property definition, | ||||
| /// or \c nil if this property was not created with \c property:onClass or | ||||
| /// if this property was probably defined at runtime. | ||||
| @property (nonatomic, readonly) NSString *imageName; | ||||
| /// The full path of the image that contains this property definition, | ||||
| /// or \c nil if this property was not created with \c property:onClass or | ||||
| /// if this property was probably defined at runtime. | ||||
| @property (nonatomic, readonly) NSString *imagePath; | ||||
|  | ||||
| /// For internal use | ||||
| @property (nonatomic) id tag; | ||||
|  | ||||
| /// @return The value of this property on \c target as given by \c -valueForKey: | ||||
| /// A source-like description of the property, with all of its attributes. | ||||
| @property (nonatomic, readonly) NSString *fullDescription; | ||||
|  | ||||
| /// If this is a class property, you must class the class object. | ||||
| - (id)getValue:(id)target; | ||||
| /// Calls into -getValue: and passes that value into | ||||
| /// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:] | ||||
| /// and returns the result. | ||||
| /// | ||||
| /// If this is a class property, you must class the class object. | ||||
| - (id)getPotentiallyUnboxedValue:(id)target; | ||||
|  | ||||
| /// Safe to use regardless of how the \c FLEXProperty instance was initialized. | ||||
| /// | ||||
| /// This uses \c self.objc_property if it exists, otherwise it uses \c self.attributes | ||||
| - (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount; | ||||
|  | ||||
| /// Replace the attributes of the current property in the given class, | ||||
| /// using the attributes in \c self.attributes | ||||
| /// | ||||
| /// What happens when the property does not exist is undocumented. | ||||
| - (void)replacePropertyOnClass:(Class)cls; | ||||
|  | ||||
| #pragma mark Convenience getters and setters | ||||
| /// @return A getter for the property with the given implementation. | ||||
| /// @discussion Consider using the \c FLEXPropertyGetter macros. | ||||
| - (FLEXMethodBase *)getterWithImplementation:(IMP)implementation; | ||||
| /// @return A setter for the property with the given implementation. | ||||
| /// @discussion Consider using the \c FLEXPropertySetter macros. | ||||
| - (FLEXMethodBase *)setterWithImplementation:(IMP)implementation; | ||||
|  | ||||
| #pragma mark FLEXMethod property getter / setter macros | ||||
| // Easier than using the above methods yourself in most cases | ||||
|  | ||||
| /// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and | ||||
| /// uses the \c FLEXProperty's \c attribute's \c backingIvarName to get the Ivar. | ||||
| #define FLEXPropertyGetter(FLEXProperty, type) [FLEXProperty \ | ||||
|     getterWithImplementation:imp_implementationWithBlock(^(id self) { \ | ||||
|         return *(type *)[self getIvarAddressByName:FLEXProperty.attributes.backingIvar]; \ | ||||
|     }) \ | ||||
| ]; | ||||
| /// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and | ||||
| /// uses the \c FLEXProperty's \c attribute's \c backingIvarName to set the Ivar. | ||||
| #define FLEXPropertySetter(FLEXProperty, type) [FLEXProperty \ | ||||
|     setterWithImplementation:imp_implementationWithBlock(^(id self, type value) { \ | ||||
|         [self setIvarByName:FLEXProperty.attributes.backingIvar value:&value size:sizeof(type)]; \ | ||||
|     }) \ | ||||
| ]; | ||||
| /// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and an Ivar name string to get the Ivar. | ||||
| #define FLEXPropertyGetterWithIvar(FLEXProperty, ivarName, type) [FLEXProperty \ | ||||
|     getterWithImplementation:imp_implementationWithBlock(^(id self) { \ | ||||
|         return *(type *)[self getIvarAddressByName:ivarName]; \ | ||||
|     }) \ | ||||
| ]; | ||||
| /// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and an Ivar name string to set the Ivar. | ||||
| #define FLEXPropertySetterWithIvar(FLEXProperty, ivarName, type) [FLEXProperty \ | ||||
|     setterWithImplementation:imp_implementationWithBlock(^(id self, type value) { \ | ||||
|         [self setIvarByName:ivarName value:&value size:sizeof(type)]; \ | ||||
|     }) \ | ||||
| ]; | ||||
|  | ||||
| @end | ||||
							
								
								
									
										295
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,295 @@ | ||||
| // | ||||
| //  FLEXProperty.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/30/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXProperty.h" | ||||
| #import "FLEXPropertyAttributes.h" | ||||
| #import "FLEXMethodBase.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #include <dlfcn.h> | ||||
|  | ||||
|  | ||||
| @interface FLEXProperty () { | ||||
|     NSString *_flex_description; | ||||
| } | ||||
| @property (nonatomic          ) BOOL uniqueCheckFlag; | ||||
| @property (nonatomic, readonly) Class cls; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXProperty | ||||
| @synthesize multiple = _multiple; | ||||
| @synthesize imageName = _imageName; | ||||
| @synthesize imagePath = _imagePath; | ||||
|  | ||||
| #pragma mark Initializers | ||||
|  | ||||
| - (id)init { | ||||
|     [NSException | ||||
|         raise:NSInternalInconsistencyException | ||||
|         format:@"Class instance should not be created with -init" | ||||
|     ]; | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| + (instancetype)property:(objc_property_t)property { | ||||
|     return [[self alloc] initWithProperty:property onClass:nil]; | ||||
| } | ||||
|  | ||||
| + (instancetype)property:(objc_property_t)property onClass:(Class)cls { | ||||
|     return [[self alloc] initWithProperty:property onClass:cls]; | ||||
| } | ||||
|  | ||||
| + (instancetype)named:(NSString *)name onClass:(Class)cls { | ||||
|     objc_property_t _Nullable property = class_getProperty(cls, name.UTF8String); | ||||
|     NSAssert(property, @"Cannot find property with name %@ on class %@", name, cls); | ||||
|     return [self property:property onClass:cls]; | ||||
| } | ||||
|  | ||||
| + (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes { | ||||
|     return [[self alloc] initWithName:name attributes:attributes]; | ||||
| } | ||||
|  | ||||
| - (id)initWithProperty:(objc_property_t)property onClass:(Class)cls { | ||||
|     NSParameterAssert(property); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _objc_property = property; | ||||
|         _attributes    = [FLEXPropertyAttributes attributesForProperty:property]; | ||||
|         _name          = @(property_getName(property) ?: "(nil)"); | ||||
|         _cls           = cls; | ||||
|          | ||||
|         if (!_attributes) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property attributes"]; | ||||
|         if (!_name) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property name"]; | ||||
|          | ||||
|         [self examine]; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (id)initWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes { | ||||
|     NSParameterAssert(name); NSParameterAssert(attributes); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _attributes    = attributes; | ||||
|         _name          = name; | ||||
|          | ||||
|         [self examine]; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| #pragma mark Private | ||||
|  | ||||
| - (void)examine { | ||||
|     if (self.attributes.typeEncoding.length) { | ||||
|         _type = (FLEXTypeEncoding)[self.attributes.typeEncoding characterAtIndex:0]; | ||||
|     } | ||||
|  | ||||
|     // Return the given selector if the class responds to it | ||||
|     Class cls = _cls; | ||||
|     SEL (^selectorIfValid)(SEL) = ^SEL(SEL sel) { | ||||
|         if (!sel || !cls) return nil; | ||||
|         return [cls instancesRespondToSelector:sel] ? sel : nil; | ||||
|     }; | ||||
|  | ||||
|     SEL customGetter = self.attributes.customGetter; | ||||
|     SEL customSetter = self.attributes.customSetter; | ||||
|     SEL defaultGetter = NSSelectorFromString(self.name); | ||||
|     SEL defaultSetter = NSSelectorFromString([NSString | ||||
|         stringWithFormat:@"set%c%@:", | ||||
|         (char)toupper([self.name characterAtIndex:0]), | ||||
|         [self.name substringFromIndex:1] | ||||
|     ]); | ||||
|  | ||||
|     // Check if the likely getters/setters exist | ||||
|     SEL validGetter = selectorIfValid(customGetter) ?: selectorIfValid(defaultGetter); | ||||
|     SEL validSetter = selectorIfValid(customSetter) ?: selectorIfValid(defaultSetter); | ||||
|     _likelyGetterExists = validGetter != nil; | ||||
|     _likelySetterExists = validSetter != nil; | ||||
|  | ||||
|     // Assign likely getters and setters to the valid one, | ||||
|     // or the default, regardless of whether the default exists | ||||
|     _likelyGetter = validGetter ?: defaultGetter; | ||||
|     _likelySetter = validSetter ?: defaultSetter; | ||||
|     _likelyGetterString = NSStringFromSelector(_likelyGetter); | ||||
|     _likelySetterString = NSStringFromSelector(_likelySetter); | ||||
|  | ||||
|     _isClassProperty = _cls ? class_isMetaClass(_cls) : NO; | ||||
|      | ||||
|     _likelyIvarName = _isClassProperty ? nil : ( | ||||
|         self.attributes.backingIvar ?: [@"_" stringByAppendingString:_name] | ||||
|     ); | ||||
| } | ||||
|  | ||||
| #pragma mark Overrides | ||||
|  | ||||
| - (NSString *)description { | ||||
|     if (!_flex_description) { | ||||
|         NSString *readableType = [FLEXRuntimeUtility readableTypeForEncoding:self.attributes.typeEncoding]; | ||||
|         _flex_description = [FLEXRuntimeUtility appendName:self.name toType:readableType]; | ||||
|     } | ||||
|  | ||||
|     return _flex_description; | ||||
| } | ||||
|  | ||||
| - (NSString *)debugDescription { | ||||
|     return [NSString stringWithFormat:@"<%@ name=%@, property=%p, attributes:\n\t%@\n>", | ||||
|             NSStringFromClass(self.class), self.name, self.objc_property, self.attributes]; | ||||
| } | ||||
|  | ||||
| #pragma mark Public | ||||
|  | ||||
| - (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount { | ||||
|     if (self.objc_property) { | ||||
|         return property_copyAttributeList(self.objc_property, attributesCount); | ||||
|     } else { | ||||
|         return [self.attributes copyAttributesList:attributesCount]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (void)replacePropertyOnClass:(Class)cls { | ||||
|     class_replaceProperty(cls, self.name.UTF8String, self.attributes.list, (unsigned int)self.attributes.count); | ||||
| } | ||||
|  | ||||
| - (void)computeSymbolInfo:(BOOL)forceBundle { | ||||
|     Dl_info exeInfo; | ||||
|     if (dladdr(_objc_property, &exeInfo)) { | ||||
|         _imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil; | ||||
|     } | ||||
|      | ||||
|     if ((!_multiple || !_uniqueCheckFlag) && _cls) { | ||||
|         _multiple = _objc_property != class_getProperty(_cls, self.name.UTF8String); | ||||
|  | ||||
|         if (_multiple || forceBundle) { | ||||
|             NSString *path = _imagePath.stringByDeletingLastPathComponent; | ||||
|             _imageName = [NSBundle bundleWithPath:path].executablePath.lastPathComponent; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (BOOL)multiple { | ||||
|     [self computeSymbolInfo:NO]; | ||||
|     return _multiple; | ||||
| } | ||||
|  | ||||
| - (NSString *)imagePath { | ||||
|     [self computeSymbolInfo:YES]; | ||||
|     return _imagePath; | ||||
| } | ||||
|  | ||||
| - (NSString *)imageName { | ||||
|     [self computeSymbolInfo:YES]; | ||||
|     return _imageName; | ||||
| } | ||||
|  | ||||
| - (BOOL)likelyIvarExists { | ||||
|     if (_likelyIvarName && _cls) { | ||||
|         return class_getInstanceVariable(_cls, _likelyIvarName.UTF8String) != nil; | ||||
|     } | ||||
|      | ||||
|     return NO; | ||||
| } | ||||
|  | ||||
| - (NSString *)fullDescription { | ||||
|     NSMutableArray<NSString *> *attributesStrings = [NSMutableArray new]; | ||||
|     FLEXPropertyAttributes *attributes = self.attributes; | ||||
|  | ||||
|     // Atomicity | ||||
|     if (attributes.isNonatomic) { | ||||
|         [attributesStrings addObject:@"nonatomic"]; | ||||
|     } else { | ||||
|         [attributesStrings addObject:@"atomic"]; | ||||
|     } | ||||
|  | ||||
|     // Storage | ||||
|     if (attributes.isRetained) { | ||||
|         [attributesStrings addObject:@"strong"]; | ||||
|     } else if (attributes.isCopy) { | ||||
|         [attributesStrings addObject:@"copy"]; | ||||
|     } else if (attributes.isWeak) { | ||||
|         [attributesStrings addObject:@"weak"]; | ||||
|     } else { | ||||
|         [attributesStrings addObject:@"assign"]; | ||||
|     } | ||||
|  | ||||
|     // Mutability | ||||
|     if (attributes.isReadOnly) { | ||||
|         [attributesStrings addObject:@"readonly"]; | ||||
|     } else { | ||||
|         [attributesStrings addObject:@"readwrite"]; | ||||
|     } | ||||
|      | ||||
|     // Class or not | ||||
|     if (self.isClassProperty) { | ||||
|         [attributesStrings addObject:@"class"]; | ||||
|     } | ||||
|  | ||||
|     // Custom getter/setter | ||||
|     SEL customGetter = attributes.customGetter; | ||||
|     SEL customSetter = attributes.customSetter; | ||||
|     if (customGetter) { | ||||
|         [attributesStrings addObject:[NSString stringWithFormat:@"getter=%s", sel_getName(customGetter)]]; | ||||
|     } | ||||
|     if (customSetter) { | ||||
|         [attributesStrings addObject:[NSString stringWithFormat:@"setter=%s", sel_getName(customSetter)]]; | ||||
|     } | ||||
|  | ||||
|     NSString *attributesString = [attributesStrings componentsJoinedByString:@", "]; | ||||
|     return [NSString stringWithFormat:@"@property (%@) %@", attributesString, self.description]; | ||||
| } | ||||
|  | ||||
| - (id)getValue:(id)target { | ||||
|     if (!target) return nil; | ||||
|      | ||||
|     // We don't care about checking dynamically whether the getter | ||||
|     // _now_ exists on this object. If the getter doesn't exist | ||||
|     // when this property is initialized, it will never call it. | ||||
|     // Just re-create the property object if you need to call it. | ||||
|     if (self.likelyGetterExists) { | ||||
|         BOOL objectIsClass = object_isClass(target); | ||||
|         BOOL instanceAndInstanceProperty = !objectIsClass && !self.isClassProperty; | ||||
|         BOOL classAndClassProperty = objectIsClass && self.isClassProperty; | ||||
|  | ||||
|         if (instanceAndInstanceProperty || classAndClassProperty) { | ||||
|             return [FLEXRuntimeUtility performSelector:self.likelyGetter onObject:target]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| - (id)getPotentiallyUnboxedValue:(id)target { | ||||
|     if (!target) return nil; | ||||
|  | ||||
|     return [FLEXRuntimeUtility | ||||
|         potentiallyUnwrapBoxedPointer:[self getValue:target] | ||||
|         type:self.attributes.typeEncoding.UTF8String | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| #pragma mark Suggested getters and setters | ||||
|  | ||||
| - (FLEXMethodBase *)getterWithImplementation:(IMP)implementation { | ||||
|     NSString *types        = [NSString stringWithFormat:@"%@%s%s", self.attributes.typeEncoding, @encode(id), @encode(SEL)]; | ||||
|     NSString *name         = [NSString stringWithFormat:@"%@", self.name]; | ||||
|     FLEXMethodBase *getter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation]; | ||||
|     return getter; | ||||
| } | ||||
|  | ||||
| - (FLEXMethodBase *)setterWithImplementation:(IMP)implementation { | ||||
|     NSString *types        = [NSString stringWithFormat:@"%s%s%s%@", @encode(void), @encode(id), @encode(SEL), self.attributes.typeEncoding]; | ||||
|     NSString *name         = [NSString stringWithFormat:@"set%@:", self.name.capitalizedString]; | ||||
|     FLEXMethodBase *setter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation]; | ||||
|     return setter; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,110 @@ | ||||
| // | ||||
| //  FLEXPropertyAttributes.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 7/5/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
| #import <objc/runtime.h> | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| #pragma mark FLEXPropertyAttributes | ||||
|  | ||||
| /// See \e FLEXRuntimeUtilitiy.h for valid string tokens. | ||||
| /// See this link on how to construct a proper attributes string: | ||||
| /// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html | ||||
| @interface FLEXPropertyAttributes : NSObject <NSCopying, NSMutableCopying> { | ||||
| // These are necessary for the mutable subclass to function | ||||
| @protected | ||||
|     NSUInteger _count; | ||||
|     NSString *_string, *_backingIvar, *_typeEncoding, *_oldTypeEncoding, *_fullDeclaration; | ||||
|     NSDictionary *_dictionary; | ||||
|     objc_property_attribute_t *_list; | ||||
|     SEL _customGetter, _customSetter; | ||||
|     BOOL _isReadOnly, _isCopy, _isRetained, _isNonatomic, _isDynamic, _isWeak, _isGarbageCollectable; | ||||
| } | ||||
|  | ||||
| + (instancetype)attributesForProperty:(objc_property_t)property; | ||||
| /// @warning Raises an exception if \e attributes is invalid, \c nil, or contains unsupported keys. | ||||
| + (instancetype)attributesFromDictionary:(NSDictionary *)attributes; | ||||
|  | ||||
| /// Copies the attributes list to a buffer you must \c free() yourself. | ||||
| /// Use \c list instead if you do not need more control over the lifetime of the list. | ||||
| /// @param attributesCountOut the number of attributes is returned in this parameter. | ||||
| - (objc_property_attribute_t *)copyAttributesList:(nullable unsigned int *)attributesCountOut; | ||||
|  | ||||
| /// The number of property attributes. | ||||
| @property (nonatomic, readonly) NSUInteger count; | ||||
| /// For use with \c class_replaceProperty and the like. | ||||
| @property (nonatomic, readonly) objc_property_attribute_t *list; | ||||
| /// The string value of the property attributes. | ||||
| @property (nonatomic, readonly) NSString *string; | ||||
| /// A human-readable version of the property attributes. | ||||
| @property (nonatomic, readonly) NSString *fullDeclaration; | ||||
| /// A dictionary of the property attributes. | ||||
| /// Values are either a string or \c YES. Boolean attributes | ||||
| /// which are false will not be present in the dictionary. | ||||
| @property (nonatomic, readonly) NSDictionary *dictionary; | ||||
|  | ||||
| /// The name of the instance variable backing the property. | ||||
| @property (nonatomic, readonly, nullable) NSString *backingIvar; | ||||
| /// The type encoding of the property. | ||||
| @property (nonatomic, readonly, nullable) NSString *typeEncoding; | ||||
| /// The \e old type encoding of the property. | ||||
| @property (nonatomic, readonly, nullable) NSString *oldTypeEncoding; | ||||
| /// The property's custom getter, if any. | ||||
| @property (nonatomic, readonly, nullable) SEL customGetter; | ||||
| /// The property's custom setter, if any. | ||||
| @property (nonatomic, readonly, nullable) SEL customSetter; | ||||
| /// The property's custom getter as a string, if any. | ||||
| @property (nonatomic, readonly, nullable) NSString *customGetterString; | ||||
| /// The property's custom setter as a string, if any. | ||||
| @property (nonatomic, readonly, nullable) NSString *customSetterString; | ||||
|  | ||||
| @property (nonatomic, readonly) BOOL isReadOnly; | ||||
| @property (nonatomic, readonly) BOOL isCopy; | ||||
| @property (nonatomic, readonly) BOOL isRetained; | ||||
| @property (nonatomic, readonly) BOOL isNonatomic; | ||||
| @property (nonatomic, readonly) BOOL isDynamic; | ||||
| @property (nonatomic, readonly) BOOL isWeak; | ||||
| @property (nonatomic, readonly) BOOL isGarbageCollectable; | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| #pragma mark FLEXPropertyAttributes | ||||
| @interface FLEXMutablePropertyAttributes : FLEXPropertyAttributes | ||||
|  | ||||
| /// Creates and returns an empty property attributes object. | ||||
| + (instancetype)attributes; | ||||
|  | ||||
| /// The name of the instance variable backing the property. | ||||
| @property (nonatomic, nullable) NSString *backingIvar; | ||||
| /// The type encoding of the property. | ||||
| @property (nonatomic, nullable) NSString *typeEncoding; | ||||
| /// The \e old type encoding of the property. | ||||
| @property (nonatomic, nullable) NSString *oldTypeEncoding; | ||||
| /// The property's custom getter, if any. | ||||
| @property (nonatomic, nullable) SEL customGetter; | ||||
| /// The property's custom setter, if any. | ||||
| @property (nonatomic, nullable) SEL customSetter; | ||||
|  | ||||
| @property (nonatomic) BOOL isReadOnly; | ||||
| @property (nonatomic) BOOL isCopy; | ||||
| @property (nonatomic) BOOL isRetained; | ||||
| @property (nonatomic) BOOL isNonatomic; | ||||
| @property (nonatomic) BOOL isDynamic; | ||||
| @property (nonatomic) BOOL isWeak; | ||||
| @property (nonatomic) BOOL isGarbageCollectable; | ||||
|  | ||||
| /// A more convenient method of setting the \c typeEncoding property. | ||||
| /// @discussion This will not work for complex types like structs and primitive pointers. | ||||
| - (void)setTypeEncodingChar:(char)type; | ||||
|  | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
| @@ -0,0 +1,376 @@ | ||||
| // | ||||
| //  FLEXPropertyAttributes.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 7/5/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXPropertyAttributes.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "NSString+ObjcRuntime.h" | ||||
| #import "NSDictionary+ObjcRuntime.h" | ||||
|  | ||||
|  | ||||
| #pragma mark FLEXPropertyAttributes | ||||
|  | ||||
| @interface FLEXPropertyAttributes () | ||||
|  | ||||
| @property (nonatomic) NSString *backingIvar; | ||||
| @property (nonatomic) NSString *typeEncoding; | ||||
| @property (nonatomic) NSString *oldTypeEncoding; | ||||
| @property (nonatomic) SEL customGetter; | ||||
| @property (nonatomic) SEL customSetter; | ||||
| @property (nonatomic) BOOL isReadOnly; | ||||
| @property (nonatomic) BOOL isCopy; | ||||
| @property (nonatomic) BOOL isRetained; | ||||
| @property (nonatomic) BOOL isNonatomic; | ||||
| @property (nonatomic) BOOL isDynamic; | ||||
| @property (nonatomic) BOOL isWeak; | ||||
| @property (nonatomic) BOOL isGarbageCollectable; | ||||
|  | ||||
| - (NSString *)buildFullDeclaration; | ||||
|  | ||||
| @end | ||||
|  | ||||
| @implementation FLEXPropertyAttributes | ||||
| @synthesize list = _list; | ||||
|  | ||||
| #pragma mark Initializers | ||||
|  | ||||
| + (instancetype)attributesForProperty:(objc_property_t)property { | ||||
|     return [self attributesFromDictionary:[NSDictionary attributesDictionaryForProperty:property]]; | ||||
| } | ||||
|  | ||||
| + (instancetype)attributesFromDictionary:(NSDictionary *)attributes { | ||||
|     return [[self alloc] initWithAttributesDictionary:attributes]; | ||||
| } | ||||
|  | ||||
| - (id)initWithAttributesDictionary:(NSDictionary *)attributes { | ||||
|     NSParameterAssert(attributes); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _dictionary           = attributes; | ||||
|         _string               = attributes.propertyAttributesString; | ||||
|         _count                = attributes.count; | ||||
|         _typeEncoding         = attributes[kFLEXPropertyAttributeKeyTypeEncoding]; | ||||
|         _backingIvar          = attributes[kFLEXPropertyAttributeKeyBackingIvarName]; | ||||
|         _oldTypeEncoding      = attributes[kFLEXPropertyAttributeKeyOldStyleTypeEncoding]; | ||||
|         _customGetterString   = attributes[kFLEXPropertyAttributeKeyCustomGetter]; | ||||
|         _customSetterString   = attributes[kFLEXPropertyAttributeKeyCustomSetter]; | ||||
|         _customGetter         = NSSelectorFromString(_customGetterString); | ||||
|         _customSetter         = NSSelectorFromString(_customSetterString); | ||||
|         _isReadOnly           = attributes[kFLEXPropertyAttributeKeyReadOnly] != nil; | ||||
|         _isCopy               = attributes[kFLEXPropertyAttributeKeyCopy] != nil; | ||||
|         _isRetained           = attributes[kFLEXPropertyAttributeKeyRetain] != nil; | ||||
|         _isNonatomic          = attributes[kFLEXPropertyAttributeKeyNonAtomic] != nil; | ||||
|         _isWeak               = attributes[kFLEXPropertyAttributeKeyWeak] != nil; | ||||
|         _isGarbageCollectable = attributes[kFLEXPropertyAttributeKeyGarbageCollectable] != nil; | ||||
|  | ||||
|         _fullDeclaration = [self buildFullDeclaration]; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| #pragma mark Misc | ||||
|  | ||||
| - (NSString *)description { | ||||
|     return [NSString | ||||
|         stringWithFormat:@"<%@ \"%@\", ivar=%@, readonly=%d, nonatomic=%d, getter=%@, setter=%@>", | ||||
|         NSStringFromClass(self.class), | ||||
|         self.string, | ||||
|         self.backingIvar ?: @"none", | ||||
|         self.isReadOnly, | ||||
|         self.isNonatomic, | ||||
|         NSStringFromSelector(self.customGetter) ?: @"none", | ||||
|         NSStringFromSelector(self.customSetter) ?: @"none" | ||||
|     ]; | ||||
| } | ||||
|  | ||||
| - (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount { | ||||
|     NSDictionary *attrs = self.string.propertyAttributes; | ||||
|     objc_property_attribute_t *propertyAttributes = malloc(attrs.count * sizeof(objc_property_attribute_t)); | ||||
|  | ||||
|     if (attributesCount) { | ||||
|         *attributesCount = (unsigned int)attrs.count; | ||||
|     } | ||||
|      | ||||
|     NSUInteger i = 0; | ||||
|     for (NSString *key in attrs.allKeys) { | ||||
|         FLEXPropertyAttribute c = (FLEXPropertyAttribute)[key characterAtIndex:0]; | ||||
|         switch (c) { | ||||
|             case FLEXPropertyAttributeTypeEncoding: { | ||||
|                 objc_property_attribute_t pa = { | ||||
|                     kFLEXPropertyAttributeKeyTypeEncoding.UTF8String, | ||||
|                     self.typeEncoding.UTF8String | ||||
|                 }; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeBackingIvarName: { | ||||
|                 objc_property_attribute_t pa = { | ||||
|                     kFLEXPropertyAttributeKeyBackingIvarName.UTF8String, | ||||
|                     self.backingIvar.UTF8String | ||||
|                 }; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeCopy: { | ||||
|                 objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyCopy.UTF8String, ""}; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeCustomGetter: { | ||||
|                 objc_property_attribute_t pa = { | ||||
|                     kFLEXPropertyAttributeKeyCustomGetter.UTF8String, | ||||
|                     NSStringFromSelector(self.customGetter).UTF8String ?: "" | ||||
|                 }; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeCustomSetter: { | ||||
|                 objc_property_attribute_t pa = { | ||||
|                     kFLEXPropertyAttributeKeyCustomSetter.UTF8String, | ||||
|                     NSStringFromSelector(self.customSetter).UTF8String ?: "" | ||||
|                 }; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeDynamic: { | ||||
|                 objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyDynamic.UTF8String, ""}; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeGarbageCollectible: { | ||||
|                 objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyGarbageCollectable.UTF8String, ""}; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeNonAtomic: { | ||||
|                 objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyNonAtomic.UTF8String, ""}; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeOldTypeEncoding: { | ||||
|                 objc_property_attribute_t pa = { | ||||
|                     kFLEXPropertyAttributeKeyOldStyleTypeEncoding.UTF8String, | ||||
|                     self.oldTypeEncoding.UTF8String ?: "" | ||||
|                 }; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeReadOnly: { | ||||
|                 objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyReadOnly.UTF8String, ""}; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeRetain: { | ||||
|                 objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyRetain.UTF8String, ""}; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|             case FLEXPropertyAttributeWeak: { | ||||
|                 objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyWeak.UTF8String, ""}; | ||||
|                 propertyAttributes[i] = pa; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         i++; | ||||
|     } | ||||
|      | ||||
|     return propertyAttributes; | ||||
| } | ||||
|  | ||||
| - (objc_property_attribute_t *)list { | ||||
|     if (!_list) { | ||||
|         _list = [self copyAttributesList:nil]; | ||||
|     } | ||||
|  | ||||
|     return _list; | ||||
| } | ||||
|  | ||||
| - (NSString *)buildFullDeclaration { | ||||
|     NSMutableString *decl = [NSMutableString new]; | ||||
|  | ||||
|     [decl appendFormat:@"%@, ", _isNonatomic ? @"nonatomic" : @"atomic"]; | ||||
|     [decl appendFormat:@"%@, ", _isReadOnly ? @"readonly" : @"readwrite"]; | ||||
|  | ||||
|     BOOL noExplicitMemorySemantics = YES; | ||||
|     if (_isCopy) { noExplicitMemorySemantics = NO; | ||||
|         [decl appendString:@"copy, "]; | ||||
|     } | ||||
|     if (_isRetained) { noExplicitMemorySemantics = NO; | ||||
|         [decl appendString:@"strong, "]; | ||||
|     } | ||||
|     if (_isWeak) { noExplicitMemorySemantics = NO; | ||||
|         [decl appendString:@"weak, "]; | ||||
|     } | ||||
|  | ||||
|     if ([_typeEncoding hasPrefix:@"@"] && noExplicitMemorySemantics) { | ||||
|         // *probably* strong if this is an object; strong is the default. | ||||
|         [decl appendString:@"strong, "]; | ||||
|     } else if (noExplicitMemorySemantics) { | ||||
|         // *probably* assign if this is not an object | ||||
|         [decl appendString:@"assign, "]; | ||||
|     } | ||||
|  | ||||
|     if (_customGetter) { | ||||
|         [decl appendFormat:@"getter=%@, ", NSStringFromSelector(_customGetter)]; | ||||
|     } | ||||
|     if (_customSetter) { | ||||
|         [decl appendFormat:@"setter=%@, ", NSStringFromSelector(_customSetter)]; | ||||
|     } | ||||
|  | ||||
|     [decl deleteCharactersInRange:NSMakeRange(decl.length-2, 2)]; | ||||
|     return decl.copy; | ||||
| } | ||||
|  | ||||
| - (void)dealloc { | ||||
|     if (_list) { | ||||
|         free(_list); | ||||
|         _list = nil; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #pragma mark Copying | ||||
|  | ||||
| - (id)copyWithZone:(NSZone *)zone { | ||||
|     return [[FLEXPropertyAttributes class] attributesFromDictionary:self.dictionary]; | ||||
| } | ||||
|  | ||||
| - (id)mutableCopyWithZone:(NSZone *)zone { | ||||
|     return [[FLEXMutablePropertyAttributes class] attributesFromDictionary:self.dictionary]; | ||||
| } | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
|  | ||||
| #pragma mark FLEXMutablePropertyAttributes | ||||
|  | ||||
| @interface FLEXMutablePropertyAttributes () | ||||
| @property (nonatomic) BOOL countDelta; | ||||
| @property (nonatomic) BOOL stringDelta; | ||||
| @property (nonatomic) BOOL dictDelta; | ||||
| @property (nonatomic) BOOL listDelta; | ||||
| @property (nonatomic) BOOL declDelta; | ||||
| @end | ||||
|  | ||||
| #define PropertyWithDeltaFlag(type, name, Name) @dynamic name; \ | ||||
| - (void)set ## Name:(type)name { \ | ||||
|     if (name != _ ## name) { \ | ||||
|         _countDelta = _stringDelta = _dictDelta = _listDelta = _declDelta = YES; \ | ||||
|         _ ## name = name; \ | ||||
|     } \ | ||||
| } | ||||
|  | ||||
| @implementation FLEXMutablePropertyAttributes | ||||
|  | ||||
| PropertyWithDeltaFlag(NSString *, backingIvar, BackingIvar); | ||||
| PropertyWithDeltaFlag(NSString *, typeEncoding, TypeEncoding); | ||||
| PropertyWithDeltaFlag(NSString *, oldTypeEncoding, OldTypeEncoding); | ||||
| PropertyWithDeltaFlag(SEL, customGetter, CustomGetter); | ||||
| PropertyWithDeltaFlag(SEL, customSetter, CustomSetter); | ||||
| PropertyWithDeltaFlag(BOOL, isReadOnly, IsReadOnly); | ||||
| PropertyWithDeltaFlag(BOOL, isCopy, IsCopy); | ||||
| PropertyWithDeltaFlag(BOOL, isRetained, IsRetained); | ||||
| PropertyWithDeltaFlag(BOOL, isNonatomic, IsNonatomic); | ||||
| PropertyWithDeltaFlag(BOOL, isDynamic, IsDynamic); | ||||
| PropertyWithDeltaFlag(BOOL, isWeak, IsWeak); | ||||
| PropertyWithDeltaFlag(BOOL, isGarbageCollectable, IsGarbageCollectable); | ||||
|  | ||||
| + (instancetype)attributes { | ||||
|     return [self new]; | ||||
| } | ||||
|  | ||||
| - (void)setTypeEncodingChar:(char)type { | ||||
|     self.typeEncoding = [NSString stringWithFormat:@"%c", type]; | ||||
| } | ||||
|  | ||||
| - (NSUInteger)count { | ||||
|     // Recalculate attribute count after mutations | ||||
|     if (self.countDelta) { | ||||
|         self.countDelta = NO; | ||||
|         _count = self.dictionary.count; | ||||
|     } | ||||
|  | ||||
|     return _count; | ||||
| } | ||||
|  | ||||
| - (objc_property_attribute_t *)list { | ||||
|     // Regenerate list after mutations | ||||
|     if (self.listDelta) { | ||||
|         self.listDelta = NO; | ||||
|         if (_list) { | ||||
|             free(_list); | ||||
|             _list = nil; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Super will generate the list if it isn't set | ||||
|     return super.list; | ||||
| } | ||||
|  | ||||
| - (NSString *)string { | ||||
|     // Regenerate string after mutations | ||||
|     if (self.stringDelta || !_string) { | ||||
|         self.stringDelta = NO; | ||||
|         _string = self.dictionary.propertyAttributesString; | ||||
|     } | ||||
|  | ||||
|     return _string; | ||||
| } | ||||
|  | ||||
| - (NSDictionary *)dictionary { | ||||
|     // Regenerate dictionary after mutations | ||||
|     if (self.dictDelta || !_dictionary) { | ||||
|         // _stringa nd _dictionary depend on each other, | ||||
|         // so we must generate ONE by hand using our properties. | ||||
|         // We arbitrarily choose to generate the dictionary. | ||||
|         NSMutableDictionary *attrs = [NSMutableDictionary new]; | ||||
|         if (self.typeEncoding) | ||||
|             attrs[kFLEXPropertyAttributeKeyTypeEncoding]         = self.typeEncoding; | ||||
|         if (self.backingIvar) | ||||
|             attrs[kFLEXPropertyAttributeKeyBackingIvarName]      = self.backingIvar; | ||||
|         if (self.oldTypeEncoding) | ||||
|             attrs[kFLEXPropertyAttributeKeyOldStyleTypeEncoding] = self.oldTypeEncoding; | ||||
|         if (self.customGetter) | ||||
|             attrs[kFLEXPropertyAttributeKeyCustomGetter]         = NSStringFromSelector(self.customGetter); | ||||
|         if (self.customSetter) | ||||
|             attrs[kFLEXPropertyAttributeKeyCustomSetter]         = NSStringFromSelector(self.customSetter); | ||||
|  | ||||
|         if (self.isReadOnly)           attrs[kFLEXPropertyAttributeKeyReadOnly] = @YES; | ||||
|         if (self.isCopy)               attrs[kFLEXPropertyAttributeKeyCopy] = @YES; | ||||
|         if (self.isRetained)           attrs[kFLEXPropertyAttributeKeyRetain] = @YES; | ||||
|         if (self.isNonatomic)          attrs[kFLEXPropertyAttributeKeyNonAtomic] = @YES; | ||||
|         if (self.isDynamic)            attrs[kFLEXPropertyAttributeKeyDynamic] = @YES; | ||||
|         if (self.isWeak)               attrs[kFLEXPropertyAttributeKeyWeak] = @YES; | ||||
|         if (self.isGarbageCollectable) attrs[kFLEXPropertyAttributeKeyGarbageCollectable] = @YES; | ||||
|  | ||||
|         _dictionary = attrs.copy; | ||||
|     } | ||||
|  | ||||
|     return _dictionary; | ||||
| } | ||||
|  | ||||
| - (NSString *)fullDeclaration { | ||||
|     if (self.declDelta || !_fullDeclaration) { | ||||
|         _declDelta = NO; | ||||
|         _fullDeclaration = [self buildFullDeclaration]; | ||||
|     } | ||||
|  | ||||
|     return _fullDeclaration; | ||||
| } | ||||
|  | ||||
| - (NSString *)customGetterString { | ||||
|     return _customGetter ? NSStringFromSelector(_customGetter) : nil; | ||||
| } | ||||
|  | ||||
| - (NSString *)customSetterString { | ||||
|     return _customSetter ? NSStringFromSelector(_customSetter) : nil; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										73
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| // | ||||
| //  FLEXProtocol.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/30/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXRuntimeConstants.h" | ||||
| @class FLEXProperty, FLEXMethodDescription; | ||||
|  | ||||
| NS_ASSUME_NONNULL_BEGIN | ||||
|  | ||||
| #pragma mark FLEXProtocol | ||||
| @interface FLEXProtocol : NSObject | ||||
|  | ||||
| /// Every protocol registered with the runtime. | ||||
| + (NSArray<FLEXProtocol *> *)allProtocols; | ||||
| + (instancetype)protocol:(Protocol *)protocol; | ||||
|  | ||||
| /// The underlying protocol data structure. | ||||
| @property (nonatomic, readonly) Protocol *objc_protocol; | ||||
|  | ||||
| /// The name of the protocol. | ||||
| @property (nonatomic, readonly) NSString *name; | ||||
| /// The required methods of the protocol, if any. This includes property getters and setters. | ||||
| @property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *requiredMethods; | ||||
| /// The optional methods of the protocol, if any. This includes property getters and setters. | ||||
| @property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *optionalMethods; | ||||
| /// All protocols that this protocol conforms to, if any. | ||||
| @property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols; | ||||
| /// The full path of the image that contains this protocol definition, | ||||
| /// or \c nil if this protocol was probably defined at runtime. | ||||
| @property (nonatomic, readonly, nullable) NSString *imagePath; | ||||
|  | ||||
| /// The properties in the protocol, if any. \c nil on iOS 10+  | ||||
| @property (nonatomic, readonly, nullable) NSArray<FLEXProperty *> *properties API_DEPRECATED("Use the more specific accessors below", ios(2.0, 10.0)); | ||||
|  | ||||
| /// The required properties in the protocol, if any. | ||||
| @property (nonatomic, readonly) NSArray<FLEXProperty *> *requiredProperties API_AVAILABLE(ios(10.0)); | ||||
| /// The optional properties in the protocol, if any. | ||||
| @property (nonatomic, readonly) NSArray<FLEXProperty *> *optionalProperties API_AVAILABLE(ios(10.0)); | ||||
|  | ||||
| /// For internal use | ||||
| @property (nonatomic) id tag; | ||||
|  | ||||
| /// Not to be confused with \c -conformsToProtocol:, which refers to the current | ||||
| /// \c FLEXProtocol instance and not the underlying \c Protocol object. | ||||
| - (BOOL)conformsTo:(Protocol *)protocol; | ||||
|  | ||||
| @end | ||||
|  | ||||
|  | ||||
| #pragma mark Method descriptions | ||||
| @interface FLEXMethodDescription : NSObject | ||||
|  | ||||
| + (instancetype)description:(struct objc_method_description)description; | ||||
| + (instancetype)description:(struct objc_method_description)description instance:(BOOL)isInstance; | ||||
|  | ||||
| /// The underlying method description data structure. | ||||
| @property (nonatomic, readonly) struct objc_method_description objc_description; | ||||
| /// The method's selector. | ||||
| @property (nonatomic, readonly) SEL selector; | ||||
| /// The method's type encoding. | ||||
| @property (nonatomic, readonly) NSString *typeEncoding; | ||||
| /// The method's return type. | ||||
| @property (nonatomic, readonly) FLEXTypeEncoding returnType; | ||||
| /// \c YES if this is an instance method, \c NO if it is a class method, or \c nil if unspecified | ||||
| @property (nonatomic, readonly) NSNumber *instance; | ||||
| @end | ||||
|  | ||||
| NS_ASSUME_NONNULL_END | ||||
							
								
								
									
										212
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.m
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,212 @@ | ||||
| // | ||||
| //  FLEXProtocol.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 6/30/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXProtocol.h" | ||||
| #import "FLEXProperty.h" | ||||
| #import "FLEXRuntimeUtility.h" | ||||
| #import "NSArray+FLEX.h" | ||||
| #include <dlfcn.h> | ||||
|  | ||||
| @implementation FLEXProtocol | ||||
|  | ||||
| #pragma mark Initializers | ||||
|  | ||||
| + (NSArray *)allProtocols { | ||||
|     unsigned int prcount; | ||||
|     Protocol *__unsafe_unretained*protocols = objc_copyProtocolList(&prcount); | ||||
|      | ||||
|     NSMutableArray *all = [NSMutableArray new]; | ||||
|     for(NSUInteger i = 0; i < prcount; i++) | ||||
|         [all addObject:[self protocol:protocols[i]]]; | ||||
|      | ||||
|     free(protocols); | ||||
|     return all; | ||||
| } | ||||
|  | ||||
| + (instancetype)protocol:(Protocol *)protocol { | ||||
|     return [[self alloc] initWithProtocol:protocol]; | ||||
| } | ||||
|  | ||||
| - (id)initWithProtocol:(Protocol *)protocol { | ||||
|     NSParameterAssert(protocol); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _objc_protocol = protocol; | ||||
|         [self examine]; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| #pragma mark Other | ||||
|  | ||||
| - (NSString *)description { | ||||
|     return self.name; | ||||
| } | ||||
|  | ||||
| - (NSString *)debugDescription { | ||||
|     return [NSString stringWithFormat:@"<%@ name=%@, %lu properties, %lu required methods, %lu optional methods, %lu protocols>", | ||||
|             NSStringFromClass(self.class), self.name, (unsigned long)self.properties.count, | ||||
|             (unsigned long)self.requiredMethods.count, (unsigned long)self.optionalMethods.count, (unsigned long)self.protocols.count]; | ||||
| } | ||||
|  | ||||
| - (void)examine { | ||||
|     _name = @(protocol_getName(self.objc_protocol)); | ||||
|      | ||||
|     // imagePath | ||||
|     Dl_info exeInfo; | ||||
|     if (dladdr((__bridge const void *)(_objc_protocol), &exeInfo)) { | ||||
|         _imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil; | ||||
|     } | ||||
|      | ||||
|     // Conformances and methods // | ||||
|      | ||||
|     unsigned int pccount, mdrcount, mdocount; | ||||
|     struct objc_method_description *objcrMethods, *objcoMethods; | ||||
|     Protocol *protocol = _objc_protocol; | ||||
|     Protocol * __unsafe_unretained *protocols = protocol_copyProtocolList(protocol, &pccount); | ||||
|      | ||||
|     // Protocols | ||||
|     _protocols = [NSArray flex_forEachUpTo:pccount map:^id(NSUInteger i) { | ||||
|         return [FLEXProtocol protocol:protocols[i]]; | ||||
|     }]; | ||||
|     free(protocols); | ||||
|      | ||||
|     // Required instance methods | ||||
|     objcrMethods = protocol_copyMethodDescriptionList(protocol, YES, YES, &mdrcount); | ||||
|     NSArray *rMethods = [NSArray flex_forEachUpTo:mdrcount map:^id(NSUInteger i) { | ||||
|         return [FLEXMethodDescription description:objcrMethods[i] instance:YES]; | ||||
|     }]; | ||||
|     free(objcrMethods); | ||||
|      | ||||
|     // Required class methods  | ||||
|     objcrMethods = protocol_copyMethodDescriptionList(protocol, YES, NO, &mdrcount); | ||||
|     _requiredMethods = [[NSArray flex_forEachUpTo:mdrcount map:^id(NSUInteger i) { | ||||
|         return [FLEXMethodDescription description:objcrMethods[i] instance:NO]; | ||||
|     }] arrayByAddingObjectsFromArray:rMethods]; | ||||
|     free(objcrMethods); | ||||
|      | ||||
|     // Optional instance methods | ||||
|     objcoMethods = protocol_copyMethodDescriptionList(protocol, NO, YES, &mdocount); | ||||
|     NSArray *oMethods = [NSArray flex_forEachUpTo:mdocount map:^id(NSUInteger i) { | ||||
|         return [FLEXMethodDescription description:objcoMethods[i] instance:YES]; | ||||
|     }]; | ||||
|     free(objcoMethods); | ||||
|      | ||||
|     // Optional class methods | ||||
|     objcoMethods = protocol_copyMethodDescriptionList(protocol, NO, NO, &mdocount); | ||||
|     _optionalMethods = [[NSArray flex_forEachUpTo:mdocount map:^id(NSUInteger i) { | ||||
|         return [FLEXMethodDescription description:objcoMethods[i] instance:NO]; | ||||
|     }] arrayByAddingObjectsFromArray:oMethods]; | ||||
|     free(objcoMethods); | ||||
|      | ||||
|     // Properties is a hassle because they didn't fix the API until iOS 10 // | ||||
|      | ||||
|     if (@available(iOS 10.0, *)) { | ||||
|         unsigned int prrcount, procount; | ||||
|         Class instance = [NSObject class], meta = objc_getMetaClass("NSObject"); | ||||
|          | ||||
|         // Required class and instance properties // | ||||
|          | ||||
|         // Instance first | ||||
|         objc_property_t *rProps = protocol_copyPropertyList2(protocol, &prrcount, YES, YES); | ||||
|         NSArray *rProperties = [NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) { | ||||
|             return [FLEXProperty property:rProps[i] onClass:instance]; | ||||
|         }]; | ||||
|         free(rProps); | ||||
|          | ||||
|         // Then class | ||||
|         rProps = protocol_copyPropertyList2(protocol, &prrcount, NO, YES); | ||||
|         _requiredProperties = [[NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) { | ||||
|             return [FLEXProperty property:rProps[i] onClass:instance]; | ||||
|         }] arrayByAddingObjectsFromArray:rProperties]; | ||||
|         free(rProps); | ||||
|          | ||||
|         // Optional class and instance properties // | ||||
|          | ||||
|         // Instance first | ||||
|         objc_property_t *oProps = protocol_copyPropertyList2(protocol, &procount, YES, YES); | ||||
|         NSArray *oProperties = [NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) { | ||||
|             return [FLEXProperty property:oProps[i] onClass:meta]; | ||||
|         }]; | ||||
|         free(oProps); | ||||
|          | ||||
|         // Then class | ||||
|         oProps = protocol_copyPropertyList2(protocol, &procount, NO, YES); | ||||
|         _optionalProperties = [[NSArray flex_forEachUpTo:procount map:^id(NSUInteger i) { | ||||
|             return [FLEXProperty property:oProps[i] onClass:meta]; | ||||
|         }] arrayByAddingObjectsFromArray:oProperties]; | ||||
|         free(oProps); | ||||
|          | ||||
|     } else { | ||||
|         unsigned int prcount; | ||||
|         objc_property_t *objcproperties = protocol_copyPropertyList(protocol, &prcount); | ||||
|         _properties = [NSArray flex_forEachUpTo:prcount map:^id(NSUInteger i) { | ||||
|             return [FLEXProperty property:objcproperties[i]]; | ||||
|         }]; | ||||
|          | ||||
|         _requiredProperties = @[]; | ||||
|         _optionalProperties = @[]; | ||||
|          | ||||
|         free(objcproperties); | ||||
|     } | ||||
| } | ||||
|  | ||||
| - (BOOL)conformsTo:(Protocol *)protocol { | ||||
|     return protocol_conformsToProtocol(self.objc_protocol, protocol); | ||||
| } | ||||
|  | ||||
| @end | ||||
|  | ||||
| #pragma mark FLEXMethodDescription | ||||
|  | ||||
| @implementation FLEXMethodDescription | ||||
|  | ||||
| - (id)init { | ||||
|     [NSException | ||||
|         raise:NSInternalInconsistencyException | ||||
|         format:@"Class instance should not be created with -init" | ||||
|     ]; | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| + (instancetype)description:(struct objc_method_description)description { | ||||
|     return [[self alloc] initWithDescription:description instance:nil]; | ||||
| } | ||||
|  | ||||
| + (instancetype)description:(struct objc_method_description)description instance:(BOOL)isInstance { | ||||
|     return [[self alloc] initWithDescription:description instance:@(isInstance)]; | ||||
| } | ||||
|  | ||||
| - (id)initWithDescription:(struct objc_method_description)md instance:(NSNumber *)instance { | ||||
|     NSParameterAssert(md.name != NULL); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _objc_description = md; | ||||
|         _selector         = md.name; | ||||
|         _typeEncoding     = @(md.types); | ||||
|         _returnType       = (FLEXTypeEncoding)[self.typeEncoding characterAtIndex:0]; | ||||
|         _instance         = instance; | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (NSString *)description { | ||||
|     return NSStringFromSelector(self.selector); | ||||
| } | ||||
|  | ||||
| - (NSString *)debugDescription { | ||||
|     return [NSString stringWithFormat:@"<%@ name=%@, type=%@>", | ||||
|             NSStringFromClass(self.class), NSStringFromSelector(self.selector), self.typeEncoding]; | ||||
| } | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,41 @@ | ||||
| // | ||||
| //  FLEXProtocolBuilder.h | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 7/4/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import <Foundation/Foundation.h> | ||||
| @class FLEXProperty, FLEXProtocol, Protocol; | ||||
|  | ||||
| @interface FLEXProtocolBuilder : NSObject | ||||
|  | ||||
| /// Begins to construct a new protocol with the given name. | ||||
| /// @discussion You must register the protocol with the | ||||
| /// \c registerProtocol method before you can use it. | ||||
| + (instancetype)allocateProtocol:(NSString *)name; | ||||
|  | ||||
| /// Adds a property to a protocol. | ||||
| /// @param property The property to add. | ||||
| /// @param isRequired Whether the property is required to implement the protocol. | ||||
| - (void)addProperty:(FLEXProperty *)property isRequired:(BOOL)isRequired; | ||||
| /// Adds a property to a protocol. | ||||
| /// @param selector The selector of the method to add. | ||||
| /// @param typeEncoding The type encoding of the method to add. | ||||
| /// @param isRequired Whether the method is required to implement the protocol. | ||||
| /// @param isInstanceMethod \c YES if the method is an instance method, \c NO if it is a class method. | ||||
| - (void)addMethod:(SEL)selector | ||||
|      typeEncoding:(NSString *)typeEncoding | ||||
|        isRequired:(BOOL)isRequired | ||||
|  isInstanceMethod:(BOOL)isInstanceMethod; | ||||
| /// Makes the recieving protocol conform to the given protocol. | ||||
| - (void)addProtocol:(Protocol *)protocol; | ||||
|  | ||||
| /// Registers and returns the recieving protocol, which was previously under construction. | ||||
| - (FLEXProtocol *)registerProtocol; | ||||
| /// Whether the protocol is still under construction or already registered. | ||||
| @property (nonatomic, readonly) BOOL isRegistered; | ||||
|  | ||||
| @end | ||||
| @@ -0,0 +1,93 @@ | ||||
| // | ||||
| //  FLEXProtocolBuilder.m | ||||
| //  FLEX | ||||
| // | ||||
| //  Derived from MirrorKit. | ||||
| //  Created by Tanner on 7/4/15. | ||||
| //  Copyright (c) 2020 FLEX Team. All rights reserved. | ||||
| // | ||||
|  | ||||
| #import "FLEXProtocolBuilder.h" | ||||
| #import "FLEXProtocol.h" | ||||
| #import "FLEXProperty.h" | ||||
| #import <objc/runtime.h> | ||||
|  | ||||
| #define MutationAssertion(msg) if (self.isRegistered) { \ | ||||
|     [NSException \ | ||||
|         raise:NSInternalInconsistencyException \ | ||||
|         format:msg \ | ||||
|     ]; \ | ||||
| } | ||||
|  | ||||
| @interface FLEXProtocolBuilder () | ||||
| @property (nonatomic) Protocol *workingProtocol; | ||||
| @property (nonatomic) NSString *name; | ||||
| @end | ||||
|  | ||||
| @implementation FLEXProtocolBuilder | ||||
|  | ||||
| - (id)init { | ||||
|     [NSException | ||||
|         raise:NSInternalInconsistencyException | ||||
|         format:@"Class instance should not be created with -init" | ||||
|     ]; | ||||
|     return nil; | ||||
| } | ||||
|  | ||||
| #pragma mark Initializers | ||||
| + (instancetype)allocateProtocol:(NSString *)name { | ||||
|     NSParameterAssert(name); | ||||
|     return [[self alloc] initWithProtocol:objc_allocateProtocol(name.UTF8String)]; | ||||
|      | ||||
| } | ||||
|  | ||||
| - (id)initWithProtocol:(Protocol *)protocol { | ||||
|     NSParameterAssert(protocol); | ||||
|      | ||||
|     self = [super init]; | ||||
|     if (self) { | ||||
|         _workingProtocol = protocol; | ||||
|         _name = NSStringFromProtocol(self.workingProtocol); | ||||
|     } | ||||
|      | ||||
|     return self; | ||||
| } | ||||
|  | ||||
| - (NSString *)description { | ||||
|     return [NSString stringWithFormat:@"<%@ name=%@, registered=%d>", | ||||
|             NSStringFromClass(self.class), self.name, self.isRegistered]; | ||||
| } | ||||
|  | ||||
| #pragma mark Building | ||||
|  | ||||
| - (void)addProperty:(FLEXProperty *)property isRequired:(BOOL)isRequired { | ||||
|     MutationAssertion(@"Properties cannot be added once a protocol has been registered"); | ||||
|  | ||||
|     unsigned int count; | ||||
|     objc_property_attribute_t *attributes = [property copyAttributesList:&count]; | ||||
|     protocol_addProperty(self.workingProtocol, property.name.UTF8String, attributes, count, isRequired, YES); | ||||
|     free(attributes); | ||||
| } | ||||
|  | ||||
| - (void)addMethod:(SEL)selector | ||||
|     typeEncoding:(NSString *)typeEncoding | ||||
|        isRequired:(BOOL)isRequired | ||||
|  isInstanceMethod:(BOOL)isInstanceMethod { | ||||
|     MutationAssertion(@"Methods cannot be added once a protocol has been registered"); | ||||
|     protocol_addMethodDescription(self.workingProtocol, selector, typeEncoding.UTF8String, isRequired, isInstanceMethod); | ||||
| } | ||||
|  | ||||
| - (void)addProtocol:(Protocol *)protocol { | ||||
|     MutationAssertion(@"Protocols cannot be added once a protocol has been registered"); | ||||
|     protocol_addProtocol(self.workingProtocol, protocol); | ||||
| } | ||||
|  | ||||
| - (FLEXProtocol *)registerProtocol { | ||||
|     MutationAssertion(@"Protocol is already registered"); | ||||
|      | ||||
|     _isRegistered = YES; | ||||
|     objc_registerProtocol(self.workingProtocol); | ||||
|     return [FLEXProtocol protocol:self.workingProtocol]; | ||||
| } | ||||
|  | ||||
| @end | ||||
							
								
								
									
										290
									
								
								Tweaks/FLEX/Utility/Runtime/flex_fishhook.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								Tweaks/FLEX/Utility/Runtime/flex_fishhook.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,290 @@ | ||||
| // Copyright (c) 2013, Facebook, Inc. | ||||
| // All rights reserved. | ||||
| // Redistribution and use in source and binary forms, with or without | ||||
| // modification, are permitted provided that the following conditions are met: | ||||
| //   * Redistributions of source code must retain the above copyright notice, | ||||
| //     this list of conditions and the following disclaimer. | ||||
| //   * Redistributions in binary form must reproduce the above copyright notice, | ||||
| //     this list of conditions and the following disclaimer in the documentation | ||||
| //     and/or other materials provided with the distribution. | ||||
| //   * Neither the name Facebook nor the names of its contributors may be used to | ||||
| //     endorse or promote products derived from this software without specific | ||||
| //     prior written permission. | ||||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  | ||||
| #include "flex_fishhook.h" | ||||
|  | ||||
| #include <dlfcn.h> | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <sys/mman.h> | ||||
| #include <sys/types.h> | ||||
| #include <mach/mach.h> | ||||
| #include <mach/vm_map.h> | ||||
| #include <mach/vm_region.h> | ||||
| #include <mach-o/dyld.h> | ||||
| #include <mach-o/loader.h> | ||||
| #include <mach-o/nlist.h> | ||||
|  | ||||
| #ifdef __LP64__ | ||||
| typedef struct mach_header_64 mach_header_t; | ||||
| typedef struct segment_command_64 segment_command_t; | ||||
| typedef struct section_64 section_t; | ||||
| typedef struct nlist_64 nlist_t; | ||||
| #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 | ||||
| #else | ||||
| typedef struct mach_header mach_header_t; | ||||
| typedef struct segment_command segment_command_t; | ||||
| typedef struct section section_t; | ||||
| typedef struct nlist nlist_t; | ||||
| #define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT | ||||
| #endif | ||||
|  | ||||
| #ifndef SEG_DATA_CONST | ||||
| #define SEG_DATA_CONST  "__DATA_CONST" | ||||
| #endif | ||||
|  | ||||
| struct rebindings_entry { | ||||
|     struct rebinding *rebindings; | ||||
|     size_t rebindings_nel; | ||||
|     struct rebindings_entry *next; | ||||
| }; | ||||
|  | ||||
| static struct rebindings_entry *_flex_rebindings_head; | ||||
|  | ||||
| /// @return 0 on success | ||||
| static int flex_prepend_rebindings(struct rebindings_entry **rebindings_head, | ||||
|                               struct rebinding rebindings[], | ||||
|                               size_t nel) { | ||||
|     struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry)); | ||||
|     if (!new_entry) { | ||||
|         return -1; | ||||
|     } | ||||
|      | ||||
|     new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel); | ||||
|     if (!new_entry->rebindings) { | ||||
|         free(new_entry); | ||||
|         return -1; | ||||
|     } | ||||
|      | ||||
|     memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel); | ||||
|     new_entry->rebindings_nel = nel; | ||||
|     new_entry->next = *rebindings_head; | ||||
|     *rebindings_head = new_entry; | ||||
|      | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static vm_prot_t flex_get_protection(void *sectionStart) { | ||||
|     mach_port_t task = mach_task_self(); | ||||
|     vm_size_t size = 0; | ||||
|     vm_address_t address = (vm_address_t)sectionStart; | ||||
|     memory_object_name_t object; | ||||
| #if __LP64__ | ||||
|     mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; | ||||
|     vm_region_basic_info_data_64_t info; | ||||
|     kern_return_t info_ret = vm_region_64( | ||||
|         task, &address, &size, VM_REGION_BASIC_INFO_64, | ||||
|         (vm_region_info_64_t)&info, &count, &object | ||||
|     ); | ||||
| #else | ||||
|     mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT; | ||||
|     vm_region_basic_info_data_t info; | ||||
|     kern_return_t info_ret = vm_region( | ||||
|         task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object | ||||
|     ); | ||||
| #endif | ||||
|     if (info_ret == KERN_SUCCESS) { | ||||
|         return info.protection; | ||||
|     } else { | ||||
|         return VM_PROT_READ; | ||||
|     } | ||||
| } | ||||
| static void flex_perform_rebinding_with_section(struct rebindings_entry *rebindings, | ||||
|                                                 section_t *section, | ||||
|                                                 intptr_t slide, | ||||
|                                                 nlist_t *symtab, | ||||
|                                                 char *strtab, | ||||
|                                                 uint32_t *indirect_symtab) { | ||||
|     const bool isDataConst = strcmp(section->segname, "__DATA_CONST") == 0; | ||||
|     uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; | ||||
|     void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); | ||||
|     vm_prot_t oldProtection = VM_PROT_READ; | ||||
|      | ||||
|     if (isDataConst) { | ||||
|         oldProtection = flex_get_protection(rebindings); | ||||
|         mprotect(indirect_symbol_bindings, section->size, PROT_READ | PROT_WRITE); | ||||
|     } | ||||
|      | ||||
|     for (uint i = 0; i < section->size / sizeof(void *); i++) { | ||||
|         uint32_t symtab_index = indirect_symbol_indices[i]; | ||||
|          | ||||
|         if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || | ||||
|             symtab_index == (INDIRECT_SYMBOL_LOCAL   | INDIRECT_SYMBOL_ABS)) { | ||||
|             continue; | ||||
|         } | ||||
|          | ||||
|         uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; | ||||
|         char *symbol_name = strtab + strtab_offset; | ||||
|         bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1]; | ||||
|         struct rebindings_entry *cur = rebindings; | ||||
|          | ||||
|         while (cur) { | ||||
|             for (uint j = 0; j < cur->rebindings_nel; j++) { | ||||
|                 if (symbol_name_longer_than_1 && | ||||
|                   strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) { | ||||
|                      | ||||
|                     if (cur->rebindings[j].replaced != NULL && | ||||
|                       indirect_symbol_bindings[i] != cur->rebindings[j].replacement) { | ||||
|                          | ||||
|                         *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i]; | ||||
|                     } | ||||
|                      | ||||
|                     indirect_symbol_bindings[i] = cur->rebindings[j].replacement; | ||||
|                     goto symbol_loop; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             cur = cur->next; | ||||
|         } | ||||
|          | ||||
|     symbol_loop:; | ||||
|     } | ||||
|      | ||||
|     if (isDataConst) { | ||||
|         int protection = 0; | ||||
|         if (oldProtection & VM_PROT_READ) { | ||||
|             protection |= PROT_READ; | ||||
|         } | ||||
|         if (oldProtection & VM_PROT_WRITE) { | ||||
|             protection |= PROT_WRITE; | ||||
|         } | ||||
|         if (oldProtection & VM_PROT_EXECUTE) { | ||||
|             protection |= PROT_EXEC; | ||||
|         } | ||||
|          | ||||
|         mprotect(indirect_symbol_bindings, section->size, protection); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void flex_rebind_symbols_for_image(struct rebindings_entry *rebindings, | ||||
|                                           const struct mach_header *header, | ||||
|                                           intptr_t slide) { | ||||
|     Dl_info info; | ||||
|     if (dladdr(header, &info) == 0) { | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     segment_command_t *cur_seg_cmd; | ||||
|     segment_command_t *linkedit_segment = NULL; | ||||
|     struct symtab_command* symtab_cmd = NULL; | ||||
|     struct dysymtab_command* dysymtab_cmd = NULL; | ||||
|      | ||||
|     uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); | ||||
|     for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { | ||||
|         cur_seg_cmd = (segment_command_t *)cur; | ||||
|          | ||||
|         if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { | ||||
|             if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { | ||||
|                 linkedit_segment = cur_seg_cmd; | ||||
|             } | ||||
|         } else if (cur_seg_cmd->cmd == LC_SYMTAB) { | ||||
|             symtab_cmd = (struct symtab_command*)cur_seg_cmd; | ||||
|         } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { | ||||
|             dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || | ||||
|         !dysymtab_cmd->nindirectsyms) { | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     // Find base symbol/string table addresses | ||||
|     uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; | ||||
|     nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); | ||||
|     char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); | ||||
|      | ||||
|     // Get indirect symbol table (array of uint32_t indices into symbol table) | ||||
|     uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); | ||||
|      | ||||
|     cur = (uintptr_t)header + sizeof(mach_header_t); | ||||
|     for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { | ||||
|         cur_seg_cmd = (segment_command_t *)cur; | ||||
|          | ||||
|         if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { | ||||
|             if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 && | ||||
|                 strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             for (uint j = 0; j < cur_seg_cmd->nsects; j++) { | ||||
|                 section_t *sect = (section_t *)(cur + sizeof(segment_command_t)) + j; | ||||
|                  | ||||
|                 if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { | ||||
|                     flex_perform_rebinding_with_section( | ||||
|                         rebindings, sect, slide, symtab, strtab, indirect_symtab | ||||
|                     ); | ||||
|                 } | ||||
|                 if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { | ||||
|                     flex_perform_rebinding_with_section( | ||||
|                         rebindings, sect, slide, symtab, strtab, indirect_symtab | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void _flex_rebind_symbols_for_image(const struct mach_header *header, | ||||
|                                            intptr_t slide) { | ||||
|     flex_rebind_symbols_for_image(_flex_rebindings_head, header, slide); | ||||
| } | ||||
|  | ||||
| int flex_rebind_symbols_image(void *header, | ||||
|                               intptr_t slide, | ||||
|                               struct rebinding rebindings[], | ||||
|                               size_t rebindings_nel) { | ||||
|     struct rebindings_entry *rebindings_head = NULL; | ||||
|      | ||||
|     int retval = flex_prepend_rebindings(&rebindings_head, rebindings, rebindings_nel); | ||||
|     flex_rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide); | ||||
|      | ||||
|     if (rebindings_head) { | ||||
|         free(rebindings_head->rebindings); | ||||
|     } | ||||
|      | ||||
|     free(rebindings_head); | ||||
|     return retval; | ||||
| } | ||||
|  | ||||
| /// @return 0 on success | ||||
| int flex_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) { | ||||
|     int retval = flex_prepend_rebindings(&_flex_rebindings_head, rebindings, rebindings_nel); | ||||
|     if (retval < 0) { | ||||
|         return retval; | ||||
|     } | ||||
|      | ||||
|     // If this was the first call, register callback for image additions (which is also invoked for | ||||
|     // existing images, otherwise, just run on existing images | ||||
|     if (!_flex_rebindings_head->next) { | ||||
|         _dyld_register_func_for_add_image(_flex_rebind_symbols_for_image); | ||||
|     } else { | ||||
|         uint32_t c = _dyld_image_count(); | ||||
|         for (uint32_t i = 0; i < c; i++) { | ||||
|             _flex_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return retval; | ||||
| } | ||||
							
								
								
									
										77
									
								
								Tweaks/FLEX/Utility/Runtime/flex_fishhook.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Tweaks/FLEX/Utility/Runtime/flex_fishhook.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| // Copyright (c) 2013, Facebook, Inc. | ||||
| // All rights reserved. | ||||
| // Redistribution and use in source and binary forms, with or without | ||||
| // modification, are permitted provided that the following conditions are met: | ||||
| //   * Redistributions of source code must retain the above copyright notice, | ||||
| //     this list of conditions and the following disclaimer. | ||||
| //   * Redistributions in binary form must reproduce the above copyright notice, | ||||
| //     this list of conditions and the following disclaimer in the documentation | ||||
| //     and/or other materials provided with the distribution. | ||||
| //   * Neither the name Facebook nor the names of its contributors may be used to | ||||
| //     endorse or promote products derived from this software without specific | ||||
| //     prior written permission. | ||||
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  | ||||
| #ifndef fishhook_h | ||||
| #define fishhook_h | ||||
|  | ||||
| #include <stddef.h> | ||||
| #include <stdint.h> | ||||
|  | ||||
| #if !defined(FISHHOOK_EXPORT) | ||||
| #define FISHHOOK_VISIBILITY __attribute__((visibility("hidden"))) | ||||
| #else | ||||
| #define FISHHOOK_VISIBILITY __attribute__((visibility("default"))) | ||||
| #endif | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif //__cplusplus | ||||
|  | ||||
| /** | ||||
|  * A structure representing a particular intended rebinding from a symbol | ||||
|  * name to its replacement | ||||
|  */ | ||||
| struct rebinding { | ||||
|     const char *name; | ||||
|     void *replacement; | ||||
|     void **replaced; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * For each rebinding in rebindings, rebinds references to external, indirect | ||||
|  * symbols with the specified name to instead point at replacement for each | ||||
|  * image in the calling process as well as for all future images that are loaded | ||||
|  * by the process. If rebind_functions is called more than once, the symbols to | ||||
|  * rebind are added to the existing list of rebindings, and if a given symbol | ||||
|  * is rebound more than once, the later rebinding will take precedence. | ||||
|  * @return 0 on success | ||||
|  */ | ||||
| FISHHOOK_VISIBILITY | ||||
| int flex_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel); | ||||
|  | ||||
| /** | ||||
|  * Rebinds as above, but only in the specified image. The header should point | ||||
|  * to the mach-o header, the slide should be the slide offset. Others as above. | ||||
|  * @return 0 on success | ||||
|  */ | ||||
| FISHHOOK_VISIBILITY | ||||
| int flex_rebind_symbols_image(void *header, | ||||
|                               intptr_t slide, | ||||
|                               struct rebinding rebindings[], | ||||
|                               size_t rebindings_nel); | ||||
|  | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif //__cplusplus | ||||
|  | ||||
| #endif //fishhook_h | ||||
		Reference in New Issue
	
	Block a user
	 Balackburn
					Balackburn