FLEXRuntimeUtility.m 34 KB

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