gnustep / libobjc2

Objective-C runtime library intended for use with Clang.
http://www.gnustep.org/
MIT License
434 stars 118 forks source link

Calling an unimplemented method on super does not raise an exception #153

Closed wlux closed 4 years ago

wlux commented 4 years ago

When a program calls a selector on super, which is not implemented by the current super class or its ancestors, the method call is simply ignored, as if the method was called on nil. For instance, for the test program shown below, I would expect two exceptions to be reported as for the Apple's Foundation and runtime:

 2020-04-13 16:13:34.742 Test[48512:1642397] -[D initWithObject:ptr:]: unrecognized selector sent to instance 0x7fecbb704a00
 2020-04-13 16:13:34.743 Test[48512:1642397] Caught exception -[D initWithObject:ptr:]: unrecognized selector sent to instance 0x7fecbb704a00
 2020-04-13 16:13:34.743 Test[48512:1642397] Calling [e run] for <E: 0x7fecbb407890>
 2020-04-13 16:13:34.743 Test[48512:1642397] -[E method:pointer:]: unrecognized selector sent to instance 0x7fecbb407890
 2020-04-13 16:13:34.743 Test[48512:1642397] Caught exception -[E method:pointer:]: unrecognized selector sent to instance 0x7fecbb407890

However, for libobjc2 and GNUstep-base I'm seeing this:

 2020-04-13 16:18:10.644 Test[17264:17264] Calling [d run] for (null)
 2020-04-13 16:18:10.645 Test[17264:17264] Calling [e run] for <E: 0x1cc65c8>

Test program:

#import <Foundation/Foundation.h>

@interface Base : NSObject
{
    id<NSObject> obj;
    void *ptr;
}
- (instancetype)initWithObject: (id<NSObject>)anObj pointer: (void *)aPtr;
- (void)run;
- (void)method: (id<NSObject>)anObj with: (void *)aPtr;
@end

@implementation Base

- (instancetype)initWithObject: (id<NSObject>)anObj pointer: (void *)aPtr
{
    if ((self = [super init]) != nil)
    {
        obj = [anObj retain];
        ptr = aPtr;
    }
    return self;
}

- (void)dealloc
{
    [obj release];
    [super dealloc];
}

- (void)run
{
    [self method: obj with: ptr];
}

- (void)method: (id<NSObject>)anObj with: (void *)aPtr
{
    NSLog(@"In base implementation of -method:with:");
}

@end

@interface D : Base
@end

@implementation D

- (instancetype)initWithObject: (id<NSObject>)anObj ptr: (void *)aPtr
{
    return [super initWithObject: anObj ptr: aPtr];
}

@end

@interface E : Base
@end

@implementation E

- (instancetype)initWithObject: (id<NSObject>)anObj ptr: (void *)aPtr
{
    return [super initWithObject: anObj pointer: aPtr];
}

- (void)run
{
    [super method: obj pointer: ptr];
}

@end

int
main()
{
    @autoreleasepool
    {
        NSObject *o = [NSNull null];

#define nsstr(s) @#s
#define run(v) do { NSLog(@"Calling [" nsstr(v) @" run] for %@", v); [v run]; } while (0)

        @try
        {
            D *d = [[D alloc] initWithObject: o ptr: &o];
            run(d);
        }
        @catch (NSException *e)
        {
            NSLog(@"Caught exception %@", e);
        }

        @try
        {
            E *e = [[E alloc] initWithObject: o ptr: &o];
            run(e);
        }
        @catch (NSException *e)
        {
            NSLog(@"Caught exception %@", e);
        }
    }
}
davidchisnall commented 4 years ago

Thank you. Is it possible for you to submit a test case that doesn't depend on Foundation (e.g. in the style of the existing test suite)?

wlux commented 4 years ago

Sorry, I feel I'm unable to do that (at least at the moment).