blob: d903ff2200daa74d4a098bc5973e7c81122b3cb8 [file] [log] [blame]
// 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-2006 Steve Nygard
#import "CDType.h"
#import <Foundation/Foundation.h>
#import "NSArray-Extensions.h"
#import "NSString-Extensions.h"
#import "CDSymbolReferences.h"
#import "CDTypeName.h"
#import "CDTypeLexer.h" // For T_NAMED_OBJECT
#import "CDTypeFormatter.h"
@implementation CDType
- (id)init;
{
if ([super init] == nil)
return nil;
type = 0; // ??
protocols = nil;
subtype = nil;
typeName = nil;
members = nil;
variableName = nil;
bitfieldSize = nil;
arraySize = nil;
return self;
}
- (id)initSimpleType:(int)aTypeCode;
{
if ([self init] == nil)
return nil;
if (aTypeCode == '*') {
type = '^';
subtype = [[CDType alloc] initSimpleType:'c'];
} else {
type = aTypeCode;
}
return self;
}
- (id)initIDType:(CDTypeName *)aName;
{
if ([self init] == nil)
return nil;
if (aName != nil) {
type = '^';
subtype = [[CDType alloc] initNamedType:aName];
} else {
type = '@';
}
return self;
}
- (id)initIDTypeWithProtocols:(NSArray *)someProtocols;
{
if ([self init] == nil)
return nil;
type = '@';
protocols = [someProtocols retain];
return self;
}
- (id)initNamedType:(CDTypeName *)aName;
{
if ([self init] == nil)
return nil;
type = T_NAMED_OBJECT;
typeName = [aName retain];
return self;
}
- (id)initStructType:(CDTypeName *)aName members:(NSArray *)someMembers;
{
if ([self init] == nil)
return nil;
type = '{';
typeName = [aName retain];
members = [someMembers retain];
return self;
}
- (id)initUnionType:(CDTypeName *)aName members:(NSArray *)someMembers;
{
if ([self init] == nil)
return nil;
type = '(';
typeName = [aName retain];
members = [someMembers retain];
return self;
}
- (id)initBitfieldType:(NSString *)aBitfieldSize;
{
if ([self init] == nil)
return nil;
type = 'b';
bitfieldSize = [aBitfieldSize retain];
return self;
}
- (id)initArrayType:(CDType *)aType count:(NSString *)aCount;
{
if ([self init] == nil)
return nil;
type = '[';
arraySize = [aCount retain];
subtype = [aType retain];
return self;
}
- (id)initPointerType:(CDType *)aType;
{
if ([self init] == nil)
return nil;
type = '^';
subtype = [aType retain];
return self;
}
- (id)initModifier:(int)aModifier type:(CDType *)aType;
{
if ([self init] == nil)
return nil;
type = aModifier;
subtype = [aType retain];
return self;
}
- (void)dealloc;
{
[protocols release];
[subtype release];
[typeName release];
[members release];
[variableName release];
[bitfieldSize release];
[arraySize release];
[super dealloc];
}
- (NSString *)variableName;
{
return variableName;
}
- (void)setVariableName:(NSString *)newVariableName;
{
if (newVariableName == variableName)
return;
[variableName release];
variableName = [newVariableName retain];
}
- (int)type;
{
return type;
}
- (BOOL)isIDType;
{
return type == '@' && typeName == nil;
}
- (CDType *)subtype;
{
return subtype;
}
- (CDTypeName *)typeName;
{
return typeName;
}
- (NSArray *)members;
{
return members;
}
- (void)setMembers:(NSArray *)newMembers;
{
if (newMembers == members)
return;
[members release];
members = [newMembers retain];
}
- (BOOL)isModifierType;
{
return type == 'r' || type == 'n' || type == 'N' || type == 'o' || type == 'O' || type == 'R' || type == 'V';
}
- (int)typeIgnoringModifiers;
{
if ([self isModifierType] == YES && subtype != nil)
return [subtype typeIgnoringModifiers];
return type;
}
- (NSString *)description;
{
return [NSString stringWithFormat:@"[%@] type: %d('%c'), name: %@, subtype: %@, bitfieldSize: %@, arraySize: %@, members: %@, variableName: %@",
NSStringFromClass([self class]), type, type, typeName, subtype, bitfieldSize, arraySize, members, variableName];
}
- (NSString *)formattedString:(NSString *)previousName formatter:(CDTypeFormatter *)typeFormatter level:(int)level symbolReferences:(CDSymbolReferences *)symbolReferences;
{
NSString *result, *currentName;
NSString *baseType, *memberString;
assert(variableName == nil || previousName == nil);
if (variableName != nil)
currentName = variableName;
else
currentName = previousName;
switch (type) {
case T_NAMED_OBJECT:
assert(typeName != nil);
[symbolReferences addClassName:[typeName name]];
if (currentName == nil)
result = [typeName description];
else
result = [NSString stringWithFormat:@"%@ %@", typeName, currentName];
break;
case '@':
if (currentName == nil) {
if (protocols == nil)
result = @"id";
else
result = [NSString stringWithFormat:@"id <%@>", [protocols componentsJoinedByString:@", "]];
} else {
if (protocols == nil)
result = [NSString stringWithFormat:@"id %@", currentName];
else
result = [NSString stringWithFormat:@"id <%@> %@", [protocols componentsJoinedByString:@", "], currentName];
}
break;
case 'b':
if (currentName == nil) {
// This actually compiles!
result = [NSString stringWithFormat:@"unsigned int :%@", bitfieldSize];
} else
result = [NSString stringWithFormat:@"unsigned int %@:%@", currentName, bitfieldSize];
break;
case '[':
if (currentName == nil)
result = [NSString stringWithFormat:@"[%@]", arraySize];
else
result = [NSString stringWithFormat:@"%@[%@]", currentName, arraySize];
result = [subtype formattedString:result formatter:typeFormatter level:level symbolReferences:symbolReferences];
break;
case '(':
baseType = nil;
if (typeName == nil || [@"?" isEqual:[typeName description]] == YES) {
NSString *typedefName;
typedefName = [typeFormatter typedefNameForStruct:self level:level];
if (typedefName != nil) {
baseType = typedefName;
}
}
if (baseType == nil) {
if (typeName == nil || [@"?" isEqual:[typeName description]] == YES)
baseType = @"union";
else
baseType = [NSString stringWithFormat:@"union %@", typeName];
if (([typeFormatter shouldAutoExpand] == YES && [@"?" isEqual:[typeName description]] == YES)
|| (level == 0 && [typeFormatter shouldExpand] == YES && [members count] > 0))
memberString = [NSString stringWithFormat:@" {\n%@%@}",
[self formattedStringForMembersAtLevel:level + 1 formatter:typeFormatter symbolReferences:symbolReferences],
[NSString spacesIndentedToLevel:[typeFormatter baseLevel] + level spacesPerLevel:4]];
else
memberString = @"";
baseType = [baseType stringByAppendingString:memberString];
}
if (currentName == nil || [currentName hasPrefix:@"?"] == YES) // Not sure about this
result = baseType;
else
result = [NSString stringWithFormat:@"%@ %@", baseType, currentName];
break;
case '{':
baseType = nil;
if (typeName == nil || [@"?" isEqual:[typeName description]] == YES) {
NSString *typedefName;
typedefName = [typeFormatter typedefNameForStruct:self level:level];
if (typedefName != nil) {
baseType = typedefName;
}
}
if (baseType == nil) {
if (typeName == nil || [@"?" isEqual:[typeName description]] == YES)
baseType = @"struct";
else
baseType = [NSString stringWithFormat:@"struct %@", typeName];
if (([typeFormatter shouldAutoExpand] == YES && [@"?" isEqual:[typeName description]] == YES)
|| (level == 0 && [typeFormatter shouldExpand] == YES && [members count] > 0))
memberString = [NSString stringWithFormat:@" {\n%@%@}",
[self formattedStringForMembersAtLevel:level + 1 formatter:typeFormatter symbolReferences:symbolReferences],
[NSString spacesIndentedToLevel:[typeFormatter baseLevel] + level spacesPerLevel:4]];
else
memberString = @"";
baseType = [baseType stringByAppendingString:memberString];
}
if (currentName == nil || [currentName hasPrefix:@"?"] == YES) // Not sure about this
result = baseType;
else
result = [NSString stringWithFormat:@"%@ %@", baseType, currentName];
break;
case '^':
if (currentName == nil)
result = @"*";
else
result = [@"*" stringByAppendingString:currentName];
if (subtype != nil && [subtype type] == '[')
result = [NSString stringWithFormat:@"(%@)", result];
result = [subtype formattedString:result formatter:typeFormatter level:level symbolReferences:symbolReferences];
break;
case 'r':
case 'n':
case 'N':
case 'o':
case 'O':
case 'R':
case 'V':
if (subtype == nil) {
if (currentName == nil)
result = [self formattedStringForSimpleType];
else
result = [NSString stringWithFormat:@"%@ %@", [self formattedStringForSimpleType], currentName];
} else
result = [NSString stringWithFormat:@"%@ %@",
[self formattedStringForSimpleType], [subtype formattedString:currentName formatter:typeFormatter level:level symbolReferences:symbolReferences]];
break;
default:
if (currentName == nil)
result = [self formattedStringForSimpleType];
else
result = [NSString stringWithFormat:@"%@ %@", [self formattedStringForSimpleType], currentName];
break;
}
return result;
}
- (NSString *)formattedStringForMembersAtLevel:(int)level formatter:(CDTypeFormatter *)typeFormatter symbolReferences:(CDSymbolReferences *)symbolReferences;
{
NSMutableString *str;
int count, index;
CDType *replacementType;
NSArray *targetMembers;
assert(type == '{' || type == '(');
str = [NSMutableString string];
// The replacement type will have member names, while ours don't.
replacementType = [typeFormatter replacementForType:self];
if (replacementType != nil)
targetMembers = [replacementType members];
else
targetMembers = members;
count = [targetMembers count];
for (index = 0; index < count; index++) {
[str appendString:[NSString spacesIndentedToLevel:[typeFormatter baseLevel] + level spacesPerLevel:4]];
[str appendString:[[targetMembers objectAtIndex:index] formattedString:nil
formatter:typeFormatter
level:level
symbolReferences:symbolReferences]];
[str appendString:@";\n"];
}
return str;
}
- (NSString *)formattedStringForSimpleType;
{
// Ugly but simple:
switch (type) {
case 'c': return @"char";
case 'i': return @"int";
case 's': return @"short";
case 'l': return @"long";
case 'q': return @"long long";
case 'C': return @"unsigned char";
case 'I': return @"unsigned int";
case 'S': return @"unsigned short";
case 'L': return @"unsigned long";
case 'Q': return @"unsigned long long";
case 'f': return @"float";
case 'd': return @"double";
case 'B': return @"_Bool"; /* C99 _Bool or C++ bool */
case 'v': return @"void";
case '*': return @"STR";
case '#': return @"Class";
case ':': return @"SEL";
case '%': return @"NXAtom";
case '?': return @"void";
//case '?': return @"UNKNOWN"; // For easier regression testing.
case 'r': return @"const";
case 'n': return @"in";
case 'N': return @"inout";
case 'o': return @"out";
case 'O': return @"bycopy";
case 'R': return @"byref";
case 'V': return @"oneway";
default:
break;
}
return nil;
}
- (NSString *)typeString;
{
return [self _typeStringWithVariableNamesToLevel:1e6];
}
- (NSString *)bareTypeString;
{
return [self _typeStringWithVariableNamesToLevel:0];
}
- (NSString *)keyTypeString;
{
// use variable names at top level
return [self _typeStringWithVariableNamesToLevel:1];
}
- (NSString *)_typeStringWithVariableNamesToLevel:(int)level;
{
NSString *result;
switch (type) {
case T_NAMED_OBJECT:
assert(typeName != nil);
result = [NSString stringWithFormat:@"@\"%@\"", typeName];
break;
case '@':
result = @"@";
break;
case 'b':
result = [NSString stringWithFormat:@"b%@", bitfieldSize];
break;
case '[':
result = [NSString stringWithFormat:@"[%@%@]", arraySize, [subtype _typeStringWithVariableNamesToLevel:level]];
break;
case '(':
if (typeName == nil) {
return [NSString stringWithFormat:@"(%@)", [self _typeStringForMembersWithVariableNamesToLevel:level]];
} else if ([members count] == 0) {
return [NSString stringWithFormat:@"(%@)", typeName];
} else {
return [NSString stringWithFormat:@"(%@=%@)", typeName, [self _typeStringForMembersWithVariableNamesToLevel:level]];
}
break;
case '{':
if (typeName == nil) {
return [NSString stringWithFormat:@"{%@}", [self _typeStringForMembersWithVariableNamesToLevel:level]];
} else if ([members count] == 0) {
return [NSString stringWithFormat:@"{%@}", typeName];
} else {
return [NSString stringWithFormat:@"{%@=%@}", typeName, [self _typeStringForMembersWithVariableNamesToLevel:level]];
}
break;
case '^':
if ([subtype type] == T_NAMED_OBJECT)
result = [subtype _typeStringWithVariableNamesToLevel:level];
else
result = [NSString stringWithFormat:@"^%@", [subtype _typeStringWithVariableNamesToLevel:level]];
break;
case 'r':
case 'n':
case 'N':
case 'o':
case 'O':
case 'R':
case 'V':
result = [NSString stringWithFormat:@"%c%@", type, [subtype _typeStringWithVariableNamesToLevel:level]];
break;
default:
result = [NSString stringWithFormat:@"%c", type];
break;
}
return result;
}
- (NSString *)_typeStringForMembersWithVariableNamesToLevel:(int)level;
{
NSMutableString *str;
int count, index;
assert(type == '{' || type == '(');
str = [NSMutableString string];
count = [members count];
for (index = 0; index < count; index++) {
CDType *aMember;
aMember = [members objectAtIndex:index];
if ([aMember variableName] != nil && level > 0)
[str appendFormat:@"\"%@\"", [aMember variableName]];
[str appendString:[aMember _typeStringWithVariableNamesToLevel:level - 1]];
}
return str;
}
- (void)phase:(int)phase registerStructuresWithObject:(id <CDStructureRegistration>)anObject usedInMethod:(BOOL)isUsedInMethod;
{
if (phase == 1)
[self phase1RegisterStructuresWithObject:anObject];
else if (phase == 2)
[self phase2RegisterStructuresWithObject:anObject usedInMethod:isUsedInMethod countReferences:YES];
}
- (void)phase1RegisterStructuresWithObject:(id <CDStructureRegistration>)anObject;
{
int count, index;
if (subtype != nil)
[subtype phase1RegisterStructuresWithObject:anObject];
count = [members count];
if ((type == '{' || type == '(') && count > 0) {
[anObject phase1RegisterStructure:self];
for (index = 0; index < count; index++) {
[[members objectAtIndex:index] phase1RegisterStructuresWithObject:anObject];
}
}
}
- (void)phase2RegisterStructuresWithObject:(id <CDStructureRegistration>)anObject
usedInMethod:(BOOL)isUsedInMethod
countReferences:(BOOL)shouldCountReferences;
{
if (subtype != nil)
[subtype phase2RegisterStructuresWithObject:anObject usedInMethod:isUsedInMethod countReferences:shouldCountReferences];
if (type == '{' || type == '(') {
int count, index;
BOOL newFlag;
newFlag = [anObject phase2RegisterStructure:self usedInMethod:isUsedInMethod countReferences:shouldCountReferences];
if (shouldCountReferences == NO)
newFlag = NO;
count = [members count];
for (index = 0; index < count; index++) {
[[members objectAtIndex:index] phase2RegisterStructuresWithObject:anObject usedInMethod:NO countReferences:newFlag];
}
}
}
- (BOOL)isEqual:(CDType *)otherType;
{
return [[self typeString] isEqual:[otherType typeString]];
}
- (BOOL)isBasicallyEqual:(CDType *)otherType;
{
return [[self keyTypeString] isEqual:[otherType keyTypeString]];
}
- (BOOL)isStructureEqual:(CDType *)otherType;
{
return [[self bareTypeString] isEqual:[otherType bareTypeString]];
}
// Merge struct/union member names
- (void)mergeWithType:(CDType *)otherType;
{
int count, index;
int otherCount;
NSArray *otherMembers;
if ([self type] != [otherType type]) {
NSLog(@"Warning: Trying to merge different types in %s", _cmd);
return;
}
[subtype mergeWithType:[otherType subtype]];
otherMembers = [otherType members];
count = [members count];
otherCount = [otherMembers count];
// The counts can be zero when we register structures that just have a name. That happened while I was working on the
// structure registration.
if (otherCount == 0) {
return;
} else if (count == 0 && otherCount != 0) {
[self setMembers:otherMembers];
} else if (count != otherCount) {
NSLog(@"Warning: Types have different number of members. This is bad. (%d vs %d)", count, otherCount);
NSLog(@"%@ vs %@", [self typeString], [otherType typeString]);
return;
}
for (index = 0; index < count; index++) {
CDType *thisMember, *otherMember;
CDTypeName *thisTypeName, *otherTypeName;
NSString *thisVariableName, *otherVariableName;
thisMember = [members objectAtIndex:index];
otherMember = [otherMembers objectAtIndex:index];
thisTypeName = [thisMember typeName];
otherTypeName = [otherMember typeName];
thisVariableName = [thisMember variableName];
otherVariableName = [otherMember variableName];
//NSLog(@"%d: type: %@ vs %@", index, thisTypeName, otherTypeName);
//NSLog(@"%d: vari: %@ vs %@", index, thisVariableName, otherVariableName);
if ((thisTypeName == nil && otherTypeName != nil) || (thisTypeName != nil && otherTypeName == nil))
; // It seems to be okay if one of them didn't have a name
//NSLog(@"Warning: (1) type names don't match, %@ vs %@", thisTypeName, otherTypeName);
else if (thisTypeName != nil && [thisTypeName isEqual:otherTypeName] == NO)
NSLog(@"Warning: (2) type names don't match, %@ vs %@.", thisTypeName, otherTypeName);
if (otherVariableName != nil) {
if (thisVariableName == nil)
[thisMember setVariableName:otherVariableName];
else if ([thisVariableName isEqual:otherVariableName] == NO)
NSLog(@"Warning: Different variable names for same member...");
}
}
}
- (void)generateMemberNames;
{
if (type == '{' || type == '(') {
int count, index;
NSSet *usedNames;
int number;
CDType *aMember;
NSString *name;
usedNames = [[NSSet alloc] initWithArray:[members arrayByMappingSelector:@selector(variableName)]];
count = [members count];
number = 1;
for (index = 0; index < count; index++) {
aMember = [members objectAtIndex:index];
[aMember generateMemberNames];
// Bitfields don't need a name.
if ([aMember variableName] == nil && [aMember type] != 'b') {
do {
name = [NSString stringWithFormat:@"_field%d", number++];
} while ([usedNames containsObject:name] == YES);
[aMember setVariableName:name];
}
}
[usedNames release];
}
[subtype generateMemberNames];
}
@end