From 31cdce672a1bf55f13be15ba4331273ae89d7151 Mon Sep 17 00:00:00 2001 From: gengjie <497222499@qq.com> Date: Wed, 30 Jan 2019 16:19:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9KVC=E5=B4=A9?= =?UTF-8?q?=E6=BA=83=E7=9A=84Hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JJException.xcodeproj/project.pbxproj | 12 +++ JJException/JJPerson.h | 24 +++++ JJException/JJPerson.m | 16 ++++ JJException/Source/ARC/NSObject+KVCCrash.h | 17 ++++ JJException/Source/ARC/NSObject+KVCCrash.m | 100 +++++++++++++++++++++ JJException/Source/Main/JJException.h | 3 +- JJException/Source/Main/JJExceptionProxy.m | 5 ++ JJException/ViewController.m | 16 ++++ 8 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 JJException/JJPerson.h create mode 100644 JJException/JJPerson.m create mode 100644 JJException/Source/ARC/NSObject+KVCCrash.h create mode 100644 JJException/Source/ARC/NSObject+KVCCrash.m diff --git a/JJException.xcodeproj/project.pbxproj b/JJException.xcodeproj/project.pbxproj index 78f48a3..6b4c5dc 100644 --- a/JJException.xcodeproj/project.pbxproj +++ b/JJException.xcodeproj/project.pbxproj @@ -74,6 +74,8 @@ 61A7F5602144ED9D00C87FA1 /* NSNotificationCenter+ClearNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 61A7F55D2144ED9C00C87FA1 /* NSNotificationCenter+ClearNotification.m */; }; 61A7F5632145071800C87FA1 /* KVOObjectDemo.m in Sources */ = {isa = PBXBuildFile; fileRef = 61A7F5622145071800C87FA1 /* KVOObjectDemo.m */; }; 61BC3FC720F7ACE500D8C791 /* JJExceptionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 61BC3FC620F7ACE500D8C791 /* JJExceptionTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + B6500EC022018563006CE751 /* NSObject+KVCCrash.m in Sources */ = {isa = PBXBuildFile; fileRef = B6500EBF22018563006CE751 /* NSObject+KVCCrash.m */; }; + B6500EC3220186CF006CE751 /* JJPerson.m in Sources */ = {isa = PBXBuildFile; fileRef = B6500EC2220186CF006CE751 /* JJPerson.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -144,6 +146,10 @@ 61BC3FC420F7ACE500D8C791 /* JJExceptionTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JJExceptionTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61BC3FC620F7ACE500D8C791 /* JJExceptionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JJExceptionTests.m; sourceTree = ""; }; 61BC3FC820F7ACE500D8C791 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B6500EBE22018563006CE751 /* NSObject+KVCCrash.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+KVCCrash.h"; sourceTree = ""; }; + B6500EBF22018563006CE751 /* NSObject+KVCCrash.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+KVCCrash.m"; sourceTree = ""; }; + B6500EC1220186CF006CE751 /* JJPerson.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JJPerson.h; sourceTree = ""; }; + B6500EC2220186CF006CE751 /* JJPerson.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JJPerson.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -216,6 +222,8 @@ 61A7F51B213B859300C87FA1 /* PushViewController.m */, 61A7F5612145071800C87FA1 /* KVOObjectDemo.h */, 61A7F5622145071800C87FA1 /* KVOObjectDemo.m */, + B6500EC1220186CF006CE751 /* JJPerson.h */, + B6500EC2220186CF006CE751 /* JJPerson.m */, ); path = JJException; sourceTree = ""; @@ -301,6 +309,8 @@ 6109EE792198244A00CB15E6 /* NSSet+SetHook.m */, 6109EE7B2198264F00CB15E6 /* NSMutableSet+MutableSetHook.h */, 6109EE7C2198264F00CB15E6 /* NSMutableSet+MutableSetHook.m */, + B6500EBE22018563006CE751 /* NSObject+KVCCrash.h */, + B6500EBF22018563006CE751 /* NSObject+KVCCrash.m */, ); path = ARC; sourceTree = ""; @@ -504,6 +514,8 @@ 61025B4D2153E36100191522 /* NSAttributedString+AttributedStringHook.m in Sources */, 61A7F54321427E7F00C87FA1 /* NSObject+KVOCrash.m in Sources */, 61A7F54F21427EB700C87FA1 /* NSMutableDictionary+MutableDictionaryHook.m in Sources */, + B6500EC022018563006CE751 /* NSObject+KVCCrash.m in Sources */, + B6500EC3220186CF006CE751 /* JJPerson.m in Sources */, 61196D4A20F261C800022DDC /* ViewController.m in Sources */, 613E0ABA21514E2E00B8D0DF /* NSMutableString+MutableStringHook.m in Sources */, 613E0A95214974B800B8D0DF /* JJExceptionProxy.m in Sources */, diff --git a/JJException/JJPerson.h b/JJException/JJPerson.h new file mode 100644 index 0000000..f4c9cab --- /dev/null +++ b/JJException/JJPerson.h @@ -0,0 +1,24 @@ +// +// JJPerson.h +// JJException +// +// Created by 无头骑士 GJ on 2019/1/30. +// Copyright © 2019 Jezz. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface JJPerson : NSObject +{ + int age; +} + + +@property (nonatomic, strong) NSString *name; + + +@end + +NS_ASSUME_NONNULL_END diff --git a/JJException/JJPerson.m b/JJException/JJPerson.m new file mode 100644 index 0000000..3de26ae --- /dev/null +++ b/JJException/JJPerson.m @@ -0,0 +1,16 @@ +// +// JJPerson.m +// JJException +// +// Created by 无头骑士 GJ on 2019/1/30. +// Copyright © 2019 Jezz. All rights reserved. +// + +#import "JJPerson.h" + +@implementation JJPerson ++ (BOOL)accessInstanceVariablesDirectly +{ + return YES; +} +@end diff --git a/JJException/Source/ARC/NSObject+KVCCrash.h b/JJException/Source/ARC/NSObject+KVCCrash.h new file mode 100644 index 0000000..8200854 --- /dev/null +++ b/JJException/Source/ARC/NSObject+KVCCrash.h @@ -0,0 +1,17 @@ +// +// NSObject+KVCCrash.h +// JJException +// +// Created by 无头骑士 GJ on 2019/1/30. +// Copyright © 2019 Jezz. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSObject (KVCCrash) ++ (void)jj_swizzleKVCCrash; +@end + +NS_ASSUME_NONNULL_END diff --git a/JJException/Source/ARC/NSObject+KVCCrash.m b/JJException/Source/ARC/NSObject+KVCCrash.m new file mode 100644 index 0000000..937dd6c --- /dev/null +++ b/JJException/Source/ARC/NSObject+KVCCrash.m @@ -0,0 +1,100 @@ +// +// NSObject+KVCCrash.m +// JJException +// +// Created by 无头骑士 GJ on 2019/1/30. +// Copyright © 2019 Jezz. All rights reserved. +// + +#import "NSObject+KVCCrash.h" +#import "NSObject+SwizzleHook.h" +#import "JJExceptionProxy.h" +#import + +@implementation NSObject (KVCCrash) ++ (void)jj_swizzleKVCCrash +{ + swizzleInstanceMethod([self class], @selector(setValue:forKey:), @selector(hookSetValue:forKey:)); +} + +- (void)hookSetValue:(id)value forKey:(NSString *)key +{ + if (key.length == 0) return; + + + NSString *methodSuffix = [NSString stringWithFormat: @"%@%@", [[key substringToIndex: 1] uppercaseString], [key substringFromIndex: 1]]; + + // 1、判断setKey方法是否存在 + SEL setXXSelector = NSSelectorFromString([NSString stringWithFormat: @"set%@", methodSuffix]); + Method setXXMethod = class_getInstanceMethod([self class], setXXSelector); + + if (setXXMethod) + { + [self hookSetValue: value forKey: key]; + return; + } + + // 2、判断_setKey方法是否存在 + SEL _setXXSelector = NSSelectorFromString([NSString stringWithFormat: @"_set%@", methodSuffix]); + Method _setXXMethod = class_getInstanceMethod([self class], _setXXSelector); + if (_setXXMethod) + { + [self hookSetValue: value forKey: key]; + return; + } + + // 3、accessInstanceVariablesDirectly的返回值,如果为NO,直接抛出异常 + SEL accessInstanceVariablesDirectlySEL = @selector(accessInstanceVariablesDirectly); + NSMethodSignature *methodSignature = [NSMethodSignature methodSignatureForSelector: accessInstanceVariablesDirectlySEL]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: methodSignature]; + [invocation setSelector: accessInstanceVariablesDirectlySEL]; + [invocation invokeWithTarget: [self class]]; + + BOOL result; + [invocation getReturnValue: &result]; + + // 异常处理 + if (result == NO) + { + handleCrashException(JJExceptionGuardKVCCrash, @"hookSetValue:forKey: key is not exist"); + return; + } + + // 4、检查是否存在 _key、_isKey、key, isKey任一属性是否包含在成员变量中 + NSMutableArray *keys = [NSMutableArray array]; + [keys addObject: [NSString stringWithFormat: @"_%@", key]]; + [keys addObject: [NSString stringWithFormat: @"_is%@", methodSuffix]]; + [keys addObject: [NSString stringWithFormat: @"%@", key]]; + [keys addObject: [NSString stringWithFormat: @"is%@", methodSuffix]]; + + unsigned int count = 0; + Ivar *members = class_copyIvarList([self class], &count); + + __block BOOL find = NO; + for (int i = 0; i < count; i++) + { + Ivar var = members[i]; + NSString *memeberName = [[NSString alloc] initWithUTF8String: ivar_getName(var)]; + + if (![[memeberName lowercaseString] containsString: key]) continue; + + [keys enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + + if ([obj isEqualToString: memeberName]) + { + find = YES; + [self hookSetValue: value forKey: key]; + *stop = YES; + } + }]; + + if (find) break; + + } + + if (find == NO) + handleCrashException(JJExceptionGuardKVCCrash, @"hookSetValue:forKey: key is not exist"); +} + + +@end diff --git a/JJException/Source/Main/JJException.h b/JJException/Source/Main/JJException.h index a5d9d61..f0ab78a 100644 --- a/JJException/Source/Main/JJException.h +++ b/JJException/Source/Main/JJException.h @@ -35,8 +35,9 @@ typedef NS_OPTIONS(NSInteger,JJExceptionGuardCategory){ JJExceptionGuardNSTimer = 1 << 6, JJExceptionGuardNSNotificationCenter = 1 << 7, JJExceptionGuardNSStringContainer = 1 << 8, + JJExceptionGuardKVCCrash = 1 << 9, JJExceptionGuardAllExceptZombie = JJExceptionGuardUnrecognizedSelector | JJExceptionGuardDictionaryContainer | JJExceptionGuardArrayContainer | JJExceptionGuardKVOCrash | JJExceptionGuardNSTimer | JJExceptionGuardNSNotificationCenter | JJExceptionGuardNSStringContainer, - JJExceptionGuardAll = JJExceptionGuardUnrecognizedSelector | JJExceptionGuardDictionaryContainer | JJExceptionGuardArrayContainer | JJExceptionGuardZombie | JJExceptionGuardKVOCrash | JJExceptionGuardNSTimer | JJExceptionGuardNSNotificationCenter | JJExceptionGuardNSStringContainer, + JJExceptionGuardAll = JJExceptionGuardUnrecognizedSelector | JJExceptionGuardDictionaryContainer | JJExceptionGuardArrayContainer | JJExceptionGuardZombie | JJExceptionGuardKVOCrash | JJExceptionGuardNSTimer | JJExceptionGuardNSNotificationCenter | JJExceptionGuardNSStringContainer | JJExceptionGuardKVCCrash, }; /** diff --git a/JJException/Source/Main/JJExceptionProxy.m b/JJException/Source/Main/JJExceptionProxy.m index 85dbb04..a4db2bc 100644 --- a/JJException/Source/Main/JJExceptionProxy.m +++ b/JJException/Source/Main/JJExceptionProxy.m @@ -177,6 +177,11 @@ - (void)setIsProtectException:(BOOL)isProtectException{ [NSAttributedString performSelector:@selector(jj_swizzleNSAttributedString)]; [NSMutableAttributedString performSelector:@selector(jj_swizzleNSMutableAttributedString)]; } + + if (self.exceptionGuardCategory & JJExceptionGuardKVCCrash) + { + [NSObject performSelector: @selector(jj_swizzleKVCCrash)]; + } #pragma clang diagnostic pop } dispatch_semaphore_signal(_swizzleLock); diff --git a/JJException/ViewController.m b/JJException/ViewController.m index f0b775c..c07ddd6 100644 --- a/JJException/ViewController.m +++ b/JJException/ViewController.m @@ -9,6 +9,7 @@ #import "ViewController.h" #import "JJException.h" #import +#import "JJPerson.h" #import "PushViewController.h" @interface ViewController () @@ -48,6 +49,13 @@ - (void)viewDidLoad { otherButton.frame = CGRectMake(0, 350, self.view.frame.size.width, 50); [otherButton addTarget:self action:@selector(testArrayDictionaryUnrecognizedSelector) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:otherButton]; + + UIButton* kvcButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [kvcButton setTitle:@"Test KVC" forState:UIControlStateNormal]; + [kvcButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; + kvcButton.frame = CGRectMake(0, 450, self.view.frame.size.width, 50); + [kvcButton addTarget:self action:@selector(keyButtonClick) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:kvcButton]; } #pragma mark - Exception Delegate @@ -106,4 +114,12 @@ - (void)testNull{ NSLog(@"Str length:%ld",str.length); } +- (void)keyButtonClick +{ + JJPerson *p = [JJPerson new]; + [p setValue: @"123" forKeyPath: @"age"]; + + NSLog(@"%@", [p valueForKey: @"age"]); +} + @end