FLEXRuntimeUtility.m 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. //
  2. // FLEXRuntimeUtility.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 6/8/14.
  6. // Copyright (c) 2020 FLEX Team. All rights reserved.
  7. //
  8. #import <UIKit/UIKit.h>
  9. #import "FLEXRuntimeUtility.h"
  10. #import "FLEXObjcInternal.h"
  11. #import "FLEXTypeEncodingParser.h"
  12. static NSString *const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
  13. typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
  14. FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0,
  15. FLEXRuntimeUtilityErrorCodeInvocationFailed = 1,
  16. FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch = 2
  17. };
  18. @implementation FLEXRuntimeUtility
  19. #pragma mark - General Helpers (Public)
  20. + (BOOL)pointerIsValidObjcObject:(const void *)pointer {
  21. return FLEXPointerIsValidObjcObject(pointer);
  22. }
  23. + (id)potentiallyUnwrapBoxedPointer:(id)returnedObjectOrNil type:(const FLEXTypeEncoding *)returnType {
  24. if (!returnedObjectOrNil) {
  25. return nil;
  26. }
  27. NSInteger i = 0;
  28. if (returnType[i] == FLEXTypeEncodingConst) {
  29. i++;
  30. }
  31. BOOL returnsObjectOrClass = returnType[i] == FLEXTypeEncodingObjcObject ||
  32. returnType[i] == FLEXTypeEncodingObjcClass;
  33. BOOL returnsVoidPointer = returnType[i] == FLEXTypeEncodingPointer &&
  34. returnType[i+1] == FLEXTypeEncodingVoid;
  35. BOOL returnsCString = returnType[i] == FLEXTypeEncodingCString;
  36. // If we got back an NSValue and the return type is not an object,
  37. // we check to see if the pointer is of a valid object. If not,
  38. // we just display the NSValue.
  39. if (!returnsObjectOrClass) {
  40. // Skip NSNumber instances
  41. if ([returnedObjectOrNil isKindOfClass:[NSNumber class]]) {
  42. return returnedObjectOrNil;
  43. }
  44. // Can only be NSValue since return type is not an object,
  45. // so we bail if this doesn't add up
  46. if (![returnedObjectOrNil isKindOfClass:[NSValue class]]) {
  47. return returnedObjectOrNil;
  48. }
  49. NSValue *value = (NSValue *)returnedObjectOrNil;
  50. if (returnsCString) {
  51. // Wrap char * in NSString
  52. const char *string = (const char *)value.pointerValue;
  53. returnedObjectOrNil = string ? [NSString stringWithCString:string encoding:NSUTF8StringEncoding] : NULL;
  54. } else if (returnsVoidPointer) {
  55. // Cast valid objects disguised as void * to id
  56. if ([FLEXRuntimeUtility pointerIsValidObjcObject:value.pointerValue]) {
  57. returnedObjectOrNil = (__bridge id)value.pointerValue;
  58. }
  59. }
  60. }
  61. return returnedObjectOrNil;
  62. }
  63. + (NSUInteger)fieldNameOffsetForTypeEncoding:(const FLEXTypeEncoding *)typeEncoding {
  64. NSUInteger beginIndex = 0;
  65. while (typeEncoding[beginIndex] == FLEXTypeEncodingQuote) {
  66. NSUInteger endIndex = beginIndex + 1;
  67. while (typeEncoding[endIndex] != FLEXTypeEncodingQuote) {
  68. ++endIndex;
  69. }
  70. beginIndex = endIndex + 1;
  71. }
  72. return beginIndex;
  73. }
  74. + (NSArray<Class> *)classHierarchyOfObject:(id)objectOrClass {
  75. NSMutableArray<Class> *superClasses = [NSMutableArray new];
  76. id cls = [objectOrClass class];
  77. do {
  78. [superClasses addObject:cls];
  79. } while ((cls = [cls superclass]));
  80. return superClasses;
  81. }
  82. + (NSString *)safeClassNameForObject:(id)object {
  83. // Don't assume that we have an NSObject subclass
  84. if ([self safeObject:object respondsToSelector:@selector(class)]) {
  85. return NSStringFromClass([object class]);
  86. }
  87. return NSStringFromClass(object_getClass(object));
  88. }
  89. /// Could be nil
  90. + (NSString *)safeDescriptionForObject:(id)object {
  91. // Don't assume that we have an NSObject subclass; not all objects respond to -description
  92. if ([self safeObject:object respondsToSelector:@selector(description)]) {
  93. return [object description];
  94. }
  95. return nil;
  96. }
  97. /// Never nil
  98. + (NSString *)safeDebugDescriptionForObject:(id)object {
  99. NSString *description = nil;
  100. if ([self safeObject:object respondsToSelector:@selector(debugDescription)]) {
  101. description = [object debugDescription];
  102. } else {
  103. description = [self safeDescriptionForObject:object];
  104. }
  105. if (!description.length) {
  106. NSString *cls = NSStringFromClass(object_getClass(object));
  107. if (object_isClass(object)) {
  108. description = [cls stringByAppendingString:@" class (no description)"];
  109. } else {
  110. description = [cls stringByAppendingString:@" instance (no description)"];
  111. }
  112. }
  113. return description;
  114. }
  115. + (NSString *)summaryForObject:(id)value {
  116. NSString *description = nil;
  117. // Special case BOOL for better readability.
  118. if ([self safeObject:value isKindOfClass:[NSValue class]]) {
  119. const char *type = [value objCType];
  120. if (strcmp(type, @encode(BOOL)) == 0) {
  121. BOOL boolValue = NO;
  122. [value getValue:&boolValue];
  123. return boolValue ? @"YES" : @"NO";
  124. } else if (strcmp(type, @encode(SEL)) == 0) {
  125. SEL selector = NULL;
  126. [value getValue:&selector];
  127. return NSStringFromSelector(selector);
  128. }
  129. }
  130. @try {
  131. // Single line display - replace newlines and tabs with spaces.
  132. description = [[self safeDescriptionForObject:value] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
  133. description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
  134. } @catch (NSException *e) {
  135. description = [@"Thrown: " stringByAppendingString:e.reason ?: @"(nil exception reason)"];
  136. }
  137. if (!description) {
  138. description = @"nil";
  139. }
  140. return description;
  141. }
  142. + (BOOL)safeObject:(id)object isKindOfClass:(Class)cls {
  143. static BOOL (*isKindOfClass)(id, SEL, Class) = nil;
  144. static BOOL (*isKindOfClass_meta)(id, SEL, Class) = nil;
  145. static dispatch_once_t onceToken;
  146. dispatch_once(&onceToken, ^{
  147. isKindOfClass = (BOOL(*)(id, SEL, Class))[NSObject instanceMethodForSelector:@selector(isKindOfClass:)];
  148. isKindOfClass_meta = (BOOL(*)(id, SEL, Class))[NSObject methodForSelector:@selector(isKindOfClass:)];
  149. });
  150. BOOL isClass = object_isClass(object);
  151. return (isClass ? isKindOfClass_meta : isKindOfClass)(object, @selector(isKindOfClass:), cls);
  152. }
  153. + (BOOL)safeObject:(id)object respondsToSelector:(SEL)sel {
  154. // If we're given a class, we want to know if classes respond to this selector.
  155. // Similarly, if we're given an instance, we want to know if instances respond.
  156. BOOL isClass = object_isClass(object);
  157. Class cls = isClass ? object : object_getClass(object);
  158. // BOOL isMetaclass = class_isMetaClass(cls);
  159. if (isClass) {
  160. // In theory, this should also work for metaclasses...
  161. return class_getClassMethod(cls, sel) != nil;
  162. } else {
  163. return class_getInstanceMethod(cls, sel) != nil;
  164. }
  165. }
  166. #pragma mark - Property Helpers (Public)
  167. + (BOOL)tryAddPropertyWithName:(const char *)name
  168. attributes:(NSDictionary<NSString *, NSString *> *)attributePairs
  169. toClass:(__unsafe_unretained Class)theClass {
  170. objc_property_t property = class_getProperty(theClass, name);
  171. if (!property) {
  172. unsigned int totalAttributesCount = (unsigned int)attributePairs.count;
  173. objc_property_attribute_t *attributes = malloc(sizeof(objc_property_attribute_t) * totalAttributesCount);
  174. if (attributes) {
  175. unsigned int attributeIndex = 0;
  176. for (NSString *attributeName in attributePairs.allKeys) {
  177. objc_property_attribute_t attribute;
  178. attribute.name = attributeName.UTF8String;
  179. attribute.value = attributePairs[attributeName].UTF8String;
  180. attributes[attributeIndex++] = attribute;
  181. }
  182. BOOL success = class_addProperty(theClass, name, attributes, totalAttributesCount);
  183. free(attributes);
  184. return success;
  185. } else {
  186. return NO;
  187. }
  188. }
  189. return YES;
  190. }
  191. + (NSArray<NSString *> *)allPropertyAttributeKeys {
  192. static NSArray<NSString *> *allPropertyAttributeKeys = nil;
  193. static dispatch_once_t onceToken;
  194. dispatch_once(&onceToken, ^{
  195. allPropertyAttributeKeys = @[
  196. kFLEXPropertyAttributeKeyTypeEncoding,
  197. kFLEXPropertyAttributeKeyBackingIvarName,
  198. kFLEXPropertyAttributeKeyReadOnly,
  199. kFLEXPropertyAttributeKeyCopy,
  200. kFLEXPropertyAttributeKeyRetain,
  201. kFLEXPropertyAttributeKeyNonAtomic,
  202. kFLEXPropertyAttributeKeyCustomGetter,
  203. kFLEXPropertyAttributeKeyCustomSetter,
  204. kFLEXPropertyAttributeKeyDynamic,
  205. kFLEXPropertyAttributeKeyWeak,
  206. kFLEXPropertyAttributeKeyGarbageCollectable,
  207. kFLEXPropertyAttributeKeyOldStyleTypeEncoding,
  208. ];
  209. });
  210. return allPropertyAttributeKeys;
  211. }
  212. #pragma mark - Method Helpers (Public)
  213. + (NSArray<NSString *> *)prettyArgumentComponentsForMethod:(Method)method {
  214. NSMutableArray<NSString *> *components = [NSMutableArray new];
  215. NSString *selectorName = NSStringFromSelector(method_getName(method));
  216. NSMutableArray<NSString *> *selectorComponents = [selectorName componentsSeparatedByString:@":"].mutableCopy;
  217. // this is a workaround cause method_getNumberOfArguments() returns wrong number for some methods
  218. if (selectorComponents.count == 1) {
  219. return @[];
  220. }
  221. if ([selectorComponents.lastObject isEqualToString:@""]) {
  222. [selectorComponents removeLastObject];
  223. }
  224. for (unsigned int argIndex = 0; argIndex < selectorComponents.count; argIndex++) {
  225. char *argType = method_copyArgumentType(method, argIndex + kFLEXNumberOfImplicitArgs);
  226. NSString *readableArgType = (argType != NULL) ? [self readableTypeForEncoding:@(argType)] : nil;
  227. free(argType);
  228. NSString *prettyComponent = [NSString
  229. stringWithFormat:@"%@:(%@) ",
  230. selectorComponents[argIndex],
  231. readableArgType
  232. ];
  233. [components addObject:prettyComponent];
  234. }
  235. return components;
  236. }
  237. #pragma mark - Method Calling/Field Editing (Public)
  238. + (id)performSelector:(SEL)selector onObject:(id)object {
  239. return [self performSelector:selector onObject:object withArguments:@[] error:nil];
  240. }
  241. + (id)performSelector:(SEL)selector
  242. onObject:(id)object
  243. withArguments:(NSArray *)arguments
  244. error:(NSError * __autoreleasing *)error {
  245. static dispatch_once_t onceToken;
  246. static SEL stdStringExclusion = nil;
  247. dispatch_once(&onceToken, ^{
  248. stdStringExclusion = NSSelectorFromString(@"stdString");
  249. });
  250. // Bail if the object won't respond to this selector.
  251. if (![self safeObject:object respondsToSelector:selector]) {
  252. if (error) {
  253. NSString *msg = [NSString
  254. stringWithFormat:@"%@ does not respond to the selector %@",
  255. object, NSStringFromSelector(selector)
  256. ];
  257. NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg };
  258. *error = [NSError
  259. errorWithDomain:FLEXRuntimeUtilityErrorDomain
  260. code:FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector
  261. userInfo:userInfo
  262. ];
  263. }
  264. return nil;
  265. }
  266. NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:({
  267. Method method;
  268. if (object_isClass(object)) {
  269. method = class_getClassMethod(object, selector);
  270. } else {
  271. method = class_getInstanceMethod(object_getClass(object), selector);
  272. }
  273. method_getTypeEncoding(method);
  274. })];
  275. // Probably an unsupported type encoding, like bitfields.
  276. // In the future, we could calculate the return length
  277. // on our own. For now, we abort.
  278. //
  279. // For future reference, the code here will get the true type encoding.
  280. // NSMethodSignature will convert {?=b8b4b1b1b18[8S]} to {?}
  281. //
  282. // returnType = method_getTypeEncoding(class_getInstanceMethod([object class], selector));
  283. if (!methodSignature.methodReturnLength &&
  284. methodSignature.methodReturnType[0] != FLEXTypeEncodingVoid) {
  285. return nil;
  286. }
  287. // Build the invocation
  288. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
  289. [invocation setSelector:selector];
  290. [invocation setTarget:object];
  291. [invocation retainArguments];
  292. // Always self and _cmd
  293. NSUInteger numberOfArguments = [methodSignature numberOfArguments];
  294. for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) {
  295. NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs;
  296. id argumentObject = arguments.count > argumentsArrayIndex ? arguments[argumentsArrayIndex] : nil;
  297. // NSNull in the arguments array can be passed as a placeholder to indicate nil.
  298. // We only need to set the argument if it will be non-nil.
  299. if (argumentObject && ![argumentObject isKindOfClass:[NSNull class]]) {
  300. const char *typeEncodingCString = [methodSignature getArgumentTypeAtIndex:argumentIndex];
  301. if (typeEncodingCString[0] == FLEXTypeEncodingObjcObject ||
  302. typeEncodingCString[0] == FLEXTypeEncodingObjcClass ||
  303. [self isTollFreeBridgedValue:argumentObject forCFType:typeEncodingCString]) {
  304. // Object
  305. [invocation setArgument:&argumentObject atIndex:argumentIndex];
  306. } else if (strcmp(typeEncodingCString, @encode(CGColorRef)) == 0 &&
  307. [argumentObject isKindOfClass:[UIColor class]]) {
  308. // Bridging UIColor to CGColorRef
  309. CGColorRef colorRef = [argumentObject CGColor];
  310. [invocation setArgument:&colorRef atIndex:argumentIndex];
  311. } else if ([argumentObject isKindOfClass:[NSValue class]]) {
  312. // Primitive boxed in NSValue
  313. NSValue *argumentValue = (NSValue *)argumentObject;
  314. // Ensure that the type encoding on the NSValue matches the type encoding of the argument in the method signature
  315. if (strcmp([argumentValue objCType], typeEncodingCString) != 0) {
  316. if (error) {
  317. NSString *msg = [NSString
  318. stringWithFormat:@"Type encoding mismatch for argument at index %lu. "
  319. "Value type: %s; Method argument type: %s.",
  320. (unsigned long)argumentsArrayIndex, argumentValue.objCType, typeEncodingCString
  321. ];
  322. NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : msg };
  323. *error = [NSError
  324. errorWithDomain:FLEXRuntimeUtilityErrorDomain
  325. code:FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch
  326. userInfo:userInfo
  327. ];
  328. }
  329. return nil;
  330. }
  331. @try {
  332. NSUInteger bufferSize = 0;
  333. FLEXGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
  334. if (bufferSize > 0) {
  335. void *buffer = alloca(bufferSize);
  336. [argumentValue getValue:buffer];
  337. [invocation setArgument:buffer atIndex:argumentIndex];
  338. }
  339. } @catch (NSException *exception) { }
  340. }
  341. }
  342. }
  343. // Try to invoke the invocation but guard against an exception being thrown.
  344. id returnObject = nil;
  345. @try {
  346. [invocation invoke];
  347. // Retrieve the return value and box if necessary.
  348. const char *returnType = methodSignature.methodReturnType;
  349. if (returnType[0] == FLEXTypeEncodingObjcObject || returnType[0] == FLEXTypeEncodingObjcClass) {
  350. // Return value is an object.
  351. __unsafe_unretained id objectReturnedFromMethod = nil;
  352. [invocation getReturnValue:&objectReturnedFromMethod];
  353. returnObject = objectReturnedFromMethod;
  354. } else if (returnType[0] != FLEXTypeEncodingVoid) {
  355. NSAssert(methodSignature.methodReturnLength, @"Memory corruption lies ahead");
  356. if (returnType[0] == FLEXTypeEncodingStructBegin) {
  357. if (selector == stdStringExclusion && [object isKindOfClass:[NSString class]]) {
  358. // stdString is a C++ object and we will crash if we try to access it
  359. if (error) {
  360. *error = [NSError
  361. errorWithDomain:FLEXRuntimeUtilityErrorDomain
  362. code:FLEXRuntimeUtilityErrorCodeInvocationFailed
  363. userInfo:@{ NSLocalizedDescriptionKey : @"Skipping -[NSString stdString]" }
  364. ];
  365. }
  366. return nil;
  367. }
  368. }
  369. // Will use arbitrary buffer for return value and box it.
  370. void *returnValue = malloc(methodSignature.methodReturnLength);
  371. [invocation getReturnValue:returnValue];
  372. returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType];
  373. free(returnValue);
  374. }
  375. } @catch (NSException *exception) {
  376. // Bummer...
  377. if (error) {
  378. // "… on <class>" / "… on instance of <class>"
  379. NSString *class = NSStringFromClass([object class]);
  380. NSString *calledOn = object == [object class] ? class : [@"an instance of " stringByAppendingString:class];
  381. NSString *message = [NSString
  382. stringWithFormat:@"Exception '%@' thrown while performing selector '%@' on %@.\nReason:\n\n%@",
  383. exception.name, NSStringFromSelector(selector), calledOn, exception.reason
  384. ];
  385. *error = [NSError
  386. errorWithDomain:FLEXRuntimeUtilityErrorDomain
  387. code:FLEXRuntimeUtilityErrorCodeInvocationFailed
  388. userInfo:@{ NSLocalizedDescriptionKey : message }
  389. ];
  390. }
  391. }
  392. return returnObject;
  393. }
  394. + (BOOL)isTollFreeBridgedValue:(id)value forCFType:(const char *)typeEncoding {
  395. // See https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/Toll-FreeBridgin/Toll-FreeBridgin.html
  396. #define CASE(cftype, foundationClass) \
  397. if (strcmp(typeEncoding, @encode(cftype)) == 0) { \
  398. return [value isKindOfClass:[foundationClass class]]; \
  399. }
  400. CASE(CFArrayRef, NSArray);
  401. CASE(CFAttributedStringRef, NSAttributedString);
  402. CASE(CFCalendarRef, NSCalendar);
  403. CASE(CFCharacterSetRef, NSCharacterSet);
  404. CASE(CFDataRef, NSData);
  405. CASE(CFDateRef, NSDate);
  406. CASE(CFDictionaryRef, NSDictionary);
  407. CASE(CFErrorRef, NSError);
  408. CASE(CFLocaleRef, NSLocale);
  409. CASE(CFMutableArrayRef, NSMutableArray);
  410. CASE(CFMutableAttributedStringRef, NSMutableAttributedString);
  411. CASE(CFMutableCharacterSetRef, NSMutableCharacterSet);
  412. CASE(CFMutableDataRef, NSMutableData);
  413. CASE(CFMutableDictionaryRef, NSMutableDictionary);
  414. CASE(CFMutableSetRef, NSMutableSet);
  415. CASE(CFMutableStringRef, NSMutableString);
  416. CASE(CFNumberRef, NSNumber);
  417. CASE(CFReadStreamRef, NSInputStream);
  418. CASE(CFRunLoopTimerRef, NSTimer);
  419. CASE(CFSetRef, NSSet);
  420. CASE(CFStringRef, NSString);
  421. CASE(CFTimeZoneRef, NSTimeZone);
  422. CASE(CFURLRef, NSURL);
  423. CASE(CFWriteStreamRef, NSOutputStream);
  424. #undef CASE
  425. return NO;
  426. }
  427. + (NSString *)editableJSONStringForObject:(id)object {
  428. NSString *editableDescription = nil;
  429. if (object) {
  430. // This is a hack to use JSON serialization for our editable objects.
  431. // NSJSONSerialization doesn't allow writing fragments - the top level object must be an array or dictionary.
  432. // We always wrap the object inside an array and then strip the outer square braces off the final string.
  433. NSArray *wrappedObject = @[object];
  434. if ([NSJSONSerialization isValidJSONObject:wrappedObject]) {
  435. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:wrappedObject options:0 error:NULL];
  436. NSString *wrappedDescription = [NSString stringWithUTF8String:jsonData.bytes];
  437. editableDescription = [wrappedDescription substringWithRange:NSMakeRange(1, wrappedDescription.length - 2)];
  438. }
  439. }
  440. return editableDescription;
  441. }
  442. + (id)objectValueFromEditableJSONString:(NSString *)string {
  443. id value = nil;
  444. // nil for empty string/whitespace
  445. if ([string stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet].length) {
  446. value = [NSJSONSerialization
  447. JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding]
  448. options:NSJSONReadingAllowFragments
  449. error:NULL
  450. ];
  451. }
  452. return value;
  453. }
  454. + (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString {
  455. NSNumberFormatter *formatter = [NSNumberFormatter new];
  456. [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
  457. NSNumber *number = [formatter numberFromString:inputString];
  458. // Is the type encoding longer than one character?
  459. if (strlen(typeEncoding) > 1) {
  460. NSString *type = @(typeEncoding);
  461. // Is it NSDecimalNumber or NSNumber?
  462. if ([type isEqualToString:@FLEXEncodeClass(NSDecimalNumber)]) {
  463. return [NSDecimalNumber decimalNumberWithString:inputString];
  464. } else if ([type isEqualToString:@FLEXEncodeClass(NSNumber)]) {
  465. return number;
  466. }
  467. return nil;
  468. }
  469. // Type encoding is one character, switch on the type
  470. FLEXTypeEncoding type = typeEncoding[0];
  471. uint8_t value[32];
  472. void *bufferStart = &value[0];
  473. // Make sure we box the number with the correct type encoding
  474. // so it can be properly unboxed later via getValue:
  475. switch (type) {
  476. case FLEXTypeEncodingChar:
  477. *(char *)bufferStart = number.charValue; break;
  478. case FLEXTypeEncodingInt:
  479. *(int *)bufferStart = number.intValue; break;
  480. case FLEXTypeEncodingShort:
  481. *(short *)bufferStart = number.shortValue; break;
  482. case FLEXTypeEncodingLong:
  483. *(long *)bufferStart = number.longValue; break;
  484. case FLEXTypeEncodingLongLong:
  485. *(long long *)bufferStart = number.longLongValue; break;
  486. case FLEXTypeEncodingUnsignedChar:
  487. *(unsigned char *)bufferStart = number.unsignedCharValue; break;
  488. case FLEXTypeEncodingUnsignedInt:
  489. *(unsigned int *)bufferStart = number.unsignedIntValue; break;
  490. case FLEXTypeEncodingUnsignedShort:
  491. *(unsigned short *)bufferStart = number.unsignedShortValue; break;
  492. case FLEXTypeEncodingUnsignedLong:
  493. *(unsigned long *)bufferStart = number.unsignedLongValue; break;
  494. case FLEXTypeEncodingUnsignedLongLong:
  495. *(unsigned long long *)bufferStart = number.unsignedLongLongValue; break;
  496. case FLEXTypeEncodingFloat:
  497. *(float *)bufferStart = number.floatValue; break;
  498. case FLEXTypeEncodingDouble:
  499. *(double *)bufferStart = number.doubleValue; break;
  500. case FLEXTypeEncodingLongDouble:
  501. // NSNumber does not support long double
  502. default:
  503. return nil;
  504. }
  505. return [NSValue value:value withObjCType:typeEncoding];
  506. }
  507. + (void)enumerateTypesInStructEncoding:(const char *)structEncoding
  508. usingBlock:(void (^)(NSString *structName,
  509. const char *fieldTypeEncoding,
  510. NSString *prettyTypeEncoding,
  511. NSUInteger fieldIndex,
  512. NSUInteger fieldOffset))typeBlock {
  513. if (structEncoding && structEncoding[0] == FLEXTypeEncodingStructBegin) {
  514. const char *equals = strchr(structEncoding, '=');
  515. if (equals) {
  516. const char *nameStart = structEncoding + 1;
  517. NSString *structName = [@(structEncoding)
  518. substringWithRange:NSMakeRange(nameStart - structEncoding, equals - nameStart)
  519. ];
  520. NSUInteger fieldAlignment = 0, structSize = 0;
  521. if (FLEXGetSizeAndAlignment(structEncoding, &structSize, &fieldAlignment)) {
  522. NSUInteger runningFieldIndex = 0;
  523. NSUInteger runningFieldOffset = 0;
  524. const char *typeStart = equals + 1;
  525. while (*typeStart != FLEXTypeEncodingStructEnd) {
  526. NSUInteger fieldSize = 0;
  527. // If the struct type encoding was successfully handled by
  528. // FLEXGetSizeAndAlignment above, we *should* be ok with the field here.
  529. const char *nextTypeStart = NSGetSizeAndAlignment(typeStart, &fieldSize, NULL);
  530. NSString *typeEncoding = [@(structEncoding)
  531. substringWithRange:NSMakeRange(typeStart - structEncoding, nextTypeStart - typeStart)
  532. ];
  533. // Padding to keep proper alignment. __attribute((packed)) structs
  534. // will break here. The type encoding is no different for packed structs,
  535. // so it's not clear there's anything we can do for those.
  536. const NSUInteger currentSizeSum = runningFieldOffset % fieldAlignment;
  537. if (currentSizeSum != 0 && currentSizeSum + fieldSize > fieldAlignment) {
  538. runningFieldOffset += fieldAlignment - currentSizeSum;
  539. }
  540. typeBlock(
  541. structName,
  542. typeEncoding.UTF8String,
  543. [self readableTypeForEncoding:typeEncoding],
  544. runningFieldIndex,
  545. runningFieldOffset
  546. );
  547. runningFieldOffset += fieldSize;
  548. runningFieldIndex++;
  549. typeStart = nextTypeStart;
  550. }
  551. }
  552. }
  553. }
  554. }
  555. #pragma mark - Metadata Helpers
  556. + (NSDictionary<NSString *, NSString *> *)attributesForProperty:(objc_property_t)property {
  557. NSString *attributes = @(property_getAttributes(property) ?: "");
  558. // Thanks to MAObjcRuntime for inspiration here.
  559. NSArray<NSString *> *attributePairs = [attributes componentsSeparatedByString:@","];
  560. NSMutableDictionary<NSString *, NSString *> *attributesDictionary = [NSMutableDictionary new];
  561. for (NSString *attributePair in attributePairs) {
  562. attributesDictionary[[attributePair substringToIndex:1]] = [attributePair substringFromIndex:1];
  563. }
  564. return attributesDictionary;
  565. }
  566. + (NSString *)appendName:(NSString *)name toType:(NSString *)type {
  567. if (!type.length) {
  568. type = @"(?)";
  569. }
  570. NSString *combined = nil;
  571. if ([type characterAtIndex:type.length - 1] == FLEXTypeEncodingCString) {
  572. combined = [type stringByAppendingString:name];
  573. } else {
  574. combined = [type stringByAppendingFormat:@" %@", name];
  575. }
  576. return combined;
  577. }
  578. + (NSString *)readableTypeForEncoding:(NSString *)encodingString {
  579. if (!encodingString.length) {
  580. return @"?";
  581. }
  582. // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
  583. // class-dump has a much nicer and much more complete implementation for this task, but it is distributed under GPLv2 :/
  584. // See https://github.com/nygard/class-dump/blob/master/Source/CDType.m
  585. // Warning: this method uses multiple middle returns and macros to cut down on boilerplate.
  586. // The use of macros here was inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
  587. const char *encodingCString = encodingString.UTF8String;
  588. // Some fields have a name, such as {Size=\"width\"d\"height\"d}, we need to extract the name out and recursive
  589. const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:encodingCString];
  590. if (fieldNameOffset > 0) {
  591. // According to https://github.com/nygard/class-dump/commit/33fb5ed221810685f57c192e1ce8ab6054949a7c,
  592. // there are some consecutive quoted strings, so use `_` to concatenate the names.
  593. NSString *const fieldNamesString = [encodingString substringWithRange:NSMakeRange(0, fieldNameOffset)];
  594. NSArray<NSString *> *const fieldNames = [fieldNamesString
  595. componentsSeparatedByString:[NSString stringWithFormat:@"%c", FLEXTypeEncodingQuote]
  596. ];
  597. NSMutableString *finalFieldNamesString = [NSMutableString new];
  598. for (NSString *const fieldName in fieldNames) {
  599. if (fieldName.length > 0) {
  600. if (finalFieldNamesString.length > 0) {
  601. [finalFieldNamesString appendString:@"_"];
  602. }
  603. [finalFieldNamesString appendString:fieldName];
  604. }
  605. }
  606. NSString *const recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:fieldNameOffset]];
  607. return [NSString stringWithFormat:@"%@ %@", recursiveType, finalFieldNamesString];
  608. }
  609. // Objects
  610. if (encodingCString[0] == FLEXTypeEncodingObjcObject) {
  611. NSString *class = [encodingString substringFromIndex:1];
  612. class = [class stringByReplacingOccurrencesOfString:@"\"" withString:@""];
  613. if (class.length == 0 || (class.length == 1 && [class characterAtIndex:0] == FLEXTypeEncodingUnknown)) {
  614. class = @"id";
  615. } else {
  616. class = [class stringByAppendingString:@" *"];
  617. }
  618. return class;
  619. }
  620. // Qualifier Prefixes
  621. // Do this first since some of the direct translations (i.e. Method) contain a prefix.
  622. #define RECURSIVE_TRANSLATE(prefix, formatString) \
  623. if (encodingCString[0] == prefix) { \
  624. NSString *recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:1]]; \
  625. return [NSString stringWithFormat:formatString, recursiveType]; \
  626. }
  627. // If there's a qualifier prefix on the encoding, translate it and then
  628. // recursively call this method with the rest of the encoding string.
  629. RECURSIVE_TRANSLATE('^', @"%@ *");
  630. RECURSIVE_TRANSLATE('r', @"const %@");
  631. RECURSIVE_TRANSLATE('n', @"in %@");
  632. RECURSIVE_TRANSLATE('N', @"inout %@");
  633. RECURSIVE_TRANSLATE('o', @"out %@");
  634. RECURSIVE_TRANSLATE('O', @"bycopy %@");
  635. RECURSIVE_TRANSLATE('R', @"byref %@");
  636. RECURSIVE_TRANSLATE('V', @"oneway %@");
  637. RECURSIVE_TRANSLATE('b', @"bitfield(%@)");
  638. #undef RECURSIVE_TRANSLATE
  639. // C Types
  640. #define TRANSLATE(ctype) \
  641. if (strcmp(encodingCString, @encode(ctype)) == 0) { \
  642. return (NSString *)CFSTR(#ctype); \
  643. }
  644. // Order matters here since some of the cocoa types are typedefed to c types.
  645. // We can't recover the exact mapping, but we choose to prefer the cocoa types.
  646. // This is not an exhaustive list, but it covers the most common types
  647. TRANSLATE(CGRect);
  648. TRANSLATE(CGPoint);
  649. TRANSLATE(CGSize);
  650. TRANSLATE(CGVector);
  651. TRANSLATE(UIEdgeInsets);
  652. if (@available(iOS 11.0, *)) {
  653. TRANSLATE(NSDirectionalEdgeInsets);
  654. }
  655. TRANSLATE(UIOffset);
  656. TRANSLATE(NSRange);
  657. TRANSLATE(CGAffineTransform);
  658. TRANSLATE(CATransform3D);
  659. TRANSLATE(CGColorRef);
  660. TRANSLATE(CGPathRef);
  661. TRANSLATE(CGContextRef);
  662. TRANSLATE(NSInteger);
  663. TRANSLATE(NSUInteger);
  664. TRANSLATE(CGFloat);
  665. TRANSLATE(BOOL);
  666. TRANSLATE(int);
  667. TRANSLATE(short);
  668. TRANSLATE(long);
  669. TRANSLATE(long long);
  670. TRANSLATE(unsigned char);
  671. TRANSLATE(unsigned int);
  672. TRANSLATE(unsigned short);
  673. TRANSLATE(unsigned long);
  674. TRANSLATE(unsigned long long);
  675. TRANSLATE(float);
  676. TRANSLATE(double);
  677. TRANSLATE(long double);
  678. TRANSLATE(char *);
  679. TRANSLATE(Class);
  680. TRANSLATE(objc_property_t);
  681. TRANSLATE(Ivar);
  682. TRANSLATE(Method);
  683. TRANSLATE(Category);
  684. TRANSLATE(NSZone *);
  685. TRANSLATE(SEL);
  686. TRANSLATE(void);
  687. #undef TRANSLATE
  688. // For structs, we only use the name of the structs
  689. if (encodingCString[0] == FLEXTypeEncodingStructBegin) {
  690. // Special case: std::string
  691. if ([encodingString hasPrefix:@"{basic_string<char"]) {
  692. return @"std::string";
  693. }
  694. const char *equals = strchr(encodingCString, '=');
  695. if (equals) {
  696. const char *nameStart = encodingCString + 1;
  697. // For anonymous structs
  698. if (nameStart[0] == FLEXTypeEncodingUnknown) {
  699. return @"anonymous struct";
  700. } else {
  701. NSString *const structName = [encodingString
  702. substringWithRange:NSMakeRange(nameStart - encodingCString, equals - nameStart)
  703. ];
  704. return structName;
  705. }
  706. }
  707. }
  708. // If we couldn't translate, just return the original encoding string
  709. return encodingString;
  710. }
  711. #pragma mark - Internal Helpers
  712. + (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type {
  713. // Remove the field name if there is any (e.g. \"width\"d -> d)
  714. const NSUInteger fieldNameOffset = [FLEXRuntimeUtility fieldNameOffsetForTypeEncoding:type];
  715. if (fieldNameOffset > 0) {
  716. return [self valueForPrimitivePointer:pointer objCType:type + fieldNameOffset];
  717. }
  718. // CASE macro inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
  719. #define CASE(ctype, selectorpart) \
  720. if (strcmp(type, @encode(ctype)) == 0) { \
  721. return [NSNumber numberWith ## selectorpart: *(ctype *)pointer]; \
  722. }
  723. CASE(BOOL, Bool);
  724. CASE(unsigned char, UnsignedChar);
  725. CASE(short, Short);
  726. CASE(unsigned short, UnsignedShort);
  727. CASE(int, Int);
  728. CASE(unsigned int, UnsignedInt);
  729. CASE(long, Long);
  730. CASE(unsigned long, UnsignedLong);
  731. CASE(long long, LongLong);
  732. CASE(unsigned long long, UnsignedLongLong);
  733. CASE(float, Float);
  734. CASE(double, Double);
  735. CASE(long double, Double);
  736. #undef CASE
  737. NSValue *value = nil;
  738. if (FLEXGetSizeAndAlignment(type, nil, nil)) {
  739. @try {
  740. value = [NSValue valueWithBytes:pointer objCType:type];
  741. } @catch (NSException *exception) {
  742. // Certain type encodings are not supported by valueWithBytes:objCType:.
  743. // Just fail silently if an exception is thrown.
  744. }
  745. }
  746. return value;
  747. }
  748. @end