added files via upload

This commit is contained in:
Balackburn
2023-06-27 09:54:41 +02:00
commit 2ff6aac218
1420 changed files with 88898 additions and 0 deletions

View 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

View 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

View 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

View 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"

View 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);

View 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";

View 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);
}

View 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;
}

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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)) \
]

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View 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

View 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

View File

@@ -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

View File

@@ -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

View 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;
}

View 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