Aspects.m 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. //
  2. // Aspects.m
  3. // Aspects - A delightful, simple library for aspect oriented programming.
  4. //
  5. // Copyright (c) 2014 Peter Steinberger. Licensed under the MIT license.
  6. //
  7. #import "Aspects.h"
  8. #import <libkern/OSAtomic.h>
  9. #import <objc/runtime.h>
  10. #import <objc/message.h>
  11. #define AspectLog(...)
  12. //#define AspectLog(...) do { NSLog(__VA_ARGS__); }while(0)
  13. #define AspectLogError(...) do { NSLog(__VA_ARGS__); }while(0)
  14. // Block internals.
  15. typedef NS_OPTIONS(int, AspectBlockFlags) {
  16. AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
  17. AspectBlockFlagsHasSignature = (1 << 30)
  18. };
  19. typedef struct _AspectBlock {
  20. __unused Class isa;
  21. AspectBlockFlags flags;
  22. __unused int reserved;
  23. void (__unused *invoke)(struct _AspectBlock *block, ...);
  24. struct {
  25. unsigned long int reserved;
  26. unsigned long int size;
  27. // requires AspectBlockFlagsHasCopyDisposeHelpers
  28. void (*copy)(void *dst, const void *src);
  29. void (*dispose)(const void *);
  30. // requires AspectBlockFlagsHasSignature
  31. const char *signature;
  32. const char *layout;
  33. } *descriptor;
  34. // imported variables
  35. } *AspectBlockRef;
  36. @interface AspectInfo : NSObject <AspectInfo>
  37. - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
  38. @property (nonatomic, unsafe_unretained, readonly) id instance;
  39. @property (nonatomic, strong, readonly) NSArray *arguments;
  40. @property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
  41. @end
  42. // Tracks a single aspect.
  43. @interface AspectIdentifier : NSObject
  44. + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
  45. - (BOOL)invokeWithInfo:(id<AspectInfo>)info;
  46. @property (nonatomic, assign) SEL selector;
  47. @property (nonatomic, strong) id block;
  48. @property (nonatomic, strong) NSMethodSignature *blockSignature;
  49. @property (nonatomic, weak) id object;
  50. @property (nonatomic, assign) AspectOptions options;
  51. @end
  52. // Tracks all aspects for an object/class.
  53. @interface AspectsContainer : NSObject
  54. - (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
  55. - (BOOL)removeAspect:(id)aspect;
  56. - (BOOL)hasAspects;
  57. @property (atomic, copy) NSArray *beforeAspects;
  58. @property (atomic, copy) NSArray *insteadAspects;
  59. @property (atomic, copy) NSArray *afterAspects;
  60. @end
  61. @interface AspectTracker : NSObject
  62. - (id)initWithTrackedClass:(Class)trackedClass parent:(AspectTracker *)parent;
  63. @property (nonatomic, strong) Class trackedClass;
  64. @property (nonatomic, strong) NSMutableSet *selectorNames;
  65. @property (nonatomic, weak) AspectTracker *parentEntry;
  66. @end
  67. @interface NSInvocation (Aspects)
  68. - (NSArray *)aspects_arguments;
  69. @end
  70. #define AspectPositionFilter 0x07
  71. #define AspectError(errorCode, errorDescription) do { \
  72. AspectLogError(@"Aspects: %@", errorDescription); \
  73. if (error) { *error = [NSError errorWithDomain:AspectErrorDomain code:errorCode userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; }}while(0)
  74. NSString *const AspectErrorDomain = @"AspectErrorDomain";
  75. static NSString *const AspectsSubclassSuffix = @"_Aspects_";
  76. static NSString *const AspectsMessagePrefix = @"aspects_";
  77. @implementation NSObject (Aspects)
  78. ///////////////////////////////////////////////////////////////////////////////////////////
  79. #pragma mark - Public Aspects API
  80. + (id<AspectToken>)aspect_hookSelector:(SEL)selector
  81. withOptions:(AspectOptions)options
  82. usingBlock:(id)block
  83. error:(NSError **)error {
  84. return aspect_add((id)self, selector, options, block, error);
  85. }
  86. /// @return A token which allows to later deregister the aspect.
  87. - (id<AspectToken>)aspect_hookSelector:(SEL)selector
  88. withOptions:(AspectOptions)options
  89. usingBlock:(id)block
  90. error:(NSError **)error {
  91. return aspect_add(self, selector, options, block, error);
  92. }
  93. ///////////////////////////////////////////////////////////////////////////////////////////
  94. #pragma mark - Private Helper
  95. static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
  96. NSCParameterAssert(self);
  97. NSCParameterAssert(selector);
  98. NSCParameterAssert(block);
  99. __block AspectIdentifier *identifier = nil;
  100. aspect_performLocked(^{
  101. if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
  102. AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
  103. identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
  104. if (identifier) {
  105. [aspectContainer addAspect:identifier withOptions:options];
  106. // Modify the class to allow message interception.
  107. aspect_prepareClassAndHookSelector(self, selector, error);
  108. }
  109. }
  110. });
  111. return identifier;
  112. }
  113. static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) {
  114. NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type.");
  115. __block BOOL success = NO;
  116. aspect_performLocked(^{
  117. id self = aspect.object; // strongify
  118. if (self) {
  119. AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
  120. success = [aspectContainer removeAspect:aspect];
  121. aspect_cleanupHookedClassAndSelector(self, aspect.selector);
  122. // destroy token
  123. aspect.object = nil;
  124. aspect.block = nil;
  125. aspect.selector = NULL;
  126. }else {
  127. NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
  128. AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
  129. }
  130. });
  131. return success;
  132. }
  133. static void aspect_performLocked(dispatch_block_t block) {
  134. static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
  135. OSSpinLockLock(&aspect_lock);
  136. block();
  137. OSSpinLockUnlock(&aspect_lock);
  138. }
  139. static SEL aspect_aliasForSelector(SEL selector) {
  140. NSCParameterAssert(selector);
  141. return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);
  142. }
  143. static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
  144. AspectBlockRef layout = (__bridge void *)block;
  145. if (!(layout->flags & AspectBlockFlagsHasSignature)) {
  146. NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
  147. AspectError(AspectErrorMissingBlockSignature, description);
  148. return nil;
  149. }
  150. void *desc = layout->descriptor;
  151. desc += 2 * sizeof(unsigned long int);
  152. if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
  153. desc += 2 * sizeof(void *);
  154. }
  155. if (!desc) {
  156. NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
  157. AspectError(AspectErrorMissingBlockSignature, description);
  158. return nil;
  159. }
  160. const char *signature = (*(const char **)desc);
  161. return [NSMethodSignature signatureWithObjCTypes:signature];
  162. }
  163. static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
  164. NSCParameterAssert(blockSignature);
  165. NSCParameterAssert(object);
  166. NSCParameterAssert(selector);
  167. BOOL signaturesMatch = YES;
  168. NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
  169. if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
  170. signaturesMatch = NO;
  171. }else {
  172. if (blockSignature.numberOfArguments > 1) {
  173. const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
  174. if (blockType[0] != '@') {
  175. signaturesMatch = NO;
  176. }
  177. }
  178. // Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2.
  179. // The block can have less arguments than the method, that's ok.
  180. if (signaturesMatch) {
  181. for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
  182. const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
  183. const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
  184. // Only compare parameter, not the optional type data.
  185. if (!methodType || !blockType || methodType[0] != blockType[0]) {
  186. signaturesMatch = NO; break;
  187. }
  188. }
  189. }
  190. }
  191. if (!signaturesMatch) {
  192. NSString *description = [NSString stringWithFormat:@"Blog signature %@ doesn't match %@.", blockSignature, methodSignature];
  193. AspectError(AspectErrorIncompatibleBlockSignature, description);
  194. return NO;
  195. }
  196. return YES;
  197. }
  198. ///////////////////////////////////////////////////////////////////////////////////////////
  199. #pragma mark - Class + Selector Preparation
  200. static BOOL aspect_isMsgForwardIMP(IMP impl) {
  201. return impl == _objc_msgForward
  202. #if !defined(__arm64__)
  203. || impl == (IMP)_objc_msgForward_stret
  204. #endif
  205. ;
  206. }
  207. static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) {
  208. IMP msgForwardIMP = _objc_msgForward;
  209. #if !defined(__arm64__)
  210. // As an ugly internal runtime implementation detail in the 32bit runtime, we need to determine of the method we hook returns a struct or anything larger than id.
  211. // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html
  212. // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783
  213. // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4)
  214. Method method = class_getInstanceMethod(self.class, selector);
  215. const char *encoding = method_getTypeEncoding(method);
  216. BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;
  217. if (methodReturnsStructValue) {
  218. @try {
  219. NSUInteger valueSize = 0;
  220. NSGetSizeAndAlignment(encoding, &valueSize, NULL);
  221. if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
  222. methodReturnsStructValue = NO;
  223. }
  224. } @catch (NSException *e) {}
  225. }
  226. if (methodReturnsStructValue) {
  227. msgForwardIMP = (IMP)_objc_msgForward_stret;
  228. }
  229. #endif
  230. return msgForwardIMP;
  231. }
  232. static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
  233. NSCParameterAssert(selector);
  234. Class klass = aspect_hookClass(self, error);
  235. Method targetMethod = class_getInstanceMethod(klass, selector);
  236. IMP targetMethodIMP = method_getImplementation(targetMethod);
  237. if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
  238. // Make a method alias for the existing method implementation, it not already copied.
  239. const char *typeEncoding = method_getTypeEncoding(targetMethod);
  240. SEL aliasSelector = aspect_aliasForSelector(selector);
  241. if (![klass instancesRespondToSelector:aliasSelector]) {
  242. __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
  243. NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
  244. }
  245. // We use forwardInvocation to hook in.
  246. class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
  247. AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
  248. }
  249. }
  250. // Will undo the runtime changes made.
  251. static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
  252. NSCParameterAssert(self);
  253. NSCParameterAssert(selector);
  254. Class klass = object_getClass(self);
  255. BOOL isMetaClass = class_isMetaClass(klass);
  256. if (isMetaClass) {
  257. klass = (Class)self;
  258. }
  259. // Check if the method is marked as forwarded and undo that.
  260. Method targetMethod = class_getInstanceMethod(klass, selector);
  261. IMP targetMethodIMP = method_getImplementation(targetMethod);
  262. if (aspect_isMsgForwardIMP(targetMethodIMP)) {
  263. // Restore the original method implementation.
  264. const char *typeEncoding = method_getTypeEncoding(targetMethod);
  265. SEL aliasSelector = aspect_aliasForSelector(selector);
  266. Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
  267. IMP originalIMP = method_getImplementation(originalMethod);
  268. NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
  269. class_replaceMethod(klass, selector, originalIMP, typeEncoding);
  270. AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
  271. }
  272. // Deregister global tracked selector
  273. aspect_deregisterTrackedSelector(self, selector);
  274. // Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
  275. AspectsContainer *container = aspect_getContainerForObject(self, selector);
  276. if (!container.hasAspects) {
  277. // Destroy the container
  278. aspect_destroyContainerForObject(self, selector);
  279. // Figure out how the class was modified to undo the changes.
  280. NSString *className = NSStringFromClass(klass);
  281. if ([className hasSuffix:AspectsSubclassSuffix]) {
  282. Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
  283. NSCAssert(originalClass != nil, @"Original class must exist");
  284. object_setClass(self, originalClass);
  285. AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));
  286. // We can only dispose the class pair if we can ensure that no instances exist using our subclass.
  287. // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
  288. //objc_disposeClassPair(object.class);
  289. }else {
  290. // Class is most likely swizzled in place. Undo that.
  291. if (isMetaClass) {
  292. aspect_undoSwizzleClassInPlace((Class)self);
  293. }
  294. }
  295. }
  296. }
  297. ///////////////////////////////////////////////////////////////////////////////////////////
  298. #pragma mark - Hook Class
  299. static Class aspect_hookClass(NSObject *self, NSError **error) {
  300. NSCParameterAssert(self);
  301. Class statedClass = self.class;
  302. Class baseClass = object_getClass(self);
  303. NSString *className = NSStringFromClass(baseClass);
  304. // Already subclassed
  305. if ([className hasSuffix:AspectsSubclassSuffix]) {
  306. return baseClass;
  307. // We swizzle a class object, not a single object.
  308. }else if (class_isMetaClass(baseClass)) {
  309. return aspect_swizzleClassInPlace((Class)self);
  310. // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
  311. }else if (statedClass != baseClass) {
  312. return aspect_swizzleClassInPlace(baseClass);
  313. }
  314. // Default case. Create dynamic subclass.
  315. const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
  316. Class subclass = objc_getClass(subclassName);
  317. if (subclass == nil) {
  318. subclass = objc_allocateClassPair(baseClass, subclassName, 0);
  319. if (subclass == nil) {
  320. NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
  321. AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
  322. return nil;
  323. }
  324. aspect_swizzleForwardInvocation(subclass);
  325. aspect_hookedGetClass(subclass, statedClass);
  326. aspect_hookedGetClass(object_getClass(subclass), statedClass);
  327. objc_registerClassPair(subclass);
  328. }
  329. object_setClass(self, subclass);
  330. return subclass;
  331. }
  332. static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
  333. static void aspect_swizzleForwardInvocation(Class klass) {
  334. NSCParameterAssert(klass);
  335. // If there is no method, replace will act like class_addMethod.
  336. IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
  337. if (originalImplementation) {
  338. class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
  339. }
  340. AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
  341. }
  342. static void aspect_undoSwizzleForwardInvocation(Class klass) {
  343. NSCParameterAssert(klass);
  344. Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName));
  345. Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:));
  346. // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy.
  347. IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod);
  348. class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@");
  349. AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass));
  350. }
  351. static void aspect_hookedGetClass(Class class, Class statedClass) {
  352. NSCParameterAssert(class);
  353. NSCParameterAssert(statedClass);
  354. Method method = class_getInstanceMethod(class, @selector(class));
  355. IMP newIMP = imp_implementationWithBlock(^(id self) {
  356. return statedClass;
  357. });
  358. class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
  359. }
  360. ///////////////////////////////////////////////////////////////////////////////////////////
  361. #pragma mark - Swizzle Class In Place
  362. static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
  363. static NSMutableSet *swizzledClasses;
  364. static dispatch_once_t pred;
  365. dispatch_once(&pred, ^{
  366. swizzledClasses = [NSMutableSet new];
  367. });
  368. @synchronized(swizzledClasses) {
  369. block(swizzledClasses);
  370. }
  371. }
  372. static Class aspect_swizzleClassInPlace(Class klass) {
  373. NSCParameterAssert(klass);
  374. NSString *className = NSStringFromClass(klass);
  375. _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
  376. if (![swizzledClasses containsObject:className]) {
  377. aspect_swizzleForwardInvocation(klass);
  378. [swizzledClasses addObject:className];
  379. }
  380. });
  381. return klass;
  382. }
  383. static void aspect_undoSwizzleClassInPlace(Class klass) {
  384. NSCParameterAssert(klass);
  385. NSString *className = NSStringFromClass(klass);
  386. _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
  387. if ([swizzledClasses containsObject:className]) {
  388. aspect_undoSwizzleForwardInvocation(klass);
  389. [swizzledClasses removeObject:className];
  390. }
  391. });
  392. }
  393. ///////////////////////////////////////////////////////////////////////////////////////////
  394. #pragma mark - Aspect Invoke Point
  395. // This is a macro so we get a cleaner stack trace.
  396. #define aspect_invoke(aspects, info) \
  397. for (AspectIdentifier *aspect in aspects) {\
  398. [aspect invokeWithInfo:info];\
  399. if (aspect.options & AspectOptionAutomaticRemoval) { \
  400. aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
  401. } \
  402. }
  403. // This is the swizzled forwardInvocation: method.
  404. static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
  405. NSCParameterAssert(self);
  406. NSCParameterAssert(invocation);
  407. SEL originalSelector = invocation.selector;
  408. SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
  409. invocation.selector = aliasSelector;
  410. AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
  411. AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
  412. AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
  413. NSArray *aspectsToRemove = nil;
  414. // Before hooks.
  415. aspect_invoke(classContainer.beforeAspects, info);
  416. aspect_invoke(objectContainer.beforeAspects, info);
  417. // Instead hooks.
  418. BOOL respondsToAlias = YES;
  419. if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
  420. aspect_invoke(classContainer.insteadAspects, info);
  421. aspect_invoke(objectContainer.insteadAspects, info);
  422. }else {
  423. Class klass = object_getClass(invocation.target);
  424. do {
  425. if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
  426. [invocation invoke];
  427. break;
  428. }
  429. }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
  430. }
  431. // After hooks.
  432. aspect_invoke(classContainer.afterAspects, info);
  433. aspect_invoke(objectContainer.afterAspects, info);
  434. // If no hooks are installed, call original implementation (usually to throw an exception)
  435. if (!respondsToAlias) {
  436. invocation.selector = originalSelector;
  437. SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
  438. if ([self respondsToSelector:originalForwardInvocationSEL]) {
  439. ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
  440. }else {
  441. [self doesNotRecognizeSelector:invocation.selector];
  442. }
  443. }
  444. // Remove any hooks that are queued for deregistration.
  445. [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
  446. }
  447. #undef aspect_invoke
  448. ///////////////////////////////////////////////////////////////////////////////////////////
  449. #pragma mark - Aspect Container Management
  450. // Loads or creates the aspect container.
  451. static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
  452. NSCParameterAssert(self);
  453. SEL aliasSelector = aspect_aliasForSelector(selector);
  454. AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
  455. if (!aspectContainer) {
  456. aspectContainer = [AspectsContainer new];
  457. objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
  458. }
  459. return aspectContainer;
  460. }
  461. static AspectsContainer *aspect_getContainerForClass(Class klass, SEL selector) {
  462. NSCParameterAssert(klass);
  463. AspectsContainer *classContainer = nil;
  464. do {
  465. classContainer = objc_getAssociatedObject(klass, selector);
  466. if (classContainer.hasAspects) break;
  467. }while ((klass = class_getSuperclass(klass)));
  468. return classContainer;
  469. }
  470. static void aspect_destroyContainerForObject(id<NSObject> self, SEL selector) {
  471. NSCParameterAssert(self);
  472. SEL aliasSelector = aspect_aliasForSelector(selector);
  473. objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN);
  474. }
  475. ///////////////////////////////////////////////////////////////////////////////////////////
  476. #pragma mark - Selector Blacklist Checking
  477. static NSMutableDictionary *aspect_getSwizzledClassesDict() {
  478. static NSMutableDictionary *swizzledClassesDict;
  479. static dispatch_once_t pred;
  480. dispatch_once(&pred, ^{
  481. swizzledClassesDict = [NSMutableDictionary new];
  482. });
  483. return swizzledClassesDict;
  484. }
  485. static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
  486. static NSSet *disallowedSelectorList;
  487. static dispatch_once_t pred;
  488. dispatch_once(&pred, ^{
  489. disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
  490. });
  491. // Check against the blacklist.
  492. NSString *selectorName = NSStringFromSelector(selector);
  493. if ([disallowedSelectorList containsObject:selectorName]) {
  494. NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
  495. AspectError(AspectErrorSelectorBlacklisted, errorDescription);
  496. return NO;
  497. }
  498. // Additional checks.
  499. AspectOptions position = options&AspectPositionFilter;
  500. if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
  501. NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
  502. AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
  503. return NO;
  504. }
  505. if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
  506. NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
  507. AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
  508. return NO;
  509. }
  510. // Search for the current class and the class hierarchy IF we are modifying a class object
  511. if (class_isMetaClass(object_getClass(self))) {
  512. Class klass = [self class];
  513. NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
  514. Class currentClass = [self class];
  515. do {
  516. AspectTracker *tracker = swizzledClassesDict[currentClass];
  517. if ([tracker.selectorNames containsObject:selectorName]) {
  518. // Find the topmost class for the log.
  519. if (tracker.parentEntry) {
  520. AspectTracker *topmostEntry = tracker.parentEntry;
  521. while (topmostEntry.parentEntry) {
  522. topmostEntry = topmostEntry.parentEntry;
  523. }
  524. NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(topmostEntry.trackedClass)];
  525. AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
  526. return NO;
  527. }else if (klass == currentClass) {
  528. // Already modified and topmost!
  529. return YES;
  530. }
  531. }
  532. }while ((currentClass = class_getSuperclass(currentClass)));
  533. // Add the selector as being modified.
  534. currentClass = klass;
  535. AspectTracker *parentTracker = nil;
  536. do {
  537. AspectTracker *tracker = swizzledClassesDict[currentClass];
  538. if (!tracker) {
  539. tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass parent:parentTracker];
  540. swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
  541. }
  542. [tracker.selectorNames addObject:selectorName];
  543. // All superclasses get marked as having a subclass that is modified.
  544. parentTracker = tracker;
  545. }while ((currentClass = class_getSuperclass(currentClass)));
  546. }
  547. return YES;
  548. }
  549. static void aspect_deregisterTrackedSelector(id self, SEL selector) {
  550. if (!class_isMetaClass(object_getClass(self))) return;
  551. NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
  552. NSString *selectorName = NSStringFromSelector(selector);
  553. Class currentClass = [self class];
  554. do {
  555. AspectTracker *tracker = swizzledClassesDict[currentClass];
  556. if (tracker) {
  557. [tracker.selectorNames removeObject:selectorName];
  558. if (tracker.selectorNames.count == 0) {
  559. [swizzledClassesDict removeObjectForKey:tracker];
  560. }
  561. }
  562. }while ((currentClass = class_getSuperclass(currentClass)));
  563. }
  564. @end
  565. @implementation AspectTracker
  566. - (id)initWithTrackedClass:(Class)trackedClass parent:(AspectTracker *)parent {
  567. if (self = [super init]) {
  568. _trackedClass = trackedClass;
  569. _parentEntry = parent;
  570. _selectorNames = [NSMutableSet new];
  571. }
  572. return self;
  573. }
  574. - (NSString *)description {
  575. return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, parent:%p>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.parentEntry];
  576. }
  577. @end
  578. ///////////////////////////////////////////////////////////////////////////////////////////
  579. #pragma mark - NSInvocation (Aspects)
  580. @implementation NSInvocation (Aspects)
  581. // Thanks to the ReactiveCocoa team for providing a generic solution for this.
  582. - (id)aspect_argumentAtIndex:(NSUInteger)index {
  583. const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
  584. // Skip const type qualifier.
  585. if (argType[0] == _C_CONST) argType++;
  586. #define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)
  587. if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
  588. __autoreleasing id returnObj;
  589. [self getArgument:&returnObj atIndex:(NSInteger)index];
  590. return returnObj;
  591. } else if (strcmp(argType, @encode(SEL)) == 0) {
  592. SEL selector = 0;
  593. [self getArgument:&selector atIndex:(NSInteger)index];
  594. return NSStringFromSelector(selector);
  595. } else if (strcmp(argType, @encode(Class)) == 0) {
  596. __autoreleasing Class theClass = Nil;
  597. [self getArgument:&theClass atIndex:(NSInteger)index];
  598. return theClass;
  599. // Using this list will box the number with the appropriate constructor, instead of the generic NSValue.
  600. } else if (strcmp(argType, @encode(char)) == 0) {
  601. WRAP_AND_RETURN(char);
  602. } else if (strcmp(argType, @encode(int)) == 0) {
  603. WRAP_AND_RETURN(int);
  604. } else if (strcmp(argType, @encode(short)) == 0) {
  605. WRAP_AND_RETURN(short);
  606. } else if (strcmp(argType, @encode(long)) == 0) {
  607. WRAP_AND_RETURN(long);
  608. } else if (strcmp(argType, @encode(long long)) == 0) {
  609. WRAP_AND_RETURN(long long);
  610. } else if (strcmp(argType, @encode(unsigned char)) == 0) {
  611. WRAP_AND_RETURN(unsigned char);
  612. } else if (strcmp(argType, @encode(unsigned int)) == 0) {
  613. WRAP_AND_RETURN(unsigned int);
  614. } else if (strcmp(argType, @encode(unsigned short)) == 0) {
  615. WRAP_AND_RETURN(unsigned short);
  616. } else if (strcmp(argType, @encode(unsigned long)) == 0) {
  617. WRAP_AND_RETURN(unsigned long);
  618. } else if (strcmp(argType, @encode(unsigned long long)) == 0) {
  619. WRAP_AND_RETURN(unsigned long long);
  620. } else if (strcmp(argType, @encode(float)) == 0) {
  621. WRAP_AND_RETURN(float);
  622. } else if (strcmp(argType, @encode(double)) == 0) {
  623. WRAP_AND_RETURN(double);
  624. } else if (strcmp(argType, @encode(BOOL)) == 0) {
  625. WRAP_AND_RETURN(BOOL);
  626. } else if (strcmp(argType, @encode(bool)) == 0) {
  627. WRAP_AND_RETURN(BOOL);
  628. } else if (strcmp(argType, @encode(char *)) == 0) {
  629. WRAP_AND_RETURN(const char *);
  630. } else if (strcmp(argType, @encode(void (^)(void))) == 0) {
  631. __unsafe_unretained id block = nil;
  632. [self getArgument:&block atIndex:(NSInteger)index];
  633. return [block copy];
  634. } else {
  635. NSUInteger valueSize = 0;
  636. NSGetSizeAndAlignment(argType, &valueSize, NULL);
  637. unsigned char valueBytes[valueSize];
  638. [self getArgument:valueBytes atIndex:(NSInteger)index];
  639. return [NSValue valueWithBytes:valueBytes objCType:argType];
  640. }
  641. return nil;
  642. #undef WRAP_AND_RETURN
  643. }
  644. - (NSArray *)aspects_arguments {
  645. NSMutableArray *argumentsArray = [NSMutableArray array];
  646. for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
  647. [argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null];
  648. }
  649. return [argumentsArray copy];
  650. }
  651. @end
  652. ///////////////////////////////////////////////////////////////////////////////////////////
  653. #pragma mark - AspectIdentifier
  654. @implementation AspectIdentifier
  655. + (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
  656. NSCParameterAssert(block);
  657. NSCParameterAssert(selector);
  658. NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
  659. if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
  660. return nil;
  661. }
  662. AspectIdentifier *identifier = nil;
  663. if (blockSignature) {
  664. identifier = [AspectIdentifier new];
  665. identifier.selector = selector;
  666. identifier.block = block;
  667. identifier.blockSignature = blockSignature;
  668. identifier.options = options;
  669. identifier.object = object; // weak
  670. }
  671. return identifier;
  672. }
  673. - (BOOL)invokeWithInfo:(id<AspectInfo>)info {
  674. NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
  675. NSInvocation *originalInvocation = info.originalInvocation;
  676. NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
  677. // Be extra paranoid. We already check that on hook registration.
  678. if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
  679. AspectLogError(@"Block has too many arguments. Not calling %@", info);
  680. return NO;
  681. }
  682. // The `self` of the block will be the AspectInfo. Optional.
  683. if (numberOfArguments > 1) {
  684. [blockInvocation setArgument:&info atIndex:1];
  685. }
  686. void *argBuf = NULL;
  687. for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
  688. const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
  689. NSUInteger argSize;
  690. NSGetSizeAndAlignment(type, &argSize, NULL);
  691. if (!(argBuf = reallocf(argBuf, argSize))) {
  692. AspectLogError(@"Failed to allocate memory for block invocation.");
  693. return NO;
  694. }
  695. [originalInvocation getArgument:argBuf atIndex:idx];
  696. [blockInvocation setArgument:argBuf atIndex:idx];
  697. }
  698. [blockInvocation invokeWithTarget:self.block];
  699. if (argBuf != NULL) {
  700. free(argBuf);
  701. }
  702. return YES;
  703. }
  704. - (NSString *)description {
  705. return [NSString stringWithFormat:@"<%@: %p, SEL:%@ object:%@ options:%tu block:%@ (#%tu args)>", self.class, self, NSStringFromSelector(self.selector), self.object, self.options, self.block, self.blockSignature.numberOfArguments];
  706. }
  707. - (BOOL)remove {
  708. return aspect_remove(self, NULL);
  709. }
  710. @end
  711. ///////////////////////////////////////////////////////////////////////////////////////////
  712. #pragma mark - AspectsContainer
  713. @implementation AspectsContainer
  714. - (BOOL)hasAspects {
  715. return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0;
  716. }
  717. - (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options {
  718. NSParameterAssert(aspect);
  719. NSUInteger position = options&AspectPositionFilter;
  720. switch (position) {
  721. case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
  722. case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
  723. case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break;
  724. }
  725. }
  726. - (BOOL)removeAspect:(id)aspect {
  727. for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)),
  728. NSStringFromSelector(@selector(insteadAspects)),
  729. NSStringFromSelector(@selector(afterAspects))]) {
  730. NSArray *array = [self valueForKey:aspectArrayName];
  731. NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
  732. if (array && index != NSNotFound) {
  733. NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
  734. [newArray removeObjectAtIndex:index];
  735. [self setValue:newArray forKey:aspectArrayName];
  736. return YES;
  737. }
  738. }
  739. return NO;
  740. }
  741. - (NSString *)description {
  742. return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeAspects, self.insteadAspects, self.afterAspects];
  743. }
  744. @end
  745. ///////////////////////////////////////////////////////////////////////////////////////////
  746. #pragma mark - AspectInfo
  747. @implementation AspectInfo
  748. @synthesize arguments = _arguments;
  749. - (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation {
  750. NSCParameterAssert(instance);
  751. NSCParameterAssert(invocation);
  752. if (self = [super init]) {
  753. _instance = instance;
  754. _originalInvocation = invocation;
  755. }
  756. return self;
  757. }
  758. - (NSArray *)arguments {
  759. // Lazily evaluate arguments, boxing is expensive.
  760. if (!_arguments) {
  761. _arguments = self.originalInvocation.aspects_arguments;
  762. }
  763. return _arguments;
  764. }
  765. @end