// Created by EasyIOS on 14-4-10. // Copyright (c) 2014年 zhuchao. All rights reserved. // #import "Easy_Runtime.h" #import "NSArray+EasyExtend.h" // ---------------------------------- // Source code // ---------------------------------- #pragma mark - #undef MAX_CALLSTACK_DEPTH #define MAX_CALLSTACK_DEPTH (64) #pragma mark - @implementation BeeTypeEncoding DEF_INT( UNKNOWN, 0 ) DEF_INT( OBJECT, 1 ) DEF_INT( NSNUMBER, 2 ) DEF_INT( NSSTRING, 3 ) DEF_INT( NSARRAY, 4 ) DEF_INT( NSDICTIONARY, 5 ) DEF_INT( NSDATE, 6 ) + (NSUInteger)typeOf:(const char *)attr { if ( attr[0] != 'T' ) return BeeTypeEncoding.UNKNOWN; const char * type = &attr[1]; if ( type[0] == '@' ) { if ( type[1] != '"' ) return BeeTypeEncoding.UNKNOWN; char typeClazz[128] = { 0 }; const char * clazz = &type[2]; const char * clazzEnd = strchr( clazz, '"' ); if ( clazzEnd && clazz != clazzEnd ) { unsigned int size = (unsigned int)(clazzEnd - clazz); strncpy( &typeClazz[0], clazz, size ); } if ( 0 == strcmp((const char *)typeClazz, "NSNumber") ) { return BeeTypeEncoding.NSNUMBER; } else if ( 0 == strcmp((const char *)typeClazz, "NSString") ) { return BeeTypeEncoding.NSSTRING; } else if ( 0 == strcmp((const char *)typeClazz, "NSDate") ) { return BeeTypeEncoding.NSDATE; } else if ( 0 == strcmp((const char *)typeClazz, "NSArray") ) { return BeeTypeEncoding.NSARRAY; } else if ( 0 == strcmp((const char *)typeClazz, "NSDictionary") ) { return BeeTypeEncoding.NSDICTIONARY; } else { return BeeTypeEncoding.OBJECT; } } else if ( type[0] == '[' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == '{' ) { return BeeTypeEncoding.UNKNOWN; } else { if ( type[0] == 'c' || type[0] == 'C' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == 'i' || type[0] == 's' || type[0] == 'l' || type[0] == 'q' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == 'I' || type[0] == 'S' || type[0] == 'L' || type[0] == 'Q' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == 'f' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == 'd' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == 'B' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == 'v' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == '*' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == ':' ) { return BeeTypeEncoding.UNKNOWN; } else if ( 0 == strcmp(type, "bnum") ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == '^' ) { return BeeTypeEncoding.UNKNOWN; } else if ( type[0] == '?' ) { return BeeTypeEncoding.UNKNOWN; } else { return BeeTypeEncoding.UNKNOWN; } } return BeeTypeEncoding.UNKNOWN; } + (NSUInteger)typeOfAttribute:(const char *)attr { return [self typeOf:attr]; } + (NSUInteger)typeOfObject:(id)obj { if ( nil == obj ) return BeeTypeEncoding.UNKNOWN; if ( [obj isKindOfClass:[NSNumber class]] ) { return BeeTypeEncoding.NSNUMBER; } else if ( [obj isKindOfClass:[NSString class]] ) { return BeeTypeEncoding.NSSTRING; } else if ( [obj isKindOfClass:[NSArray class]] ) { return BeeTypeEncoding.NSARRAY; } else if ( [obj isKindOfClass:[NSDictionary class]] ) { return BeeTypeEncoding.NSDICTIONARY; } else if ( [obj isKindOfClass:[NSDate class]] ) { return BeeTypeEncoding.NSDATE; } else if ( [obj isKindOfClass:[NSObject class]] ) { return BeeTypeEncoding.OBJECT; } return BeeTypeEncoding.UNKNOWN; } + (NSString *)classNameOf:(const char *)attr { if ( attr[0] != 'T' ) return nil; const char * type = &attr[1]; if ( type[0] == '@' ) { if ( type[1] != '"' ) return nil; char typeClazz[128] = { 0 }; const char * clazz = &type[2]; const char * clazzEnd = strchr( clazz, '"' ); if ( clazzEnd && clazz != clazzEnd ) { unsigned int size = (unsigned int)(clazzEnd - clazz); strncpy( &typeClazz[0], clazz, size ); } return [NSString stringWithUTF8String:typeClazz]; } return nil; } + (NSString *)classNameOfAttribute:(const char *)attr { return [self classNameOf:attr]; } + (Class)classOfAttribute:(const char *)attr { NSString * className = [self classNameOf:attr]; if ( nil == className ) return nil; return NSClassFromString( className ); } + (BOOL)isAtomClass:(Class)clazz { if ( clazz == [NSArray class] || [[clazz description] isEqualToString:@"__NSCFArray"] ) return YES; if ( clazz == [NSData class] ) return YES; if ( clazz == [NSDate class] ) return YES; if ( clazz == [NSDictionary class] ) return YES; if ( clazz == [NSNull class] ) return YES; if ( clazz == [NSNumber class] || [[clazz description] isEqualToString:@"__NSCFNumber"] ) return YES; if ( clazz == [NSObject class] ) return YES; if ( clazz == [NSString class] ) return YES; if ( clazz == [NSURL class] ) return YES; if ( clazz == [NSValue class] ) return YES; return NO; } @end #pragma mark - @interface BeeCallFrame() { NSUInteger _type; NSString * _process; NSUInteger _entry; NSUInteger _offset; NSString * _clazz; NSString * _method; } + (NSUInteger)hex:(NSString *)text; + (id)parseFormat1:(NSString *)line; + (id)parseFormat2:(NSString *)line; @end #pragma mark - @implementation BeeCallFrame DEF_INT( TYPE_UNKNOWN, 0 ) DEF_INT( TYPE_OBJC, 1 ) DEF_INT( TYPE_NATIVEC, 2 ) @synthesize type = _type; @synthesize process = _process; @synthesize entry = _entry; @synthesize offset = _offset; @synthesize clazz = _clazz; @synthesize method = _method; - (NSString *)description { if ( BeeCallFrame.TYPE_OBJC == _type ) { return [NSString stringWithFormat:@"[O] %@(0x%08x + %llu) -> [%@ %@]", _process, (unsigned int)_entry, (unsigned long long)_offset, _clazz, _method]; } else if ( BeeCallFrame.TYPE_NATIVEC == _type ) { return [NSString stringWithFormat:@"[C] %@(0x%08x + %llu) -> %@", _process, (unsigned int)_entry, (unsigned long long)_offset, _method]; } else { return [NSString stringWithFormat:@"[X] (0x%08x + %llu)", (unsigned int)_entry, (unsigned long long)_offset]; } } + (NSUInteger)hex:(NSString *)text { unsigned int number = 0; [[NSScanner scannerWithString:text] scanHexInt:&number]; return (NSUInteger)number; } + (id)parseFormat1:(NSString *)line { // example: peeper 0x00001eca -[PPAppDelegate application:didFinishLaunchingWithOptions:] + 106 NSError * error = NULL; NSString * expr = @"^[0-9]*\\s*([a-z0-9_]+)\\s+(0x[0-9a-f]+)\\s+-\\[([a-z0-9_]+)\\s+([a-z0-9_:]+)]\\s+\\+\\s+([0-9]+)$"; NSRegularExpression * regex = [NSRegularExpression regularExpressionWithPattern:expr options:NSRegularExpressionCaseInsensitive error:&error]; NSTextCheckingResult * result = [regex firstMatchInString:line options:0 range:NSMakeRange(0, [line length])]; if ( result && (regex.numberOfCaptureGroups + 1) == result.numberOfRanges ) { BeeCallFrame * frame = [[BeeCallFrame alloc] init]; frame.type = BeeCallFrame.TYPE_OBJC; frame.process = [line substringWithRange:[result rangeAtIndex:1]]; frame.entry = [BeeCallFrame hex:[line substringWithRange:[result rangeAtIndex:2]]]; frame.clazz = [line substringWithRange:[result rangeAtIndex:3]]; frame.method = [line substringWithRange:[result rangeAtIndex:4]]; frame.offset = [[line substringWithRange:[result rangeAtIndex:5]] intValue]; return frame; } return nil; } + (id)parseFormat2:(NSString *)line { // example: UIKit 0x0105f42e UIApplicationMain + 1160 NSError * error = NULL; NSString * expr = @"^[0-9]*\\s*([a-z0-9_]+)\\s+(0x[0-9a-f]+)\\s+([a-z0-9_]+)\\s+\\+\\s+([0-9]+)$"; NSRegularExpression * regex = [NSRegularExpression regularExpressionWithPattern:expr options:NSRegularExpressionCaseInsensitive error:&error]; NSTextCheckingResult * result = [regex firstMatchInString:line options:0 range:NSMakeRange(0, [line length])]; if ( result && (regex.numberOfCaptureGroups + 1) == result.numberOfRanges ) { BeeCallFrame * frame = [[BeeCallFrame alloc] init]; frame.type = BeeCallFrame.TYPE_NATIVEC; frame.process = [line substringWithRange:[result rangeAtIndex:1]]; frame.entry = [self hex:[line substringWithRange:[result rangeAtIndex:2]]]; frame.clazz = nil; frame.method = [line substringWithRange:[result rangeAtIndex:3]]; frame.offset = [[line substringWithRange:[result rangeAtIndex:4]] intValue]; return frame; } return nil; } + (id)unknown { BeeCallFrame * frame = [[BeeCallFrame alloc] init]; frame.type = BeeCallFrame.TYPE_UNKNOWN; return frame; } + (id)parse:(NSString *)line { if ( 0 == [line length] ) return nil; id frame1 = [BeeCallFrame parseFormat1:line]; if ( frame1 ) return frame1; id frame2 = [BeeCallFrame parseFormat2:line]; if ( frame2 ) return frame2; return [BeeCallFrame unknown]; } @end #pragma mark - static void __uncaughtExceptionHandler( NSException * exception ) { NSLog( @"uncaught exception: %@\n%@", exception, [exception callStackSymbols] ); } #pragma mark - @implementation BeeRuntime @dynamic allClasses; @dynamic callstack; @dynamic callframes; DEF_SINGLETON( BeeRuntime ) + (void)load { NSSetUncaughtExceptionHandler( &__uncaughtExceptionHandler ); } + (id)allocByClass:(Class)clazz { if ( nil == clazz ) return nil; return [clazz alloc]; } + (id)allocByClassName:(NSString *)clazzName { if ( nil == clazzName || 0 == [clazzName length] ) return nil; Class clazz = NSClassFromString( clazzName ); if ( nil == clazz ) return nil; return [clazz alloc]; } + (NSArray *)allClasses { static NSMutableArray * __allClasses = nil; if ( nil == __allClasses ) { __allClasses = [NSMutableArray nonRetainingArray]; } if ( 0 == __allClasses.count ) { static const char * __blackList[] = { "AGActionSheet", "AGShareActionSheet", "AGAlertView", "AGSharePublishContentView", "AG_SKJDictionary", "AG_SKJSerializer", "AG_SKJSONDecoder", "AG_SKJDictionaryEnumerator", "AG_SKJArray", "AGCommon", "AGShareItemView", "AGSharePageContentView", "AGBackground" }; unsigned int classesCount = 0; Class * classes = objc_copyClassList( &classesCount ); for ( unsigned int i = 0; i < classesCount; ++i ) { Class classType = classes[i]; Class superClass = class_getSuperclass( classType ); if ( nil == superClass ) continue; // if ( NO == class_conformsToProtocol( classType, @protocol(NSObject)) ) // continue; if ( NO == class_respondsToSelector( classType, @selector(doesNotRecognizeSelector:) ) ) continue; if ( NO == class_respondsToSelector( classType, @selector(methodSignatureForSelector:) ) ) continue; // if ( class_respondsToSelector( classType, @selector(initialize) ) ) // continue; // if ( NO == [classType isSubclassOfClass:[NSObject class]] ) // continue; BOOL isBlack = NO; const char * className = class_getName( classType ); NSInteger listSize = sizeof( __blackList ) / sizeof( __blackList[0] ); for ( int i = 0; i < listSize; ++i ) { if ( 0 == strcmp( className, __blackList[i] ) ) { isBlack = YES; break; } } if ( isBlack ) continue; [__allClasses addObject:classType]; } free( classes ); } return __allClasses; } + (NSArray *)allSubClassesOf:(Class)superClass { NSMutableArray * results = [[NSMutableArray alloc] init]; for ( Class classType in [self allClasses] ) { if ( classType == superClass ) continue; if ( NO == [classType isSubclassOfClass:superClass] ) continue; [results addObject:classType]; } return results; } + (NSArray *)allInstanceMethodsOf:(Class)clazz { static NSMutableDictionary * __cache = nil; if ( nil == __cache ) { __cache = [[NSMutableDictionary alloc] init]; } NSMutableArray * methodNames = [__cache objectForKey:[clazz description]]; if ( nil == methodNames ) { methodNames = [NSMutableArray array]; Class thisClass = clazz; while ( NULL != thisClass ) { unsigned int methodCount = 0; Method * methods = class_copyMethodList( thisClass, &methodCount ); for ( unsigned int i = 0; i < methodCount; ++i ) { SEL selector = method_getName( methods[i] ); if ( selector ) { const char * cstrName = sel_getName(selector); if ( NULL == cstrName ) continue; NSString * selectorName = [NSString stringWithUTF8String:cstrName]; if ( NULL == selectorName ) continue; [methodNames addObject:selectorName]; } } thisClass = class_getSuperclass( thisClass ); if ( thisClass == [NSObject class] ) { break; } } [__cache setObject:methodNames forKey:[clazz description]]; } return methodNames; } + (NSArray *)allInstanceMethodsOf:(Class)clazz withPrefix:(NSString *)prefix { NSArray * methods = [self allInstanceMethodsOf:clazz]; if ( nil == methods || 0 == methods.count ) { return nil; } if ( nil == prefix ) { return methods; } NSMutableArray * result = [NSMutableArray array]; for ( NSString * selectorName in methods ) { if ( NO == [selectorName hasPrefix:prefix] ) { continue; } [result addObject:selectorName]; } return result; } + (NSArray *)callstack:(NSUInteger)depth { NSMutableArray * array = [[NSMutableArray alloc] init]; void * stacks[MAX_CALLSTACK_DEPTH] = { 0 }; depth = backtrace( stacks, (int)((depth > MAX_CALLSTACK_DEPTH) ? MAX_CALLSTACK_DEPTH : depth) ); if ( depth ) { char ** symbols = backtrace_symbols( stacks, (int)depth ); if ( symbols ) { for ( int i = 0; i < depth; ++i ) { NSString * symbol = [NSString stringWithUTF8String:(const char *)symbols[i]]; if ( 0 == [symbol length] ) continue; NSRange range1 = [symbol rangeOfString:@"["]; NSRange range2 = [symbol rangeOfString:@"]"]; if ( range1.length > 0 && range2.length > 0 ) { NSRange range3; range3.location = range1.location; range3.length = range2.location + range2.length - range1.location; [array addObject:[symbol substringWithRange:range3]]; } else { [array addObject:symbol]; } } free( symbols ); } } return array; } + (NSArray *)callframes:(NSUInteger)depth { NSMutableArray * array = [[NSMutableArray alloc] init]; void * stacks[MAX_CALLSTACK_DEPTH] = { 0 }; depth = backtrace( stacks, int((depth > MAX_CALLSTACK_DEPTH) ? MAX_CALLSTACK_DEPTH : depth) ); if ( depth ) { char ** symbols = backtrace_symbols( stacks, (int)depth ); if ( symbols ) { for ( int i = 0; i < depth; ++i ) { NSString * line = [NSString stringWithUTF8String:(const char *)symbols[i]]; if ( 0 == [line length] ) continue; BeeCallFrame * frame = [BeeCallFrame parse:line]; if ( nil == frame ) continue; [array addObject:frame]; } free( symbols ); } } return array; } + (void)breakPoint { #if __BEE_DEVELOPMENT__ #if defined(__ppc__) asm("trap"); #elif defined(__i386__) asm("int3"); #endif // #elif defined(__i386__) #endif // #if __BEE_DEVELOPMENT__ } - (NSArray *)allClasses { return [BeeRuntime allClasses]; } - (NSArray *)callstack { return [BeeRuntime callstack:MAX_CALLSTACK_DEPTH]; } - (NSArray *)callframes { return [BeeRuntime callframes:MAX_CALLSTACK_DEPTH]; } @end // ---------------------------------- // Unit test // ---------------------------------- #pragma mark - #if defined(__BEE_UNITTEST__) && __BEE_UNITTEST__ TEST_CASE( BeeRuntime ) { TIMES( 3 ) { NSString * str = (NSString *)[BeeRuntime allocByClass:[NSString class]]; EXPECTED( str ); [str release]; NSString * str2 = (NSString *)[BeeRuntime allocByClassName:@"NSString"]; EXPECTED( str2 ); [str2 release]; NSArray * emptyStack = [BeeRuntime callstack:0]; EXPECTED( emptyStack ); EXPECTED( emptyStack.count == 0 ); NSArray * maxStack = [BeeRuntime callstack:100000]; EXPECTED( maxStack ); EXPECTED( maxStack.count ); NSArray * stack = [BeeRuntime callstack:1]; EXPECTED( stack && stack.count ); EXPECTED( [[stack objectAtIndex:0] isKindOfClass:[NSString class]] ); NSArray * emptyFrames = [BeeRuntime callframes:0]; EXPECTED( emptyFrames ); EXPECTED( emptyFrames.count == 0 ); NSArray * maxFrames = [BeeRuntime callframes:100000]; EXPECTED( maxFrames ); EXPECTED( maxFrames.count ); NSArray * frames = [BeeRuntime callframes:1]; EXPECTED( frames && frames.count ); EXPECTED( [[frames objectAtIndex:0] isKindOfClass:[BeeCallFrame class]] ); [BeeRuntime printCallstack:0]; [BeeRuntime printCallstack:1]; [BeeRuntime printCallstack:100000]; } } TEST_CASE_END #endif // #if defined(__BEE_UNITTEST__) && __BEE_UNITTEST__