OLD | NEW |
(Empty) | |
| 1 // -*- mode: ObjC -*- |
| 2 |
| 3 // This file is part of class-dump, a utility for examining the Objective-C seg
ment of Mach-O files. |
| 4 // Copyright (C) 1997-1998, 2000-2001, 2004-2010 Steve Nygard. |
| 5 |
| 6 #import "CDTypeParser.h" |
| 7 |
| 8 #include <assert.h> |
| 9 #import "CDMethodType.h" |
| 10 #import "CDType.h" |
| 11 #import "CDTypeName.h" |
| 12 #import "CDTypeLexer.h" |
| 13 #import "NSString-Extensions.h" |
| 14 |
| 15 NSString *CDSyntaxError = @"Syntax Error"; |
| 16 NSString *CDTypeParserErrorDomain = @"CDTypeParserErrorDomain"; |
| 17 |
| 18 static BOOL debug = NO; |
| 19 |
| 20 static NSString *CDTokenDescription(int token) |
| 21 { |
| 22 if (token < 128) |
| 23 return [NSString stringWithFormat:@"%d(%c)", token, token]; |
| 24 |
| 25 return [NSString stringWithFormat:@"%d", token]; |
| 26 } |
| 27 |
| 28 @implementation CDTypeParser |
| 29 |
| 30 - (id)initWithType:(NSString *)aType; |
| 31 { |
| 32 NSMutableString *str; |
| 33 |
| 34 if ([super init] == nil) |
| 35 return nil; |
| 36 |
| 37 // Do some preprocessing first: Replace "<unnamed>::" with just "unnamed::". |
| 38 str = [aType mutableCopy]; |
| 39 [str replaceOccurrencesOfString:@"<unnamed>::" withString:@"unnamed::" optio
ns:0 range:NSMakeRange(0, [aType length])]; |
| 40 |
| 41 lexer = [[CDTypeLexer alloc] initWithString:str]; |
| 42 lookahead = 0; |
| 43 |
| 44 [str release]; |
| 45 |
| 46 return self; |
| 47 } |
| 48 |
| 49 - (void)dealloc; |
| 50 { |
| 51 [lexer release]; |
| 52 |
| 53 [super dealloc]; |
| 54 } |
| 55 |
| 56 - (CDTypeLexer *)lexer; |
| 57 { |
| 58 return lexer; |
| 59 } |
| 60 |
| 61 - (NSArray *)parseMethodType:(NSError **)error; |
| 62 { |
| 63 NSArray *result; |
| 64 |
| 65 *error = nil; |
| 66 |
| 67 @try { |
| 68 lookahead = [lexer scanNextToken]; |
| 69 result = [self _parseMethodType]; |
| 70 } |
| 71 @catch (NSException *exception) { |
| 72 NSDictionary *userInfo; |
| 73 int code; |
| 74 |
| 75 // Obviously I need to figure out a sane method of dealing with errors h
ere. This is not. |
| 76 if ([[exception name] isEqual:CDSyntaxError]) { |
| 77 code = CDTypeParserCodeSyntaxError; |
| 78 userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:@"Syntax Err
or", @"reason", |
| 79 [NSString stringWithFormat:@"Syntax
Error, %@:\n\t type: %@\n\tremaining: %@", |
| 80 [exception reason], [lexe
r string], [lexer remainingString]], @"explanation", |
| 81 [lexer string], @"type", |
| 82 [lexer remainingString], @"remainin
g string", |
| 83 nil]; |
| 84 } else { |
| 85 code = CDTypeParserCodeDefault; |
| 86 userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[exception r
eason], @"reason", |
| 87 [lexer string], @"type", |
| 88 [lexer remainingString], @"remainin
g string", |
| 89 nil]; |
| 90 } |
| 91 *error = [NSError errorWithDomain:CDTypeParserErrorDomain code:code user
Info:userInfo]; |
| 92 [userInfo release]; |
| 93 |
| 94 result = nil; |
| 95 } |
| 96 |
| 97 return result; |
| 98 } |
| 99 |
| 100 - (CDType *)parseType:(NSError **)error; |
| 101 { |
| 102 CDType *result; |
| 103 |
| 104 *error = nil; |
| 105 |
| 106 @try { |
| 107 lookahead = [lexer scanNextToken]; |
| 108 result = [self _parseType]; |
| 109 } |
| 110 @catch (NSException *exception) { |
| 111 NSDictionary *userInfo; |
| 112 int code; |
| 113 |
| 114 // Obviously I need to figure out a sane method of dealing with errors h
ere. This is not. |
| 115 if ([[exception name] isEqual:CDSyntaxError]) { |
| 116 code = CDTypeParserCodeSyntaxError; |
| 117 userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:@"Syntax Err
or", @"reason", |
| 118 [NSString stringWithFormat:@"%@:\n\
t type: %@\n\tremaining: %@", |
| 119 [exception reason], [lexe
r string], [lexer remainingString]], @"explanation", |
| 120 [lexer string], @"type", |
| 121 [lexer remainingString], @"remainin
g string", |
| 122 nil]; |
| 123 } else { |
| 124 code = CDTypeParserCodeDefault; |
| 125 userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:[exception r
eason], @"reason", |
| 126 [lexer string], @"type", |
| 127 [lexer remainingString], @"remainin
g string", |
| 128 nil]; |
| 129 } |
| 130 *error = [NSError errorWithDomain:CDTypeParserErrorDomain code:code user
Info:userInfo]; |
| 131 [userInfo release]; |
| 132 |
| 133 result = nil; |
| 134 } |
| 135 |
| 136 return result; |
| 137 } |
| 138 |
| 139 @end |
| 140 |
| 141 @implementation CDTypeParser (Private) |
| 142 |
| 143 - (void)match:(int)token; |
| 144 { |
| 145 [self match:token enterState:[lexer state]]; |
| 146 } |
| 147 |
| 148 - (void)match:(int)token enterState:(int)newState; |
| 149 { |
| 150 if (lookahead == token) { |
| 151 if (debug) NSLog(@"matched %@", CDTokenDescription(token)); |
| 152 [lexer setState:newState]; |
| 153 lookahead = [lexer scanNextToken]; |
| 154 } else { |
| 155 [NSException raise:CDSyntaxError format:@"expected token %@, got %@", |
| 156 CDTokenDescription(token), |
| 157 CDTokenDescription(lookahead)]; |
| 158 } |
| 159 } |
| 160 |
| 161 - (void)error:(NSString *)errorString; |
| 162 { |
| 163 [NSException raise:CDSyntaxError format:@"%@", errorString]; |
| 164 } |
| 165 |
| 166 - (NSArray *)_parseMethodType; |
| 167 { |
| 168 NSMutableArray *methodTypes; |
| 169 CDMethodType *aMethodType; |
| 170 CDType *type; |
| 171 NSString *number; |
| 172 |
| 173 methodTypes = [NSMutableArray array]; |
| 174 |
| 175 // Has to have at least one pair for the return type; |
| 176 // Probably needs at least two more, for object and selector |
| 177 // So it must be <type><number><type><number><type><number>. Three pairs at
a minimum. |
| 178 |
| 179 do { |
| 180 type = [self _parseType]; |
| 181 number = [self parseNumber]; |
| 182 |
| 183 aMethodType = [[CDMethodType alloc] initWithType:type offset:number]; |
| 184 [methodTypes addObject:aMethodType]; |
| 185 [aMethodType release]; |
| 186 } while ([self isTokenInTypeStartSet:lookahead]); |
| 187 |
| 188 return methodTypes; |
| 189 } |
| 190 |
| 191 // Plain object types can be: |
| 192 // @ - plain id type |
| 193 // @"NSObject" - NSObject * |
| 194 // @"<MyProtocol>" - id <MyProtocol> |
| 195 // But these can also be part of a structure, with the field name in quotes befo
re the type: |
| 196 // "foo"i"bar"i - int foo, int bar |
| 197 // "foo"@"bar"i - id foo, int bar |
| 198 // "foo"@"Foo""bar"i - Foo *foo, int bar |
| 199 // So this is where we need to be careful. |
| 200 // |
| 201 // I'm going to make a simplifying assumption: Either the structure/union has m
ember names, |
| 202 // or is doesn't, it can't have some names and be missing others. |
| 203 // The two key tests are: |
| 204 // {my_struct3="field1"@"field2"i} |
| 205 // {my_struct4="field1"@"NSObject""field2"i} |
| 206 |
| 207 - (CDType *)_parseType; |
| 208 { |
| 209 return [self _parseTypeInStruct:NO]; |
| 210 } |
| 211 |
| 212 - (CDType *)_parseTypeInStruct:(BOOL)isInStruct; |
| 213 { |
| 214 CDType *result; |
| 215 |
| 216 if (lookahead == 'r' |
| 217 || lookahead == 'n' |
| 218 || lookahead == 'N' |
| 219 || lookahead == 'o' |
| 220 || lookahead == 'O' |
| 221 || lookahead == 'R' |
| 222 || lookahead == 'V') { // modifiers |
| 223 int modifier; |
| 224 CDType *unmodifiedType; |
| 225 modifier = lookahead; |
| 226 [self match:modifier]; |
| 227 |
| 228 if ([self isTokenInTypeStartSet:lookahead]) |
| 229 unmodifiedType = [self _parseTypeInStruct:isInStruct]; |
| 230 else |
| 231 unmodifiedType = nil; |
| 232 result = [[CDType alloc] initModifier:modifier type:unmodifiedType]; |
| 233 } else if (lookahead == '^') { // pointer |
| 234 CDType *type; |
| 235 |
| 236 [self match:'^']; |
| 237 if (lookahead == TK_QUOTED_STRING || lookahead == '}' || lookahead == ')
') { |
| 238 type = [[CDType alloc] initSimpleType:'v']; |
| 239 // Safari on 10.5 has: "m_function"{?="__pfn"^"__delta"i} |
| 240 result = [[CDType alloc] initPointerType:type]; |
| 241 [type release]; |
| 242 } else { |
| 243 type = [self _parseTypeInStruct:isInStruct]; |
| 244 result = [[CDType alloc] initPointerType:type]; |
| 245 } |
| 246 } else if (lookahead == 'b') { // bitfield |
| 247 NSString *number; |
| 248 |
| 249 [self match:'b']; |
| 250 number = [self parseNumber]; |
| 251 result = [[CDType alloc] initBitfieldType:number]; |
| 252 } else if (lookahead == '@') { // id |
| 253 [self match:'@']; |
| 254 #if 0 |
| 255 if (lookahead == TK_QUOTED_STRING) { |
| 256 NSLog(@"%s, quoted string ahead, shouldCheckFieldNames: %d, end: %d"
, |
| 257 _cmd, shouldCheckFieldNames, [[lexer scanner] isAtEnd]); |
| 258 if ([[lexer scanner] isAtEnd] == NO) |
| 259 NSLog(@"next character: %d (%c), isInTypeStartSet: %d", [lexer p
eekChar], [lexer peekChar], [self isTokenInTypeStartSet:[lexer peekChar]]); |
| 260 } |
| 261 #endif |
| 262 if (lookahead == TK_QUOTED_STRING && (isInStruct == NO || [[lexer lexTex
t] isFirstLetterUppercase] || [self isTokenInTypeStartSet:[lexer peekChar]] == N
O)) { |
| 263 NSString *str; |
| 264 CDTypeName *typeName; |
| 265 |
| 266 str = [lexer lexText]; |
| 267 if ([str hasPrefix:@"<"] && [str hasSuffix:@">"]) { |
| 268 str = [str substringWithRange:NSMakeRange(1, [str length] - 2)]; |
| 269 result = [[CDType alloc] initIDTypeWithProtocols:[str components
SeparatedByString:@","]]; |
| 270 } else { |
| 271 typeName = [[CDTypeName alloc] init]; |
| 272 [typeName setName:str]; |
| 273 result = [[CDType alloc] initIDType:typeName]; |
| 274 [typeName release]; |
| 275 } |
| 276 |
| 277 [self match:TK_QUOTED_STRING]; |
| 278 } else { |
| 279 result = [[CDType alloc] initIDType:nil]; |
| 280 } |
| 281 } else if (lookahead == '{') { // structure |
| 282 CDTypeName *typeName; |
| 283 NSArray *optionalMembers; |
| 284 CDTypeLexerState savedState; |
| 285 |
| 286 savedState = [lexer state]; |
| 287 [self match:'{' enterState:CDTypeLexerStateIdentifier]; |
| 288 typeName = [self parseTypeName]; |
| 289 optionalMembers = [self parseOptionalMembers]; |
| 290 [self match:'}' enterState:savedState]; |
| 291 |
| 292 result = [[CDType alloc] initStructType:typeName members:optionalMembers
]; |
| 293 } else if (lookahead == '(') { // union |
| 294 CDTypeLexerState savedState; |
| 295 |
| 296 savedState = [lexer state]; |
| 297 [self match:'(' enterState:CDTypeLexerStateIdentifier]; |
| 298 if (lookahead == TK_IDENTIFIER) { |
| 299 CDTypeName *typeName; |
| 300 NSArray *optionalMembers; |
| 301 |
| 302 typeName = [self parseTypeName]; |
| 303 optionalMembers = [self parseOptionalMembers]; |
| 304 [self match:')' enterState:savedState]; |
| 305 |
| 306 result = [[CDType alloc] initUnionType:typeName members:optionalMemb
ers]; |
| 307 } else { |
| 308 NSArray *unionTypes; |
| 309 |
| 310 unionTypes = [self parseUnionTypes]; |
| 311 [self match:')' enterState:savedState]; |
| 312 |
| 313 result = [[CDType alloc] initUnionType:nil members:unionTypes]; |
| 314 } |
| 315 } else if (lookahead == '[') { // array |
| 316 NSString *number; |
| 317 CDType *type; |
| 318 |
| 319 [self match:'[']; |
| 320 number = [self parseNumber]; |
| 321 type = [self _parseType]; |
| 322 [self match:']']; |
| 323 |
| 324 result = [[CDType alloc] initArrayType:type count:number]; |
| 325 } else if ([self isTokenInSimpleTypeSet:lookahead]) { // simple type |
| 326 int simpleType; |
| 327 |
| 328 simpleType = lookahead; |
| 329 [self match:simpleType]; |
| 330 result = [[CDType alloc] initSimpleType:simpleType]; |
| 331 } else { |
| 332 result = nil; |
| 333 [NSException raise:CDSyntaxError format:@"expected (many things), got %@
", CDTokenDescription(lookahead)]; |
| 334 } |
| 335 |
| 336 return [result autorelease]; |
| 337 } |
| 338 |
| 339 // This seems to be used in method types -- no names |
| 340 - (NSArray *)parseUnionTypes; |
| 341 { |
| 342 NSMutableArray *members; |
| 343 |
| 344 members = [NSMutableArray array]; |
| 345 |
| 346 while ([self isTokenInTypeSet:lookahead]) { |
| 347 CDType *aType; |
| 348 |
| 349 aType = [self _parseType]; |
| 350 //[aType setVariableName:@"___"]; |
| 351 [members addObject:aType]; |
| 352 } |
| 353 |
| 354 return members; |
| 355 } |
| 356 |
| 357 - (NSArray *)parseOptionalMembers; |
| 358 { |
| 359 NSArray *result; |
| 360 |
| 361 if (lookahead == '=') { |
| 362 [self match:'=']; |
| 363 result = [self parseMemberList]; |
| 364 } else |
| 365 result = nil; |
| 366 |
| 367 return result; |
| 368 } |
| 369 |
| 370 - (NSArray *)parseMemberList; |
| 371 { |
| 372 NSMutableArray *result; |
| 373 |
| 374 //NSLog(@" > %s", _cmd); |
| 375 |
| 376 result = [NSMutableArray array]; |
| 377 |
| 378 while (lookahead == TK_QUOTED_STRING || [self isTokenInTypeSet:lookahead]) |
| 379 [result addObject:[self parseMember]]; |
| 380 |
| 381 //NSLog(@"< %s", _cmd); |
| 382 |
| 383 return result; |
| 384 } |
| 385 |
| 386 - (CDType *)parseMember; |
| 387 { |
| 388 CDType *result; |
| 389 |
| 390 //NSLog(@" > %s", _cmd); |
| 391 |
| 392 if (lookahead == TK_QUOTED_STRING) { |
| 393 NSString *identifier = nil; |
| 394 |
| 395 while (lookahead == TK_QUOTED_STRING) { |
| 396 if (identifier == nil) |
| 397 identifier = [lexer lexText]; |
| 398 else { |
| 399 // TextMate 1.5.4 has structures like... "storage""stack"{etc} -
- two quoted strings next to each other. |
| 400 identifier = [NSString stringWithFormat:@"%@__%@", identifier, [
lexer lexText]]; |
| 401 } |
| 402 [self match:TK_QUOTED_STRING]; |
| 403 } |
| 404 |
| 405 //NSLog(@"got identifier: %@", identifier); |
| 406 result = [self _parseTypeInStruct:YES]; |
| 407 [result setVariableName:identifier]; |
| 408 //NSLog(@"And parsed struct type."); |
| 409 } else { |
| 410 result = [self _parseTypeInStruct:YES]; |
| 411 } |
| 412 |
| 413 //NSLog(@"< %s", _cmd); |
| 414 return result; |
| 415 } |
| 416 |
| 417 - (CDTypeName *)parseTypeName; |
| 418 { |
| 419 CDTypeName *typeName; |
| 420 |
| 421 typeName = [[[CDTypeName alloc] init] autorelease]; |
| 422 [typeName setName:[self parseIdentifier]]; |
| 423 |
| 424 if (lookahead == '<') { |
| 425 CDTypeLexerState savedState; |
| 426 |
| 427 savedState = [lexer state]; |
| 428 [self match:'<' enterState:CDTypeLexerStateTemplateTypes]; |
| 429 [typeName addTemplateType:[self parseTypeName]]; |
| 430 while (lookahead == ',') { |
| 431 [self match:',']; |
| 432 [typeName addTemplateType:[self parseTypeName]]; |
| 433 } |
| 434 [self match:'>' enterState:savedState]; |
| 435 |
| 436 if ([lexer state] == CDTypeLexerStateTemplateTypes) { |
| 437 if (lookahead == TK_IDENTIFIER) { |
| 438 NSString *suffix = [lexer lexText]; |
| 439 |
| 440 [self match:TK_IDENTIFIER]; |
| 441 [typeName setSuffix:suffix]; |
| 442 } |
| 443 } |
| 444 } |
| 445 |
| 446 #if 0 |
| 447 // This breaks a bunch of the unit tests... need to figure out what's up wit
h that first. |
| 448 // We'll treat "?" as no name, returning nil here instead of testing the typ
e name for this later. |
| 449 if ([[typeName name] isEqualToString:@"?"] && [typeName isTemplateType] == N
O) |
| 450 typeName = nil; |
| 451 #endif |
| 452 |
| 453 return typeName; |
| 454 } |
| 455 |
| 456 - (NSString *)parseIdentifier; |
| 457 { |
| 458 NSString *result = nil; |
| 459 |
| 460 if (lookahead == TK_IDENTIFIER) { |
| 461 result = [lexer lexText]; |
| 462 [self match:TK_IDENTIFIER]; |
| 463 } |
| 464 |
| 465 return result; |
| 466 } |
| 467 |
| 468 - (NSString *)parseNumber; |
| 469 { |
| 470 if (lookahead == TK_NUMBER) { |
| 471 NSString *result; |
| 472 |
| 473 result = [lexer lexText]; |
| 474 [self match:TK_NUMBER]; |
| 475 return result; |
| 476 } |
| 477 |
| 478 return nil; |
| 479 } |
| 480 |
| 481 - (BOOL)isTokenInModifierSet:(int)aToken; |
| 482 { |
| 483 if (aToken == 'r' |
| 484 || aToken == 'n' |
| 485 || aToken == 'N' |
| 486 || aToken == 'o' |
| 487 || aToken == 'O' |
| 488 || aToken == 'R' |
| 489 || aToken == 'V') |
| 490 return YES; |
| 491 |
| 492 return NO; |
| 493 } |
| 494 |
| 495 - (BOOL)isTokenInSimpleTypeSet:(int)aToken; |
| 496 { |
| 497 if (aToken == 'c' |
| 498 || aToken == 'i' |
| 499 || aToken == 's' |
| 500 || aToken == 'l' |
| 501 || aToken == 'q' |
| 502 || aToken == 'C' |
| 503 || aToken == 'I' |
| 504 || aToken == 'S' |
| 505 || aToken == 'L' |
| 506 || aToken == 'Q' |
| 507 || aToken == 'f' |
| 508 || aToken == 'd' |
| 509 || aToken == 'B' |
| 510 || aToken == 'v' |
| 511 || aToken == '*' |
| 512 || aToken == '#' |
| 513 || aToken == ':' |
| 514 || aToken == '%' |
| 515 || aToken == '?') |
| 516 return YES; |
| 517 |
| 518 return NO; |
| 519 } |
| 520 |
| 521 - (BOOL)isTokenInTypeSet:(int)aToken; |
| 522 { |
| 523 if ([self isTokenInModifierSet:aToken] |
| 524 || [self isTokenInSimpleTypeSet:aToken] |
| 525 || aToken == '^' |
| 526 || aToken == 'b' |
| 527 || aToken == '@' |
| 528 || aToken == '{' |
| 529 || aToken == '(' |
| 530 || aToken == '[') |
| 531 return YES; |
| 532 |
| 533 return NO; |
| 534 } |
| 535 |
| 536 - (BOOL)isTokenInTypeStartSet:(int)aToken; |
| 537 { |
| 538 if (aToken == 'r' |
| 539 || aToken == 'n' |
| 540 || aToken == 'N' |
| 541 || aToken == 'o' |
| 542 || aToken == 'O' |
| 543 || aToken == 'R' |
| 544 || aToken == 'V' |
| 545 || aToken == '^' |
| 546 || aToken == 'b' |
| 547 || aToken == '@' |
| 548 || aToken == '{' |
| 549 || aToken == '(' |
| 550 || aToken == '[' |
| 551 || [self isTokenInSimpleTypeSet:aToken]) |
| 552 return YES; |
| 553 |
| 554 return NO; |
| 555 } |
| 556 |
| 557 @end |
OLD | NEW |