GULRuntimeClassSnapshot.m 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // Copyright 2018 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "GULRuntimeClassSnapshot.h"
  15. #import <objc/runtime.h>
  16. #import "GULRuntimeClassDiff.h"
  17. @implementation GULRuntimeClassSnapshot {
  18. /** The class this snapshot is related to. */
  19. Class _aClass;
  20. /** The metaclass of aClass. */
  21. Class _metaclass;
  22. /** The current set of class properties on aClass. */
  23. NSMutableSet<NSString *> *_classProperties;
  24. /** The current set of instance properties on aClass. */
  25. NSMutableSet<NSString *> *_instanceProperties;
  26. /** The current set of class selectors on aClass. */
  27. NSMutableSet<NSString *> *_classSelectors;
  28. /** The current set of instance selectors on aClass. */
  29. NSMutableSet<NSString *> *_instanceSelectors;
  30. /** The current set of class and instance selector IMPs on aClass. */
  31. NSMutableSet<NSString *> *_imps;
  32. /** The current hash of this object, updated as the state of this instance changes. */
  33. NSUInteger _runningHash;
  34. }
  35. - (instancetype)init {
  36. NSAssert(NO, @"Please use the designated initializer.");
  37. return nil;
  38. }
  39. - (instancetype)initWithClass:(Class)aClass {
  40. self = [super init];
  41. if (self) {
  42. _aClass = aClass;
  43. _metaclass = object_getClass(aClass);
  44. _classProperties = [[NSMutableSet alloc] init];
  45. _instanceProperties = [[NSMutableSet alloc] init];
  46. _instanceSelectors = [[NSMutableSet alloc] init];
  47. _classSelectors = [[NSMutableSet alloc] init];
  48. _imps = [[NSMutableSet alloc] init];
  49. _runningHash = [NSStringFromClass(_aClass) hash] ^ [NSStringFromClass(_metaclass) hash];
  50. }
  51. return self;
  52. }
  53. - (void)capture {
  54. [self captureProperties];
  55. [self captureSelectorsAndImps];
  56. }
  57. - (GULRuntimeClassDiff *)diff:(GULRuntimeClassSnapshot *)otherClassSnapshot {
  58. GULRuntimeClassDiff *classDiff = [[GULRuntimeClassDiff alloc] init];
  59. if (_runningHash != [otherClassSnapshot hash]) {
  60. classDiff.aClass = _aClass;
  61. [self computeDiffOfProperties:otherClassSnapshot withClassDiff:classDiff];
  62. [self computeDiffOfSelectorsAndImps:otherClassSnapshot withClassDiff:classDiff];
  63. }
  64. return classDiff;
  65. }
  66. - (NSUInteger)hash {
  67. return _runningHash;
  68. }
  69. - (BOOL)isEqual:(id)object {
  70. return self->_runningHash == ((GULRuntimeClassSnapshot *)object)->_runningHash;
  71. }
  72. - (NSString *)description {
  73. return [NSString stringWithFormat:@"<%@> Hash: 0x%lX", _aClass, (unsigned long)[self hash]];
  74. }
  75. #pragma mark - Private methods below -
  76. #pragma mark State capturing methods
  77. /** Captures class and instance properties and saves state in ivars. */
  78. - (void)captureProperties {
  79. // Capture instance properties.
  80. unsigned int outCount;
  81. objc_property_t *instanceProperties = class_copyPropertyList(_aClass, &outCount);
  82. for (int i = 0; i < outCount; i++) {
  83. objc_property_t property = instanceProperties[i];
  84. NSString *propertyString = [NSString stringWithUTF8String:property_getName(property)];
  85. [_instanceProperties addObject:propertyString];
  86. _runningHash ^= [propertyString hash];
  87. }
  88. free(instanceProperties);
  89. // Capture class properties.
  90. outCount = 0;
  91. objc_property_t *classProperties = class_copyPropertyList(_metaclass, &outCount);
  92. for (int i = 0; i < outCount; i++) {
  93. objc_property_t property = classProperties[i];
  94. NSString *propertyString = [NSString stringWithUTF8String:property_getName(property)];
  95. [_classProperties addObject:propertyString];
  96. _runningHash ^= [propertyString hash];
  97. }
  98. free(classProperties);
  99. }
  100. /** Captures the class and instance selectors and their IMPs and saves their state in ivars. */
  101. - (void)captureSelectorsAndImps {
  102. // Capture instance methods and their IMPs.
  103. unsigned int outCount;
  104. Method *instanceMethods = class_copyMethodList(_aClass, &outCount);
  105. for (int i = 0; i < outCount; i++) {
  106. Method method = instanceMethods[i];
  107. NSString *methodString = NSStringFromSelector(method_getName(method));
  108. [_instanceSelectors addObject:methodString];
  109. IMP imp = method_getImplementation(method);
  110. NSString *impString =
  111. [NSString stringWithFormat:@"%p -[%@ %@]", imp, NSStringFromClass(_aClass), methodString];
  112. if (![_imps containsObject:impString]) {
  113. [_imps addObject:impString];
  114. }
  115. _runningHash ^= [impString hash];
  116. }
  117. free(instanceMethods);
  118. // Capture class methods and their IMPs.
  119. outCount = 0;
  120. Method *classMethods = class_copyMethodList(_metaclass, &outCount);
  121. for (int i = 0; i < outCount; i++) {
  122. Method method = classMethods[i];
  123. NSString *methodString = NSStringFromSelector(method_getName(method));
  124. [_classSelectors addObject:methodString];
  125. IMP imp = method_getImplementation(method);
  126. NSString *impString = [NSString
  127. stringWithFormat:@"%p +[%@ %@]", imp, NSStringFromClass(_metaclass), methodString];
  128. NSAssert(![_imps containsObject:impString],
  129. @"This IMP/method combination has already been captured: %@:%@",
  130. NSStringFromClass(_aClass), impString);
  131. [_imps addObject:impString];
  132. _runningHash ^= [impString hash];
  133. }
  134. free(classMethods);
  135. }
  136. #pragma mark Diff computation methods
  137. /** Compute the diff of class and instance properties and populates the classDiff with that info.
  138. *
  139. * @param otherClassSnapshot The other class snapshot to diff against.
  140. * @param classDiff The diff object to modify.
  141. */
  142. - (void)computeDiffOfProperties:(GULRuntimeClassSnapshot *)otherClassSnapshot
  143. withClassDiff:(GULRuntimeClassDiff *)classDiff {
  144. if ([_classProperties hash] != [otherClassSnapshot->_classProperties hash]) {
  145. classDiff.addedClassProperties = [otherClassSnapshot->_classProperties
  146. objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
  147. return ![self->_classProperties containsObject:obj];
  148. }];
  149. }
  150. if ([_instanceProperties hash] != [otherClassSnapshot->_instanceProperties hash]) {
  151. classDiff.addedInstanceProperties = [otherClassSnapshot->_instanceProperties
  152. objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
  153. return ![self->_instanceProperties containsObject:obj];
  154. }];
  155. }
  156. }
  157. /** Computes the diff of class and instance selectors and their IMPs and populates the classDiff.
  158. *
  159. * @param otherClassSnapshot The other class snapshot to diff against.
  160. * @param classDiff The diff object to modify.
  161. */
  162. - (void)computeDiffOfSelectorsAndImps:(GULRuntimeClassSnapshot *)otherClassSnapshot
  163. withClassDiff:(GULRuntimeClassDiff *)classDiff {
  164. if ([_classSelectors hash] != [otherClassSnapshot->_classSelectors hash]) {
  165. classDiff.addedClassSelectors = [otherClassSnapshot->_classSelectors
  166. objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
  167. return ![self->_classSelectors containsObject:obj];
  168. }];
  169. }
  170. if ([_instanceSelectors hash] != [otherClassSnapshot->_instanceSelectors hash]) {
  171. classDiff.addedInstanceSelectors = [otherClassSnapshot->_instanceSelectors
  172. objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
  173. return ![self->_instanceSelectors containsObject:obj];
  174. }];
  175. }
  176. // modifiedImps contains the prior IMP address, not the current IMP address.
  177. classDiff.modifiedImps =
  178. [_imps objectsPassingTest:^BOOL(NSString *_Nonnull obj, BOOL *_Nonnull stop) {
  179. return ![otherClassSnapshot->_imps containsObject:obj];
  180. }];
  181. }
  182. @end