mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-12-27 08:45:22 -05:00
added files via upload
This commit is contained in:
73
Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.h
Normal file
73
Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.h
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// FLEXObjcInternal.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 11/1/18.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// The macros below are copied straight from
|
||||
// objc-internal.h, objc-private.h, objc-object.h, and objc-config.h with
|
||||
// as few modifications as possible. Changes are noted in boxed comments.
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-internal.h.auto.html
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-object.h.auto.html
|
||||
|
||||
/////////////////////
|
||||
// objc-internal.h //
|
||||
/////////////////////
|
||||
|
||||
#if __LP64__
|
||||
#define OBJC_HAVE_TAGGED_POINTERS 1
|
||||
#endif
|
||||
|
||||
#if OBJC_HAVE_TAGGED_POINTERS
|
||||
|
||||
#if TARGET_OS_OSX && __x86_64__
|
||||
// 64-bit Mac - tag bit is LSB
|
||||
# define OBJC_MSB_TAGGED_POINTERS 0
|
||||
#else
|
||||
// Everything else - tag bit is MSB
|
||||
# define OBJC_MSB_TAGGED_POINTERS 1
|
||||
#endif
|
||||
|
||||
#if OBJC_MSB_TAGGED_POINTERS
|
||||
# define _OBJC_TAG_MASK (1UL<<63)
|
||||
# define _OBJC_TAG_EXT_MASK (0xfUL<<60)
|
||||
#else
|
||||
# define _OBJC_TAG_MASK 1UL
|
||||
# define _OBJC_TAG_EXT_MASK 0xfUL
|
||||
#endif
|
||||
|
||||
#endif // OBJC_HAVE_TAGGED_POINTERS
|
||||
|
||||
//////////////////////////////////////
|
||||
// originally _objc_isTaggedPointer //
|
||||
//////////////////////////////////////
|
||||
NS_INLINE BOOL flex_isTaggedPointer(const void *ptr) {
|
||||
#if OBJC_HAVE_TAGGED_POINTERS
|
||||
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
|
||||
#else
|
||||
return NO;
|
||||
#endif
|
||||
}
|
||||
|
||||
#define FLEXPointerIsTaggedPointer(obj) flex_isTaggedPointer((__bridge void *)obj)
|
||||
|
||||
BOOL FLEXPointerIsReadable(const void * ptr);
|
||||
|
||||
/// @brief Assumes memory is valid and readable.
|
||||
/// @discussion objc-internal.h, objc-private.h, and objc-config.h
|
||||
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
|
||||
/// https://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py
|
||||
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
|
||||
BOOL FLEXPointerIsValidObjcObject(const void * ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
196
Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.mm
Normal file
196
Tweaks/FLEX/Utility/Runtime/Objc/FLEXObjcInternal.mm
Normal file
@@ -0,0 +1,196 @@
|
||||
//
|
||||
// FLEXObjcInternal.mm
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 11/1/18.
|
||||
//
|
||||
|
||||
/*
|
||||
* Copyright (c) 2005-2007 Apple Inc. All Rights Reserved.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_START@
|
||||
*
|
||||
* This file contains Original Code and/or Modifications of Original Code
|
||||
* as defined in and that are subject to the Apple Public Source License
|
||||
* Version 2.0 (the 'License'). You may not use this file except in
|
||||
* compliance with the License. Please obtain a copy of the License at
|
||||
* http://www.opensource.apple.com/apsl/ and read it before using this
|
||||
* file.
|
||||
*
|
||||
* The Original Code and all software distributed under the License are
|
||||
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||||
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||||
* Please see the License for the specific language governing rights and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @APPLE_LICENSE_HEADER_END@
|
||||
*/
|
||||
|
||||
#import "FLEXObjcInternal.h"
|
||||
#import <objc/runtime.h>
|
||||
// For malloc_size
|
||||
#import <malloc/malloc.h>
|
||||
// For vm_region_64
|
||||
#include <mach/mach.h>
|
||||
|
||||
#if __arm64e__
|
||||
#include <ptrauth.h>
|
||||
#endif
|
||||
|
||||
#define ALWAYS_INLINE inline __attribute__((always_inline))
|
||||
#define NEVER_INLINE inline __attribute__((noinline))
|
||||
|
||||
// The macros below are copied straight from
|
||||
// objc-internal.h, objc-private.h, objc-object.h, and objc-config.h with
|
||||
// as few modifications as possible. Changes are noted in boxed comments.
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-internal.h.auto.html
|
||||
// https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-object.h.auto.html
|
||||
|
||||
/////////////////////
|
||||
// objc-internal.h //
|
||||
/////////////////////
|
||||
|
||||
#if OBJC_HAVE_TAGGED_POINTERS
|
||||
|
||||
///////////////////
|
||||
// objc-object.h //
|
||||
///////////////////
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// originally objc_object::isExtTaggedPointer //
|
||||
////////////////////////////////////////////////
|
||||
NS_INLINE BOOL flex_isExtTaggedPointer(const void *ptr) {
|
||||
return ((uintptr_t)ptr & _OBJC_TAG_EXT_MASK) == _OBJC_TAG_EXT_MASK;
|
||||
}
|
||||
|
||||
#endif // OBJC_HAVE_TAGGED_POINTERS
|
||||
|
||||
/////////////////////////////////////
|
||||
// FLEXObjectInternal //
|
||||
// No Apple code beyond this point //
|
||||
/////////////////////////////////////
|
||||
|
||||
extern "C" {
|
||||
|
||||
BOOL FLEXPointerIsReadable(const void *inPtr) {
|
||||
kern_return_t error = KERN_SUCCESS;
|
||||
|
||||
vm_size_t vmsize;
|
||||
#if __arm64e__
|
||||
// On arm64e, we need to strip the PAC from the pointer so the adress is readable
|
||||
vm_address_t address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer);
|
||||
#else
|
||||
vm_address_t address = (vm_address_t)inPtr;
|
||||
#endif
|
||||
vm_region_basic_info_data_t info;
|
||||
mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
|
||||
memory_object_name_t object;
|
||||
|
||||
error = vm_region_64(
|
||||
mach_task_self(),
|
||||
&address,
|
||||
&vmsize,
|
||||
VM_REGION_BASIC_INFO,
|
||||
(vm_region_info_t)&info,
|
||||
&info_count,
|
||||
&object
|
||||
);
|
||||
|
||||
if (error != KERN_SUCCESS) {
|
||||
// vm_region/vm_region_64 returned an error
|
||||
return NO;
|
||||
} else if (!(BOOL)(info.protection & VM_PROT_READ)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
#if __arm64e__
|
||||
address = (vm_address_t)ptrauth_strip(inPtr, ptrauth_key_function_pointer);
|
||||
#else
|
||||
address = (vm_address_t)inPtr;
|
||||
#endif
|
||||
|
||||
// Read the memory
|
||||
vm_size_t size = 0;
|
||||
char buf[sizeof(uintptr_t)];
|
||||
error = vm_read_overwrite(mach_task_self(), address, sizeof(uintptr_t), (vm_address_t)buf, &size);
|
||||
if (error != KERN_SUCCESS) {
|
||||
// vm_read_overwrite returned an error
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/// Accepts addresses that may or may not be readable.
|
||||
/// https://blog.timac.org/2016/1124-testing-if-an-arbitrary-pointer-is-a-valid-objective-c-object/
|
||||
BOOL FLEXPointerIsValidObjcObject(const void *ptr) {
|
||||
uintptr_t pointer = (uintptr_t)ptr;
|
||||
|
||||
if (!ptr) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
#if OBJC_HAVE_TAGGED_POINTERS
|
||||
// Tagged pointers have 0x1 set, no other valid pointers do
|
||||
// objc-internal.h -> _objc_isTaggedPointer()
|
||||
if (flex_isTaggedPointer(ptr) || flex_isExtTaggedPointer(ptr)) {
|
||||
return YES;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Check pointer alignment
|
||||
if ((pointer % sizeof(uintptr_t)) != 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// From LLDB:
|
||||
// Pointers in a class_t will only have bits 0 through 46 set,
|
||||
// so if any pointer has bits 47 through 63 high, we know that this is not a valid isa
|
||||
// https://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py
|
||||
if ((pointer & 0xFFFF800000000000) != 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Make sure dereferencing this address won't crash
|
||||
if (!FLEXPointerIsReadable(ptr)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
|
||||
// We check if the returned class is readable because object_getClass
|
||||
// can return a garbage value when given a non-nil pointer to a non-object
|
||||
Class cls = object_getClass((__bridge id)ptr);
|
||||
if (!cls || !FLEXPointerIsReadable((__bridge void *)cls)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Just because this pointer is readable doesn't mean whatever is at
|
||||
// it's ISA offset is readable. We need to do the same checks on it's ISA.
|
||||
// Even this isn't perfect, because once we call object_isClass, we're
|
||||
// going to dereference a member of the metaclass, which may or may not
|
||||
// be readable itself. For the time being there is no way to access it
|
||||
// to check here, and I have yet to hard-code a solution.
|
||||
Class metaclass = object_getClass(cls);
|
||||
if (!metaclass || !FLEXPointerIsReadable((__bridge void *)metaclass)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Does the class pointer we got appear as a class to the runtime?
|
||||
if (!object_isClass(cls)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Is the allocation size at least as large as the expected instance size?
|
||||
ssize_t instanceSize = class_getInstanceSize(cls);
|
||||
if (malloc_size(ptr) < instanceSize) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
} // End extern "C"
|
||||
79
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.h
Normal file
79
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.h
Normal file
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// FLEXRuntimeConstants.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/11/20.
|
||||
// Copyright © 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#define FLEXEncodeClass(class) ("@\"" #class "\"")
|
||||
#define FLEXEncodeObject(obj) (obj ? [NSString stringWithFormat:@"@\"%@\"", [obj class]].UTF8String : @encode(id))
|
||||
|
||||
// Arguments 0 and 1 are self and _cmd always
|
||||
extern const unsigned int kFLEXNumberOfImplicitArgs;
|
||||
|
||||
// See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
|
||||
extern NSString *const kFLEXPropertyAttributeKeyTypeEncoding;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyBackingIvarName;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyReadOnly;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyCopy;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyRetain;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyNonAtomic;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyCustomGetter;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyCustomSetter;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyDynamic;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyWeak;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyGarbageCollectable;
|
||||
extern NSString *const kFLEXPropertyAttributeKeyOldStyleTypeEncoding;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FLEXPropertyAttribute) {
|
||||
FLEXPropertyAttributeTypeEncoding = 'T',
|
||||
FLEXPropertyAttributeBackingIvarName = 'V',
|
||||
FLEXPropertyAttributeCopy = 'C',
|
||||
FLEXPropertyAttributeCustomGetter = 'G',
|
||||
FLEXPropertyAttributeCustomSetter = 'S',
|
||||
FLEXPropertyAttributeDynamic = 'D',
|
||||
FLEXPropertyAttributeGarbageCollectible = 'P',
|
||||
FLEXPropertyAttributeNonAtomic = 'N',
|
||||
FLEXPropertyAttributeOldTypeEncoding = 't',
|
||||
FLEXPropertyAttributeReadOnly = 'R',
|
||||
FLEXPropertyAttributeRetain = '&',
|
||||
FLEXPropertyAttributeWeak = 'W'
|
||||
}; //NS_SWIFT_NAME(FLEX.PropertyAttribute);
|
||||
|
||||
typedef NS_ENUM(char, FLEXTypeEncoding) {
|
||||
FLEXTypeEncodingNull = '\0',
|
||||
FLEXTypeEncodingUnknown = '?',
|
||||
FLEXTypeEncodingChar = 'c',
|
||||
FLEXTypeEncodingInt = 'i',
|
||||
FLEXTypeEncodingShort = 's',
|
||||
FLEXTypeEncodingLong = 'l',
|
||||
FLEXTypeEncodingLongLong = 'q',
|
||||
FLEXTypeEncodingUnsignedChar = 'C',
|
||||
FLEXTypeEncodingUnsignedInt = 'I',
|
||||
FLEXTypeEncodingUnsignedShort = 'S',
|
||||
FLEXTypeEncodingUnsignedLong = 'L',
|
||||
FLEXTypeEncodingUnsignedLongLong = 'Q',
|
||||
FLEXTypeEncodingFloat = 'f',
|
||||
FLEXTypeEncodingDouble = 'd',
|
||||
FLEXTypeEncodingLongDouble = 'D',
|
||||
FLEXTypeEncodingCBool = 'B',
|
||||
FLEXTypeEncodingVoid = 'v',
|
||||
FLEXTypeEncodingCString = '*',
|
||||
FLEXTypeEncodingObjcObject = '@',
|
||||
FLEXTypeEncodingObjcClass = '#',
|
||||
FLEXTypeEncodingSelector = ':',
|
||||
FLEXTypeEncodingArrayBegin = '[',
|
||||
FLEXTypeEncodingArrayEnd = ']',
|
||||
FLEXTypeEncodingStructBegin = '{',
|
||||
FLEXTypeEncodingStructEnd = '}',
|
||||
FLEXTypeEncodingUnionBegin = '(',
|
||||
FLEXTypeEncodingUnionEnd = ')',
|
||||
FLEXTypeEncodingQuote = '\"',
|
||||
FLEXTypeEncodingBitField = 'b',
|
||||
FLEXTypeEncodingPointer = '^',
|
||||
FLEXTypeEncodingConst = 'r'
|
||||
}; //NS_SWIFT_NAME(FLEX.TypeEncoding);
|
||||
24
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.m
Normal file
24
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeConstants.m
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// FLEXRuntimeConstants.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/11/20.
|
||||
// Copyright © 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXRuntimeConstants.h"
|
||||
|
||||
const unsigned int kFLEXNumberOfImplicitArgs = 2;
|
||||
|
||||
NSString *const kFLEXPropertyAttributeKeyTypeEncoding = @"T";
|
||||
NSString *const kFLEXPropertyAttributeKeyBackingIvarName = @"V";
|
||||
NSString *const kFLEXPropertyAttributeKeyReadOnly = @"R";
|
||||
NSString *const kFLEXPropertyAttributeKeyCopy = @"C";
|
||||
NSString *const kFLEXPropertyAttributeKeyRetain = @"&";
|
||||
NSString *const kFLEXPropertyAttributeKeyNonAtomic = @"N";
|
||||
NSString *const kFLEXPropertyAttributeKeyCustomGetter = @"G";
|
||||
NSString *const kFLEXPropertyAttributeKeyCustomSetter = @"S";
|
||||
NSString *const kFLEXPropertyAttributeKeyDynamic = @"D";
|
||||
NSString *const kFLEXPropertyAttributeKeyWeak = @"W";
|
||||
NSString *const kFLEXPropertyAttributeKeyGarbageCollectable = @"P";
|
||||
NSString *const kFLEXPropertyAttributeKeyOldStyleTypeEncoding = @"t";
|
||||
56
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.h
Normal file
56
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.h
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// FLEXRuntimeSafety.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/25/17.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#pragma mark - Classes
|
||||
|
||||
extern NSUInteger const kFLEXKnownUnsafeClassCount;
|
||||
extern const Class * FLEXKnownUnsafeClassList(void);
|
||||
extern NSSet * FLEXKnownUnsafeClassNames(void);
|
||||
extern CFSetRef FLEXKnownUnsafeClasses;
|
||||
|
||||
static Class cNSObject = nil, cNSProxy = nil;
|
||||
|
||||
__attribute__((constructor))
|
||||
static void FLEXInitKnownRootClasses(void) {
|
||||
cNSObject = [NSObject class];
|
||||
cNSProxy = [NSProxy class];
|
||||
}
|
||||
|
||||
static inline BOOL FLEXClassIsSafe(Class cls) {
|
||||
// Is it nil or known to be unsafe?
|
||||
if (!cls || CFSetContainsValue(FLEXKnownUnsafeClasses, (__bridge void *)cls)) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Is it a known root class?
|
||||
if (!class_getSuperclass(cls)) {
|
||||
return cls == cNSObject || cls == cNSProxy;
|
||||
}
|
||||
|
||||
// Probably safe
|
||||
return YES;
|
||||
}
|
||||
|
||||
static inline BOOL FLEXClassNameIsSafe(NSString *cls) {
|
||||
if (!cls) return NO;
|
||||
|
||||
NSSet *ignored = FLEXKnownUnsafeClassNames();
|
||||
return ![ignored containsObject:cls];
|
||||
}
|
||||
|
||||
#pragma mark - Ivars
|
||||
|
||||
extern CFSetRef FLEXKnownUnsafeIvars;
|
||||
|
||||
static inline BOOL FLEXIvarIsSafe(Ivar ivar) {
|
||||
if (!ivar) return NO;
|
||||
|
||||
return !CFSetContainsValue(FLEXKnownUnsafeIvars, ivar);
|
||||
}
|
||||
107
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.m
Normal file
107
Tweaks/FLEX/Utility/Runtime/Objc/FLEXRuntimeSafety.m
Normal file
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// FLEXRuntimeSafety.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/25/17.
|
||||
//
|
||||
|
||||
#import "FLEXRuntimeSafety.h"
|
||||
|
||||
NSUInteger const kFLEXKnownUnsafeClassCount = 19;
|
||||
Class * _UnsafeClasses = NULL;
|
||||
CFSetRef FLEXKnownUnsafeClasses = nil;
|
||||
CFSetRef FLEXKnownUnsafeIvars = nil;
|
||||
|
||||
#define FLEXClassPointerOrCFNull(name) \
|
||||
(NSClassFromString(name) ?: (__bridge id)kCFNull)
|
||||
|
||||
#define FLEXIvarOrCFNull(cls, name) \
|
||||
(class_getInstanceVariable([cls class], name) ?: (void *)kCFNull)
|
||||
|
||||
__attribute__((constructor))
|
||||
static void FLEXRuntimeSafteyInit() {
|
||||
FLEXKnownUnsafeClasses = CFSetCreate(
|
||||
kCFAllocatorDefault,
|
||||
(const void **)(uintptr_t)FLEXKnownUnsafeClassList(),
|
||||
kFLEXKnownUnsafeClassCount,
|
||||
nil
|
||||
);
|
||||
|
||||
Ivar unsafeIvars[] = {
|
||||
FLEXIvarOrCFNull(NSURL, "_urlString"),
|
||||
FLEXIvarOrCFNull(NSURL, "_baseURL"),
|
||||
};
|
||||
FLEXKnownUnsafeIvars = CFSetCreate(
|
||||
kCFAllocatorDefault,
|
||||
(const void **)unsafeIvars,
|
||||
sizeof(unsafeIvars),
|
||||
nil
|
||||
);
|
||||
}
|
||||
|
||||
const Class * FLEXKnownUnsafeClassList() {
|
||||
if (!_UnsafeClasses) {
|
||||
const Class ignored[] = {
|
||||
FLEXClassPointerOrCFNull(@"__ARCLite__"),
|
||||
FLEXClassPointerOrCFNull(@"__NSCFCalendar"),
|
||||
FLEXClassPointerOrCFNull(@"__NSCFTimer"),
|
||||
FLEXClassPointerOrCFNull(@"NSCFTimer"),
|
||||
FLEXClassPointerOrCFNull(@"__NSGenericDeallocHandler"),
|
||||
FLEXClassPointerOrCFNull(@"NSAutoreleasePool"),
|
||||
FLEXClassPointerOrCFNull(@"NSPlaceholderNumber"),
|
||||
FLEXClassPointerOrCFNull(@"NSPlaceholderString"),
|
||||
FLEXClassPointerOrCFNull(@"NSPlaceholderValue"),
|
||||
FLEXClassPointerOrCFNull(@"Object"),
|
||||
FLEXClassPointerOrCFNull(@"VMUArchitecture"),
|
||||
FLEXClassPointerOrCFNull(@"JSExport"),
|
||||
FLEXClassPointerOrCFNull(@"__NSAtom"),
|
||||
FLEXClassPointerOrCFNull(@"_NSZombie_"),
|
||||
FLEXClassPointerOrCFNull(@"_CNZombie_"),
|
||||
FLEXClassPointerOrCFNull(@"__NSMessage"),
|
||||
FLEXClassPointerOrCFNull(@"__NSMessageBuilder"),
|
||||
FLEXClassPointerOrCFNull(@"FigIrisAutoTrimmerMotionSampleExport"),
|
||||
// Temporary until we have our own type encoding parser;
|
||||
// setVectors: has an invalid type encoding and crashes NSMethodSignature
|
||||
FLEXClassPointerOrCFNull(@"_UIPointVector"),
|
||||
};
|
||||
|
||||
assert((sizeof(ignored) / sizeof(Class)) == kFLEXKnownUnsafeClassCount);
|
||||
|
||||
_UnsafeClasses = (Class *)malloc(sizeof(ignored));
|
||||
memcpy(_UnsafeClasses, ignored, sizeof(ignored));
|
||||
}
|
||||
|
||||
return _UnsafeClasses;
|
||||
}
|
||||
|
||||
NSSet * FLEXKnownUnsafeClassNames() {
|
||||
static NSSet *set = nil;
|
||||
if (!set) {
|
||||
NSArray *ignored = @[
|
||||
@"__ARCLite__",
|
||||
@"__NSCFCalendar",
|
||||
@"__NSCFTimer",
|
||||
@"NSCFTimer",
|
||||
@"__NSGenericDeallocHandler",
|
||||
@"NSAutoreleasePool",
|
||||
@"NSPlaceholderNumber",
|
||||
@"NSPlaceholderString",
|
||||
@"NSPlaceholderValue",
|
||||
@"Object",
|
||||
@"VMUArchitecture",
|
||||
@"JSExport",
|
||||
@"__NSAtom",
|
||||
@"_NSZombie_",
|
||||
@"_CNZombie_",
|
||||
@"__NSMessage",
|
||||
@"__NSMessageBuilder",
|
||||
@"FigIrisAutoTrimmerMotionSampleExport",
|
||||
@"_UIPointVector",
|
||||
];
|
||||
|
||||
set = [NSSet setWithArray:ignored];
|
||||
assert(set.count == kFLEXKnownUnsafeClassCount);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
46
Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.h
Normal file
46
Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.h
Normal file
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// FLEXTypeEncodingParser.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 8/22/19.
|
||||
// Copyright © 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// @return \c YES if the type is supported, \c NO otherwise
|
||||
BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger * _Nullable sizep, NSUInteger * _Nullable alignp);
|
||||
|
||||
@interface FLEXTypeEncodingParser : NSObject
|
||||
|
||||
/// \c cleanedEncoding is necessary because a type encoding may contain a pointer
|
||||
/// to an unsupported type. \c NSMethodSignature will pass each type to \c NSGetSizeAndAlignment
|
||||
/// which will throw an exception on unsupported struct pointers, and this exception is caught
|
||||
/// by \c NSMethodSignature, but it still bothers anyone debugging with \c objc_exception_throw
|
||||
///
|
||||
/// @param cleanedEncoding the "safe" type encoding you can pass to \c NSMethodSignature
|
||||
/// @return whether the given type encoding can be passed to
|
||||
/// \c NSMethodSignature without it throwing an exception.
|
||||
+ (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding cleaned:(NSString *_Nonnull*_Nullable)cleanedEncoding;
|
||||
|
||||
/// @return The type encoding of an individual argument in a method's type encoding string.
|
||||
/// Pass 0 to get the type of the return value. 1 and 2 are `self` and `_cmd` respectively.
|
||||
+ (NSString *)type:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx;
|
||||
|
||||
/// @return The size in bytes of the typeof an individual argument in a method's type encoding string.
|
||||
/// Pass 0 to get the size of the return value. 1 and 2 are `self` and `_cmd` respectively.
|
||||
+ (ssize_t)size:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx;
|
||||
|
||||
/// @param unaligned whether to compute the aligned or unaligned size.
|
||||
/// @return The size in bytes, or \c -1 if the type encoding is unsupported.
|
||||
/// Do not pass in the result of \c method_getTypeEncoding
|
||||
+ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(nullable ssize_t *)alignOut unaligned:(BOOL)unaligned;
|
||||
|
||||
/// Defaults to \C unaligned:NO
|
||||
+ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(nullable ssize_t *)alignOut;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
900
Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.m
Normal file
900
Tweaks/FLEX/Utility/Runtime/Objc/FLEXTypeEncodingParser.m
Normal file
@@ -0,0 +1,900 @@
|
||||
//
|
||||
// FLEXTypeEncodingParser.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner Bennett on 8/22/19.
|
||||
// Copyright © 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXTypeEncodingParser.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
|
||||
#define S(__ch) ({ \
|
||||
unichar __c = __ch; \
|
||||
[[NSString alloc] initWithCharacters:&__c length:1]; \
|
||||
})
|
||||
|
||||
typedef struct FLEXTypeInfo {
|
||||
/// The size is unaligned. -1 if not supported at all.
|
||||
ssize_t size;
|
||||
ssize_t align;
|
||||
/// NO if the type cannot be supported at all
|
||||
/// YES if the type is either fully or partially supported.
|
||||
BOOL supported;
|
||||
/// YES if the type was only partially supported, such as in
|
||||
/// the case of unions in pointer types, or named structure
|
||||
/// types without member info. These can be corrected manually
|
||||
/// since they can be fixed or replaced with less info.
|
||||
BOOL fixesApplied;
|
||||
/// Whether this type is a union or one of its members
|
||||
/// recursively contains a union, exlcuding pointers.
|
||||
///
|
||||
/// Unions are tricky because they're supported by
|
||||
/// \c NSGetSizeAndAlignment but not by \c NSMethodSignature
|
||||
/// so we need to track whenever a type contains a union
|
||||
/// so that we can clean it out of pointer types.
|
||||
BOOL containsUnion;
|
||||
/// size can only be 0 if not void
|
||||
BOOL isVoid;
|
||||
} FLEXTypeInfo;
|
||||
|
||||
/// Type info for a completely unsupported type.
|
||||
static FLEXTypeInfo FLEXTypeInfoUnsupported = (FLEXTypeInfo){ -1, 0, NO, NO, NO, NO };
|
||||
/// Type info for the void return type.
|
||||
static FLEXTypeInfo FLEXTypeInfoVoid = (FLEXTypeInfo){ 0, 0, YES, NO, NO, YES };
|
||||
|
||||
/// Builds type info for a fully or partially supported type.
|
||||
static inline FLEXTypeInfo FLEXTypeInfoMake(ssize_t size, ssize_t align, BOOL fixed) {
|
||||
return (FLEXTypeInfo){ size, align, YES, fixed, NO, NO };
|
||||
}
|
||||
|
||||
/// Builds type info for a fully or partially supported type.
|
||||
static inline FLEXTypeInfo FLEXTypeInfoMakeU(ssize_t size, ssize_t align, BOOL fixed, BOOL hasUnion) {
|
||||
return (FLEXTypeInfo){ size, align, YES, fixed, hasUnion, NO };
|
||||
}
|
||||
|
||||
BOOL FLEXGetSizeAndAlignment(const char *type, NSUInteger *sizep, NSUInteger *alignp) {
|
||||
NSInteger size = 0;
|
||||
ssize_t align = 0;
|
||||
size = [FLEXTypeEncodingParser sizeForTypeEncoding:@(type) alignment:&align];
|
||||
|
||||
if (size == -1) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (sizep) {
|
||||
*sizep = (NSUInteger)size;
|
||||
}
|
||||
|
||||
if (alignp) {
|
||||
*alignp = (NSUInteger)size;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@interface FLEXTypeEncodingParser ()
|
||||
@property (nonatomic, readonly) NSScanner *scan;
|
||||
@property (nonatomic, readonly) NSString *scanned;
|
||||
@property (nonatomic, readonly) NSString *unscanned;
|
||||
@property (nonatomic, readonly) char nextChar;
|
||||
|
||||
/// Replacements are made to this string as we scan as needed
|
||||
@property (nonatomic) NSMutableString *cleaned;
|
||||
/// Offset for \e further replacements to be made within \c cleaned
|
||||
@property (nonatomic, readonly) NSUInteger cleanedReplacingOffset;
|
||||
@end
|
||||
|
||||
@implementation FLEXTypeEncodingParser
|
||||
|
||||
- (NSString *)scanned {
|
||||
return [self.scan.string substringToIndex:self.scan.scanLocation];
|
||||
}
|
||||
|
||||
- (NSString *)unscanned {
|
||||
return [self.scan.string substringFromIndex:self.scan.scanLocation];
|
||||
}
|
||||
|
||||
#pragma mark Initialization
|
||||
|
||||
- (id)initWithObjCTypes:(NSString *)typeEncoding {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_scan = [NSScanner scannerWithString:typeEncoding];
|
||||
_scan.caseSensitive = YES;
|
||||
_cleaned = typeEncoding.mutableCopy;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Public
|
||||
|
||||
+ (BOOL)methodTypeEncodingSupported:(NSString *)typeEncoding cleaned:(NSString * __autoreleasing *)cleanedEncoding {
|
||||
if (!typeEncoding.length) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding];
|
||||
|
||||
while (!parser.scan.isAtEnd) {
|
||||
FLEXTypeInfo info = [parser parseNextType];
|
||||
|
||||
if (!info.supported || info.containsUnion || (info.size == 0 && !info.isVoid)) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanedEncoding) {
|
||||
*cleanedEncoding = parser.cleaned.copy;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)type:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx {
|
||||
FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:typeEncoding];
|
||||
|
||||
// Scan up to the argument we want
|
||||
for (NSUInteger i = 0; i < idx; i++) {
|
||||
if (![parser scanPastArg]) {
|
||||
[NSException raise:NSRangeException
|
||||
format:@"Index %@ out of bounds for type encoding '%@'",
|
||||
@(idx), typeEncoding
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [parser scanArg];
|
||||
}
|
||||
|
||||
+ (ssize_t)size:(NSString *)typeEncoding forMethodArgumentAtIndex:(NSUInteger)idx {
|
||||
return [self sizeForTypeEncoding:[self type:typeEncoding forMethodArgumentAtIndex:idx] alignment:nil];
|
||||
}
|
||||
|
||||
+ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut {
|
||||
return [self sizeForTypeEncoding:type alignment:alignOut unaligned:NO];
|
||||
}
|
||||
|
||||
+ (ssize_t)sizeForTypeEncoding:(NSString *)type alignment:(ssize_t *)alignOut unaligned:(BOOL)unaligned {
|
||||
FLEXTypeInfo info = [self parseType:type];
|
||||
|
||||
ssize_t size = info.size;
|
||||
ssize_t align = info.align;
|
||||
|
||||
if (info.supported) {
|
||||
if (alignOut) {
|
||||
*alignOut = align;
|
||||
}
|
||||
|
||||
if (!unaligned) {
|
||||
size += size % align;
|
||||
}
|
||||
}
|
||||
|
||||
// size is -1 if not supported
|
||||
return size;
|
||||
}
|
||||
|
||||
+ (FLEXTypeInfo)parseType:(NSString *)type cleaned:(NSString * __autoreleasing *)cleanedEncoding {
|
||||
FLEXTypeEncodingParser *parser = [[self alloc] initWithObjCTypes:type];
|
||||
FLEXTypeInfo info = [parser parseNextType];
|
||||
if (cleanedEncoding) {
|
||||
*cleanedEncoding = parser.cleaned;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
+ (FLEXTypeInfo)parseType:(NSString *)type {
|
||||
return [self parseType:type cleaned:nil];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (NSCharacterSet *)identifierFirstCharCharacterSet {
|
||||
static NSCharacterSet *identifierFirstSet = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *allowed = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$";
|
||||
identifierFirstSet = [NSCharacterSet characterSetWithCharactersInString:allowed];
|
||||
});
|
||||
|
||||
return identifierFirstSet;
|
||||
}
|
||||
|
||||
- (NSCharacterSet *)identifierCharacterSet {
|
||||
static NSCharacterSet *identifierSet = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *allowed = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$1234567890";
|
||||
identifierSet = [NSCharacterSet characterSetWithCharactersInString:allowed];
|
||||
});
|
||||
|
||||
return identifierSet;
|
||||
}
|
||||
|
||||
- (char)nextChar {
|
||||
NSScanner *scan = self.scan;
|
||||
return [scan.string characterAtIndex:scan.scanLocation];
|
||||
}
|
||||
|
||||
/// For scanning struct/class names
|
||||
- (NSString *)scanIdentifier {
|
||||
NSString *prefix = nil, *suffix = nil;
|
||||
|
||||
// Identifiers cannot start with a number
|
||||
if (![self.scan scanCharactersFromSet:self.identifierFirstCharCharacterSet intoString:&prefix]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Optional because identifier may just be one character
|
||||
[self.scan scanCharactersFromSet:self.identifierCharacterSet intoString:&suffix];
|
||||
|
||||
if (suffix) {
|
||||
return [prefix stringByAppendingString:suffix];
|
||||
}
|
||||
|
||||
return prefix;
|
||||
}
|
||||
|
||||
/// @return the size in bytes
|
||||
- (ssize_t)sizeForType:(FLEXTypeEncoding)type {
|
||||
switch (type) {
|
||||
case FLEXTypeEncodingChar: return sizeof(char);
|
||||
case FLEXTypeEncodingInt: return sizeof(int);
|
||||
case FLEXTypeEncodingShort: return sizeof(short);
|
||||
case FLEXTypeEncodingLong: return sizeof(long);
|
||||
case FLEXTypeEncodingLongLong: return sizeof(long long);
|
||||
case FLEXTypeEncodingUnsignedChar: return sizeof(unsigned char);
|
||||
case FLEXTypeEncodingUnsignedInt: return sizeof(unsigned int);
|
||||
case FLEXTypeEncodingUnsignedShort: return sizeof(unsigned short);
|
||||
case FLEXTypeEncodingUnsignedLong: return sizeof(unsigned long);
|
||||
case FLEXTypeEncodingUnsignedLongLong: return sizeof(unsigned long long);
|
||||
case FLEXTypeEncodingFloat: return sizeof(float);
|
||||
case FLEXTypeEncodingDouble: return sizeof(double);
|
||||
case FLEXTypeEncodingLongDouble: return sizeof(long double);
|
||||
case FLEXTypeEncodingCBool: return sizeof(_Bool);
|
||||
case FLEXTypeEncodingVoid: return 0;
|
||||
case FLEXTypeEncodingCString: return sizeof(char *);
|
||||
case FLEXTypeEncodingObjcObject: return sizeof(id);
|
||||
case FLEXTypeEncodingObjcClass: return sizeof(Class);
|
||||
case FLEXTypeEncodingSelector: return sizeof(SEL);
|
||||
// Unknown / '?' is typically a pointer. In the rare case
|
||||
// it isn't, such as in '{?=...}', it is never passed here.
|
||||
case FLEXTypeEncodingUnknown:
|
||||
case FLEXTypeEncodingPointer: return sizeof(uintptr_t);
|
||||
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
- (FLEXTypeInfo)parseNextType {
|
||||
NSUInteger start = self.scan.scanLocation;
|
||||
|
||||
// Check for void first
|
||||
if ([self scanChar:FLEXTypeEncodingVoid]) {
|
||||
// Skip argument frame for method signatures
|
||||
[self scanSize];
|
||||
return FLEXTypeInfoVoid;
|
||||
}
|
||||
|
||||
// Scan optional const
|
||||
[self scanChar:FLEXTypeEncodingConst];
|
||||
|
||||
// Check for pointer, then scan next
|
||||
if ([self scanChar:FLEXTypeEncodingPointer]) {
|
||||
// Recurse to scan something else
|
||||
NSUInteger pointerTypeStart = self.scan.scanLocation;
|
||||
if ([self scanPastArg]) {
|
||||
// Make sure the pointer type is supported, and clean it if not
|
||||
NSUInteger pointerTypeLength = self.scan.scanLocation - pointerTypeStart;
|
||||
NSString *pointerType = [self.scan.string
|
||||
substringWithRange:NSMakeRange(pointerTypeStart, pointerTypeLength)
|
||||
];
|
||||
|
||||
// Deeeep nested cleaning info gets lost here
|
||||
NSString *cleaned = nil;
|
||||
FLEXTypeInfo info = [self.class parseType:pointerType cleaned:&cleaned];
|
||||
BOOL needsCleaning = !info.supported || info.containsUnion || info.fixesApplied;
|
||||
|
||||
// Clean the type if it is unsupported, malformed, or contains a union.
|
||||
// (Unions are supported by NSGetSizeAndAlignment but not
|
||||
// supported by NSMethodSignature for some reason)
|
||||
if (needsCleaning) {
|
||||
// If unsupported, no cleaning occurred in parseType:cleaned: above.
|
||||
// Otherwise, the type is partially supported and we did clean it,
|
||||
// and we will replace this type with the cleaned type from above.
|
||||
if (!info.supported || info.containsUnion) {
|
||||
cleaned = [self cleanPointeeTypeAtLocation:pointerTypeStart];
|
||||
}
|
||||
|
||||
NSInteger offset = self.cleanedReplacingOffset;
|
||||
NSInteger location = pointerTypeStart - offset;
|
||||
[self.cleaned replaceCharactersInRange:NSMakeRange(
|
||||
location, pointerTypeLength
|
||||
) withString:cleaned];
|
||||
}
|
||||
|
||||
// Skip optional frame offset
|
||||
[self scanSize];
|
||||
|
||||
ssize_t size = [self sizeForType:FLEXTypeEncodingPointer];
|
||||
return FLEXTypeInfoMake(size, size, !info.supported || info.fixesApplied);
|
||||
} else {
|
||||
// Scan failed, abort
|
||||
self.scan.scanLocation = start;
|
||||
return FLEXTypeInfoUnsupported;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for struct/union/array
|
||||
char next = self.nextChar;
|
||||
BOOL didScanSUA = YES, structOrUnion = NO, isUnion = NO;
|
||||
FLEXTypeEncoding opening = FLEXTypeEncodingNull, closing = FLEXTypeEncodingNull;
|
||||
switch (next) {
|
||||
case FLEXTypeEncodingStructBegin:
|
||||
structOrUnion = YES;
|
||||
opening = FLEXTypeEncodingStructBegin;
|
||||
closing = FLEXTypeEncodingStructEnd;
|
||||
break;
|
||||
case FLEXTypeEncodingUnionBegin:
|
||||
structOrUnion = isUnion = YES;
|
||||
opening = FLEXTypeEncodingUnionBegin;
|
||||
closing = FLEXTypeEncodingUnionEnd;
|
||||
break;
|
||||
case FLEXTypeEncodingArrayBegin:
|
||||
opening = FLEXTypeEncodingArrayBegin;
|
||||
closing = FLEXTypeEncodingArrayEnd;
|
||||
break;
|
||||
|
||||
default:
|
||||
didScanSUA = NO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (didScanSUA) {
|
||||
BOOL containsUnion = isUnion;
|
||||
BOOL fixesApplied = NO;
|
||||
|
||||
NSUInteger backup = self.scan.scanLocation;
|
||||
|
||||
// Ensure we have a closing tag
|
||||
if (![self scanPair:opening close:closing]) {
|
||||
// Scan failed, abort
|
||||
self.scan.scanLocation = start;
|
||||
return FLEXTypeInfoUnsupported;
|
||||
}
|
||||
|
||||
// Move cursor just after opening tag (struct/union/array)
|
||||
NSInteger arrayCount = -1;
|
||||
self.scan.scanLocation = backup + 1;
|
||||
|
||||
if (!structOrUnion) {
|
||||
arrayCount = [self scanSize];
|
||||
if (!arrayCount || self.nextChar == FLEXTypeEncodingArrayEnd) {
|
||||
// Malformed array type:
|
||||
// 1. Arrays must have a count after the opening brace
|
||||
// 2. Arrays must have an element type after the count
|
||||
self.scan.scanLocation = start;
|
||||
return FLEXTypeInfoUnsupported;
|
||||
}
|
||||
} else {
|
||||
// If we encounter the ?= portion of something like {?=b8b4b1b1b18[8S]}
|
||||
// then we skip over it, since it means nothing to us in this context.
|
||||
// It is completely optional, and if it fails, we go right back where we were.
|
||||
if (![self scanTypeName] && self.nextChar == FLEXTypeEncodingUnknown) {
|
||||
// Exception: we are trying to parse {?} which is invalid
|
||||
self.scan.scanLocation = start;
|
||||
return FLEXTypeInfoUnsupported;
|
||||
}
|
||||
}
|
||||
|
||||
// Sum sizes of members together:
|
||||
// Scan for bitfields before checking for other members
|
||||
//
|
||||
// Arrays will only have one "member," but
|
||||
// this logic still works for them
|
||||
ssize_t sizeSoFar = 0;
|
||||
ssize_t maxAlign = 0;
|
||||
NSMutableString *cleanedBackup = self.cleaned.mutableCopy;
|
||||
|
||||
while (![self scanChar:closing]) {
|
||||
next = self.nextChar;
|
||||
// Check for bitfields, which we cannot support because
|
||||
// type encodings for bitfields do not include alignment info
|
||||
if (next == FLEXTypeEncodingBitField) {
|
||||
self.scan.scanLocation = start;
|
||||
return FLEXTypeInfoUnsupported;
|
||||
}
|
||||
|
||||
// Structure fields could be named
|
||||
if (next == FLEXTypeEncodingQuote) {
|
||||
[self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
|
||||
}
|
||||
|
||||
FLEXTypeInfo info = [self parseNextType];
|
||||
if (!info.supported || info.containsUnion) {
|
||||
// The above call is the only time in this method where
|
||||
// `cleaned` might be mutated recursively, so this is the
|
||||
// only place where we need to keep and restore a backup
|
||||
//
|
||||
// For instance, if we've been iterating over the members
|
||||
// of a struct and we've encountered a few pointers so far
|
||||
// that we needed to clean, and suddenly we come across an
|
||||
// unsupported member, we need to be able to "rewind" and
|
||||
// undo any changes to `self.cleaned` so that the parent
|
||||
// call in the call stack can wipe the current structure
|
||||
// clean entirely if needed. Example below:
|
||||
//
|
||||
// Initial: ^{foo=^{pair<d,d>}{^pair<i,i>}{invalid_type<d>}}
|
||||
// v-- here
|
||||
// 1st clean: ^{foo=^{?=}{^pair<i,i>}{invalid_type<d>}
|
||||
// v-- here
|
||||
// 2nd clean: ^{foo=^{?=}{?=}{invalid_type<d>}
|
||||
// v-- here
|
||||
// Can't clean: ^{foo=^{?=}{?=}{invalid_type<d>}
|
||||
// v-- to here
|
||||
// Rewind: ^{foo=^{pair<d,d>}{^pair<i,i>}{invalid_type<d>}}
|
||||
// Final clean: ^{foo=}
|
||||
self.cleaned = cleanedBackup;
|
||||
self.scan.scanLocation = start;
|
||||
return FLEXTypeInfoUnsupported;
|
||||
}
|
||||
|
||||
// Unions are the size of their largest member,
|
||||
// arrays are element.size x length, and
|
||||
// structs are the sum of their members
|
||||
if (structOrUnion) {
|
||||
if (isUnion) { // Union
|
||||
sizeSoFar = MAX(sizeSoFar, info.size);
|
||||
} else { // Struct
|
||||
sizeSoFar += info.size;
|
||||
}
|
||||
} else { // Array
|
||||
sizeSoFar = info.size * arrayCount;
|
||||
}
|
||||
|
||||
// Propogate the max alignment and other metadata
|
||||
maxAlign = MAX(maxAlign, info.align);
|
||||
containsUnion = containsUnion || info.containsUnion;
|
||||
fixesApplied = fixesApplied || info.fixesApplied;
|
||||
}
|
||||
|
||||
// Skip optional frame offset
|
||||
[self scanSize];
|
||||
|
||||
return FLEXTypeInfoMakeU(sizeSoFar, maxAlign, fixesApplied, containsUnion);
|
||||
}
|
||||
|
||||
// Scan single thing and possible size and return
|
||||
ssize_t size = -1;
|
||||
char t = self.nextChar;
|
||||
switch (t) {
|
||||
case FLEXTypeEncodingUnknown:
|
||||
case FLEXTypeEncodingChar:
|
||||
case FLEXTypeEncodingInt:
|
||||
case FLEXTypeEncodingShort:
|
||||
case FLEXTypeEncodingLong:
|
||||
case FLEXTypeEncodingLongLong:
|
||||
case FLEXTypeEncodingUnsignedChar:
|
||||
case FLEXTypeEncodingUnsignedInt:
|
||||
case FLEXTypeEncodingUnsignedShort:
|
||||
case FLEXTypeEncodingUnsignedLong:
|
||||
case FLEXTypeEncodingUnsignedLongLong:
|
||||
case FLEXTypeEncodingFloat:
|
||||
case FLEXTypeEncodingDouble:
|
||||
case FLEXTypeEncodingLongDouble:
|
||||
case FLEXTypeEncodingCBool:
|
||||
case FLEXTypeEncodingCString:
|
||||
case FLEXTypeEncodingSelector:
|
||||
case FLEXTypeEncodingBitField: {
|
||||
self.scan.scanLocation++;
|
||||
// Skip optional frame offset
|
||||
[self scanSize];
|
||||
|
||||
if (t == FLEXTypeEncodingBitField) {
|
||||
self.scan.scanLocation = start;
|
||||
return FLEXTypeInfoUnsupported;
|
||||
} else {
|
||||
// Compute size
|
||||
size = [self sizeForType:t];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FLEXTypeEncodingObjcObject:
|
||||
case FLEXTypeEncodingObjcClass: {
|
||||
self.scan.scanLocation++;
|
||||
// These might have numbers OR quotes after them
|
||||
// Skip optional frame offset
|
||||
[self scanSize];
|
||||
[self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
|
||||
size = sizeof(id);
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
// Alignment of scalar types is its size
|
||||
return FLEXTypeInfoMake(size, size, NO);
|
||||
}
|
||||
|
||||
self.scan.scanLocation = start;
|
||||
return FLEXTypeInfoUnsupported;
|
||||
}
|
||||
|
||||
- (BOOL)scanString:(NSString *)str {
|
||||
return [self.scan scanString:str intoString:nil];
|
||||
}
|
||||
|
||||
- (BOOL)canScanString:(NSString *)str {
|
||||
NSScanner *scan = self.scan;
|
||||
NSUInteger len = str.length;
|
||||
unichar buff1[len], buff2[len];
|
||||
|
||||
[str getCharacters:buff1];
|
||||
[scan.string getCharacters:buff2 range:NSMakeRange(scan.scanLocation, len)];
|
||||
if (memcmp(buff1, buff2, len) == 0) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)canScanChar:(char)c {
|
||||
// By avoiding any ARC calls on these two objects which we know won't be
|
||||
// free'd out from under us, we're making HUGE performance savings in this
|
||||
// parser, because this method is one of the most-used methods of the parser.
|
||||
// This is probably the most performance-critical method in this class.
|
||||
__unsafe_unretained NSScanner *scan = self.scan;
|
||||
__unsafe_unretained NSString *string = scan.string;
|
||||
if (scan.scanLocation >= string.length) return NO;
|
||||
|
||||
return [string characterAtIndex:scan.scanLocation] == c;
|
||||
}
|
||||
|
||||
- (BOOL)scanChar:(char)c {
|
||||
if ([self canScanChar:c]) {
|
||||
self.scan.scanLocation++;
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)scanChar:(char)c into:(char *)ref {
|
||||
if ([self scanChar:c]) {
|
||||
*ref = c;
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (ssize_t)scanSize {
|
||||
NSInteger size = 0;
|
||||
if ([self.scan scanInteger:&size]) {
|
||||
return size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSString *)scanPair:(char)c1 close:(char)c2 {
|
||||
// Starting position and string variables
|
||||
NSUInteger start = self.scan.scanLocation;
|
||||
NSString *s1 = S(c1);
|
||||
|
||||
// Scan opening tag
|
||||
if (![self scanChar:c1]) {
|
||||
self.scan.scanLocation = start;
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Character set for scanning up to either symbol
|
||||
NSCharacterSet *bothChars = ({
|
||||
unichar buff[2] = { c1, c2 };
|
||||
NSString *bothCharsStr = [[NSString alloc] initWithCharacters:buff length:2];
|
||||
[NSCharacterSet characterSetWithCharactersInString:bothCharsStr];
|
||||
});
|
||||
|
||||
// Stack for finding pairs, starting with the opening symbol
|
||||
NSMutableArray *stack = [NSMutableArray arrayWithObject:s1];
|
||||
|
||||
// Algorithm for scanning to the closing end of a pair of opening/closing symbols
|
||||
// scanUpToCharactersFromSet:intoString: returns NO if you're already at one of the chars,
|
||||
// so we need to check if we can actually scan one if it returns NO
|
||||
while ([self.scan scanUpToCharactersFromSet:bothChars intoString:nil] ||
|
||||
[self canScanChar:c1] || [self canScanChar:c2]) {
|
||||
// Closing symbol found
|
||||
if ([self scanChar:c2]) {
|
||||
if (!stack.count) {
|
||||
// Abort, no matching opening symbol
|
||||
self.scan.scanLocation = start;
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Pair found, pop opening symbol
|
||||
[stack removeLastObject];
|
||||
// Exit loop if we reached the closing brace we needed
|
||||
if (!stack.count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Opening symbol found
|
||||
if ([self scanChar:c1]) {
|
||||
// Begin pair
|
||||
[stack addObject:s1];
|
||||
}
|
||||
}
|
||||
|
||||
if (stack.count) {
|
||||
// Abort, no matching closing symbol
|
||||
self.scan.scanLocation = start;
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Slice out the string we just scanned
|
||||
return [self.scan.string
|
||||
substringWithRange:NSMakeRange(start, self.scan.scanLocation - start)
|
||||
];
|
||||
}
|
||||
|
||||
- (BOOL)scanPastArg {
|
||||
NSUInteger start = self.scan.scanLocation;
|
||||
|
||||
// Check for void first
|
||||
if ([self scanChar:FLEXTypeEncodingVoid]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Scan optional const
|
||||
[self scanChar:FLEXTypeEncodingConst];
|
||||
|
||||
// Check for pointer, then scan next
|
||||
if ([self scanChar:FLEXTypeEncodingPointer]) {
|
||||
// Recurse to scan something else
|
||||
if ([self scanPastArg]) {
|
||||
return YES;
|
||||
} else {
|
||||
// Scan failed, abort
|
||||
self.scan.scanLocation = start;
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
char next = self.nextChar;
|
||||
|
||||
// Check for struct/union/array, scan past it
|
||||
FLEXTypeEncoding opening = FLEXTypeEncodingNull, closing = FLEXTypeEncodingNull;
|
||||
BOOL checkPair = YES;
|
||||
switch (next) {
|
||||
case FLEXTypeEncodingStructBegin:
|
||||
opening = FLEXTypeEncodingStructBegin;
|
||||
closing = FLEXTypeEncodingStructEnd;
|
||||
break;
|
||||
case FLEXTypeEncodingUnionBegin:
|
||||
opening = FLEXTypeEncodingUnionBegin;
|
||||
closing = FLEXTypeEncodingUnionEnd;
|
||||
break;
|
||||
case FLEXTypeEncodingArrayBegin:
|
||||
opening = FLEXTypeEncodingArrayBegin;
|
||||
closing = FLEXTypeEncodingArrayEnd;
|
||||
break;
|
||||
|
||||
default:
|
||||
checkPair = NO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (checkPair && [self scanPair:opening close:closing]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Scan single thing and possible size and return
|
||||
switch (next) {
|
||||
case FLEXTypeEncodingUnknown:
|
||||
case FLEXTypeEncodingChar:
|
||||
case FLEXTypeEncodingInt:
|
||||
case FLEXTypeEncodingShort:
|
||||
case FLEXTypeEncodingLong:
|
||||
case FLEXTypeEncodingLongLong:
|
||||
case FLEXTypeEncodingUnsignedChar:
|
||||
case FLEXTypeEncodingUnsignedInt:
|
||||
case FLEXTypeEncodingUnsignedShort:
|
||||
case FLEXTypeEncodingUnsignedLong:
|
||||
case FLEXTypeEncodingUnsignedLongLong:
|
||||
case FLEXTypeEncodingFloat:
|
||||
case FLEXTypeEncodingDouble:
|
||||
case FLEXTypeEncodingLongDouble:
|
||||
case FLEXTypeEncodingCBool:
|
||||
case FLEXTypeEncodingCString:
|
||||
case FLEXTypeEncodingSelector:
|
||||
case FLEXTypeEncodingBitField: {
|
||||
self.scan.scanLocation++;
|
||||
// Size is optional
|
||||
[self scanSize];
|
||||
return YES;
|
||||
}
|
||||
|
||||
case FLEXTypeEncodingObjcObject:
|
||||
case FLEXTypeEncodingObjcClass: {
|
||||
self.scan.scanLocation++;
|
||||
// These might have numbers OR quotes after them
|
||||
[self scanSize] || [self scanPair:FLEXTypeEncodingQuote close:FLEXTypeEncodingQuote];
|
||||
return YES;
|
||||
}
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
self.scan.scanLocation = start;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)scanArg {
|
||||
NSUInteger start = self.scan.scanLocation;
|
||||
if (![self scanPastArg]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self.scan.string
|
||||
substringWithRange:NSMakeRange(start, self.scan.scanLocation - start)
|
||||
];
|
||||
}
|
||||
|
||||
- (BOOL)scanTypeName {
|
||||
NSUInteger start = self.scan.scanLocation;
|
||||
|
||||
// The ?= portion of something like {?=b8b4b1b1b18[8S]}
|
||||
if ([self scanChar:FLEXTypeEncodingUnknown]) {
|
||||
if (![self scanString:@"="]) {
|
||||
// No size information available for strings like {?=}
|
||||
self.scan.scanLocation = start;
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
if (![self scanIdentifier] || ![self scanString:@"="]) {
|
||||
// 1. Not a valid identifier
|
||||
// 2. No size information available for strings like {CGPoint}
|
||||
self.scan.scanLocation = start;
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)extractTypeNameFromScanLocation:(BOOL)allowMissingTypeInfo closing:(FLEXTypeEncoding)closeTag {
|
||||
NSUInteger start = self.scan.scanLocation;
|
||||
|
||||
// The ?= portion of something like {?=b8b4b1b1b18[8S]}
|
||||
if ([self scanChar:FLEXTypeEncodingUnknown]) {
|
||||
return @"?";
|
||||
} else {
|
||||
NSString *typeName = [self scanIdentifier];
|
||||
char next = self.nextChar;
|
||||
|
||||
if (!typeName) {
|
||||
// Did not scan an identifier
|
||||
self.scan.scanLocation = start;
|
||||
return nil;
|
||||
}
|
||||
|
||||
switch (next) {
|
||||
case '=':
|
||||
return typeName;
|
||||
|
||||
default: {
|
||||
// = is non-optional unless we allowMissingTypeInfo, in whcih
|
||||
// case the next character needs to be a closing brace
|
||||
if (allowMissingTypeInfo && next == closeTag) {
|
||||
return typeName;
|
||||
} else {
|
||||
// Not a valid identifier; possibly a generic C++ type
|
||||
// i.e. {pair<T, U>} where `name` was found as `pair`
|
||||
self.scan.scanLocation = start;
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)cleanPointeeTypeAtLocation:(NSUInteger)scanLocation {
|
||||
NSUInteger start = self.scan.scanLocation;
|
||||
self.scan.scanLocation = scanLocation;
|
||||
|
||||
// The return / cleanup code for when the scanned type is already clean
|
||||
NSString * (^typeIsClean)(void) = ^NSString * {
|
||||
NSString *clean = [self.scan.string
|
||||
substringWithRange:NSMakeRange(scanLocation, self.scan.scanLocation - scanLocation)
|
||||
];
|
||||
// Reset scan location even on success, because this method is not supposed to change it
|
||||
self.scan.scanLocation = start;
|
||||
return clean;
|
||||
};
|
||||
|
||||
// No void, this is not a return type
|
||||
|
||||
// Scan optional const
|
||||
[self scanChar:FLEXTypeEncodingConst];
|
||||
|
||||
char next = self.nextChar;
|
||||
switch (next) {
|
||||
case FLEXTypeEncodingPointer:
|
||||
// Recurse to scan something else
|
||||
[self scanChar:next];
|
||||
return [self cleanPointeeTypeAtLocation:self.scan.scanLocation];
|
||||
|
||||
case FLEXTypeEncodingArrayBegin:
|
||||
// All arrays are supported, scan past them
|
||||
if ([self scanPair:FLEXTypeEncodingArrayBegin close:FLEXTypeEncodingArrayEnd]) {
|
||||
return typeIsClean();
|
||||
}
|
||||
break;
|
||||
|
||||
case FLEXTypeEncodingUnionBegin:
|
||||
// Unions are not supported at all in NSMethodSignature
|
||||
// We could check for the closing token to be safe, but eh
|
||||
self.scan.scanLocation = start;
|
||||
return @"?";
|
||||
|
||||
case FLEXTypeEncodingStructBegin: {
|
||||
FLEXTypeInfo info = [self.class parseType:self.unscanned];
|
||||
if (info.supported && !info.fixesApplied) {
|
||||
[self scanPastArg];
|
||||
return typeIsClean();
|
||||
}
|
||||
|
||||
// The structure we just tried to scan is unsupported, so just return its name
|
||||
// if it has one. If not, just return a question mark.
|
||||
self.scan.scanLocation++; // Skip past {
|
||||
NSString *name = [self extractTypeNameFromScanLocation:YES closing:FLEXTypeEncodingStructEnd];
|
||||
if (name) {
|
||||
// Got the name, scan past the closing token
|
||||
[self.scan scanUpToString:@"}" intoString:nil];
|
||||
if (![self scanChar:FLEXTypeEncodingStructEnd]) {
|
||||
// Missing struct close token
|
||||
self.scan.scanLocation = start;
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
// Did not scan valid identifier, possibly a C++ type
|
||||
self.scan.scanLocation = start;
|
||||
return @"{?=}";
|
||||
}
|
||||
|
||||
// Reset scan location even on success, because this method is not supposed to change it
|
||||
self.scan.scanLocation = start;
|
||||
return ({ // "{name=}"
|
||||
NSMutableString *format = @"{".mutableCopy;
|
||||
[format appendString:name];
|
||||
[format appendString:@"=}"];
|
||||
format;
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for other types, which in theory are all valid but whatever
|
||||
FLEXTypeInfo info = [self parseNextType];
|
||||
if (info.supported && !info.fixesApplied) {
|
||||
return typeIsClean();
|
||||
}
|
||||
|
||||
self.scan.scanLocation = start;
|
||||
return @"?";
|
||||
}
|
||||
|
||||
- (NSUInteger)cleanedReplacingOffset {
|
||||
return self.scan.string.length - self.cleaned.length;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// FLEXBlockDescription.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Oliver Letterer on 2012-09-01
|
||||
// Forked from CTObjectiveCRuntimeAdditions (MIT License)
|
||||
// https://github.com/ebf/CTObjectiveCRuntimeAdditions
|
||||
//
|
||||
// Copyright (c) 2020 FLEX Team-EDV Beratung Föllmer GmbH
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, FLEXBlockOptions) {
|
||||
FLEXBlockOptionHasCopyDispose = (1 << 25),
|
||||
FLEXBlockOptionHasCtor = (1 << 26), // helpers have C++ code
|
||||
FLEXBlockOptionIsGlobal = (1 << 28),
|
||||
FLEXBlockOptionHasStret = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
|
||||
FLEXBlockOptionHasSignature = (1 << 30),
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark -
|
||||
@interface FLEXBlockDescription : NSObject
|
||||
|
||||
+ (instancetype)describing:(id)block;
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSMethodSignature *signature;
|
||||
@property (nonatomic, readonly, nullable) NSString *signatureString;
|
||||
@property (nonatomic, readonly, nullable) NSString *sourceDeclaration;
|
||||
@property (nonatomic, readonly) FLEXBlockOptions flags;
|
||||
@property (nonatomic, readonly) NSUInteger size;
|
||||
@property (nonatomic, readonly) NSString *summary;
|
||||
@property (nonatomic, readonly) id block;
|
||||
|
||||
- (BOOL)isCompatibleForBlockSwizzlingWithMethodSignature:(NSMethodSignature *)methodSignature;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
@interface NSBlock : NSObject
|
||||
- (void)invoke;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// FLEXBlockDescription.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Oliver Letterer on 2012-09-01
|
||||
// Forked from CTObjectiveCRuntimeAdditions (MIT License)
|
||||
// https://github.com/ebf/CTObjectiveCRuntimeAdditions
|
||||
//
|
||||
// Copyright (c) 2020 FLEX Team-EDV Beratung Föllmer GmbH
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#import "FLEXBlockDescription.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
|
||||
struct block_object {
|
||||
void *isa;
|
||||
int flags;
|
||||
int reserved;
|
||||
void (*invoke)(void *, ...);
|
||||
struct block_descriptor {
|
||||
unsigned long int reserved; // NULL
|
||||
unsigned long int size; // sizeof(struct Block_literal_1)
|
||||
// optional helper functions
|
||||
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
|
||||
void (*dispose_helper)(void *src); // IFF (1<<25)
|
||||
// required ABI.2010.3.16
|
||||
const char *signature; // IFF (1<<30)
|
||||
} *descriptor;
|
||||
// imported variables
|
||||
};
|
||||
|
||||
@implementation FLEXBlockDescription
|
||||
|
||||
+ (instancetype)describing:(id)block {
|
||||
return [[self alloc] initWithObjcBlock:block];
|
||||
}
|
||||
|
||||
- (id)initWithObjcBlock:(id)block {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_block = block;
|
||||
|
||||
struct block_object *blockRef = (__bridge struct block_object *)block;
|
||||
_flags = blockRef->flags;
|
||||
_size = blockRef->descriptor->size;
|
||||
|
||||
if (_flags & FLEXBlockOptionHasSignature) {
|
||||
void *signatureLocation = blockRef->descriptor;
|
||||
signatureLocation += sizeof(unsigned long int);
|
||||
signatureLocation += sizeof(unsigned long int);
|
||||
|
||||
if (_flags & FLEXBlockOptionHasCopyDispose) {
|
||||
signatureLocation += sizeof(void(*)(void *dst, void *src));
|
||||
signatureLocation += sizeof(void (*)(void *src));
|
||||
}
|
||||
|
||||
const char *signature = (*(const char **)signatureLocation);
|
||||
_signatureString = @(signature);
|
||||
|
||||
@try {
|
||||
_signature = [NSMethodSignature signatureWithObjCTypes:signature];
|
||||
} @catch (NSException *exception) { }
|
||||
}
|
||||
|
||||
NSMutableString *summary = [NSMutableString stringWithFormat:
|
||||
@"Type signature: %@\nSize: %@\nIs global: %@\nHas constructor: %@\nIs stret: %@",
|
||||
self.signatureString ?: @"nil", @(self.size),
|
||||
@((BOOL)(_flags & FLEXBlockOptionIsGlobal)),
|
||||
@((BOOL)(_flags & FLEXBlockOptionHasCtor)),
|
||||
@((BOOL)(_flags & FLEXBlockOptionHasStret))
|
||||
];
|
||||
|
||||
if (!self.signature) {
|
||||
[summary appendFormat:@"\nNumber of arguments: %@", @(self.signature.numberOfArguments)];
|
||||
}
|
||||
|
||||
_summary = summary.copy;
|
||||
_sourceDeclaration = [self buildLikelyDeclaration];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isCompatibleForBlockSwizzlingWithMethodSignature:(NSMethodSignature *)methodSignature {
|
||||
if (!self.signature) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (self.signature.numberOfArguments != methodSignature.numberOfArguments + 1) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (strcmp(self.signature.methodReturnType, methodSignature.methodReturnType) != 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
for (int i = 0; i < methodSignature.numberOfArguments; i++) {
|
||||
if (i == 1) {
|
||||
// SEL in method, IMP in block
|
||||
if (strcmp([methodSignature getArgumentTypeAtIndex:i], ":") != 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (strcmp([self.signature getArgumentTypeAtIndex:i + 1], "^?") != 0) {
|
||||
return NO;
|
||||
}
|
||||
} else {
|
||||
if (strcmp([self.signature getArgumentTypeAtIndex:i], [self.signature getArgumentTypeAtIndex:i + 1]) != 0) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)buildLikelyDeclaration {
|
||||
NSMethodSignature *signature = self.signature;
|
||||
NSUInteger numberOfArguments = signature.numberOfArguments;
|
||||
const char *returnType = signature.methodReturnType;
|
||||
|
||||
// Return type
|
||||
NSMutableString *decl = [NSMutableString stringWithString:@"^"];
|
||||
if (returnType[0] != FLEXTypeEncodingVoid) {
|
||||
[decl appendString:[FLEXRuntimeUtility readableTypeForEncoding:@(returnType)]];
|
||||
[decl appendString:@" "];
|
||||
}
|
||||
|
||||
// Arguments
|
||||
if (numberOfArguments) {
|
||||
[decl appendString:@"("];
|
||||
for (NSUInteger i = 1; i < numberOfArguments; i++) {
|
||||
const char *argType = [self.signature getArgumentTypeAtIndex:i] ?: "?";
|
||||
NSString *readableArgType = [FLEXRuntimeUtility readableTypeForEncoding:@(argType)];
|
||||
[decl appendFormat:@"%@ arg%@, ", readableArgType, @(i)];
|
||||
}
|
||||
|
||||
[decl deleteCharactersInRange:NSMakeRange(decl.length-2, 2)];
|
||||
[decl appendString:@")"];
|
||||
}
|
||||
|
||||
return decl.copy;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// FLEXClassBuilder.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 7/3/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@class FLEXIvarBuilder, FLEXMethodBase, FLEXProperty, FLEXProtocol;
|
||||
|
||||
|
||||
#pragma mark FLEXClassBuilder
|
||||
@interface FLEXClassBuilder : NSObject
|
||||
|
||||
@property (nonatomic, readonly) Class workingClass;
|
||||
|
||||
/// Begins constructing a class with the given name.
|
||||
///
|
||||
/// This new class will implicitly inherits from \c NSObject with \c 0 extra bytes.
|
||||
/// Classes created this way must be registered with \c -registerClass before being used.
|
||||
+ (instancetype)allocateClass:(NSString *)name;
|
||||
/// Begins constructing a class with the given name and superclass.
|
||||
/// @discussion Calls \c -allocateClass:superclass:extraBytes: with \c 0 extra bytes.
|
||||
/// Classes created this way must be registered with \c -registerClass before being used.
|
||||
+ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass;
|
||||
/// Begins constructing a new class object with the given name and superclass.
|
||||
/// @discussion Pass \c nil to \e superclass to create a new root class.
|
||||
/// Classes created this way must be registered with \c -registerClass before being used.
|
||||
+ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass extraBytes:(size_t)bytes;
|
||||
/// Begins constructing a new root class object with the given name and \c 0 extra bytes.
|
||||
/// @discussion Classes created this way must be registered with \c -registerClass before being used.
|
||||
+ (instancetype)allocateRootClass:(NSString *)name;
|
||||
/// Use this to modify existing classes. @warning You cannot add instance variables to existing classes.
|
||||
+ (instancetype)builderForClass:(Class)cls;
|
||||
|
||||
/// @return Any methods that failed to be added.
|
||||
- (NSArray<FLEXMethodBase *> *)addMethods:(NSArray<FLEXMethodBase *> *)methods;
|
||||
/// @return Any properties that failed to be added.
|
||||
- (NSArray<FLEXProperty *> *)addProperties:(NSArray<FLEXProperty *> *)properties;
|
||||
/// @return Any protocols that failed to be added.
|
||||
- (NSArray<FLEXProtocol *> *)addProtocols:(NSArray<FLEXProtocol *> *)protocols;
|
||||
/// @warning Adding Ivars to existing classes is not supported and will always fail.
|
||||
- (NSArray<FLEXIvarBuilder *> *)addIvars:(NSArray<FLEXIvarBuilder *> *)ivars;
|
||||
|
||||
/// Finalizes construction of a new class.
|
||||
/// @discussion Once a class is registered, instance variables cannot be added.
|
||||
/// @note Raises an exception if called on a previously registered class.
|
||||
- (Class)registerClass;
|
||||
/// Uses \c objc_lookupClass to determine if the working class is registered.
|
||||
@property (nonatomic, readonly) BOOL isRegistered;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark FLEXIvarBuilder
|
||||
@interface FLEXIvarBuilder : NSObject
|
||||
|
||||
/// Consider using the \c FLEXIvarBuilderWithNameAndType() macro below.
|
||||
/// @param name The name of the Ivar, such as \c \@"_value".
|
||||
/// @param size The size of the Ivar. Usually \c sizeof(type). For objects, this is \c sizeof(id).
|
||||
/// @param alignment The alignment of the Ivar. Usually \c log2(sizeof(type)).
|
||||
/// @param encoding The type encoding of the Ivar. For objects, this is \c \@(\@encode(id)), and for others it is \c \@(\@encode(type)).
|
||||
+ (instancetype)name:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding;
|
||||
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
@property (nonatomic, readonly) NSString *encoding;
|
||||
@property (nonatomic, readonly) size_t size;
|
||||
@property (nonatomic, readonly) uint8_t alignment;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#define FLEXIvarBuilderWithNameAndType(nameString, type) [FLEXIvarBuilder \
|
||||
name:nameString \
|
||||
size:sizeof(type) \
|
||||
alignment:log2(sizeof(type)) \
|
||||
typeEncoding:@(@encode(type)) \
|
||||
]
|
||||
168
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXClassBuilder.m
Normal file
168
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXClassBuilder.m
Normal file
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// FLEXClassBuilder.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 7/3/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXClassBuilder.h"
|
||||
#import "FLEXProperty.h"
|
||||
#import "FLEXMethodBase.h"
|
||||
#import "FLEXProtocol.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
#pragma mark FLEXClassBuilder
|
||||
|
||||
@interface FLEXClassBuilder ()
|
||||
@property (nonatomic) NSString *name;
|
||||
@end
|
||||
|
||||
@implementation FLEXClassBuilder
|
||||
|
||||
- (id)init {
|
||||
[NSException
|
||||
raise:NSInternalInconsistencyException
|
||||
format:@"Class instance should not be created with -init"
|
||||
];
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Initializers
|
||||
+ (instancetype)allocateClass:(NSString *)name {
|
||||
return [self allocateClass:name superclass:NSObject.class];
|
||||
}
|
||||
|
||||
+ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass {
|
||||
return [self allocateClass:name superclass:superclass extraBytes:0];
|
||||
}
|
||||
|
||||
+ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass extraBytes:(size_t)bytes {
|
||||
NSParameterAssert(name);
|
||||
return [[self alloc] initWithClass:objc_allocateClassPair(superclass, name.UTF8String, bytes)];
|
||||
}
|
||||
|
||||
+ (instancetype)allocateRootClass:(NSString *)name {
|
||||
NSParameterAssert(name);
|
||||
return [[self alloc] initWithClass:objc_allocateClassPair(Nil, name.UTF8String, 0)];
|
||||
}
|
||||
|
||||
+ (instancetype)builderForClass:(Class)cls {
|
||||
return [[self alloc] initWithClass:cls];
|
||||
}
|
||||
|
||||
- (id)initWithClass:(Class)cls {
|
||||
NSParameterAssert(cls);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_workingClass = cls;
|
||||
_name = NSStringFromClass(_workingClass);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<%@ name=%@, registered=%d>",
|
||||
NSStringFromClass(self.class), self.name, self.isRegistered];
|
||||
}
|
||||
|
||||
#pragma mark Building
|
||||
- (NSArray *)addMethods:(NSArray *)methods {
|
||||
NSParameterAssert(methods.count);
|
||||
|
||||
NSMutableArray *failed = [NSMutableArray new];
|
||||
for (FLEXMethodBase *m in methods) {
|
||||
if (!class_addMethod(self.workingClass, m.selector, m.implementation, m.typeEncoding.UTF8String)) {
|
||||
[failed addObject:m];
|
||||
}
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
- (NSArray *)addProperties:(NSArray *)properties {
|
||||
NSParameterAssert(properties.count);
|
||||
|
||||
NSMutableArray *failed = [NSMutableArray new];
|
||||
for (FLEXProperty *p in properties) {
|
||||
unsigned int pcount;
|
||||
objc_property_attribute_t *attributes = [p copyAttributesList:&pcount];
|
||||
if (!class_addProperty(self.workingClass, p.name.UTF8String, attributes, pcount)) {
|
||||
[failed addObject:p];
|
||||
}
|
||||
free(attributes);
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
- (NSArray *)addProtocols:(NSArray *)protocols {
|
||||
NSParameterAssert(protocols.count);
|
||||
|
||||
NSMutableArray *failed = [NSMutableArray new];
|
||||
for (FLEXProtocol *p in protocols) {
|
||||
if (!class_addProtocol(self.workingClass, p.objc_protocol)) {
|
||||
[failed addObject:p];
|
||||
}
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
- (NSArray *)addIvars:(NSArray *)ivars {
|
||||
NSParameterAssert(ivars.count);
|
||||
|
||||
NSMutableArray *failed = [NSMutableArray new];
|
||||
for (FLEXIvarBuilder *ivar in ivars) {
|
||||
if (!class_addIvar(self.workingClass, ivar.name.UTF8String, ivar.size, ivar.alignment, ivar.encoding.UTF8String)) {
|
||||
[failed addObject:ivar];
|
||||
}
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
- (Class)registerClass {
|
||||
if (self.isRegistered) {
|
||||
[NSException raise:NSInternalInconsistencyException format:@"Class is already registered"];
|
||||
}
|
||||
|
||||
objc_registerClassPair(self.workingClass);
|
||||
return self.workingClass;
|
||||
}
|
||||
|
||||
- (BOOL)isRegistered {
|
||||
return objc_lookUpClass(self.name.UTF8String) != nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark FLEXIvarBuilder
|
||||
|
||||
@implementation FLEXIvarBuilder
|
||||
|
||||
+ (instancetype)name:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding {
|
||||
return [[self alloc] initWithName:name size:size alignment:alignment typeEncoding:encoding];
|
||||
}
|
||||
|
||||
- (id)initWithName:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding {
|
||||
NSParameterAssert(name); NSParameterAssert(encoding);
|
||||
NSParameterAssert(size > 0); NSParameterAssert(alignment > 0);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_name = name;
|
||||
_encoding = encoding;
|
||||
_size = size;
|
||||
_alignment = alignment;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
51
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.h
Normal file
51
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.h
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// FLEXIvar.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/30/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXRuntimeConstants.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLEXIvar : NSObject
|
||||
|
||||
+ (instancetype)ivar:(Ivar)ivar;
|
||||
+ (instancetype)named:(NSString *)name onClass:(Class)cls;
|
||||
|
||||
/// The underlying \c Ivar data structure.
|
||||
@property (nonatomic, readonly) Ivar objc_ivar;
|
||||
|
||||
/// The name of the instance variable.
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
/// The type of the instance variable.
|
||||
@property (nonatomic, readonly) FLEXTypeEncoding type;
|
||||
/// The type encoding string of the instance variable.
|
||||
@property (nonatomic, readonly) NSString *typeEncoding;
|
||||
/// The offset of the instance variable.
|
||||
@property (nonatomic, readonly) NSInteger offset;
|
||||
/// The size of the instance variable. 0 if unknown.
|
||||
@property (nonatomic, readonly) NSUInteger size;
|
||||
/// Describes the type encoding, size, offset, and objc_ivar
|
||||
@property (nonatomic, readonly) NSString *details;
|
||||
/// The full path of the image that contains this ivar definition,
|
||||
/// or \c nil if this ivar was probably defined at runtime.
|
||||
@property (nonatomic, readonly, nullable) NSString *imagePath;
|
||||
|
||||
/// For internal use
|
||||
@property (nonatomic) id tag;
|
||||
|
||||
- (nullable id)getValue:(id)target;
|
||||
- (void)setValue:(nullable id)value onObject:(id)target;
|
||||
|
||||
/// Calls into -getValue: and passes that value into
|
||||
/// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
|
||||
/// and returns the result
|
||||
- (nullable id)getPotentiallyUnboxedValue:(id)target;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
158
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.m
Normal file
158
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXIvar.m
Normal file
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// FLEXIvar.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/30/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXIvar.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "FLEXRuntimeSafety.h"
|
||||
#import "FLEXTypeEncodingParser.h"
|
||||
#import "NSString+FLEX.h"
|
||||
#include "FLEXObjcInternal.h"
|
||||
#include <dlfcn.h>
|
||||
|
||||
@interface FLEXIvar () {
|
||||
NSString *_flex_description;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation FLEXIvar
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
+ (instancetype)ivar:(Ivar)ivar {
|
||||
return [[self alloc] initWithIvar:ivar];
|
||||
}
|
||||
|
||||
+ (instancetype)named:(NSString *)name onClass:(Class)cls {
|
||||
Ivar _Nullable ivar = class_getInstanceVariable(cls, name.UTF8String);
|
||||
NSAssert(ivar, @"Cannot find ivar with name %@ on class %@", name, cls);
|
||||
return [self ivar:ivar];
|
||||
}
|
||||
|
||||
- (id)initWithIvar:(Ivar)ivar {
|
||||
NSParameterAssert(ivar);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_objc_ivar = ivar;
|
||||
[self examine];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Other
|
||||
|
||||
- (NSString *)description {
|
||||
if (!_flex_description) {
|
||||
NSString *readableType = [FLEXRuntimeUtility readableTypeForEncoding:self.typeEncoding];
|
||||
_flex_description = [FLEXRuntimeUtility appendName:self.name toType:readableType];
|
||||
}
|
||||
|
||||
return _flex_description;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
return [NSString stringWithFormat:@"<%@ name=%@, encoding=%@, offset=%ld>",
|
||||
NSStringFromClass(self.class), self.name, self.typeEncoding, (long)self.offset];
|
||||
}
|
||||
|
||||
- (void)examine {
|
||||
_name = @(ivar_getName(self.objc_ivar) ?: "(nil)");
|
||||
_offset = ivar_getOffset(self.objc_ivar);
|
||||
_typeEncoding = @(ivar_getTypeEncoding(self.objc_ivar) ?: "");
|
||||
|
||||
NSString *typeForDetails = _typeEncoding;
|
||||
NSString *sizeForDetails = nil;
|
||||
if (_typeEncoding.length) {
|
||||
_type = (FLEXTypeEncoding)[_typeEncoding characterAtIndex:0];
|
||||
FLEXGetSizeAndAlignment(_typeEncoding.UTF8String, &_size, nil);
|
||||
sizeForDetails = [@(_size).stringValue stringByAppendingString:@" bytes"];
|
||||
} else {
|
||||
_type = FLEXTypeEncodingNull;
|
||||
typeForDetails = @"no type info";
|
||||
sizeForDetails = @"unknown size";
|
||||
}
|
||||
|
||||
Dl_info exeInfo;
|
||||
if (dladdr(_objc_ivar, &exeInfo)) {
|
||||
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
|
||||
}
|
||||
|
||||
_details = [NSString stringWithFormat:
|
||||
@"%@, offset %@ — %@",
|
||||
sizeForDetails, @(_offset), typeForDetails
|
||||
];
|
||||
}
|
||||
|
||||
- (id)getValue:(id)target {
|
||||
id value = nil;
|
||||
if (!FLEXIvarIsSafe(_objc_ivar) ||
|
||||
_type == FLEXTypeEncodingNull ||
|
||||
FLEXPointerIsTaggedPointer(target)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
#ifdef __arm64__
|
||||
// See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
|
||||
if (self.type == FLEXTypeEncodingObjcClass && [self.name isEqualToString:@"isa"]) {
|
||||
value = object_getClass(target);
|
||||
} else
|
||||
#endif
|
||||
if (self.type == FLEXTypeEncodingObjcObject || self.type == FLEXTypeEncodingObjcClass) {
|
||||
value = object_getIvar(target, self.objc_ivar);
|
||||
} else {
|
||||
void *pointer = (__bridge void *)target + self.offset;
|
||||
value = [FLEXRuntimeUtility
|
||||
valueForPrimitivePointer:pointer
|
||||
objCType:self.typeEncoding.UTF8String
|
||||
];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void)setValue:(id)value onObject:(id)target {
|
||||
const char *typeEncodingCString = self.typeEncoding.UTF8String;
|
||||
if (self.type == FLEXTypeEncodingObjcObject) {
|
||||
object_setIvar(target, self.objc_ivar, value);
|
||||
} else if ([value isKindOfClass:[NSValue class]]) {
|
||||
// Primitive - unbox the NSValue.
|
||||
NSValue *valueValue = (NSValue *)value;
|
||||
|
||||
// Make sure that the box contained the correct type.
|
||||
NSAssert(
|
||||
strcmp(valueValue.objCType, typeEncodingCString) == 0,
|
||||
@"Type encoding mismatch (value: %s; ivar: %s) in setting ivar named: %@ on object: %@",
|
||||
valueValue.objCType, typeEncodingCString, self.name, target
|
||||
);
|
||||
|
||||
NSUInteger bufferSize = 0;
|
||||
if (FLEXGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL)) {
|
||||
void *buffer = calloc(bufferSize, 1);
|
||||
[valueValue getValue:buffer];
|
||||
void *pointer = (__bridge void *)target + self.offset;
|
||||
memcpy(pointer, buffer, bufferSize);
|
||||
free(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (id)getPotentiallyUnboxedValue:(id)target {
|
||||
NSString *type = self.typeEncoding;
|
||||
if (type.flex_typeIsNonObjcPointer && type.flex_pointeeType != FLEXTypeEncodingVoid) {
|
||||
return [self getValue:target];
|
||||
}
|
||||
|
||||
return [FLEXRuntimeUtility
|
||||
potentiallyUnwrapBoxedPointer:[self getValue:target]
|
||||
type:type.UTF8String
|
||||
];
|
||||
}
|
||||
|
||||
@end
|
||||
96
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.h
Normal file
96
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.h
Normal file
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// FLEXMethod.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/30/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXRuntimeConstants.h"
|
||||
#import "FLEXMethodBase.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// A class representing a concrete method which already exists in a class.
|
||||
/// This class contains helper methods for swizzling or invoking the method.
|
||||
///
|
||||
/// Any of the initializers will return nil if the type encoding
|
||||
/// of the method is unsupported by `NSMethodSignature`. In general,
|
||||
/// any method whose return type or parameters involve a struct with
|
||||
/// bitfields or arrays is unsupported.
|
||||
///
|
||||
/// I do not remember why I didn't include \c signature in the base class
|
||||
/// when I originally wrote this, but I probably had a good reason. We can
|
||||
/// always go back and move it to \c FLEXMethodBase if we find we need to.
|
||||
@interface FLEXMethod : FLEXMethodBase
|
||||
|
||||
/// Defaults to instance method
|
||||
+ (nullable instancetype)method:(Method)method;
|
||||
+ (nullable instancetype)method:(Method)method isInstanceMethod:(BOOL)isInstanceMethod;
|
||||
|
||||
/// Constructs an \c FLEXMethod for the given method on the given class.
|
||||
/// @param cls the class, or metaclass if this is a class method
|
||||
/// @return The newly constructed \c FLEXMethod object, or \c nil if the
|
||||
/// specified class or its superclasses do not contain a method with the specified selector.
|
||||
+ (nullable instancetype)selector:(SEL)selector class:(Class)cls;
|
||||
/// Constructs an \c FLEXMethod for the given method on the given class,
|
||||
/// only if the given class itself defines or overrides the desired method.
|
||||
/// @param cls the class, or metaclass if this is a class method
|
||||
/// @return The newly constructed \c FLEXMethod object, or \c nil \e if the
|
||||
/// specified class does not define or override, or if the specified class
|
||||
/// or its superclasses do not contain, a method with the specified selector.
|
||||
+ (nullable instancetype)selector:(SEL)selector implementedInClass:(Class)cls;
|
||||
|
||||
@property (nonatomic, readonly) Method objc_method;
|
||||
/// The implementation of the method.
|
||||
/// @discussion Setting \c implementation will change the implementation of this method
|
||||
/// for the entire class which implements said method. It will also not modify the selector of said method.
|
||||
@property (nonatomic ) IMP implementation;
|
||||
/// Whether the method is an instance method or not.
|
||||
@property (nonatomic, readonly) BOOL isInstanceMethod;
|
||||
/// The number of arguments to the method.
|
||||
@property (nonatomic, readonly) NSUInteger numberOfArguments;
|
||||
/// The \c NSMethodSignature object corresponding to the method's type encoding.
|
||||
@property (nonatomic, readonly) NSMethodSignature *signature;
|
||||
/// Same as \e typeEncoding but with parameter sizes up front and offsets after the types.
|
||||
@property (nonatomic, readonly) NSString *signatureString;
|
||||
/// The return type of the method.
|
||||
@property (nonatomic, readonly) FLEXTypeEncoding *returnType;
|
||||
/// The return size of the method.
|
||||
@property (nonatomic, readonly) NSUInteger returnSize;
|
||||
/// The full path of the image that contains this method definition,
|
||||
/// or \c nil if this ivar was probably defined at runtime.
|
||||
@property (nonatomic, readonly) NSString *imagePath;
|
||||
|
||||
/// Like @code - (void)foo:(int)bar @endcode
|
||||
@property (nonatomic, readonly) NSString *description;
|
||||
/// Like @code -[Class foo:] @endcode
|
||||
- (NSString *)debugNameGivenClassName:(NSString *)name;
|
||||
|
||||
/// Swizzles the recieving method with the given method.
|
||||
- (void)swapImplementations:(FLEXMethod *)method;
|
||||
|
||||
#define FLEXMagicNumber 0xdeadbeef
|
||||
#define FLEXArg(expr) FLEXMagicNumber,/// @encode(__typeof__(expr)), (__typeof__(expr) []){ expr }
|
||||
|
||||
/// Sends a message to \e target, and returns it's value, or \c nil if not applicable.
|
||||
/// @discussion You may send any message with this method. Primitive return values will be wrapped
|
||||
/// in instances of \c NSNumber and \c NSValue. \c void and bitfield returning methods return \c nil.
|
||||
/// \c SEL return types are converted to strings using \c NSStringFromSelector.
|
||||
/// @return The object returned by this method, or an instance of \c NSValue or \c NSNumber containing
|
||||
/// the primitive return type, or a string for \c SEL return types.
|
||||
- (id)sendMessage:(id)target, ...;
|
||||
/// Used internally by \c sendMessage:target,. Pass \c NULL to the first parameter for void methods.
|
||||
- (void)getReturnValue:(void *)retPtr forMessageSend:(id)target, ...;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface FLEXMethod (Comparison)
|
||||
|
||||
- (NSComparisonResult)compare:(FLEXMethod *)method;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
430
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.m
Normal file
430
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethod.m
Normal file
@@ -0,0 +1,430 @@
|
||||
//
|
||||
// FLEXMethod.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/30/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXMethod.h"
|
||||
#import "FLEXMirror.h"
|
||||
#import "FLEXTypeEncodingParser.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#include <dlfcn.h>
|
||||
|
||||
@implementation FLEXMethod
|
||||
@synthesize imagePath = _imagePath;
|
||||
@dynamic implementation;
|
||||
|
||||
+ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation {
|
||||
[NSException raise:NSInternalInconsistencyException format:@"Class instance should not be created with +buildMethodNamed:withTypes:implementation"]; return nil;
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
[NSException
|
||||
raise:NSInternalInconsistencyException
|
||||
format:@"Class instance should not be created with -init"
|
||||
];
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
+ (instancetype)method:(Method)method {
|
||||
return [[self alloc] initWithMethod:method isInstanceMethod:YES];
|
||||
}
|
||||
|
||||
+ (instancetype)method:(Method)method isInstanceMethod:(BOOL)isInstanceMethod {
|
||||
return [[self alloc] initWithMethod:method isInstanceMethod:isInstanceMethod];
|
||||
}
|
||||
|
||||
+ (instancetype)selector:(SEL)selector class:(Class)cls {
|
||||
BOOL instance = !class_isMetaClass(cls);
|
||||
// class_getInstanceMethod will return an instance method if not given
|
||||
// not given a metaclass, or a class method if given a metaclass, but
|
||||
// this isn't documented so we just want to be safe here.
|
||||
Method m = instance ? class_getInstanceMethod(cls, selector) : class_getClassMethod(cls, selector);
|
||||
if (m == NULL) return nil;
|
||||
|
||||
return [self method:m isInstanceMethod:instance];
|
||||
}
|
||||
|
||||
+ (instancetype)selector:(SEL)selector implementedInClass:(Class)cls {
|
||||
if (![cls superclass]) { return [self selector:selector class:cls]; }
|
||||
|
||||
BOOL unique = [cls methodForSelector:selector] != [[cls superclass] methodForSelector:selector];
|
||||
|
||||
if (unique) {
|
||||
return [self selector:selector class:cls];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)initWithMethod:(Method)method isInstanceMethod:(BOOL)isInstanceMethod {
|
||||
NSParameterAssert(method);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_objc_method = method;
|
||||
_isInstanceMethod = isInstanceMethod;
|
||||
_signatureString = @(method_getTypeEncoding(method) ?: "?@:");
|
||||
|
||||
NSString *cleanSig = nil;
|
||||
if ([FLEXTypeEncodingParser methodTypeEncodingSupported:_signatureString cleaned:&cleanSig]) {
|
||||
_signature = [NSMethodSignature signatureWithObjCTypes:cleanSig.UTF8String];
|
||||
}
|
||||
|
||||
[self examine];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark Other
|
||||
|
||||
- (NSString *)description {
|
||||
if (!_flex_description) {
|
||||
_flex_description = [self prettyName];
|
||||
}
|
||||
|
||||
return _flex_description;
|
||||
}
|
||||
|
||||
- (NSString *)debugNameGivenClassName:(NSString *)name {
|
||||
NSMutableString *string = [NSMutableString stringWithString:_isInstanceMethod ? @"-[" : @"+["];
|
||||
[string appendString:name];
|
||||
[string appendString:@" "];
|
||||
[string appendString:self.selectorString];
|
||||
[string appendString:@"]"];
|
||||
return string;
|
||||
}
|
||||
|
||||
- (NSString *)prettyName {
|
||||
NSString *methodTypeString = self.isInstanceMethod ? @"-" : @"+";
|
||||
NSString *readableReturnType = [FLEXRuntimeUtility readableTypeForEncoding:@(self.signature.methodReturnType ?: "")];
|
||||
|
||||
NSString *prettyName = [NSString stringWithFormat:@"%@ (%@)", methodTypeString, readableReturnType];
|
||||
NSArray *components = [self prettyArgumentComponents];
|
||||
|
||||
if (components.count) {
|
||||
return [prettyName stringByAppendingString:[components componentsJoinedByString:@" "]];
|
||||
} else {
|
||||
return [prettyName stringByAppendingString:self.selectorString];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray *)prettyArgumentComponents {
|
||||
// NSMethodSignature can't handle some type encodings
|
||||
// like ^AI@:ir* which happen to very much exist
|
||||
if (self.signature.numberOfArguments < self.numberOfArguments) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray *components = [NSMutableArray new];
|
||||
|
||||
NSArray *selectorComponents = [self.selectorString componentsSeparatedByString:@":"];
|
||||
NSUInteger numberOfArguments = self.numberOfArguments;
|
||||
|
||||
for (NSUInteger argIndex = 2; argIndex < numberOfArguments; argIndex++) {
|
||||
assert(argIndex < self.signature.numberOfArguments);
|
||||
|
||||
const char *argType = [self.signature getArgumentTypeAtIndex:argIndex] ?: "?";
|
||||
NSString *readableArgType = [FLEXRuntimeUtility readableTypeForEncoding:@(argType)];
|
||||
NSString *prettyComponent = [NSString
|
||||
stringWithFormat:@"%@:(%@) ",
|
||||
selectorComponents[argIndex - 2],
|
||||
readableArgType
|
||||
];
|
||||
|
||||
[components addObject:prettyComponent];
|
||||
}
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
return [NSString stringWithFormat:@"<%@ selector=%@, signature=%@>",
|
||||
NSStringFromClass(self.class), self.selectorString, self.signatureString];
|
||||
}
|
||||
|
||||
- (void)examine {
|
||||
_implementation = method_getImplementation(_objc_method);
|
||||
_selector = method_getName(_objc_method);
|
||||
_numberOfArguments = method_getNumberOfArguments(_objc_method);
|
||||
_name = NSStringFromSelector(_selector);
|
||||
_returnType = (FLEXTypeEncoding *)_signature.methodReturnType ?: "";
|
||||
_returnSize = _signature.methodReturnLength;
|
||||
}
|
||||
|
||||
#pragma mark Public
|
||||
|
||||
- (void)setImplementation:(IMP)implementation {
|
||||
NSParameterAssert(implementation);
|
||||
method_setImplementation(self.objc_method, implementation);
|
||||
[self examine];
|
||||
}
|
||||
|
||||
- (NSString *)typeEncoding {
|
||||
if (!_typeEncoding) {
|
||||
_typeEncoding = [_signatureString
|
||||
stringByReplacingOccurrencesOfString:@"[0-9]"
|
||||
withString:@""
|
||||
options:NSRegularExpressionSearch
|
||||
range:NSMakeRange(0, _signatureString.length)
|
||||
];
|
||||
}
|
||||
|
||||
return _typeEncoding;
|
||||
}
|
||||
|
||||
- (NSString *)imagePath {
|
||||
if (!_imagePath) {
|
||||
Dl_info exeInfo;
|
||||
if (dladdr(_implementation, &exeInfo)) {
|
||||
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : @"";
|
||||
}
|
||||
}
|
||||
|
||||
return _imagePath;
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
- (void)swapImplementations:(FLEXMethod *)method {
|
||||
method_exchangeImplementations(self.objc_method, method.objc_method);
|
||||
[self examine];
|
||||
[method examine];
|
||||
}
|
||||
|
||||
// Some code borrowed from MAObjcRuntime, by Mike Ash.
|
||||
- (id)sendMessage:(id)target, ... {
|
||||
id ret = nil;
|
||||
va_list args;
|
||||
va_start(args, target);
|
||||
|
||||
switch (self.returnType[0]) {
|
||||
case FLEXTypeEncodingUnknown: {
|
||||
[self getReturnValue:NULL forMessageSend:target arguments:args];
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingChar: {
|
||||
char val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingInt: {
|
||||
int val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingShort: {
|
||||
short val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingLong: {
|
||||
long val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingLongLong: {
|
||||
long long val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingUnsignedChar: {
|
||||
unsigned char val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingUnsignedInt: {
|
||||
unsigned int val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingUnsignedShort: {
|
||||
unsigned short val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingUnsignedLong: {
|
||||
unsigned long val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingUnsignedLongLong: {
|
||||
unsigned long long val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingFloat: {
|
||||
float val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingDouble: {
|
||||
double val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingLongDouble: {
|
||||
long double val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = [NSValue value:&val withObjCType:self.returnType];
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingCBool: {
|
||||
bool val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingVoid: {
|
||||
[self getReturnValue:NULL forMessageSend:target arguments:args];
|
||||
return nil;
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingCString: {
|
||||
char *val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = @(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingObjcObject: {
|
||||
id val = nil;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = val;
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingObjcClass: {
|
||||
Class val = Nil;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = val;
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingSelector: {
|
||||
SEL val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = NSStringFromSelector(val);
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingArrayBegin: {
|
||||
void *val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = [NSValue valueWithBytes:val objCType:self.signature.methodReturnType];
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingUnionBegin:
|
||||
case FLEXTypeEncodingStructBegin: {
|
||||
if (self.signature.methodReturnLength) {
|
||||
void * val = malloc(self.signature.methodReturnLength);
|
||||
[self getReturnValue:val forMessageSend:target arguments:args];
|
||||
ret = [NSValue valueWithBytes:val objCType:self.signature.methodReturnType];
|
||||
} else {
|
||||
[self getReturnValue:NULL forMessageSend:target arguments:args];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingBitField: {
|
||||
[self getReturnValue:NULL forMessageSend:target arguments:args];
|
||||
break;
|
||||
}
|
||||
case FLEXTypeEncodingPointer: {
|
||||
void * val = 0;
|
||||
[self getReturnValue:&val forMessageSend:target arguments:args];
|
||||
ret = [NSValue valueWithPointer:val];
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
[NSException raise:NSInvalidArgumentException
|
||||
format:@"Unsupported type encoding: %s", (char *)self.returnType];
|
||||
}
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Code borrowed from MAObjcRuntime, by Mike Ash.
|
||||
- (void)getReturnValue:(void *)retPtr forMessageSend:(id)target, ... {
|
||||
va_list args;
|
||||
va_start(args, target);
|
||||
[self getReturnValue:retPtr forMessageSend:target arguments:args];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// Code borrowed from MAObjcRuntime, by Mike Ash.
|
||||
- (void)getReturnValue:(void *)retPtr forMessageSend:(id)target arguments:(va_list)args {
|
||||
if (!_signature) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_signature];
|
||||
NSUInteger argumentCount = _signature.numberOfArguments;
|
||||
|
||||
invocation.target = target;
|
||||
|
||||
for (NSUInteger i = 2; i < argumentCount; i++) {
|
||||
int cookie = va_arg(args, int);
|
||||
if (cookie != FLEXMagicNumber) {
|
||||
[NSException
|
||||
raise:NSInternalInconsistencyException
|
||||
format:@"%s: incorrect magic cookie %08x; make sure you didn't forget "
|
||||
"any arguments and that all arguments are wrapped in FLEXArg().", __func__, cookie
|
||||
];
|
||||
}
|
||||
const char *typeString = va_arg(args, char *);
|
||||
void *argPointer = va_arg(args, void *);
|
||||
|
||||
NSUInteger inSize, sigSize;
|
||||
NSGetSizeAndAlignment(typeString, &inSize, NULL);
|
||||
NSGetSizeAndAlignment([_signature getArgumentTypeAtIndex:i], &sigSize, NULL);
|
||||
|
||||
if (inSize != sigSize) {
|
||||
[NSException
|
||||
raise:NSInternalInconsistencyException
|
||||
format:@"%s:size mismatch between passed-in argument and "
|
||||
"required argument; in type:%s (%lu) requested:%s (%lu)",
|
||||
__func__, typeString, (long)inSize, [_signature getArgumentTypeAtIndex:i], (long)sigSize
|
||||
];
|
||||
}
|
||||
|
||||
[invocation setArgument:argPointer atIndex:i];
|
||||
}
|
||||
|
||||
// Hack to make NSInvocation invoke the desired implementation
|
||||
IMP imp = [invocation methodForSelector:NSSelectorFromString(@"invokeUsingIMP:")];
|
||||
void (*invokeWithIMP)(id, SEL, IMP) = (void *)imp;
|
||||
invokeWithIMP(invocation, 0, _implementation);
|
||||
|
||||
if (_signature.methodReturnLength && retPtr) {
|
||||
[invocation getReturnValue:retPtr];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation FLEXMethod (Comparison)
|
||||
|
||||
- (NSComparisonResult)compare:(FLEXMethod *)method {
|
||||
return [self.selectorString compare:method.selectorString];
|
||||
}
|
||||
|
||||
@end
|
||||
43
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.h
Normal file
43
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.h
Normal file
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// FLEXMethodBase.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 7/5/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
/// A base class for methods which encompasses those that may not
|
||||
/// have been added to a class yet. Useful on it's own for adding
|
||||
/// methods to a class, or building a new class from the ground up.
|
||||
@interface FLEXMethodBase : NSObject {
|
||||
@protected
|
||||
SEL _selector;
|
||||
NSString *_name;
|
||||
NSString *_typeEncoding;
|
||||
IMP _implementation;
|
||||
|
||||
NSString *_flex_description;
|
||||
}
|
||||
|
||||
/// Constructs and returns an \c FLEXSimpleMethod instance with the given name, type encoding, and implementation.
|
||||
+ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation;
|
||||
|
||||
/// The selector of the method.
|
||||
@property (nonatomic, readonly) SEL selector;
|
||||
/// The selector string of the method.
|
||||
@property (nonatomic, readonly) NSString *selectorString;
|
||||
/// Same as selectorString.
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
/// The type encoding of the method.
|
||||
@property (nonatomic, readonly) NSString *typeEncoding;
|
||||
/// The implementation of the method.
|
||||
@property (nonatomic, readonly) IMP implementation;
|
||||
|
||||
/// For internal use
|
||||
@property (nonatomic) id tag;
|
||||
|
||||
@end
|
||||
49
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.m
Normal file
49
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMethodBase.m
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// FLEXMethodBase.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 7/5/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXMethodBase.h"
|
||||
|
||||
|
||||
@implementation FLEXMethodBase
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
+ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation {
|
||||
return [[self alloc] initWithSelector:sel_registerName(name.UTF8String) types:typeEncoding imp:implementation];
|
||||
}
|
||||
|
||||
- (id)initWithSelector:(SEL)selector types:(NSString *)types imp:(IMP)imp {
|
||||
NSParameterAssert(selector); NSParameterAssert(types); NSParameterAssert(imp);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_selector = selector;
|
||||
_typeEncoding = types;
|
||||
_implementation = imp;
|
||||
_name = NSStringFromSelector(self.selector);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)selectorString {
|
||||
return _name;
|
||||
}
|
||||
|
||||
#pragma mark Overrides
|
||||
|
||||
- (NSString *)description {
|
||||
if (!_flex_description) {
|
||||
_flex_description = [NSString stringWithFormat:@"%@ '%@'", _name, _typeEncoding];
|
||||
}
|
||||
|
||||
return _flex_description;
|
||||
}
|
||||
|
||||
@end
|
||||
97
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.h
Normal file
97
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.h
Normal file
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// FLEXMirror.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/29/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <objc/runtime.h>
|
||||
@class FLEXMethod, FLEXProperty, FLEXIvar, FLEXProtocol;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark FLEXMirror Protocol
|
||||
NS_SWIFT_NAME(FLEXMirrorProtocol)
|
||||
@protocol FLEXMirror <NSObject>
|
||||
|
||||
/// Swift initializer
|
||||
/// @throws If a metaclass object is passed in.
|
||||
- (instancetype)initWithSubject:(id)objectOrClass NS_SWIFT_NAME(init(reflecting:));
|
||||
|
||||
/// The underlying object or \c Class used to create this \c FLEXMirror.
|
||||
@property (nonatomic, readonly) id value;
|
||||
/// Whether \c value was a class or a class instance.
|
||||
@property (nonatomic, readonly) BOOL isClass;
|
||||
/// The name of the \c Class of the \c value property.
|
||||
@property (nonatomic, readonly) NSString *className;
|
||||
|
||||
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
|
||||
@property (nonatomic, readonly) NSArray<FLEXProperty *> *classProperties;
|
||||
@property (nonatomic, readonly) NSArray<FLEXIvar *> *ivars;
|
||||
@property (nonatomic, readonly) NSArray<FLEXMethod *> *methods;
|
||||
@property (nonatomic, readonly) NSArray<FLEXMethod *> *classMethods;
|
||||
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
|
||||
|
||||
/// Super mirrors are initialized with the class that corresponds to the value passed in.
|
||||
/// If you passed in an instance of a class, it's superclass is used to create this mirror.
|
||||
/// If you passed in a class, then that class's superclass is used.
|
||||
///
|
||||
/// @note This property should be computed, not cached.
|
||||
@property (nonatomic, readonly, nullable) id<FLEXMirror> superMirror NS_SWIFT_NAME(superMirror);
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark FLEXMirror Class
|
||||
@interface FLEXMirror : NSObject <FLEXMirror>
|
||||
|
||||
/// Reflects an instance of an object or \c Class.
|
||||
/// @discussion \c FLEXMirror will immediately gather all useful information. Consider using the
|
||||
/// \c NSObject categories provided if your code will only use a few pieces of information,
|
||||
/// or if your code needs to run faster.
|
||||
///
|
||||
/// Regardless of whether you reflect an instance or a class object, \c methods and \c properties
|
||||
/// will be populated with instance methods and properties, and \c classMethods and \c classProperties
|
||||
/// will be populated with class methods and properties.
|
||||
///
|
||||
/// @param objectOrClass An instance of an objct or a \c Class object.
|
||||
/// @throws If a metaclass object is passed in.
|
||||
/// @return An instance of \c FLEXMirror.
|
||||
+ (instancetype)reflect:(id)objectOrClass;
|
||||
|
||||
@property (nonatomic, readonly) id value;
|
||||
@property (nonatomic, readonly) BOOL isClass;
|
||||
@property (nonatomic, readonly) NSString *className;
|
||||
|
||||
@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
|
||||
@property (nonatomic, readonly) NSArray<FLEXProperty *> *classProperties;
|
||||
@property (nonatomic, readonly) NSArray<FLEXIvar *> *ivars;
|
||||
@property (nonatomic, readonly) NSArray<FLEXMethod *> *methods;
|
||||
@property (nonatomic, readonly) NSArray<FLEXMethod *> *classMethods;
|
||||
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
|
||||
|
||||
@property (nonatomic, readonly, nullable) FLEXMirror *superMirror NS_SWIFT_NAME(superMirror);
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface FLEXMirror (ExtendedMirror)
|
||||
|
||||
/// @return The instance method with the given name, or \c nil if one does not exist.
|
||||
- (nullable FLEXMethod *)methodNamed:(nullable NSString *)name;
|
||||
/// @return The class method with the given name, or \c nil if one does not exist.
|
||||
- (nullable FLEXMethod *)classMethodNamed:(nullable NSString *)name;
|
||||
/// @return The instance property with the given name, or \c nil if one does not exist.
|
||||
- (nullable FLEXProperty *)propertyNamed:(nullable NSString *)name;
|
||||
/// @return The class property with the given name, or \c nil if one does not exist.
|
||||
- (nullable FLEXProperty *)classPropertyNamed:(nullable NSString *)name;
|
||||
/// @return The instance variable with the given name, or \c nil if one does not exist.
|
||||
- (nullable FLEXIvar *)ivarNamed:(nullable NSString *)name;
|
||||
/// @return The protocol with the given name, or \c nil if one does not exist.
|
||||
- (nullable FLEXProtocol *)protocolNamed:(nullable NSString *)name;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
145
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.m
Normal file
145
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXMirror.m
Normal file
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// FLEXMirror.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/29/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXMirror.h"
|
||||
#import "FLEXProperty.h"
|
||||
#import "FLEXMethod.h"
|
||||
#import "FLEXIvar.h"
|
||||
#import "FLEXProtocol.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
|
||||
#pragma mark FLEXMirror
|
||||
|
||||
@implementation FLEXMirror
|
||||
|
||||
- (id)init {
|
||||
[NSException
|
||||
raise:NSInternalInconsistencyException
|
||||
format:@"Class instance should not be created with -init"
|
||||
];
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Initialization
|
||||
+ (instancetype)reflect:(id)objectOrClass {
|
||||
return [[self alloc] initWithSubject:objectOrClass];
|
||||
}
|
||||
|
||||
- (id)initWithSubject:(id)objectOrClass {
|
||||
NSParameterAssert(objectOrClass);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_value = objectOrClass;
|
||||
[self examine];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<%@ %@=%@>",
|
||||
NSStringFromClass(self.class),
|
||||
self.isClass ? @"metaclass" : @"class",
|
||||
self.className
|
||||
];
|
||||
}
|
||||
|
||||
- (void)examine {
|
||||
BOOL isClass = object_isClass(self.value);
|
||||
Class cls = isClass ? self.value : object_getClass(self.value);
|
||||
Class meta = object_getClass(cls);
|
||||
_className = NSStringFromClass(cls);
|
||||
_isClass = isClass;
|
||||
|
||||
unsigned int pcount, cpcount, mcount, cmcount, ivcount, pccount;
|
||||
Ivar *objcIvars = class_copyIvarList(cls, &ivcount);
|
||||
Method *objcMethods = class_copyMethodList(cls, &mcount);
|
||||
Method *objcClsMethods = class_copyMethodList(meta, &cmcount);
|
||||
objc_property_t *objcProperties = class_copyPropertyList(cls, &pcount);
|
||||
objc_property_t *objcClsProperties = class_copyPropertyList(meta, &cpcount);
|
||||
Protocol *__unsafe_unretained *protos = class_copyProtocolList(cls, &pccount);
|
||||
|
||||
_ivars = [NSArray flex_forEachUpTo:ivcount map:^id(NSUInteger i) {
|
||||
return [FLEXIvar ivar:objcIvars[i]];
|
||||
}];
|
||||
|
||||
_methods = [NSArray flex_forEachUpTo:mcount map:^id(NSUInteger i) {
|
||||
return [FLEXMethod method:objcMethods[i] isInstanceMethod:YES];
|
||||
}];
|
||||
_classMethods = [NSArray flex_forEachUpTo:cmcount map:^id(NSUInteger i) {
|
||||
return [FLEXMethod method:objcClsMethods[i] isInstanceMethod:NO];
|
||||
}];
|
||||
|
||||
_properties = [NSArray flex_forEachUpTo:pcount map:^id(NSUInteger i) {
|
||||
return [FLEXProperty property:objcProperties[i] onClass:cls];
|
||||
}];
|
||||
_classProperties = [NSArray flex_forEachUpTo:cpcount map:^id(NSUInteger i) {
|
||||
return [FLEXProperty property:objcClsProperties[i] onClass:meta];
|
||||
}];
|
||||
|
||||
_protocols = [NSArray flex_forEachUpTo:pccount map:^id(NSUInteger i) {
|
||||
return [FLEXProtocol protocol:protos[i]];
|
||||
}];
|
||||
|
||||
// Cleanup
|
||||
free(objcClsProperties);
|
||||
free(objcProperties);
|
||||
free(objcClsMethods);
|
||||
free(objcMethods);
|
||||
free(objcIvars);
|
||||
free(protos);
|
||||
protos = NULL;
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
- (FLEXMirror *)superMirror {
|
||||
Class cls = _isClass ? _value : object_getClass(_value);
|
||||
return [FLEXMirror reflect:class_getSuperclass(cls)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark ExtendedMirror
|
||||
|
||||
@implementation FLEXMirror (ExtendedMirror)
|
||||
|
||||
- (id)filter:(NSArray *)array forName:(NSString *)name {
|
||||
NSPredicate *filter = [NSPredicate predicateWithFormat:@"%K = %@", @"name", name];
|
||||
return [array filteredArrayUsingPredicate:filter].firstObject;
|
||||
}
|
||||
|
||||
- (FLEXMethod *)methodNamed:(NSString *)name {
|
||||
return [self filter:self.methods forName:name];
|
||||
}
|
||||
|
||||
- (FLEXMethod *)classMethodNamed:(NSString *)name {
|
||||
return [self filter:self.classMethods forName:name];
|
||||
}
|
||||
|
||||
- (FLEXProperty *)propertyNamed:(NSString *)name {
|
||||
return [self filter:self.properties forName:name];
|
||||
}
|
||||
|
||||
- (FLEXProperty *)classPropertyNamed:(NSString *)name {
|
||||
return [self filter:self.classProperties forName:name];
|
||||
}
|
||||
|
||||
- (FLEXIvar *)ivarNamed:(NSString *)name {
|
||||
return [self filter:self.ivars forName:name];
|
||||
}
|
||||
|
||||
- (FLEXProtocol *)protocolNamed:(NSString *)name {
|
||||
return [self filter:self.protocols forName:name];
|
||||
}
|
||||
|
||||
@end
|
||||
138
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.h
Normal file
138
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.h
Normal file
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// FLEXProperty.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/30/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXRuntimeConstants.h"
|
||||
@class FLEXPropertyAttributes, FLEXMethodBase;
|
||||
|
||||
|
||||
#pragma mark FLEXProperty
|
||||
@interface FLEXProperty : NSObject
|
||||
|
||||
/// You may use this initializer instead of \c property:onClass: if you don't need
|
||||
/// to know anything about the uniqueness of this property or where it comes from.
|
||||
+ (instancetype)property:(objc_property_t)property;
|
||||
/// This initializer can be used to access additional information
|
||||
/// in an efficient manner. That information being whether this property
|
||||
/// is certainly not unique and the name of the binary image which declares it.
|
||||
/// @param cls the class, or metaclass if this is a class property.
|
||||
+ (instancetype)property:(objc_property_t)property onClass:(Class)cls;
|
||||
/// @param cls the class, or metaclass if this is a class property
|
||||
+ (instancetype)named:(NSString *)name onClass:(Class)cls;
|
||||
/// Constructs a new property with the given name and attributes.
|
||||
+ (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes;
|
||||
|
||||
/// \c 0 if the instance was created via \c +propertyWithName:attributes,
|
||||
/// otherwise this is the first property in \c objc_properties
|
||||
@property (nonatomic, readonly) objc_property_t objc_property;
|
||||
@property (nonatomic, readonly) objc_property_t *objc_properties;
|
||||
@property (nonatomic, readonly) NSInteger objc_propertyCount;
|
||||
@property (nonatomic, readonly) BOOL isClassProperty;
|
||||
|
||||
/// The name of the property.
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
/// The type of the property. Get the full type from the attributes.
|
||||
@property (nonatomic, readonly) FLEXTypeEncoding type;
|
||||
/// The property's attributes.
|
||||
@property (nonatomic ) FLEXPropertyAttributes *attributes;
|
||||
/// The (likely) setter, regardless of whether the property is readonly.
|
||||
/// For example, this might be the custom setter.
|
||||
@property (nonatomic, readonly) SEL likelySetter;
|
||||
@property (nonatomic, readonly) NSString *likelySetterString;
|
||||
/// Not valid unless initialized with the owning class.
|
||||
@property (nonatomic, readonly) BOOL likelySetterExists;
|
||||
/// The (likely) getter. For example, this might be the custom getter.
|
||||
@property (nonatomic, readonly) SEL likelyGetter;
|
||||
@property (nonatomic, readonly) NSString *likelyGetterString;
|
||||
/// Not valid unless initialized with the owning class.
|
||||
@property (nonatomic, readonly) BOOL likelyGetterExists;
|
||||
/// Always \c nil for class properties.
|
||||
@property (nonatomic, readonly) NSString *likelyIvarName;
|
||||
/// Not valid unless initialized with the owning class.
|
||||
@property (nonatomic, readonly) BOOL likelyIvarExists;
|
||||
|
||||
/// Whether there are certainly multiple definitions of this property,
|
||||
/// such as in categories in other binary images or something.
|
||||
/// @return Whether \c objc_property matches the return value of \c class_getProperty,
|
||||
/// or \c NO if this property was not created with \c property:onClass
|
||||
@property (nonatomic, readonly) BOOL multiple;
|
||||
/// @return The bundle of the image that contains this property definition,
|
||||
/// or \c nil if this property was not created with \c property:onClass or
|
||||
/// if this property was probably defined at runtime.
|
||||
@property (nonatomic, readonly) NSString *imageName;
|
||||
/// The full path of the image that contains this property definition,
|
||||
/// or \c nil if this property was not created with \c property:onClass or
|
||||
/// if this property was probably defined at runtime.
|
||||
@property (nonatomic, readonly) NSString *imagePath;
|
||||
|
||||
/// For internal use
|
||||
@property (nonatomic) id tag;
|
||||
|
||||
/// @return The value of this property on \c target as given by \c -valueForKey:
|
||||
/// A source-like description of the property, with all of its attributes.
|
||||
@property (nonatomic, readonly) NSString *fullDescription;
|
||||
|
||||
/// If this is a class property, you must class the class object.
|
||||
- (id)getValue:(id)target;
|
||||
/// Calls into -getValue: and passes that value into
|
||||
/// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
|
||||
/// and returns the result.
|
||||
///
|
||||
/// If this is a class property, you must class the class object.
|
||||
- (id)getPotentiallyUnboxedValue:(id)target;
|
||||
|
||||
/// Safe to use regardless of how the \c FLEXProperty instance was initialized.
|
||||
///
|
||||
/// This uses \c self.objc_property if it exists, otherwise it uses \c self.attributes
|
||||
- (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount;
|
||||
|
||||
/// Replace the attributes of the current property in the given class,
|
||||
/// using the attributes in \c self.attributes
|
||||
///
|
||||
/// What happens when the property does not exist is undocumented.
|
||||
- (void)replacePropertyOnClass:(Class)cls;
|
||||
|
||||
#pragma mark Convenience getters and setters
|
||||
/// @return A getter for the property with the given implementation.
|
||||
/// @discussion Consider using the \c FLEXPropertyGetter macros.
|
||||
- (FLEXMethodBase *)getterWithImplementation:(IMP)implementation;
|
||||
/// @return A setter for the property with the given implementation.
|
||||
/// @discussion Consider using the \c FLEXPropertySetter macros.
|
||||
- (FLEXMethodBase *)setterWithImplementation:(IMP)implementation;
|
||||
|
||||
#pragma mark FLEXMethod property getter / setter macros
|
||||
// Easier than using the above methods yourself in most cases
|
||||
|
||||
/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and
|
||||
/// uses the \c FLEXProperty's \c attribute's \c backingIvarName to get the Ivar.
|
||||
#define FLEXPropertyGetter(FLEXProperty, type) [FLEXProperty \
|
||||
getterWithImplementation:imp_implementationWithBlock(^(id self) { \
|
||||
return *(type *)[self getIvarAddressByName:FLEXProperty.attributes.backingIvar]; \
|
||||
}) \
|
||||
];
|
||||
/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and
|
||||
/// uses the \c FLEXProperty's \c attribute's \c backingIvarName to set the Ivar.
|
||||
#define FLEXPropertySetter(FLEXProperty, type) [FLEXProperty \
|
||||
setterWithImplementation:imp_implementationWithBlock(^(id self, type value) { \
|
||||
[self setIvarByName:FLEXProperty.attributes.backingIvar value:&value size:sizeof(type)]; \
|
||||
}) \
|
||||
];
|
||||
/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and an Ivar name string to get the Ivar.
|
||||
#define FLEXPropertyGetterWithIvar(FLEXProperty, ivarName, type) [FLEXProperty \
|
||||
getterWithImplementation:imp_implementationWithBlock(^(id self) { \
|
||||
return *(type *)[self getIvarAddressByName:ivarName]; \
|
||||
}) \
|
||||
];
|
||||
/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and an Ivar name string to set the Ivar.
|
||||
#define FLEXPropertySetterWithIvar(FLEXProperty, ivarName, type) [FLEXProperty \
|
||||
setterWithImplementation:imp_implementationWithBlock(^(id self, type value) { \
|
||||
[self setIvarByName:ivarName value:&value size:sizeof(type)]; \
|
||||
}) \
|
||||
];
|
||||
|
||||
@end
|
||||
295
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.m
Normal file
295
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProperty.m
Normal file
@@ -0,0 +1,295 @@
|
||||
//
|
||||
// FLEXProperty.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/30/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXProperty.h"
|
||||
#import "FLEXPropertyAttributes.h"
|
||||
#import "FLEXMethodBase.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#include <dlfcn.h>
|
||||
|
||||
|
||||
@interface FLEXProperty () {
|
||||
NSString *_flex_description;
|
||||
}
|
||||
@property (nonatomic ) BOOL uniqueCheckFlag;
|
||||
@property (nonatomic, readonly) Class cls;
|
||||
@end
|
||||
|
||||
@implementation FLEXProperty
|
||||
@synthesize multiple = _multiple;
|
||||
@synthesize imageName = _imageName;
|
||||
@synthesize imagePath = _imagePath;
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
- (id)init {
|
||||
[NSException
|
||||
raise:NSInternalInconsistencyException
|
||||
format:@"Class instance should not be created with -init"
|
||||
];
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (instancetype)property:(objc_property_t)property {
|
||||
return [[self alloc] initWithProperty:property onClass:nil];
|
||||
}
|
||||
|
||||
+ (instancetype)property:(objc_property_t)property onClass:(Class)cls {
|
||||
return [[self alloc] initWithProperty:property onClass:cls];
|
||||
}
|
||||
|
||||
+ (instancetype)named:(NSString *)name onClass:(Class)cls {
|
||||
objc_property_t _Nullable property = class_getProperty(cls, name.UTF8String);
|
||||
NSAssert(property, @"Cannot find property with name %@ on class %@", name, cls);
|
||||
return [self property:property onClass:cls];
|
||||
}
|
||||
|
||||
+ (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
|
||||
return [[self alloc] initWithName:name attributes:attributes];
|
||||
}
|
||||
|
||||
- (id)initWithProperty:(objc_property_t)property onClass:(Class)cls {
|
||||
NSParameterAssert(property);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_objc_property = property;
|
||||
_attributes = [FLEXPropertyAttributes attributesForProperty:property];
|
||||
_name = @(property_getName(property) ?: "(nil)");
|
||||
_cls = cls;
|
||||
|
||||
if (!_attributes) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property attributes"];
|
||||
if (!_name) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property name"];
|
||||
|
||||
[self examine];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
|
||||
NSParameterAssert(name); NSParameterAssert(attributes);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_attributes = attributes;
|
||||
_name = name;
|
||||
|
||||
[self examine];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
- (void)examine {
|
||||
if (self.attributes.typeEncoding.length) {
|
||||
_type = (FLEXTypeEncoding)[self.attributes.typeEncoding characterAtIndex:0];
|
||||
}
|
||||
|
||||
// Return the given selector if the class responds to it
|
||||
Class cls = _cls;
|
||||
SEL (^selectorIfValid)(SEL) = ^SEL(SEL sel) {
|
||||
if (!sel || !cls) return nil;
|
||||
return [cls instancesRespondToSelector:sel] ? sel : nil;
|
||||
};
|
||||
|
||||
SEL customGetter = self.attributes.customGetter;
|
||||
SEL customSetter = self.attributes.customSetter;
|
||||
SEL defaultGetter = NSSelectorFromString(self.name);
|
||||
SEL defaultSetter = NSSelectorFromString([NSString
|
||||
stringWithFormat:@"set%c%@:",
|
||||
(char)toupper([self.name characterAtIndex:0]),
|
||||
[self.name substringFromIndex:1]
|
||||
]);
|
||||
|
||||
// Check if the likely getters/setters exist
|
||||
SEL validGetter = selectorIfValid(customGetter) ?: selectorIfValid(defaultGetter);
|
||||
SEL validSetter = selectorIfValid(customSetter) ?: selectorIfValid(defaultSetter);
|
||||
_likelyGetterExists = validGetter != nil;
|
||||
_likelySetterExists = validSetter != nil;
|
||||
|
||||
// Assign likely getters and setters to the valid one,
|
||||
// or the default, regardless of whether the default exists
|
||||
_likelyGetter = validGetter ?: defaultGetter;
|
||||
_likelySetter = validSetter ?: defaultSetter;
|
||||
_likelyGetterString = NSStringFromSelector(_likelyGetter);
|
||||
_likelySetterString = NSStringFromSelector(_likelySetter);
|
||||
|
||||
_isClassProperty = _cls ? class_isMetaClass(_cls) : NO;
|
||||
|
||||
_likelyIvarName = _isClassProperty ? nil : (
|
||||
self.attributes.backingIvar ?: [@"_" stringByAppendingString:_name]
|
||||
);
|
||||
}
|
||||
|
||||
#pragma mark Overrides
|
||||
|
||||
- (NSString *)description {
|
||||
if (!_flex_description) {
|
||||
NSString *readableType = [FLEXRuntimeUtility readableTypeForEncoding:self.attributes.typeEncoding];
|
||||
_flex_description = [FLEXRuntimeUtility appendName:self.name toType:readableType];
|
||||
}
|
||||
|
||||
return _flex_description;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
return [NSString stringWithFormat:@"<%@ name=%@, property=%p, attributes:\n\t%@\n>",
|
||||
NSStringFromClass(self.class), self.name, self.objc_property, self.attributes];
|
||||
}
|
||||
|
||||
#pragma mark Public
|
||||
|
||||
- (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount {
|
||||
if (self.objc_property) {
|
||||
return property_copyAttributeList(self.objc_property, attributesCount);
|
||||
} else {
|
||||
return [self.attributes copyAttributesList:attributesCount];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)replacePropertyOnClass:(Class)cls {
|
||||
class_replaceProperty(cls, self.name.UTF8String, self.attributes.list, (unsigned int)self.attributes.count);
|
||||
}
|
||||
|
||||
- (void)computeSymbolInfo:(BOOL)forceBundle {
|
||||
Dl_info exeInfo;
|
||||
if (dladdr(_objc_property, &exeInfo)) {
|
||||
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
|
||||
}
|
||||
|
||||
if ((!_multiple || !_uniqueCheckFlag) && _cls) {
|
||||
_multiple = _objc_property != class_getProperty(_cls, self.name.UTF8String);
|
||||
|
||||
if (_multiple || forceBundle) {
|
||||
NSString *path = _imagePath.stringByDeletingLastPathComponent;
|
||||
_imageName = [NSBundle bundleWithPath:path].executablePath.lastPathComponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)multiple {
|
||||
[self computeSymbolInfo:NO];
|
||||
return _multiple;
|
||||
}
|
||||
|
||||
- (NSString *)imagePath {
|
||||
[self computeSymbolInfo:YES];
|
||||
return _imagePath;
|
||||
}
|
||||
|
||||
- (NSString *)imageName {
|
||||
[self computeSymbolInfo:YES];
|
||||
return _imageName;
|
||||
}
|
||||
|
||||
- (BOOL)likelyIvarExists {
|
||||
if (_likelyIvarName && _cls) {
|
||||
return class_getInstanceVariable(_cls, _likelyIvarName.UTF8String) != nil;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)fullDescription {
|
||||
NSMutableArray<NSString *> *attributesStrings = [NSMutableArray new];
|
||||
FLEXPropertyAttributes *attributes = self.attributes;
|
||||
|
||||
// Atomicity
|
||||
if (attributes.isNonatomic) {
|
||||
[attributesStrings addObject:@"nonatomic"];
|
||||
} else {
|
||||
[attributesStrings addObject:@"atomic"];
|
||||
}
|
||||
|
||||
// Storage
|
||||
if (attributes.isRetained) {
|
||||
[attributesStrings addObject:@"strong"];
|
||||
} else if (attributes.isCopy) {
|
||||
[attributesStrings addObject:@"copy"];
|
||||
} else if (attributes.isWeak) {
|
||||
[attributesStrings addObject:@"weak"];
|
||||
} else {
|
||||
[attributesStrings addObject:@"assign"];
|
||||
}
|
||||
|
||||
// Mutability
|
||||
if (attributes.isReadOnly) {
|
||||
[attributesStrings addObject:@"readonly"];
|
||||
} else {
|
||||
[attributesStrings addObject:@"readwrite"];
|
||||
}
|
||||
|
||||
// Class or not
|
||||
if (self.isClassProperty) {
|
||||
[attributesStrings addObject:@"class"];
|
||||
}
|
||||
|
||||
// Custom getter/setter
|
||||
SEL customGetter = attributes.customGetter;
|
||||
SEL customSetter = attributes.customSetter;
|
||||
if (customGetter) {
|
||||
[attributesStrings addObject:[NSString stringWithFormat:@"getter=%s", sel_getName(customGetter)]];
|
||||
}
|
||||
if (customSetter) {
|
||||
[attributesStrings addObject:[NSString stringWithFormat:@"setter=%s", sel_getName(customSetter)]];
|
||||
}
|
||||
|
||||
NSString *attributesString = [attributesStrings componentsJoinedByString:@", "];
|
||||
return [NSString stringWithFormat:@"@property (%@) %@", attributesString, self.description];
|
||||
}
|
||||
|
||||
- (id)getValue:(id)target {
|
||||
if (!target) return nil;
|
||||
|
||||
// We don't care about checking dynamically whether the getter
|
||||
// _now_ exists on this object. If the getter doesn't exist
|
||||
// when this property is initialized, it will never call it.
|
||||
// Just re-create the property object if you need to call it.
|
||||
if (self.likelyGetterExists) {
|
||||
BOOL objectIsClass = object_isClass(target);
|
||||
BOOL instanceAndInstanceProperty = !objectIsClass && !self.isClassProperty;
|
||||
BOOL classAndClassProperty = objectIsClass && self.isClassProperty;
|
||||
|
||||
if (instanceAndInstanceProperty || classAndClassProperty) {
|
||||
return [FLEXRuntimeUtility performSelector:self.likelyGetter onObject:target];
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)getPotentiallyUnboxedValue:(id)target {
|
||||
if (!target) return nil;
|
||||
|
||||
return [FLEXRuntimeUtility
|
||||
potentiallyUnwrapBoxedPointer:[self getValue:target]
|
||||
type:self.attributes.typeEncoding.UTF8String
|
||||
];
|
||||
}
|
||||
|
||||
#pragma mark Suggested getters and setters
|
||||
|
||||
- (FLEXMethodBase *)getterWithImplementation:(IMP)implementation {
|
||||
NSString *types = [NSString stringWithFormat:@"%@%s%s", self.attributes.typeEncoding, @encode(id), @encode(SEL)];
|
||||
NSString *name = [NSString stringWithFormat:@"%@", self.name];
|
||||
FLEXMethodBase *getter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation];
|
||||
return getter;
|
||||
}
|
||||
|
||||
- (FLEXMethodBase *)setterWithImplementation:(IMP)implementation {
|
||||
NSString *types = [NSString stringWithFormat:@"%s%s%s%@", @encode(void), @encode(id), @encode(SEL), self.attributes.typeEncoding];
|
||||
NSString *name = [NSString stringWithFormat:@"set%@:", self.name.capitalizedString];
|
||||
FLEXMethodBase *setter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation];
|
||||
return setter;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// FLEXPropertyAttributes.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 7/5/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark FLEXPropertyAttributes
|
||||
|
||||
/// See \e FLEXRuntimeUtilitiy.h for valid string tokens.
|
||||
/// See this link on how to construct a proper attributes string:
|
||||
/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
|
||||
@interface FLEXPropertyAttributes : NSObject <NSCopying, NSMutableCopying> {
|
||||
// These are necessary for the mutable subclass to function
|
||||
@protected
|
||||
NSUInteger _count;
|
||||
NSString *_string, *_backingIvar, *_typeEncoding, *_oldTypeEncoding, *_fullDeclaration;
|
||||
NSDictionary *_dictionary;
|
||||
objc_property_attribute_t *_list;
|
||||
SEL _customGetter, _customSetter;
|
||||
BOOL _isReadOnly, _isCopy, _isRetained, _isNonatomic, _isDynamic, _isWeak, _isGarbageCollectable;
|
||||
}
|
||||
|
||||
+ (instancetype)attributesForProperty:(objc_property_t)property;
|
||||
/// @warning Raises an exception if \e attributes is invalid, \c nil, or contains unsupported keys.
|
||||
+ (instancetype)attributesFromDictionary:(NSDictionary *)attributes;
|
||||
|
||||
/// Copies the attributes list to a buffer you must \c free() yourself.
|
||||
/// Use \c list instead if you do not need more control over the lifetime of the list.
|
||||
/// @param attributesCountOut the number of attributes is returned in this parameter.
|
||||
- (objc_property_attribute_t *)copyAttributesList:(nullable unsigned int *)attributesCountOut;
|
||||
|
||||
/// The number of property attributes.
|
||||
@property (nonatomic, readonly) NSUInteger count;
|
||||
/// For use with \c class_replaceProperty and the like.
|
||||
@property (nonatomic, readonly) objc_property_attribute_t *list;
|
||||
/// The string value of the property attributes.
|
||||
@property (nonatomic, readonly) NSString *string;
|
||||
/// A human-readable version of the property attributes.
|
||||
@property (nonatomic, readonly) NSString *fullDeclaration;
|
||||
/// A dictionary of the property attributes.
|
||||
/// Values are either a string or \c YES. Boolean attributes
|
||||
/// which are false will not be present in the dictionary.
|
||||
@property (nonatomic, readonly) NSDictionary *dictionary;
|
||||
|
||||
/// The name of the instance variable backing the property.
|
||||
@property (nonatomic, readonly, nullable) NSString *backingIvar;
|
||||
/// The type encoding of the property.
|
||||
@property (nonatomic, readonly, nullable) NSString *typeEncoding;
|
||||
/// The \e old type encoding of the property.
|
||||
@property (nonatomic, readonly, nullable) NSString *oldTypeEncoding;
|
||||
/// The property's custom getter, if any.
|
||||
@property (nonatomic, readonly, nullable) SEL customGetter;
|
||||
/// The property's custom setter, if any.
|
||||
@property (nonatomic, readonly, nullable) SEL customSetter;
|
||||
/// The property's custom getter as a string, if any.
|
||||
@property (nonatomic, readonly, nullable) NSString *customGetterString;
|
||||
/// The property's custom setter as a string, if any.
|
||||
@property (nonatomic, readonly, nullable) NSString *customSetterString;
|
||||
|
||||
@property (nonatomic, readonly) BOOL isReadOnly;
|
||||
@property (nonatomic, readonly) BOOL isCopy;
|
||||
@property (nonatomic, readonly) BOOL isRetained;
|
||||
@property (nonatomic, readonly) BOOL isNonatomic;
|
||||
@property (nonatomic, readonly) BOOL isDynamic;
|
||||
@property (nonatomic, readonly) BOOL isWeak;
|
||||
@property (nonatomic, readonly) BOOL isGarbageCollectable;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark FLEXPropertyAttributes
|
||||
@interface FLEXMutablePropertyAttributes : FLEXPropertyAttributes
|
||||
|
||||
/// Creates and returns an empty property attributes object.
|
||||
+ (instancetype)attributes;
|
||||
|
||||
/// The name of the instance variable backing the property.
|
||||
@property (nonatomic, nullable) NSString *backingIvar;
|
||||
/// The type encoding of the property.
|
||||
@property (nonatomic, nullable) NSString *typeEncoding;
|
||||
/// The \e old type encoding of the property.
|
||||
@property (nonatomic, nullable) NSString *oldTypeEncoding;
|
||||
/// The property's custom getter, if any.
|
||||
@property (nonatomic, nullable) SEL customGetter;
|
||||
/// The property's custom setter, if any.
|
||||
@property (nonatomic, nullable) SEL customSetter;
|
||||
|
||||
@property (nonatomic) BOOL isReadOnly;
|
||||
@property (nonatomic) BOOL isCopy;
|
||||
@property (nonatomic) BOOL isRetained;
|
||||
@property (nonatomic) BOOL isNonatomic;
|
||||
@property (nonatomic) BOOL isDynamic;
|
||||
@property (nonatomic) BOOL isWeak;
|
||||
@property (nonatomic) BOOL isGarbageCollectable;
|
||||
|
||||
/// A more convenient method of setting the \c typeEncoding property.
|
||||
/// @discussion This will not work for complex types like structs and primitive pointers.
|
||||
- (void)setTypeEncodingChar:(char)type;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,376 @@
|
||||
//
|
||||
// FLEXPropertyAttributes.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 7/5/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXPropertyAttributes.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "NSString+ObjcRuntime.h"
|
||||
#import "NSDictionary+ObjcRuntime.h"
|
||||
|
||||
|
||||
#pragma mark FLEXPropertyAttributes
|
||||
|
||||
@interface FLEXPropertyAttributes ()
|
||||
|
||||
@property (nonatomic) NSString *backingIvar;
|
||||
@property (nonatomic) NSString *typeEncoding;
|
||||
@property (nonatomic) NSString *oldTypeEncoding;
|
||||
@property (nonatomic) SEL customGetter;
|
||||
@property (nonatomic) SEL customSetter;
|
||||
@property (nonatomic) BOOL isReadOnly;
|
||||
@property (nonatomic) BOOL isCopy;
|
||||
@property (nonatomic) BOOL isRetained;
|
||||
@property (nonatomic) BOOL isNonatomic;
|
||||
@property (nonatomic) BOOL isDynamic;
|
||||
@property (nonatomic) BOOL isWeak;
|
||||
@property (nonatomic) BOOL isGarbageCollectable;
|
||||
|
||||
- (NSString *)buildFullDeclaration;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXPropertyAttributes
|
||||
@synthesize list = _list;
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
+ (instancetype)attributesForProperty:(objc_property_t)property {
|
||||
return [self attributesFromDictionary:[NSDictionary attributesDictionaryForProperty:property]];
|
||||
}
|
||||
|
||||
+ (instancetype)attributesFromDictionary:(NSDictionary *)attributes {
|
||||
return [[self alloc] initWithAttributesDictionary:attributes];
|
||||
}
|
||||
|
||||
- (id)initWithAttributesDictionary:(NSDictionary *)attributes {
|
||||
NSParameterAssert(attributes);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_dictionary = attributes;
|
||||
_string = attributes.propertyAttributesString;
|
||||
_count = attributes.count;
|
||||
_typeEncoding = attributes[kFLEXPropertyAttributeKeyTypeEncoding];
|
||||
_backingIvar = attributes[kFLEXPropertyAttributeKeyBackingIvarName];
|
||||
_oldTypeEncoding = attributes[kFLEXPropertyAttributeKeyOldStyleTypeEncoding];
|
||||
_customGetterString = attributes[kFLEXPropertyAttributeKeyCustomGetter];
|
||||
_customSetterString = attributes[kFLEXPropertyAttributeKeyCustomSetter];
|
||||
_customGetter = NSSelectorFromString(_customGetterString);
|
||||
_customSetter = NSSelectorFromString(_customSetterString);
|
||||
_isReadOnly = attributes[kFLEXPropertyAttributeKeyReadOnly] != nil;
|
||||
_isCopy = attributes[kFLEXPropertyAttributeKeyCopy] != nil;
|
||||
_isRetained = attributes[kFLEXPropertyAttributeKeyRetain] != nil;
|
||||
_isNonatomic = attributes[kFLEXPropertyAttributeKeyNonAtomic] != nil;
|
||||
_isWeak = attributes[kFLEXPropertyAttributeKeyWeak] != nil;
|
||||
_isGarbageCollectable = attributes[kFLEXPropertyAttributeKeyGarbageCollectable] != nil;
|
||||
|
||||
_fullDeclaration = [self buildFullDeclaration];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString
|
||||
stringWithFormat:@"<%@ \"%@\", ivar=%@, readonly=%d, nonatomic=%d, getter=%@, setter=%@>",
|
||||
NSStringFromClass(self.class),
|
||||
self.string,
|
||||
self.backingIvar ?: @"none",
|
||||
self.isReadOnly,
|
||||
self.isNonatomic,
|
||||
NSStringFromSelector(self.customGetter) ?: @"none",
|
||||
NSStringFromSelector(self.customSetter) ?: @"none"
|
||||
];
|
||||
}
|
||||
|
||||
- (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount {
|
||||
NSDictionary *attrs = self.string.propertyAttributes;
|
||||
objc_property_attribute_t *propertyAttributes = malloc(attrs.count * sizeof(objc_property_attribute_t));
|
||||
|
||||
if (attributesCount) {
|
||||
*attributesCount = (unsigned int)attrs.count;
|
||||
}
|
||||
|
||||
NSUInteger i = 0;
|
||||
for (NSString *key in attrs.allKeys) {
|
||||
FLEXPropertyAttribute c = (FLEXPropertyAttribute)[key characterAtIndex:0];
|
||||
switch (c) {
|
||||
case FLEXPropertyAttributeTypeEncoding: {
|
||||
objc_property_attribute_t pa = {
|
||||
kFLEXPropertyAttributeKeyTypeEncoding.UTF8String,
|
||||
self.typeEncoding.UTF8String
|
||||
};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeBackingIvarName: {
|
||||
objc_property_attribute_t pa = {
|
||||
kFLEXPropertyAttributeKeyBackingIvarName.UTF8String,
|
||||
self.backingIvar.UTF8String
|
||||
};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeCopy: {
|
||||
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyCopy.UTF8String, ""};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeCustomGetter: {
|
||||
objc_property_attribute_t pa = {
|
||||
kFLEXPropertyAttributeKeyCustomGetter.UTF8String,
|
||||
NSStringFromSelector(self.customGetter).UTF8String ?: ""
|
||||
};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeCustomSetter: {
|
||||
objc_property_attribute_t pa = {
|
||||
kFLEXPropertyAttributeKeyCustomSetter.UTF8String,
|
||||
NSStringFromSelector(self.customSetter).UTF8String ?: ""
|
||||
};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeDynamic: {
|
||||
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyDynamic.UTF8String, ""};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeGarbageCollectible: {
|
||||
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyGarbageCollectable.UTF8String, ""};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeNonAtomic: {
|
||||
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyNonAtomic.UTF8String, ""};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeOldTypeEncoding: {
|
||||
objc_property_attribute_t pa = {
|
||||
kFLEXPropertyAttributeKeyOldStyleTypeEncoding.UTF8String,
|
||||
self.oldTypeEncoding.UTF8String ?: ""
|
||||
};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeReadOnly: {
|
||||
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyReadOnly.UTF8String, ""};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeRetain: {
|
||||
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyRetain.UTF8String, ""};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
case FLEXPropertyAttributeWeak: {
|
||||
objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyWeak.UTF8String, ""};
|
||||
propertyAttributes[i] = pa;
|
||||
break;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return propertyAttributes;
|
||||
}
|
||||
|
||||
- (objc_property_attribute_t *)list {
|
||||
if (!_list) {
|
||||
_list = [self copyAttributesList:nil];
|
||||
}
|
||||
|
||||
return _list;
|
||||
}
|
||||
|
||||
- (NSString *)buildFullDeclaration {
|
||||
NSMutableString *decl = [NSMutableString new];
|
||||
|
||||
[decl appendFormat:@"%@, ", _isNonatomic ? @"nonatomic" : @"atomic"];
|
||||
[decl appendFormat:@"%@, ", _isReadOnly ? @"readonly" : @"readwrite"];
|
||||
|
||||
BOOL noExplicitMemorySemantics = YES;
|
||||
if (_isCopy) { noExplicitMemorySemantics = NO;
|
||||
[decl appendString:@"copy, "];
|
||||
}
|
||||
if (_isRetained) { noExplicitMemorySemantics = NO;
|
||||
[decl appendString:@"strong, "];
|
||||
}
|
||||
if (_isWeak) { noExplicitMemorySemantics = NO;
|
||||
[decl appendString:@"weak, "];
|
||||
}
|
||||
|
||||
if ([_typeEncoding hasPrefix:@"@"] && noExplicitMemorySemantics) {
|
||||
// *probably* strong if this is an object; strong is the default.
|
||||
[decl appendString:@"strong, "];
|
||||
} else if (noExplicitMemorySemantics) {
|
||||
// *probably* assign if this is not an object
|
||||
[decl appendString:@"assign, "];
|
||||
}
|
||||
|
||||
if (_customGetter) {
|
||||
[decl appendFormat:@"getter=%@, ", NSStringFromSelector(_customGetter)];
|
||||
}
|
||||
if (_customSetter) {
|
||||
[decl appendFormat:@"setter=%@, ", NSStringFromSelector(_customSetter)];
|
||||
}
|
||||
|
||||
[decl deleteCharactersInRange:NSMakeRange(decl.length-2, 2)];
|
||||
return decl.copy;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_list) {
|
||||
free(_list);
|
||||
_list = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Copying
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
return [[FLEXPropertyAttributes class] attributesFromDictionary:self.dictionary];
|
||||
}
|
||||
|
||||
- (id)mutableCopyWithZone:(NSZone *)zone {
|
||||
return [[FLEXMutablePropertyAttributes class] attributesFromDictionary:self.dictionary];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
#pragma mark FLEXMutablePropertyAttributes
|
||||
|
||||
@interface FLEXMutablePropertyAttributes ()
|
||||
@property (nonatomic) BOOL countDelta;
|
||||
@property (nonatomic) BOOL stringDelta;
|
||||
@property (nonatomic) BOOL dictDelta;
|
||||
@property (nonatomic) BOOL listDelta;
|
||||
@property (nonatomic) BOOL declDelta;
|
||||
@end
|
||||
|
||||
#define PropertyWithDeltaFlag(type, name, Name) @dynamic name; \
|
||||
- (void)set ## Name:(type)name { \
|
||||
if (name != _ ## name) { \
|
||||
_countDelta = _stringDelta = _dictDelta = _listDelta = _declDelta = YES; \
|
||||
_ ## name = name; \
|
||||
} \
|
||||
}
|
||||
|
||||
@implementation FLEXMutablePropertyAttributes
|
||||
|
||||
PropertyWithDeltaFlag(NSString *, backingIvar, BackingIvar);
|
||||
PropertyWithDeltaFlag(NSString *, typeEncoding, TypeEncoding);
|
||||
PropertyWithDeltaFlag(NSString *, oldTypeEncoding, OldTypeEncoding);
|
||||
PropertyWithDeltaFlag(SEL, customGetter, CustomGetter);
|
||||
PropertyWithDeltaFlag(SEL, customSetter, CustomSetter);
|
||||
PropertyWithDeltaFlag(BOOL, isReadOnly, IsReadOnly);
|
||||
PropertyWithDeltaFlag(BOOL, isCopy, IsCopy);
|
||||
PropertyWithDeltaFlag(BOOL, isRetained, IsRetained);
|
||||
PropertyWithDeltaFlag(BOOL, isNonatomic, IsNonatomic);
|
||||
PropertyWithDeltaFlag(BOOL, isDynamic, IsDynamic);
|
||||
PropertyWithDeltaFlag(BOOL, isWeak, IsWeak);
|
||||
PropertyWithDeltaFlag(BOOL, isGarbageCollectable, IsGarbageCollectable);
|
||||
|
||||
+ (instancetype)attributes {
|
||||
return [self new];
|
||||
}
|
||||
|
||||
- (void)setTypeEncodingChar:(char)type {
|
||||
self.typeEncoding = [NSString stringWithFormat:@"%c", type];
|
||||
}
|
||||
|
||||
- (NSUInteger)count {
|
||||
// Recalculate attribute count after mutations
|
||||
if (self.countDelta) {
|
||||
self.countDelta = NO;
|
||||
_count = self.dictionary.count;
|
||||
}
|
||||
|
||||
return _count;
|
||||
}
|
||||
|
||||
- (objc_property_attribute_t *)list {
|
||||
// Regenerate list after mutations
|
||||
if (self.listDelta) {
|
||||
self.listDelta = NO;
|
||||
if (_list) {
|
||||
free(_list);
|
||||
_list = nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Super will generate the list if it isn't set
|
||||
return super.list;
|
||||
}
|
||||
|
||||
- (NSString *)string {
|
||||
// Regenerate string after mutations
|
||||
if (self.stringDelta || !_string) {
|
||||
self.stringDelta = NO;
|
||||
_string = self.dictionary.propertyAttributesString;
|
||||
}
|
||||
|
||||
return _string;
|
||||
}
|
||||
|
||||
- (NSDictionary *)dictionary {
|
||||
// Regenerate dictionary after mutations
|
||||
if (self.dictDelta || !_dictionary) {
|
||||
// _stringa nd _dictionary depend on each other,
|
||||
// so we must generate ONE by hand using our properties.
|
||||
// We arbitrarily choose to generate the dictionary.
|
||||
NSMutableDictionary *attrs = [NSMutableDictionary new];
|
||||
if (self.typeEncoding)
|
||||
attrs[kFLEXPropertyAttributeKeyTypeEncoding] = self.typeEncoding;
|
||||
if (self.backingIvar)
|
||||
attrs[kFLEXPropertyAttributeKeyBackingIvarName] = self.backingIvar;
|
||||
if (self.oldTypeEncoding)
|
||||
attrs[kFLEXPropertyAttributeKeyOldStyleTypeEncoding] = self.oldTypeEncoding;
|
||||
if (self.customGetter)
|
||||
attrs[kFLEXPropertyAttributeKeyCustomGetter] = NSStringFromSelector(self.customGetter);
|
||||
if (self.customSetter)
|
||||
attrs[kFLEXPropertyAttributeKeyCustomSetter] = NSStringFromSelector(self.customSetter);
|
||||
|
||||
if (self.isReadOnly) attrs[kFLEXPropertyAttributeKeyReadOnly] = @YES;
|
||||
if (self.isCopy) attrs[kFLEXPropertyAttributeKeyCopy] = @YES;
|
||||
if (self.isRetained) attrs[kFLEXPropertyAttributeKeyRetain] = @YES;
|
||||
if (self.isNonatomic) attrs[kFLEXPropertyAttributeKeyNonAtomic] = @YES;
|
||||
if (self.isDynamic) attrs[kFLEXPropertyAttributeKeyDynamic] = @YES;
|
||||
if (self.isWeak) attrs[kFLEXPropertyAttributeKeyWeak] = @YES;
|
||||
if (self.isGarbageCollectable) attrs[kFLEXPropertyAttributeKeyGarbageCollectable] = @YES;
|
||||
|
||||
_dictionary = attrs.copy;
|
||||
}
|
||||
|
||||
return _dictionary;
|
||||
}
|
||||
|
||||
- (NSString *)fullDeclaration {
|
||||
if (self.declDelta || !_fullDeclaration) {
|
||||
_declDelta = NO;
|
||||
_fullDeclaration = [self buildFullDeclaration];
|
||||
}
|
||||
|
||||
return _fullDeclaration;
|
||||
}
|
||||
|
||||
- (NSString *)customGetterString {
|
||||
return _customGetter ? NSStringFromSelector(_customGetter) : nil;
|
||||
}
|
||||
|
||||
- (NSString *)customSetterString {
|
||||
return _customSetter ? NSStringFromSelector(_customSetter) : nil;
|
||||
}
|
||||
|
||||
@end
|
||||
73
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.h
Normal file
73
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.h
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// FLEXProtocol.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/30/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXRuntimeConstants.h"
|
||||
@class FLEXProperty, FLEXMethodDescription;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark FLEXProtocol
|
||||
@interface FLEXProtocol : NSObject
|
||||
|
||||
/// Every protocol registered with the runtime.
|
||||
+ (NSArray<FLEXProtocol *> *)allProtocols;
|
||||
+ (instancetype)protocol:(Protocol *)protocol;
|
||||
|
||||
/// The underlying protocol data structure.
|
||||
@property (nonatomic, readonly) Protocol *objc_protocol;
|
||||
|
||||
/// The name of the protocol.
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
/// The required methods of the protocol, if any. This includes property getters and setters.
|
||||
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *requiredMethods;
|
||||
/// The optional methods of the protocol, if any. This includes property getters and setters.
|
||||
@property (nonatomic, readonly) NSArray<FLEXMethodDescription *> *optionalMethods;
|
||||
/// All protocols that this protocol conforms to, if any.
|
||||
@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
|
||||
/// The full path of the image that contains this protocol definition,
|
||||
/// or \c nil if this protocol was probably defined at runtime.
|
||||
@property (nonatomic, readonly, nullable) NSString *imagePath;
|
||||
|
||||
/// The properties in the protocol, if any. \c nil on iOS 10+
|
||||
@property (nonatomic, readonly, nullable) NSArray<FLEXProperty *> *properties API_DEPRECATED("Use the more specific accessors below", ios(2.0, 10.0));
|
||||
|
||||
/// The required properties in the protocol, if any.
|
||||
@property (nonatomic, readonly) NSArray<FLEXProperty *> *requiredProperties API_AVAILABLE(ios(10.0));
|
||||
/// The optional properties in the protocol, if any.
|
||||
@property (nonatomic, readonly) NSArray<FLEXProperty *> *optionalProperties API_AVAILABLE(ios(10.0));
|
||||
|
||||
/// For internal use
|
||||
@property (nonatomic) id tag;
|
||||
|
||||
/// Not to be confused with \c -conformsToProtocol:, which refers to the current
|
||||
/// \c FLEXProtocol instance and not the underlying \c Protocol object.
|
||||
- (BOOL)conformsTo:(Protocol *)protocol;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark Method descriptions
|
||||
@interface FLEXMethodDescription : NSObject
|
||||
|
||||
+ (instancetype)description:(struct objc_method_description)description;
|
||||
+ (instancetype)description:(struct objc_method_description)description instance:(BOOL)isInstance;
|
||||
|
||||
/// The underlying method description data structure.
|
||||
@property (nonatomic, readonly) struct objc_method_description objc_description;
|
||||
/// The method's selector.
|
||||
@property (nonatomic, readonly) SEL selector;
|
||||
/// The method's type encoding.
|
||||
@property (nonatomic, readonly) NSString *typeEncoding;
|
||||
/// The method's return type.
|
||||
@property (nonatomic, readonly) FLEXTypeEncoding returnType;
|
||||
/// \c YES if this is an instance method, \c NO if it is a class method, or \c nil if unspecified
|
||||
@property (nonatomic, readonly) NSNumber *instance;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
212
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.m
Normal file
212
Tweaks/FLEX/Utility/Runtime/Objc/Reflection/FLEXProtocol.m
Normal file
@@ -0,0 +1,212 @@
|
||||
//
|
||||
// FLEXProtocol.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 6/30/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXProtocol.h"
|
||||
#import "FLEXProperty.h"
|
||||
#import "FLEXRuntimeUtility.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#include <dlfcn.h>
|
||||
|
||||
@implementation FLEXProtocol
|
||||
|
||||
#pragma mark Initializers
|
||||
|
||||
+ (NSArray *)allProtocols {
|
||||
unsigned int prcount;
|
||||
Protocol *__unsafe_unretained*protocols = objc_copyProtocolList(&prcount);
|
||||
|
||||
NSMutableArray *all = [NSMutableArray new];
|
||||
for(NSUInteger i = 0; i < prcount; i++)
|
||||
[all addObject:[self protocol:protocols[i]]];
|
||||
|
||||
free(protocols);
|
||||
return all;
|
||||
}
|
||||
|
||||
+ (instancetype)protocol:(Protocol *)protocol {
|
||||
return [[self alloc] initWithProtocol:protocol];
|
||||
}
|
||||
|
||||
- (id)initWithProtocol:(Protocol *)protocol {
|
||||
NSParameterAssert(protocol);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_objc_protocol = protocol;
|
||||
[self examine];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Other
|
||||
|
||||
- (NSString *)description {
|
||||
return self.name;
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
return [NSString stringWithFormat:@"<%@ name=%@, %lu properties, %lu required methods, %lu optional methods, %lu protocols>",
|
||||
NSStringFromClass(self.class), self.name, (unsigned long)self.properties.count,
|
||||
(unsigned long)self.requiredMethods.count, (unsigned long)self.optionalMethods.count, (unsigned long)self.protocols.count];
|
||||
}
|
||||
|
||||
- (void)examine {
|
||||
_name = @(protocol_getName(self.objc_protocol));
|
||||
|
||||
// imagePath
|
||||
Dl_info exeInfo;
|
||||
if (dladdr((__bridge const void *)(_objc_protocol), &exeInfo)) {
|
||||
_imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
|
||||
}
|
||||
|
||||
// Conformances and methods //
|
||||
|
||||
unsigned int pccount, mdrcount, mdocount;
|
||||
struct objc_method_description *objcrMethods, *objcoMethods;
|
||||
Protocol *protocol = _objc_protocol;
|
||||
Protocol * __unsafe_unretained *protocols = protocol_copyProtocolList(protocol, &pccount);
|
||||
|
||||
// Protocols
|
||||
_protocols = [NSArray flex_forEachUpTo:pccount map:^id(NSUInteger i) {
|
||||
return [FLEXProtocol protocol:protocols[i]];
|
||||
}];
|
||||
free(protocols);
|
||||
|
||||
// Required instance methods
|
||||
objcrMethods = protocol_copyMethodDescriptionList(protocol, YES, YES, &mdrcount);
|
||||
NSArray *rMethods = [NSArray flex_forEachUpTo:mdrcount map:^id(NSUInteger i) {
|
||||
return [FLEXMethodDescription description:objcrMethods[i] instance:YES];
|
||||
}];
|
||||
free(objcrMethods);
|
||||
|
||||
// Required class methods
|
||||
objcrMethods = protocol_copyMethodDescriptionList(protocol, YES, NO, &mdrcount);
|
||||
_requiredMethods = [[NSArray flex_forEachUpTo:mdrcount map:^id(NSUInteger i) {
|
||||
return [FLEXMethodDescription description:objcrMethods[i] instance:NO];
|
||||
}] arrayByAddingObjectsFromArray:rMethods];
|
||||
free(objcrMethods);
|
||||
|
||||
// Optional instance methods
|
||||
objcoMethods = protocol_copyMethodDescriptionList(protocol, NO, YES, &mdocount);
|
||||
NSArray *oMethods = [NSArray flex_forEachUpTo:mdocount map:^id(NSUInteger i) {
|
||||
return [FLEXMethodDescription description:objcoMethods[i] instance:YES];
|
||||
}];
|
||||
free(objcoMethods);
|
||||
|
||||
// Optional class methods
|
||||
objcoMethods = protocol_copyMethodDescriptionList(protocol, NO, NO, &mdocount);
|
||||
_optionalMethods = [[NSArray flex_forEachUpTo:mdocount map:^id(NSUInteger i) {
|
||||
return [FLEXMethodDescription description:objcoMethods[i] instance:NO];
|
||||
}] arrayByAddingObjectsFromArray:oMethods];
|
||||
free(objcoMethods);
|
||||
|
||||
// Properties is a hassle because they didn't fix the API until iOS 10 //
|
||||
|
||||
if (@available(iOS 10.0, *)) {
|
||||
unsigned int prrcount, procount;
|
||||
Class instance = [NSObject class], meta = objc_getMetaClass("NSObject");
|
||||
|
||||
// Required class and instance properties //
|
||||
|
||||
// Instance first
|
||||
objc_property_t *rProps = protocol_copyPropertyList2(protocol, &prrcount, YES, YES);
|
||||
NSArray *rProperties = [NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) {
|
||||
return [FLEXProperty property:rProps[i] onClass:instance];
|
||||
}];
|
||||
free(rProps);
|
||||
|
||||
// Then class
|
||||
rProps = protocol_copyPropertyList2(protocol, &prrcount, NO, YES);
|
||||
_requiredProperties = [[NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) {
|
||||
return [FLEXProperty property:rProps[i] onClass:instance];
|
||||
}] arrayByAddingObjectsFromArray:rProperties];
|
||||
free(rProps);
|
||||
|
||||
// Optional class and instance properties //
|
||||
|
||||
// Instance first
|
||||
objc_property_t *oProps = protocol_copyPropertyList2(protocol, &procount, YES, YES);
|
||||
NSArray *oProperties = [NSArray flex_forEachUpTo:prrcount map:^id(NSUInteger i) {
|
||||
return [FLEXProperty property:oProps[i] onClass:meta];
|
||||
}];
|
||||
free(oProps);
|
||||
|
||||
// Then class
|
||||
oProps = protocol_copyPropertyList2(protocol, &procount, NO, YES);
|
||||
_optionalProperties = [[NSArray flex_forEachUpTo:procount map:^id(NSUInteger i) {
|
||||
return [FLEXProperty property:oProps[i] onClass:meta];
|
||||
}] arrayByAddingObjectsFromArray:oProperties];
|
||||
free(oProps);
|
||||
|
||||
} else {
|
||||
unsigned int prcount;
|
||||
objc_property_t *objcproperties = protocol_copyPropertyList(protocol, &prcount);
|
||||
_properties = [NSArray flex_forEachUpTo:prcount map:^id(NSUInteger i) {
|
||||
return [FLEXProperty property:objcproperties[i]];
|
||||
}];
|
||||
|
||||
_requiredProperties = @[];
|
||||
_optionalProperties = @[];
|
||||
|
||||
free(objcproperties);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)conformsTo:(Protocol *)protocol {
|
||||
return protocol_conformsToProtocol(self.objc_protocol, protocol);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark FLEXMethodDescription
|
||||
|
||||
@implementation FLEXMethodDescription
|
||||
|
||||
- (id)init {
|
||||
[NSException
|
||||
raise:NSInternalInconsistencyException
|
||||
format:@"Class instance should not be created with -init"
|
||||
];
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (instancetype)description:(struct objc_method_description)description {
|
||||
return [[self alloc] initWithDescription:description instance:nil];
|
||||
}
|
||||
|
||||
+ (instancetype)description:(struct objc_method_description)description instance:(BOOL)isInstance {
|
||||
return [[self alloc] initWithDescription:description instance:@(isInstance)];
|
||||
}
|
||||
|
||||
- (id)initWithDescription:(struct objc_method_description)md instance:(NSNumber *)instance {
|
||||
NSParameterAssert(md.name != NULL);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_objc_description = md;
|
||||
_selector = md.name;
|
||||
_typeEncoding = @(md.types);
|
||||
_returnType = (FLEXTypeEncoding)[self.typeEncoding characterAtIndex:0];
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return NSStringFromSelector(self.selector);
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription {
|
||||
return [NSString stringWithFormat:@"<%@ name=%@, type=%@>",
|
||||
NSStringFromClass(self.class), NSStringFromSelector(self.selector), self.typeEncoding];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// FLEXProtocolBuilder.h
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 7/4/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@class FLEXProperty, FLEXProtocol, Protocol;
|
||||
|
||||
@interface FLEXProtocolBuilder : NSObject
|
||||
|
||||
/// Begins to construct a new protocol with the given name.
|
||||
/// @discussion You must register the protocol with the
|
||||
/// \c registerProtocol method before you can use it.
|
||||
+ (instancetype)allocateProtocol:(NSString *)name;
|
||||
|
||||
/// Adds a property to a protocol.
|
||||
/// @param property The property to add.
|
||||
/// @param isRequired Whether the property is required to implement the protocol.
|
||||
- (void)addProperty:(FLEXProperty *)property isRequired:(BOOL)isRequired;
|
||||
/// Adds a property to a protocol.
|
||||
/// @param selector The selector of the method to add.
|
||||
/// @param typeEncoding The type encoding of the method to add.
|
||||
/// @param isRequired Whether the method is required to implement the protocol.
|
||||
/// @param isInstanceMethod \c YES if the method is an instance method, \c NO if it is a class method.
|
||||
- (void)addMethod:(SEL)selector
|
||||
typeEncoding:(NSString *)typeEncoding
|
||||
isRequired:(BOOL)isRequired
|
||||
isInstanceMethod:(BOOL)isInstanceMethod;
|
||||
/// Makes the recieving protocol conform to the given protocol.
|
||||
- (void)addProtocol:(Protocol *)protocol;
|
||||
|
||||
/// Registers and returns the recieving protocol, which was previously under construction.
|
||||
- (FLEXProtocol *)registerProtocol;
|
||||
/// Whether the protocol is still under construction or already registered.
|
||||
@property (nonatomic, readonly) BOOL isRegistered;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// FLEXProtocolBuilder.m
|
||||
// FLEX
|
||||
//
|
||||
// Derived from MirrorKit.
|
||||
// Created by Tanner on 7/4/15.
|
||||
// Copyright (c) 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXProtocolBuilder.h"
|
||||
#import "FLEXProtocol.h"
|
||||
#import "FLEXProperty.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#define MutationAssertion(msg) if (self.isRegistered) { \
|
||||
[NSException \
|
||||
raise:NSInternalInconsistencyException \
|
||||
format:msg \
|
||||
]; \
|
||||
}
|
||||
|
||||
@interface FLEXProtocolBuilder ()
|
||||
@property (nonatomic) Protocol *workingProtocol;
|
||||
@property (nonatomic) NSString *name;
|
||||
@end
|
||||
|
||||
@implementation FLEXProtocolBuilder
|
||||
|
||||
- (id)init {
|
||||
[NSException
|
||||
raise:NSInternalInconsistencyException
|
||||
format:@"Class instance should not be created with -init"
|
||||
];
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Initializers
|
||||
+ (instancetype)allocateProtocol:(NSString *)name {
|
||||
NSParameterAssert(name);
|
||||
return [[self alloc] initWithProtocol:objc_allocateProtocol(name.UTF8String)];
|
||||
|
||||
}
|
||||
|
||||
- (id)initWithProtocol:(Protocol *)protocol {
|
||||
NSParameterAssert(protocol);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_workingProtocol = protocol;
|
||||
_name = NSStringFromProtocol(self.workingProtocol);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<%@ name=%@, registered=%d>",
|
||||
NSStringFromClass(self.class), self.name, self.isRegistered];
|
||||
}
|
||||
|
||||
#pragma mark Building
|
||||
|
||||
- (void)addProperty:(FLEXProperty *)property isRequired:(BOOL)isRequired {
|
||||
MutationAssertion(@"Properties cannot be added once a protocol has been registered");
|
||||
|
||||
unsigned int count;
|
||||
objc_property_attribute_t *attributes = [property copyAttributesList:&count];
|
||||
protocol_addProperty(self.workingProtocol, property.name.UTF8String, attributes, count, isRequired, YES);
|
||||
free(attributes);
|
||||
}
|
||||
|
||||
- (void)addMethod:(SEL)selector
|
||||
typeEncoding:(NSString *)typeEncoding
|
||||
isRequired:(BOOL)isRequired
|
||||
isInstanceMethod:(BOOL)isInstanceMethod {
|
||||
MutationAssertion(@"Methods cannot be added once a protocol has been registered");
|
||||
protocol_addMethodDescription(self.workingProtocol, selector, typeEncoding.UTF8String, isRequired, isInstanceMethod);
|
||||
}
|
||||
|
||||
- (void)addProtocol:(Protocol *)protocol {
|
||||
MutationAssertion(@"Protocols cannot be added once a protocol has been registered");
|
||||
protocol_addProtocol(self.workingProtocol, protocol);
|
||||
}
|
||||
|
||||
- (FLEXProtocol *)registerProtocol {
|
||||
MutationAssertion(@"Protocol is already registered");
|
||||
|
||||
_isRegistered = YES;
|
||||
objc_registerProtocol(self.workingProtocol);
|
||||
return [FLEXProtocol protocol:self.workingProtocol];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user