2025-01-28 15:06:11 +00:00
|
|
|
/* Crude tool to compare classes in different version of library code to
|
|
|
|
* look for possible compatiblity issues.
|
2025-02-09 13:00:43 +00:00
|
|
|
*
|
|
|
|
* 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.
|
2025-01-28 15:06:11 +00:00
|
|
|
*/
|
|
|
|
#import "Foundation/Foundation.h"
|
2025-02-14 16:26:24 +00:00
|
|
|
#import "GNUstepBase/GNUstep.h"
|
2025-01-28 15:06:11 +00:00
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|