FLEXRuntimeUtility.m 38 KB

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