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