libs-base/Tools/classes.m

528 lines
11 KiB
Objective-C

/* Crude tool to compare classes in different version of library code to
* look for possible compatiblity issues.
*
* This tools is very much subject to change!
*
* At present, the tool simply looks for the methods available in public
* classes (excluding private methods).
*
* To check for public symbols of other kinds, use the 'nm' command on the
* library with -g (public/external) and -U (defined symbols) options.
* Filter the output to remove symbols we are not interested in.
* eg.
* nm -g -U Source/obj/libgnustep-base.so.1.31.0 | fgrep -v -e ' GS' -e ' NS' \
* -e ' ._OBJC' -e ' __objc' -e ' __odr_asan' >/tmp/result.txt
*
* Names beginning GS or NS are assumed to be intentionally public.
* We probably arent intersted in classes, ivar offsets, or ASAN synbols.
*/
#import "Foundation/Foundation.h"
#import "GNUstepBase/GNUstep.h"
#import "GNUstepBase/GSObjCRuntime.h"
static NSSet *
publicClasses()
{
static NSSet *set = nil;
if (nil == set)
{
static char *base[] = {
"NSAffineTransform",
"NSAppleEventDescriptor",
"NSAppleEventManager",
"NSAppleScript",
"NSArchiver",
"NSArray",
"NSAssertionHandler",
"NSAttributedString",
"NSAutoreleasePool",
"NSBackgroundActivityScheduler",
"NSBlockOperation",
"NSBundle",
"NSByteCountFormatter",
"NSCache",
"NSCachedURLResponse",
"NSCalendar",
"NSCalendarDate",
"NSCharacterSet",
"NSClassDescription",
"NSCoder",
"NSComparisonPredicate",
"NSCompoundPredicate",
"NSCondition",
"NSConditionLock",
"NSConnection",
"NSCountedSet",
"NSData",
"NSDate",
"NSDateComponents",
"NSDateComponentsFormatter",
"NSDateFormatter",
"NSDateInterval",
"NSDateIntervalFormatter",
"NSDecimalNumber",
"NSDecimalNumberHandler",
"NSDeserializer",
"NSDictionary",
"NSDimension",
"NSDirectoryEnumerator",
"NSDistantObject",
"NSDistributedLock",
"NSDistributedNotificationCenter",
"NSEnergyFormatter",
"NSEnumerator",
"NSError",
"NSException",
"NSExpression",
"NSExtensionContext",
"NSExtensionItem",
"NSFileAccessIntent",
"NSFileCoordinator",
"NSFileHandle",
"NSFileManager",
"NSFileVersion",
"NSFileWrapper",
"NSFormatter",
"NSGarbageCollector",
"NSHTTPCookie",
"NSHTTPCookieStorage",
"NSHTTPURLResponse",
"NSHashTable",
"NSHost",
"NSISO8601DateFormatter",
"NSIndexPath",
"NSIndexSet",
"NSInputStream",
"NSInvocation",
"NSInvocationOperation",
"NSItemProvider",
"NSItemProviderReadingWriting",
"NSJSONSerialization",
"NSKeyedArchiver",
"NSKeyedUnarchiver",
"NSLengthFormatter",
"NSLinguisticTagger",
"NSLocale",
"NSLock",
"NSMapTable",
"NSMassFormatter",
"NSMeasurement",
"NSMeasurementFormatter",
"NSMessagePort",
"NSMessagePortNameServer",
"NSMetadataItem",
"NSMetadataQuery",
"NSMetadataQueryAttributeValueTuple",
"NSMetadataQueryResultGroup",
"NSMethodSignature",
"NSMutableArray",
"NSMutableAttributedString",
"NSMutableCharacterSet",
"NSMutableData",
"NSMutableDictionary",
"NSMutableIndexSet",
"NSMutableOrderedSet",
"NSMutableSet",
"NSMutableString",
"NSMutableURLRequest",
"NSNetService",
"NSNetServiceBrowser",
"NSNotification",
"NSNotificationCenter",
"NSNotificationQueue",
"NSNull",
"NSNumber",
"NSNumberFormatter",
"NSObject",
"NSObjectScripting",
"NSOperation",
"NSOperationQueue",
"NSOrderedSet",
"NSOrthography",
"NSOutputStream",
"NSPersonNameComponents",
"NSPersonNameComponentsFormatter",
"NSPipe",
"NSPointerArray",
"NSPointerFunctions",
"NSPort",
"NSPortCoder",
"NSPortMessage",
"NSPortNameServer",
"NSPredicate",
"NSProcessInfo",
"NSProgress",
"NSPropertyListSerialization",
"NSProtocolChecker",
"NSProxy",
"NSRecursiveLock",
"NSRegularExpression",
"NSRunLoop",
"NSScanner",
"NSScriptClassDescription",
"NSScriptCoercionHandler",
"NSScriptCommand",
"NSScriptCommandDescription",
"NSScriptExecutionContext",
"NSScriptKeyValueCoding",
"NSScriptObjectSpecifiers",
"NSScriptStandardSuiteCommands",
"NSScriptSuiteRegistry",
"NSSerializer",
"NSSet",
"NSSocketPort",
"NSSocketPortNameServer",
"NSSortDescriptor",
"NSSpellServer",
"NSStream",
"NSString",
"NSTask",
"NSTextCheckingResult",
"NSThread",
"NSTimeZone",
"NSTimeZoneDetail",
"NSTimer",
"NSURL",
"NSURLAuthenticationChallenge",
"NSURLCache",
"NSURLComponents",
"NSURLConnection",
"NSURLCredential",
"NSURLCredentialStorage",
"NSURLDownload",
"NSURLHandle",
"NSURLProtectionSpace",
"NSURLProtocol",
"NSURLQueryItem",
"NSURLRequest",
"NSURLResponse",
"NSURLSession",
"NSURLSessionConfiguration",
"NSURLSessionDataTask",
"NSURLSessionDownloadTask",
"NSURLSessionStreamTask",
"NSURLSessionTask",
"NSURLSessionUploadTask",
"NSUUID",
"NSUbiquitousKeyValueStore",
"NSUnarchiver",
"NSUndoManager",
"NSUnit",
"NSUnitAcceleration",
"NSUnitAngle",
"NSUnitArea",
"NSUnitConcentrationMass",
"NSUnitConverter",
"NSUnitConverterLinear",
"NSUnitDispersion",
"NSUnitDuration",
"NSUnitElectricCharge",
"NSUnitElectricCurrent",
"NSUnitElectricPotentialDifference",
"NSUnitElectricResistance",
"NSUnitEnergy",
"NSUnitFrequency",
"NSUnitFuelEfficiency",
"NSUnitIlluminance",
"NSUnitLength",
"NSUnitMass",
"NSUnitPower",
"NSUnitPressure",
"NSUnitSpeed",
"NSUnitTemperature",
"NSUnitVolume",
"NSUserActivity",
"NSUserDefaults",
"NSUserNotification",
"NSUserNotificationCenter",
"NSUserScriptTask",
"NSValue",
"NSValueTransformer",
"NSXMLDTD",
"NSXMLDTDNode",
"NSXMLDocument",
"NSXMLElement",
"NSXMLNode",
"NSXMLParser",
"NSXPCConnection",
"NSXPCInterface",
"NSXPCListener",
"NSXPCListenerEndpoint"
};
unsigned count = sizeof(base)/sizeof(*base);
unsigned index;
NSMutableSet *m;
ENTER_POOL
m = [NSMutableSet setWithCapacity: count];
for (index = 0; index < count; index++)
{
[m addObject: [NSString stringWithUTF8String: base[index]]];
}
ASSIGNCOPY(set, m);
LEAVE_POOL
}
return set;
}
static BOOL
findClassMethod(NSString *mName, NSString *cName, NSDictionary *all)
{
while (cName != nil)
{
NSDictionary *info = [all objectForKey: cName];
if ([[info objectForKey: @"classmethods"] containsObject: mName])
{
return YES;
}
cName = [info objectForKey: @"superclass"];
}
return NO;
}
static BOOL
findInstanceMethod(NSString *mName, NSString *cName, NSDictionary *all)
{
while (cName != nil)
{
NSDictionary *info = [all objectForKey: cName];
if ([[info objectForKey: @"instancemethods"] containsObject: mName])
{
return YES;
}
cName = [info objectForKey: @"superclass"];
}
return NO;
}
static BOOL
doCompare(NSString *name, NSDictionary *oinfo, NSDictionary *all)
{
NSDictionary *ninfo = [all objectForKey: name];
NSEnumerator *e;
id n;
id o;
BOOL ok = YES;
if (nil == ninfo)
{
NSLog(@"Class '%@' removed\n", name);
return NO;
}
o = [oinfo objectForKey: @"superclass"];
n = [ninfo objectForKey: @"superclass"];
if (o != n && NO == [o isEqual: n])
{
NSLog(@"Class '%@' superclass changed from %@ to %@\n", name, o, n);
ok = NO;
}
e = [[oinfo objectForKey: @"classmethods"] objectEnumerator];
while ((o = [e nextObject]) != nil)
{
if (NO == findClassMethod(o, name, all))
{
NSLog(@"Class '%@' class method '%@' removed\n", name, o);
ok = NO;
}
}
e = [[oinfo objectForKey: @"instancemethods"] objectEnumerator];
while ((o = [e nextObject]) != nil)
{
if (NO == findInstanceMethod(o, name, all))
{
NSLog(@"Class '%@' instance method '%@' removed\n", name, o);
ok = NO;
}
}
return ok;
}
static void
doMethods(Class class, NSMutableDictionary *info, BOOL instance)
{
NSMutableArray *ma = [NSMutableArray array];
Method *methods;
unsigned int count;
if (instance)
{
methods = class_copyMethodList(class, &count);
}
else
{
methods = class_copyMethodList(object_getClass(class), &count);
}
if (methods)
{
NSString *name;
unsigned i;
for (i = 0; i < count; i++)
{
SEL sel = method_getName(methods[i]);
const char *n = sel_getName(sel);
if (0 == n || '_' == *n)
{
continue;
}
name = [NSString stringWithUTF8String: n];
if ([ma containsObject: name] == NO)
{
[ma addObject: name];
}
}
free(methods);
}
if ([ma count])
{
[ma sortUsingSelector: @selector(compare:)];
if (instance)
{
[info setObject: ma forKey: @"instancemethods"];
}
else
{
[info setObject: ma forKey: @"classmethods"];
}
}
}
int
main(int argc, char *argv[])
{
ENTER_POOL
NSDictionary *oldClasses;
NSMutableDictionary *newClasses;
// Protocol **protocols;
NSString *signature;
int classCount;
NSDictionary *locale;
NSString *name;
Class class;
NSSet *set;
locale = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
oldClasses = [NSDictionary dictionaryWithContentsOfFile: @"OldClasses.plist"];
set = publicClasses();
classCount = [set count];
if (0 == classCount)
{
/* No expected classes ... try to get all classes instead.
*/
classCount = objc_getClassList(NULL, 0);
set = nil;
}
if (classCount > 0)
{
Class buf[classCount];
int ci;
if (set)
{
NSEnumerator *e = [set objectEnumerator];
ci = 0;
while ((name = [e nextObject]) != nil)
{
class = NSClassFromString(name);
if (Nil == class)
{
NSLog(@"Expected class '%@' not found.", name);
}
else
{
buf[ci++] = class;
}
}
}
else
{
ci = objc_getClassList(buf, classCount);
NSCAssert(ci == classCount, NSInternalInconsistencyException);
}
classCount = ci;
newClasses = [NSMutableDictionary dictionaryWithCapacity: ci];
for (ci = 0; ci < classCount; ci++)
{
Class superClass;
NSMutableDictionary *classDescription;
const char *n;
class = buf[ci];
if (class_isMetaClass(class))
{
continue;
}
n = class_getName(class);
if (0 == n || '_' == *n)
{
continue;
}
name = [NSString stringWithUTF8String: class_getName(class)];
if (set && nil == [set member: name])
{
NSLog(@"Unexpected class '%@' ignored", name);
continue;
}
classDescription = [NSMutableDictionary dictionaryWithCapacity: 4];
[newClasses setObject: classDescription forKey: name];
superClass = class_getSuperclass(class);
if (superClass != Nil)
{
n = class_getName(superClass);
name = [NSString stringWithUTF8String: n];
[classDescription setObject: name forKey: @"superclass"];
}
doMethods(class, classDescription, YES);
doMethods(class, classDescription, NO);
}
}
else
{
NSLog(@"Failed to find any classes");
exit(1);
}
signature = [newClasses descriptionWithLocale: locale indent: 0];
[signature writeToFile: @"NewClasses.plist" atomically: NO];
if (oldClasses)
{
NSEnumerator *e = [oldClasses keyEnumerator];
BOOL ok = YES;
while ((name = [e nextObject]) != nil)
{
NSDictionary *oinfo = [oldClasses objectForKey: name];
if (NO == doCompare(name, oinfo, newClasses))
{
ok = NO;
}
}
if (ok)
{
NSLog(@"Old and new class signature match.");
}
else
{
NSLog(@"Old and new class signature differ.");
}
}
LEAVE_POOL
return 0;
}