| Index: class-dump/src/CDTypeParser.m
|
| ===================================================================
|
| --- class-dump/src/CDTypeParser.m (revision 0)
|
| +++ class-dump/src/CDTypeParser.m (revision 0)
|
| @@ -0,0 +1,557 @@
|
| +// -*- mode: ObjC -*-
|
| +
|
| +// This file is part of class-dump, a utility for examining the Objective-C segment of Mach-O files.
|
| +// Copyright (C) 1997-1998, 2000-2001, 2004-2010 Steve Nygard.
|
| +
|
| +#import "CDTypeParser.h"
|
| +
|
| +#include <assert.h>
|
| +#import "CDMethodType.h"
|
| +#import "CDType.h"
|
| +#import "CDTypeName.h"
|
| +#import "CDTypeLexer.h"
|
| +#import "NSString-Extensions.h"
|
| +
|
| +NSString *CDSyntaxError = @"Syntax Error";
|
| +NSString *CDTypeParserErrorDomain = @"CDTypeParserErrorDomain";
|
| +
|
| +static BOOL debug = NO;
|
| +
|
| +static NSString *CDTokenDescription(int token)
|
| +{
|
| + if (token < 128)
|
| + return [NSString stringWithFormat:@"%d(%c)", token, token];
|
| +
|
| + return [NSString stringWithFormat:@"%d", token];
|
| +}
|
| +
|
| +@implementation CDTypeParser
|
| +
|
| +- (id)initWithType:(NSString *)aType;
|
| +{
|
| + NSMutableString *str;
|
| +
|
| + if ([super init] == nil)
|
| + return nil;
|
| +
|
| + // Do some preprocessing first: Replace "<unnamed>::" with just "unnamed::".
|
| + str = [aType mutableCopy];
|
| + [str replaceOccurrencesOfString:@"<unnamed>::" withString:@"unnamed::" options:0 range:NSMakeRange(0, [aType length])];
|
| +
|
| + lexer = [[CDTypeLexer alloc] initWithString:str];
|
| + lookahead = 0;
|
| +
|
| + [str release];
|
| +
|
| + return self;
|
| +}
|
| +
|
| +- (void)dealloc;
|
| +{
|
| + [lexer release];
|
| +
|
| + [super dealloc];
|
| +}
|
| +
|
| +- (CDTypeLexer *)lexer;
|
| +{
|
| + return lexer;
|
| +}
|
| +
|
| +- (NSArray *)parseMethodType:(NSError **)error;
|
| +{
|
| + NSArray *result;
|
| +
|
| + *error = nil;
|
| +
|
| + @try {
|
| + lookahead = [lexer scanNextToken];
|
| + result = [self _parseMethodType];
|
| + }
|
| + @catch (NSException *exception) {
|
| + NSDictionary *userInfo;
|
| + int code;
|
| +
|
| + // Obviously I need to figure out a sane method of dealing with errors here. This is not.
|
| + if ([[exception name] isEqual:CDSyntaxError]) {
|
| + code = CDTypeParserCodeSyntaxError;
|
| + userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:@"Syntax Error", @"reason",
|
| + [NSString stringWithFormat:@"Syntax Error, %@:\n\t type: %@\n\tremaining: %@",
|
| + [exception reason], [lexer string], [lexer remainingString]], @"explanation",
|
| + [lexer string], @"type",
|
| + [lexer remainingString], @"remaining string",
|
| + nil];
|
| + } else {
|
| + code = CDTypeParserCodeDefault;
|
| + userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[exception reason], @"reason",
|
| + [lexer string], @"type",
|
| + [lexer remainingString], @"remaining string",
|
| + nil];
|
| + }
|
| + *error = [NSError errorWithDomain:CDTypeParserErrorDomain code:code userInfo:userInfo];
|
| + [userInfo release];
|
| +
|
| + result = nil;
|
| + }
|
| +
|
| + return result;
|
| +}
|
| +
|
| +- (CDType *)parseType:(NSError **)error;
|
| +{
|
| + CDType *result;
|
| +
|
| + *error = nil;
|
| +
|
| + @try {
|
| + lookahead = [lexer scanNextToken];
|
| + result = [self _parseType];
|
| + }
|
| + @catch (NSException *exception) {
|
| + NSDictionary *userInfo;
|
| + int code;
|
| +
|
| + // Obviously I need to figure out a sane method of dealing with errors here. This is not.
|
| + if ([[exception name] isEqual:CDSyntaxError]) {
|
| + code = CDTypeParserCodeSyntaxError;
|
| + userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:@"Syntax Error", @"reason",
|
| + [NSString stringWithFormat:@"%@:\n\t type: %@\n\tremaining: %@",
|
| + [exception reason], [lexer string], [lexer remainingString]], @"explanation",
|
| + [lexer string], @"type",
|
| + [lexer remainingString], @"remaining string",
|
| + nil];
|
| + } else {
|
| + code = CDTypeParserCodeDefault;
|
| + userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[exception reason], @"reason",
|
| + [lexer string], @"type",
|
| + [lexer remainingString], @"remaining string",
|
| + nil];
|
| + }
|
| + *error = [NSError errorWithDomain:CDTypeParserErrorDomain code:code userInfo:userInfo];
|
| + [userInfo release];
|
| +
|
| + result = nil;
|
| + }
|
| +
|
| + return result;
|
| +}
|
| +
|
| +@end
|
| +
|
| +@implementation CDTypeParser (Private)
|
| +
|
| +- (void)match:(int)token;
|
| +{
|
| + [self match:token enterState:[lexer state]];
|
| +}
|
| +
|
| +- (void)match:(int)token enterState:(int)newState;
|
| +{
|
| + if (lookahead == token) {
|
| + if (debug) NSLog(@"matched %@", CDTokenDescription(token));
|
| + [lexer setState:newState];
|
| + lookahead = [lexer scanNextToken];
|
| + } else {
|
| + [NSException raise:CDSyntaxError format:@"expected token %@, got %@",
|
| + CDTokenDescription(token),
|
| + CDTokenDescription(lookahead)];
|
| + }
|
| +}
|
| +
|
| +- (void)error:(NSString *)errorString;
|
| +{
|
| + [NSException raise:CDSyntaxError format:@"%@", errorString];
|
| +}
|
| +
|
| +- (NSArray *)_parseMethodType;
|
| +{
|
| + NSMutableArray *methodTypes;
|
| + CDMethodType *aMethodType;
|
| + CDType *type;
|
| + NSString *number;
|
| +
|
| + methodTypes = [NSMutableArray array];
|
| +
|
| + // Has to have at least one pair for the return type;
|
| + // Probably needs at least two more, for object and selector
|
| + // So it must be <type><number><type><number><type><number>. Three pairs at a minimum.
|
| +
|
| + do {
|
| + type = [self _parseType];
|
| + number = [self parseNumber];
|
| +
|
| + aMethodType = [[CDMethodType alloc] initWithType:type offset:number];
|
| + [methodTypes addObject:aMethodType];
|
| + [aMethodType release];
|
| + } while ([self isTokenInTypeStartSet:lookahead]);
|
| +
|
| + return methodTypes;
|
| +}
|
| +
|
| +// Plain object types can be:
|
| +// @ - plain id type
|
| +// @"NSObject" - NSObject *
|
| +// @"<MyProtocol>" - id <MyProtocol>
|
| +// But these can also be part of a structure, with the field name in quotes before the type:
|
| +// "foo"i"bar"i - int foo, int bar
|
| +// "foo"@"bar"i - id foo, int bar
|
| +// "foo"@"Foo""bar"i - Foo *foo, int bar
|
| +// So this is where we need to be careful.
|
| +//
|
| +// I'm going to make a simplifying assumption: Either the structure/union has member names,
|
| +// or is doesn't, it can't have some names and be missing others.
|
| +// The two key tests are:
|
| +// {my_struct3="field1"@"field2"i}
|
| +// {my_struct4="field1"@"NSObject""field2"i}
|
| +
|
| +- (CDType *)_parseType;
|
| +{
|
| + return [self _parseTypeInStruct:NO];
|
| +}
|
| +
|
| +- (CDType *)_parseTypeInStruct:(BOOL)isInStruct;
|
| +{
|
| + CDType *result;
|
| +
|
| + if (lookahead == 'r'
|
| + || lookahead == 'n'
|
| + || lookahead == 'N'
|
| + || lookahead == 'o'
|
| + || lookahead == 'O'
|
| + || lookahead == 'R'
|
| + || lookahead == 'V') { // modifiers
|
| + int modifier;
|
| + CDType *unmodifiedType;
|
| + modifier = lookahead;
|
| + [self match:modifier];
|
| +
|
| + if ([self isTokenInTypeStartSet:lookahead])
|
| + unmodifiedType = [self _parseTypeInStruct:isInStruct];
|
| + else
|
| + unmodifiedType = nil;
|
| + result = [[CDType alloc] initModifier:modifier type:unmodifiedType];
|
| + } else if (lookahead == '^') { // pointer
|
| + CDType *type;
|
| +
|
| + [self match:'^'];
|
| + if (lookahead == TK_QUOTED_STRING || lookahead == '}' || lookahead == ')') {
|
| + type = [[CDType alloc] initSimpleType:'v'];
|
| + // Safari on 10.5 has: "m_function"{?="__pfn"^"__delta"i}
|
| + result = [[CDType alloc] initPointerType:type];
|
| + [type release];
|
| + } else {
|
| + type = [self _parseTypeInStruct:isInStruct];
|
| + result = [[CDType alloc] initPointerType:type];
|
| + }
|
| + } else if (lookahead == 'b') { // bitfield
|
| + NSString *number;
|
| +
|
| + [self match:'b'];
|
| + number = [self parseNumber];
|
| + result = [[CDType alloc] initBitfieldType:number];
|
| + } else if (lookahead == '@') { // id
|
| + [self match:'@'];
|
| +#if 0
|
| + if (lookahead == TK_QUOTED_STRING) {
|
| + NSLog(@"%s, quoted string ahead, shouldCheckFieldNames: %d, end: %d",
|
| + _cmd, shouldCheckFieldNames, [[lexer scanner] isAtEnd]);
|
| + if ([[lexer scanner] isAtEnd] == NO)
|
| + NSLog(@"next character: %d (%c), isInTypeStartSet: %d", [lexer peekChar], [lexer peekChar], [self isTokenInTypeStartSet:[lexer peekChar]]);
|
| + }
|
| +#endif
|
| + if (lookahead == TK_QUOTED_STRING && (isInStruct == NO || [[lexer lexText] isFirstLetterUppercase] || [self isTokenInTypeStartSet:[lexer peekChar]] == NO)) {
|
| + NSString *str;
|
| + CDTypeName *typeName;
|
| +
|
| + str = [lexer lexText];
|
| + if ([str hasPrefix:@"<"] && [str hasSuffix:@">"]) {
|
| + str = [str substringWithRange:NSMakeRange(1, [str length] - 2)];
|
| + result = [[CDType alloc] initIDTypeWithProtocols:[str componentsSeparatedByString:@","]];
|
| + } else {
|
| + typeName = [[CDTypeName alloc] init];
|
| + [typeName setName:str];
|
| + result = [[CDType alloc] initIDType:typeName];
|
| + [typeName release];
|
| + }
|
| +
|
| + [self match:TK_QUOTED_STRING];
|
| + } else {
|
| + result = [[CDType alloc] initIDType:nil];
|
| + }
|
| + } else if (lookahead == '{') { // structure
|
| + CDTypeName *typeName;
|
| + NSArray *optionalMembers;
|
| + CDTypeLexerState savedState;
|
| +
|
| + savedState = [lexer state];
|
| + [self match:'{' enterState:CDTypeLexerStateIdentifier];
|
| + typeName = [self parseTypeName];
|
| + optionalMembers = [self parseOptionalMembers];
|
| + [self match:'}' enterState:savedState];
|
| +
|
| + result = [[CDType alloc] initStructType:typeName members:optionalMembers];
|
| + } else if (lookahead == '(') { // union
|
| + CDTypeLexerState savedState;
|
| +
|
| + savedState = [lexer state];
|
| + [self match:'(' enterState:CDTypeLexerStateIdentifier];
|
| + if (lookahead == TK_IDENTIFIER) {
|
| + CDTypeName *typeName;
|
| + NSArray *optionalMembers;
|
| +
|
| + typeName = [self parseTypeName];
|
| + optionalMembers = [self parseOptionalMembers];
|
| + [self match:')' enterState:savedState];
|
| +
|
| + result = [[CDType alloc] initUnionType:typeName members:optionalMembers];
|
| + } else {
|
| + NSArray *unionTypes;
|
| +
|
| + unionTypes = [self parseUnionTypes];
|
| + [self match:')' enterState:savedState];
|
| +
|
| + result = [[CDType alloc] initUnionType:nil members:unionTypes];
|
| + }
|
| + } else if (lookahead == '[') { // array
|
| + NSString *number;
|
| + CDType *type;
|
| +
|
| + [self match:'['];
|
| + number = [self parseNumber];
|
| + type = [self _parseType];
|
| + [self match:']'];
|
| +
|
| + result = [[CDType alloc] initArrayType:type count:number];
|
| + } else if ([self isTokenInSimpleTypeSet:lookahead]) { // simple type
|
| + int simpleType;
|
| +
|
| + simpleType = lookahead;
|
| + [self match:simpleType];
|
| + result = [[CDType alloc] initSimpleType:simpleType];
|
| + } else {
|
| + result = nil;
|
| + [NSException raise:CDSyntaxError format:@"expected (many things), got %@", CDTokenDescription(lookahead)];
|
| + }
|
| +
|
| + return [result autorelease];
|
| +}
|
| +
|
| +// This seems to be used in method types -- no names
|
| +- (NSArray *)parseUnionTypes;
|
| +{
|
| + NSMutableArray *members;
|
| +
|
| + members = [NSMutableArray array];
|
| +
|
| + while ([self isTokenInTypeSet:lookahead]) {
|
| + CDType *aType;
|
| +
|
| + aType = [self _parseType];
|
| + //[aType setVariableName:@"___"];
|
| + [members addObject:aType];
|
| + }
|
| +
|
| + return members;
|
| +}
|
| +
|
| +- (NSArray *)parseOptionalMembers;
|
| +{
|
| + NSArray *result;
|
| +
|
| + if (lookahead == '=') {
|
| + [self match:'='];
|
| + result = [self parseMemberList];
|
| + } else
|
| + result = nil;
|
| +
|
| + return result;
|
| +}
|
| +
|
| +- (NSArray *)parseMemberList;
|
| +{
|
| + NSMutableArray *result;
|
| +
|
| + //NSLog(@" > %s", _cmd);
|
| +
|
| + result = [NSMutableArray array];
|
| +
|
| + while (lookahead == TK_QUOTED_STRING || [self isTokenInTypeSet:lookahead])
|
| + [result addObject:[self parseMember]];
|
| +
|
| + //NSLog(@"< %s", _cmd);
|
| +
|
| + return result;
|
| +}
|
| +
|
| +- (CDType *)parseMember;
|
| +{
|
| + CDType *result;
|
| +
|
| + //NSLog(@" > %s", _cmd);
|
| +
|
| + if (lookahead == TK_QUOTED_STRING) {
|
| + NSString *identifier = nil;
|
| +
|
| + while (lookahead == TK_QUOTED_STRING) {
|
| + if (identifier == nil)
|
| + identifier = [lexer lexText];
|
| + else {
|
| + // TextMate 1.5.4 has structures like... "storage""stack"{etc} -- two quoted strings next to each other.
|
| + identifier = [NSString stringWithFormat:@"%@__%@", identifier, [lexer lexText]];
|
| + }
|
| + [self match:TK_QUOTED_STRING];
|
| + }
|
| +
|
| + //NSLog(@"got identifier: %@", identifier);
|
| + result = [self _parseTypeInStruct:YES];
|
| + [result setVariableName:identifier];
|
| + //NSLog(@"And parsed struct type.");
|
| + } else {
|
| + result = [self _parseTypeInStruct:YES];
|
| + }
|
| +
|
| + //NSLog(@"< %s", _cmd);
|
| + return result;
|
| +}
|
| +
|
| +- (CDTypeName *)parseTypeName;
|
| +{
|
| + CDTypeName *typeName;
|
| +
|
| + typeName = [[[CDTypeName alloc] init] autorelease];
|
| + [typeName setName:[self parseIdentifier]];
|
| +
|
| + if (lookahead == '<') {
|
| + CDTypeLexerState savedState;
|
| +
|
| + savedState = [lexer state];
|
| + [self match:'<' enterState:CDTypeLexerStateTemplateTypes];
|
| + [typeName addTemplateType:[self parseTypeName]];
|
| + while (lookahead == ',') {
|
| + [self match:','];
|
| + [typeName addTemplateType:[self parseTypeName]];
|
| + }
|
| + [self match:'>' enterState:savedState];
|
| +
|
| + if ([lexer state] == CDTypeLexerStateTemplateTypes) {
|
| + if (lookahead == TK_IDENTIFIER) {
|
| + NSString *suffix = [lexer lexText];
|
| +
|
| + [self match:TK_IDENTIFIER];
|
| + [typeName setSuffix:suffix];
|
| + }
|
| + }
|
| + }
|
| +
|
| +#if 0
|
| + // This breaks a bunch of the unit tests... need to figure out what's up with that first.
|
| + // We'll treat "?" as no name, returning nil here instead of testing the type name for this later.
|
| + if ([[typeName name] isEqualToString:@"?"] && [typeName isTemplateType] == NO)
|
| + typeName = nil;
|
| +#endif
|
| +
|
| + return typeName;
|
| +}
|
| +
|
| +- (NSString *)parseIdentifier;
|
| +{
|
| + NSString *result = nil;
|
| +
|
| + if (lookahead == TK_IDENTIFIER) {
|
| + result = [lexer lexText];
|
| + [self match:TK_IDENTIFIER];
|
| + }
|
| +
|
| + return result;
|
| +}
|
| +
|
| +- (NSString *)parseNumber;
|
| +{
|
| + if (lookahead == TK_NUMBER) {
|
| + NSString *result;
|
| +
|
| + result = [lexer lexText];
|
| + [self match:TK_NUMBER];
|
| + return result;
|
| + }
|
| +
|
| + return nil;
|
| +}
|
| +
|
| +- (BOOL)isTokenInModifierSet:(int)aToken;
|
| +{
|
| + if (aToken == 'r'
|
| + || aToken == 'n'
|
| + || aToken == 'N'
|
| + || aToken == 'o'
|
| + || aToken == 'O'
|
| + || aToken == 'R'
|
| + || aToken == 'V')
|
| + return YES;
|
| +
|
| + return NO;
|
| +}
|
| +
|
| +- (BOOL)isTokenInSimpleTypeSet:(int)aToken;
|
| +{
|
| + if (aToken == 'c'
|
| + || aToken == 'i'
|
| + || aToken == 's'
|
| + || aToken == 'l'
|
| + || aToken == 'q'
|
| + || aToken == 'C'
|
| + || aToken == 'I'
|
| + || aToken == 'S'
|
| + || aToken == 'L'
|
| + || aToken == 'Q'
|
| + || aToken == 'f'
|
| + || aToken == 'd'
|
| + || aToken == 'B'
|
| + || aToken == 'v'
|
| + || aToken == '*'
|
| + || aToken == '#'
|
| + || aToken == ':'
|
| + || aToken == '%'
|
| + || aToken == '?')
|
| + return YES;
|
| +
|
| + return NO;
|
| +}
|
| +
|
| +- (BOOL)isTokenInTypeSet:(int)aToken;
|
| +{
|
| + if ([self isTokenInModifierSet:aToken]
|
| + || [self isTokenInSimpleTypeSet:aToken]
|
| + || aToken == '^'
|
| + || aToken == 'b'
|
| + || aToken == '@'
|
| + || aToken == '{'
|
| + || aToken == '('
|
| + || aToken == '[')
|
| + return YES;
|
| +
|
| + return NO;
|
| +}
|
| +
|
| +- (BOOL)isTokenInTypeStartSet:(int)aToken;
|
| +{
|
| + if (aToken == 'r'
|
| + || aToken == 'n'
|
| + || aToken == 'N'
|
| + || aToken == 'o'
|
| + || aToken == 'O'
|
| + || aToken == 'R'
|
| + || aToken == 'V'
|
| + || aToken == '^'
|
| + || aToken == 'b'
|
| + || aToken == '@'
|
| + || aToken == '{'
|
| + || aToken == '('
|
| + || aToken == '['
|
| + || [self isTokenInSimpleTypeSet:aToken])
|
| + return YES;
|
| +
|
| + return NO;
|
| +}
|
| +
|
| +@end
|
|
|
| Property changes on: class-dump/src/CDTypeParser.m
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|