FLEXRuntimeUtility.m 32 KB

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