KWIntercept.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. //
  2. // Licensed under the terms in License.txt
  3. //
  4. // Copyright 2010 Allen Ding. All rights reserved.
  5. //
  6. #import "KWIntercept.h"
  7. #import "KWMessagePattern.h"
  8. #import "KWMessageSpying.h"
  9. #import "KWStub.h"
  10. static const char * const KWInterceptClassSuffix = "_KWIntercept";
  11. void KWObjectStubsInit(void);
  12. void KWClearObjectStubs(id anObject);
  13. void KWClearAllObjectStubs(void);
  14. NSMutableArray *KWObjectStubsForObject(id anObject);
  15. void KWObjectStubsSet(id anObject, NSMutableArray *stubs);
  16. void KWMessageSpiesInit(void);
  17. NSMapTable *KWMessageSpiesForObject(id anObject);
  18. void KWClearMessageSpies(id anObject);
  19. void KWMessageSpiesSet(id anObject, NSMapTable *spies);
  20. Class KWRestoreOriginalClass(id anObject);
  21. BOOL KWObjectClassRestored(id anObject);
  22. typedef id (^KWInterceptedObjectBlock)(void);
  23. // Use KWInterceptedObjectKey, instead of the object itself, when
  24. // registering an object in a global map table, to prevent an infinite
  25. // loop when the object is hashed.
  26. KWInterceptedObjectBlock KWInterceptedObjectKey(id anObject);
  27. #pragma mark - Intercept Enabled Method Implementations
  28. void KWInterceptedForwardInvocation(id anObject, SEL aSelector, NSInvocation* anInvocation);
  29. void KWInterceptedDealloc(id anObject, SEL aSelector);
  30. Class KWInterceptedClass(id anObject, SEL aSelector);
  31. Class KWInterceptedSuperclass(id anObject, SEL aSelector);
  32. #pragma mark - Getting Forwarding Implementations
  33. #pragma clang diagnostic push
  34. #pragma clang diagnostic ignored "-Wundeclared-selector"
  35. IMP KWRegularForwardingImplementation(void) {
  36. return class_getMethodImplementation([NSObject class], @selector(KWNonExistantSelector));
  37. }
  38. IMP KWStretForwardingImplementation(void) {
  39. #ifndef __arm64__
  40. return class_getMethodImplementation_stret([NSObject class], @selector(KWNonExistantSelector));
  41. #else
  42. return class_getMethodImplementation([NSObject class], @selector(KWNonExistantSelector));
  43. #endif
  44. }
  45. #pragma clang diagnostic pop
  46. IMP KWForwardingImplementationForMethodEncoding(const char* encoding) {
  47. #if TARGET_CPU_ARM
  48. const NSUInteger stretLengthThreshold = 4;
  49. #elif TARGET_CPU_X86
  50. const NSUInteger stretLengthThreshold = 8;
  51. #elif TARGET_CPU_X86_64
  52. const NSUInteger stretLengthThreshold = 16;
  53. #else
  54. // TODO: This just makes an assumption right now. Expand to support all
  55. // official architectures correctly.
  56. const NSUInteger stretLengthThreshold = 8;
  57. #endif // #if TARGET_CPU_ARM
  58. NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:encoding];
  59. if (*[signature methodReturnType] == '{' && [signature methodReturnLength] > stretLengthThreshold) {
  60. NSLog(@"Warning: The Objective-C runtime appears to have bugs when forwarding messages with certain struct layouts as return types, so if a crash occurs this could be the culprit");
  61. return KWStretForwardingImplementation();
  62. } else {
  63. return KWRegularForwardingImplementation();
  64. }
  65. }
  66. #pragma mark - Getting Intercept Class Information
  67. BOOL KWObjectIsClass(id anObject) {
  68. return class_isMetaClass(object_getClass(anObject));
  69. }
  70. BOOL KWClassIsInterceptClass(Class aClass) {
  71. const char *name = class_getName(aClass);
  72. char *result = strstr(name, KWInterceptClassSuffix);
  73. return result != nil;
  74. }
  75. int interceptCount = 0;
  76. NSString *KWInterceptClassNameForClass(Class aClass) {
  77. const char *className = class_getName(aClass);
  78. interceptCount++;
  79. return [NSString stringWithFormat:@"%s%s%d", className, KWInterceptClassSuffix, interceptCount];
  80. }
  81. Class KWInterceptClassForCanonicalClass(Class canonicalClass) {
  82. NSString *interceptClassName = KWInterceptClassNameForClass(canonicalClass);
  83. Class interceptClass = NSClassFromString(interceptClassName);
  84. if (interceptClass != nil)
  85. return interceptClass;
  86. interceptClass = objc_allocateClassPair(canonicalClass, [interceptClassName UTF8String], 0);
  87. objc_registerClassPair(interceptClass);
  88. class_addMethod(interceptClass, @selector(forwardInvocation:), (IMP)KWInterceptedForwardInvocation, "v@:@");
  89. class_addMethod(interceptClass, @selector(class), (IMP)KWInterceptedClass, "#@:");
  90. //TODO: potentially get rid of this?
  91. class_addMethod(interceptClass, NSSelectorFromString(@"dealloc"), (IMP)KWInterceptedDealloc, "v@:");
  92. //
  93. class_addMethod(interceptClass, @selector(superclass), (IMP)KWInterceptedSuperclass, "#@:");
  94. Class interceptMetaClass = object_getClass(interceptClass);
  95. class_addMethod(interceptMetaClass, @selector(forwardInvocation:), (IMP)KWInterceptedForwardInvocation, "v@:@");
  96. return interceptClass;
  97. }
  98. Class KWRealClassForClass(Class aClass) {
  99. if (KWClassIsInterceptClass(aClass))
  100. return [aClass superclass];
  101. return aClass;
  102. }
  103. #pragma mark - Enabling Intercepting
  104. static BOOL IsTollFreeBridged(Class class, id obj)
  105. {
  106. // this is a naive check, but good enough for the purposes of failing fast
  107. return [NSStringFromClass(class) hasPrefix:@"NSCF"];
  108. }
  109. // Canonical class is the non-intercept, non-metaclass, class for an object.
  110. //
  111. // (e.g. [Animal class] would be canonical, not
  112. // object_getClass([Animal class]), if the Animal class has not been touched
  113. // by the intercept mechanism.
  114. Class KWSetupObjectInterceptSupport(id anObject) {
  115. Class objectClass = object_getClass(anObject);
  116. if (IsTollFreeBridged(objectClass, anObject)) {
  117. [NSException raise:@"KWTollFreeBridgingInterceptException" format:@"Attempted to stub object of class %@. Kiwi does not support setting expectation or stubbing methods on toll-free bridged objects.", NSStringFromClass(objectClass)];
  118. }
  119. if (KWClassIsInterceptClass(objectClass))
  120. return objectClass;
  121. BOOL objectIsClass = KWObjectIsClass(anObject);
  122. Class canonicalClass = objectIsClass ? anObject : objectClass;
  123. Class canonicalInterceptClass = KWInterceptClassForCanonicalClass(canonicalClass);
  124. Class interceptClass = objectIsClass ? object_getClass(canonicalInterceptClass) : canonicalInterceptClass;
  125. object_setClass(anObject, interceptClass);
  126. return interceptClass;
  127. }
  128. void KWSetupMethodInterceptSupport(Class interceptClass, SEL aSelector) {
  129. BOOL isMetaClass = class_isMetaClass(interceptClass);
  130. Method method = isMetaClass ? class_getClassMethod(interceptClass, aSelector)
  131. : class_getInstanceMethod(interceptClass, aSelector);
  132. if (method == nil) {
  133. [NSException raise:NSInvalidArgumentException format:@"cannot setup intercept support for -%@ because no such method exists",
  134. NSStringFromSelector(aSelector)];
  135. }
  136. const char *encoding = method_getTypeEncoding(method);
  137. IMP forwardingImplementation = KWForwardingImplementationForMethodEncoding(encoding);
  138. class_addMethod(interceptClass, aSelector, forwardingImplementation, encoding);
  139. }
  140. #pragma mark - Intercept Enabled Method Implementations
  141. void KWInterceptedForwardInvocation(id anObject, SEL aSelector, NSInvocation* anInvocation) {
  142. NSMapTable *spiesMap = KWMessageSpiesForObject(anObject);
  143. for (KWMessagePattern *messagePattern in spiesMap) {
  144. if ([messagePattern matchesInvocation:anInvocation]) {
  145. NSArray *spies = [spiesMap objectForKey:messagePattern];
  146. for (id<KWMessageSpying> spy in spies) {
  147. [spy object:anObject didReceiveInvocation:anInvocation];
  148. }
  149. }
  150. }
  151. for (KWStub *stub in KWObjectStubsForObject(anObject)) {
  152. if ([stub processInvocation:anInvocation])
  153. return;
  154. }
  155. Class interceptClass = KWRestoreOriginalClass(anObject);
  156. [anInvocation invoke];
  157. // anObject->isa = interceptClass;
  158. object_setClass(anObject, interceptClass);
  159. }
  160. void KWInterceptedDealloc(id anObject, SEL aSelector) {
  161. KWClearMessageSpies(anObject);
  162. KWClearObjectStubs(anObject);
  163. KWRestoreOriginalClass(anObject);
  164. }
  165. Class KWInterceptedClass(id anObject, SEL aSelector) {
  166. Class interceptClass = object_getClass(anObject);
  167. Class originalClass = class_getSuperclass(interceptClass);
  168. return originalClass;
  169. }
  170. Class KWInterceptedSuperclass(id anObject, SEL aSelector) {
  171. Class interceptClass = object_getClass(anObject);
  172. Class originalClass = class_getSuperclass(interceptClass);
  173. Class originalSuperclass = class_getSuperclass(originalClass);
  174. return originalSuperclass;
  175. }
  176. #pragma mark - Managing Objects Stubs
  177. void KWAssociateObjectStub(id anObject, KWStub *aStub, BOOL overrideExisting) {
  178. KWObjectStubsInit();
  179. NSMutableArray *stubs = KWObjectStubsForObject(anObject);
  180. if (stubs == nil) {
  181. stubs = [[NSMutableArray alloc] init];
  182. KWObjectStubsSet(anObject, stubs);
  183. }
  184. NSUInteger stubCount = [stubs count];
  185. for (NSUInteger i = 0; i < stubCount; ++i) {
  186. KWStub *existingStub = stubs[i];
  187. if ([aStub.messagePattern isEqualToMessagePattern:existingStub.messagePattern]) {
  188. if (overrideExisting) {
  189. [stubs removeObjectAtIndex:i];
  190. break;
  191. } else {
  192. return;
  193. }
  194. }
  195. }
  196. [stubs addObject:aStub];
  197. }
  198. #pragma mark - Managing Message Spies
  199. void KWAssociateMessageSpy(id anObject, id aSpy, KWMessagePattern *aMessagePattern) {
  200. KWMessageSpiesInit();
  201. NSMapTable *spies = KWMessageSpiesForObject(anObject);
  202. if (spies == nil) {
  203. spies = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory];
  204. KWMessageSpiesSet(anObject, spies);
  205. }
  206. NSMutableArray *messagePatternSpies = [spies objectForKey:aMessagePattern];
  207. if (messagePatternSpies == nil) {
  208. messagePatternSpies = [[NSMutableArray alloc] init];
  209. [spies setObject:messagePatternSpies forKey:aMessagePattern];
  210. }
  211. if ([messagePatternSpies containsObject:aSpy])
  212. return;
  213. [messagePatternSpies addObject:aSpy];
  214. }
  215. #pragma mark - KWMessageSpies
  216. static NSMapTable *KWMessageSpies = nil;
  217. void KWMessageSpiesInit(void) {
  218. if (KWMessageSpies == nil) {
  219. KWMessageSpies = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory];
  220. };
  221. }
  222. NSMapTable *KWMessageSpiesForObject(id anObject) {
  223. return [KWMessageSpies objectForKey:KWInterceptedObjectKey(anObject)];
  224. }
  225. void KWMessageSpiesSet(id anObject, NSMapTable *spies) {
  226. [KWMessageSpies setObject:spies forKey:KWInterceptedObjectKey(anObject)];
  227. }
  228. void KWClearObjectSpy(id anObject, id aSpy, KWMessagePattern *aMessagePattern) {
  229. NSMapTable *spyArrayDictionary = KWMessageSpiesForObject(anObject);
  230. NSMutableArray *spies = [spyArrayDictionary objectForKey:aMessagePattern];
  231. [spies removeObject:aSpy];
  232. }
  233. void KWClearMessageSpies(id anObject) {
  234. [KWMessageSpies removeObjectForKey:KWInterceptedObjectKey(anObject)];
  235. }
  236. void KWClearAllMessageSpies(void) {
  237. for (KWInterceptedObjectBlock key in KWMessageSpies) {
  238. id spiedObject = key();
  239. if (KWObjectClassRestored(spiedObject)) {
  240. continue;
  241. }
  242. KWRestoreOriginalClass(spiedObject);
  243. }
  244. [KWMessageSpies removeAllObjects];
  245. }
  246. #pragma mark KWObjectStubs
  247. static NSMapTable *KWObjectStubs = nil;
  248. void KWObjectStubsInit(void) {
  249. if (KWObjectStubs == nil) {
  250. KWObjectStubs = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory];
  251. }
  252. }
  253. NSMutableArray *KWObjectStubsForObject(id anObject) {
  254. return [KWObjectStubs objectForKey:KWInterceptedObjectKey(anObject)];
  255. }
  256. void KWObjectStubsSet(id anObject, NSMutableArray *stubs) {
  257. [KWObjectStubs setObject:stubs forKey:KWInterceptedObjectKey(anObject)];
  258. }
  259. void KWClearObjectStubs(id anObject) {
  260. [KWObjectStubs removeObjectForKey:KWInterceptedObjectKey(anObject)];
  261. }
  262. void KWClearAllObjectStubs(void) {
  263. for (KWInterceptedObjectBlock key in KWObjectStubs) {
  264. id stubbedObject = key();
  265. if (KWObjectClassRestored(stubbedObject)) {
  266. continue;
  267. }
  268. KWRestoreOriginalClass(stubbedObject);
  269. }
  270. [KWObjectStubs removeAllObjects];
  271. }
  272. #pragma mark KWRestoredObjects
  273. static NSMutableArray *KWRestoredObjects = nil;
  274. BOOL KWObjectClassRestored(id anObject) {
  275. return [KWRestoredObjects containsObject:KWInterceptedObjectKey(anObject)];
  276. }
  277. Class KWRestoreOriginalClass(id anObject) {
  278. Class interceptClass = object_getClass(anObject);
  279. if (KWClassIsInterceptClass(interceptClass))
  280. {
  281. Class originalClass = class_getSuperclass(interceptClass);
  282. // anObject->isa = originalClass;
  283. object_setClass(anObject, originalClass);
  284. }
  285. [KWRestoredObjects addObject:anObject];
  286. return interceptClass;
  287. }
  288. #pragma mark KWInterceptedObjectKey
  289. static void *kKWInterceptedObjectKey = &kKWInterceptedObjectKey;
  290. KWInterceptedObjectBlock KWInterceptedObjectKey(id anObject) {
  291. KWInterceptedObjectBlock key = objc_getAssociatedObject(anObject, kKWInterceptedObjectKey);
  292. if (key == nil) {
  293. __weak id weakobj = anObject;
  294. key = ^{ return weakobj; };
  295. objc_setAssociatedObject(anObject, kKWInterceptedObjectKey, [key copy], OBJC_ASSOCIATION_COPY);
  296. }
  297. return key;
  298. }
  299. #pragma mark - Managing Stubs & Spies
  300. void KWClearStubsAndSpies(void) {
  301. KWRestoredObjects = [NSMutableArray array];
  302. KWClearAllMessageSpies();
  303. KWClearAllObjectStubs();
  304. KWRestoredObjects = nil;
  305. }