bugGNUstep - Bugs: bug #36706, performSelector: not working with...

Group
 
 

bug #36706: performSelector: not working with message fowarding

Submitter:  Michael Johnston <mjohnston>
Submitted:  Fri 22 Jun 2012 03:28:47 PM UTC
   
 
Category:  Base/Foundation Severity:  3 - Normal
Item Group:  Bug Status:  Fixed
Privacy:  Public Assigned to:  None
Open/Closed:  Closed
* Mandatory Fields

Add a New Comment Rich Markup
   

Jump to the original submission

Mon 09 Jul 2012 04:56:15 PM UTC, comment #10: 

I applied Niels' fix along with a few other alterations (one in the FFI code to allow default forwwarding in some cases for instance).

I think if we were to stick to the Apple runtime API (and maybe we should), we'd have to do it differently.

If you call class_getMethodImplementation() and the class does not respond to the selector directly, it will return a forwarding function suitable for returing a scalar type.

The Apple API also has class_getMethodImplementation_stret() (and perhaps class_getMethodImplementation_fpret()) to return functions handling different return values, but I guess you need to know in advance if you need to call these to get forwarding functions for returning structures and floating points.

So,  I guess you could cache the address of the standard forwarding function, and always call class_getMethodImplementation(), but perform extra work if it returns the address of the standard forwarding function.
In this case, you would then call -methodSignatureForSelector: and examine the return tye for the method ... if it's a structure, or a floating point value, yu would call the appropriate runtime method to get the correct forwarding function, but if it's not then you know the function you already have is the correct one.

Maybe we should do things the apple way?


Richard Frith-Macdonald <CaS>
Group Member
Fri 06 Jul 2012 12:52:11 PM UTC, comment #9: 

Thank you for your further analysis. It is quite interesting that I am getting different behaviour from you. This is the result with the libobjc that comes with gcc:

2012-07-06 14:34:25.200 perform[25646] First Test - Direct sending known method
2012-07-06 14:34:25.236 perform[25646] Foo methodSignatureForSelector called with wibble
2012-07-06 14:34:25.236 perform[25646] Returning sig <NSMethodSignature: 0x7ad938>
2012-07-06 14:34:25.236 perform[25646] Foo methodSignatureForSelector called with wibble
2012-07-06 14:34:25.236 perform[25646] Returning sig <NSMethodSignature: 0x85e768>
2012-07-06 14:34:25.237 perform[25646] Foo - received <GSFFIInvocation 0x7985d8 selector: wibble target: Foo> on <Foo: 0x63f1d8> meta 0
2012-07-06 14:34:25.237 perform[25646] Wibble

2012-07-06 14:34:25.237 perform[25646] Second Test - performSelector with known method
2012-07-06 14:34:25.237 perform[25646] Foo methodSignatureForSelector called with wibble
2012-07-06 14:34:25.237 perform[25646] Returning sig <NSMethodSignature: 0x76e998>
2012-07-06 14:34:25.237 perform[25646] Foo - received <GSFFIInvocation 0x799718 selector: wibble target: Foo> on <Foo: 0x63f1d8> meta 0
2012-07-06 14:34:25.237 perform[25646] Wibble

2012-07-06 14:34:25.237 perform[25646] Third Test - performSelector with unknown method
/home/fred/GNUstep/Examples/decimal/perform.app/perform: Uncaught exception NSInvalidArgumentException, reason: -[nil blah]: unrecognized selector sent to instance (null)

And ater commenting out the third test I get:

2012-07-06 14:42:03.737 perform[26679] First Test - Direct sending known method
2012-07-06 14:42:03.739 perform[26679] Foo methodSignatureForSelector called with wibble
2012-07-06 14:42:03.739 perform[26679] Returning sig <NSMethodSignature: 0x7ad938>
2012-07-06 14:42:03.739 perform[26679] Foo methodSignatureForSelector called with wibble
2012-07-06 14:42:03.739 perform[26679] Returning sig <NSMethodSignature: 0x85e768>
2012-07-06 14:42:03.739 perform[26679] Foo - received <GSFFIInvocation 0x7985d8 selector: wibble target: Foo> on <Foo: 0x63f1d8> meta 0
2012-07-06 14:42:03.739 perform[26679] Wibble

2012-07-06 14:42:03.739 perform[26679] Second Test - performSelector with known method
2012-07-06 14:42:03.740 perform[26679] Foo methodSignatureForSelector called with wibble
2012-07-06 14:42:03.740 perform[26679] Returning sig <NSMethodSignature: 0x76e998>
2012-07-06 14:42:03.740 perform[26679] Foo - received <GSFFIInvocation 0x799718 selector: wibble target: Foo> on <Foo: 0x63f1d8> meta 0
2012-07-06 14:42:03.740 perform[26679] Wibble

2012-07-06 14:42:03.740 perform[26679] Fourth Test - Direct sending unknown method
2012-07-06 14:42:03.740 perform[26679] Foo methodSignatureForSelector called with blah
2012-07-06 14:42:03.740 perform[26679] Returning sig <NSMethodSignature: 0x772d38>
2012-07-06 14:42:03.740 perform[26679] Foo methodSignatureForSelector called with blah
2012-07-06 14:42:03.740 perform[26679] Returning sig <NSMethodSignature: 0x766608>
2012-07-06 14:42:03.740 perform[26679] Foo - received <GSFFIInvocation 0x7a7ee8 selector: blah target: Foo> on <Foo: 0x63f1d8> meta 0
2012-07-06 14:42:03.740 perform[26679] Redirecting unknown method blah
2012-07-06 14:42:03.740 perform[26679] Wibble

2012-07-06 14:42:03.740 perform[26679] Fifth Test - performSelector with unknown method, after directly sending it
2012-07-06 14:42:03.740 perform[26679] Foo methodSignatureForSelector called with blah
2012-07-06 14:42:03.740 perform[26679] Returning sig <NSMethodSignature: 0x83d478>
2012-07-06 14:42:03.740 perform[26679] Foo - received <GSFFIInvocation 0x83de28 selector: blah target: Foo> on <Foo: 0x63f1d8> meta 0
2012-07-06 14:42:03.740 perform[26679] Redirecting unknown method blah
2012-07-06 14:42:03.740 perform[26679] Wibble


I hope that David and Nicola will be joining us for further discussion.

Fred Kiefer <FredKiefer>
Group Member
Fri 06 Jul 2012 10:54:58 AM UTC, comment #8: 

After some investigation the main problem occurs when sending an unknown method i.e. that has no implementation locally, to an object, which you want to redirect to some other implemented method.

In this case you would have methodSignatureForSelector: return the sig of the method the method to be redirected to, and fowardInvocation: replace the invocations selector with that of the method to be redirected to.

There are then three situations when calling [Foo performSelector: @selector(wibble)]
1. It works if wibble is declared in some other class
2. It works if wibble is not declared but [foo wibble] was called previously
3. It raises an exception if wibble not declared and [foo wibble] not called previously.

I've attached a modified version of the code in the previous comment which demonstrates the above. For some reason I can't catch the exception raised in the third situation (the third test in the code). So to see situation two you have to comment out the third test. I've also attached the output for the two cases (output1.txt, output2.txt).

What happens is that performSelector: causes gs_objc_msg_forward2 to be called with a class, whereas if its directly sent it is called with the object. When called with a class methodSignatureToSelector: is sent to the class. This causes the classes instanceMethodSignatureForSelector: to be called which just checks if the class implements the method (it doesn't call an instances methodSignatureForSelector:) - so this always returns nil if the class doesn't implement the method.

The reason it works if wibble is declared somewhere is that gs_find_best_typed_sel comes up with something and the sig is created using signatureWithObjcTypes: (GSFFIInvocation,m:187-191). For some reason this also succeeds for an undeclared method once that method has been sent directly, but otherwise it fails.

Fred, you mentioned that a possible solution is that the class should implement +methodSignatureForSelector: - This could work but this is not an NSObject method and from the docs I've read it shouldn't be required to get the forwarding to work. Similarly overriding instanceMethodSignatureForSelector: could also work but shouldn't be required. I note that the attached code works fine on OSX and gnustep-base pre 1.22.

Another issue I noticed was that even if methodSignatureForSelector: returns nil, [Foo wibble] and [Foo performSelector: @selector(wibble)] will work. This doesn't seem correct to me. You can test it by calling super to try to get the signature instead of a Bar object (in Foo methodSignatureForSelector:). I also attached the output of this (output3.txt).




(file #26153, file #26154, file #26155, file #26156)

Michael Johnston <mjohnston>
Sat 30 Jun 2012 02:18:11 PM UTC, comment #7: 

Looks like my analysis was wrong. David Chisnell send me a short program that shows that the forwarding mechanism is working well for the different libobjc implementation. Here a slightly extended version of his code:

#import <Foundation/Foundation.h>

@interface Foo : NSObject @end
@implementation Foo
- (void)forwardInvocation: (NSInvocation*)inv
{
  NSLog(@"Received %@ on %@ meta %d", inv,
        self, class_isMetaClass(object_getClass(self)));
}
@end
@interface NSObject (Bar)
-(void)wibble;
@end

int main(void)
{
  CREATE_AUTORELEASE_POOL(pool);

  Foo *f= [Foo new];
  [f wibble];

  IMP i = class_getMethodImplementation([f class], @selector(wibble));
  NSLog(@"IMP: %p\n", i);
  i(f, @selector(wibble));

  [f performSelector: @selector(wibble)];
  RELEASE(pool);
  return 0;
}

This still leaves your problem open. I expect that the real issues has to do with the compiler not knowing about the signature of the method you are sending. To better understand this we will need to see more of your code. Could you try to extend the above program until it reprocudes the problem you are seeing?
And the most likely solution will be that you class needs to implement +methodSignatureForSelector:

Fred Kiefer <FredKiefer>
Group Member
Fri 29 Jun 2012 07:21:49 PM UTC, comment #6: 

Hi,

Just a quick follow-up on this: The attached patch changes the -performSelector:... family of methods to use objc_msg_lookup(), which fixes the issue described by Michael for me. Unfortunately, I don't have much time and only did some quick tests with libobjc2 trunk.

Cheers,

Niels

(file #26124)

Niels Grewe <thebeing>
Group Member
Fri 29 Jun 2012 03:36:09 PM UTC, comment #5: 

Hi Fred,

It seems like you're correct. At least, Apple runtime reference documentation explicitly states that class_getMessageImplementation() might return a pointer to `a part of the runtime's message forwarding machinery'. So we technically would need to modify the behaviour of our runtime to be compatible with the Apple implementation.

Since that is a bit tedious (since it means juggling around with varargs and various places where the return value might end up), my is a take on this issue is that we should, in the meantime, do two things:

a) Don't raise an exception in gs_objc_msg_forward2(), just return NULL if we cannot get a signature to construct a cframe for. The function is a hook for the runtime, which should tolerate if the forwarding mechanism doesn't come up with a sensible match.

b) Amend our implementation of `-performSelector:' to just use objc_msg_lookup() and call the returned IMP. This is what a message send compiles to anyways, which (I think) should be more robust than the present code that calls class_getMethodImplementation(). But then again I might be missing the reason why we did it that way in the first place...

Cheers,

Niels

Niels Grewe <thebeing>
Group Member
Fri 29 Jun 2012 01:11:19 PM UTC, comment #4: 

David or Nicola,
coudl one of you please look into this problem? My feeling is that the implementation in all our libobjc implementations are wrong. I would think that if a method is not directly implemented, class_getMethodImplementation should return a function that calls __objc_msg_forward2() and then calls the result. That way __objc_msg_forward2() would be called with the correct object as first parameter and not as it is now with nil as first argument.

What makes me doubt my analysis is that all the different libobjc implementations do it the same way.

Fred Kiefer <FredKiefer>
Group Member
Mon 25 Jun 2012 01:25:17 PM UTC, comment #3: 

Here is the relevant part of the traceback (obtained with base-1.24): You can see that the class passed to class_getMethodImplementation is the value of the receiver parameter passed to gs_objc_msg_forward2.

#2  0x005babe3 in +[NSException raise:format:] (self=0x88ca60, _cmd=0x8dd778, name=0x88cd18, format=0x8dd8d0) at NSException.m:835
#3  0x006df05b in gs_objc_msg_forward2 (receiver=0x1b8cbc0, sel=0x80d10f0) at GSFFIInvocation.m:197
#4  0x00922433 in get_imp () from /usr/lib/i386-linux-gnu/libobjc.so.2
#5  0x006e0538 in class_getMethodImplementation (cls=0x1b8cbc0, name=0x80d10f0) at runtime.c:496
#6  0x00616548 in -[NSObject performSelector:] (self=0x82e217c, _cmd=0x1b88908, aSelector=0x80d10f0) at NSObject.m:1976

Michael Johnston <mjohnston>
Fri 22 Jun 2012 10:06:52 PM UTC, comment #2: 

libobjc2 from gcc4.5.3 - from the Ubuntu 11.10 libobjc package.

Michael Johnston <mjohnston>
Fri 22 Jun 2012 09:19:41 PM UTC, comment #1: 

Which libobjc are you using? I checked with the old GNUstep libobjc and the new libobjc2 and in neither case could I follow you call sequence.
Still the bug you report look plausible, we seem to be loosing information that would be valuable for the forwarding.

Fred Kiefer <FredKiefer>
Group Member
Fri 22 Jun 2012 03:28:47 PM UTC, original submission:  

I've seem this problem in base-1.22,1.24
Regression: Same code was working in base-1.21

I have code which calls [object performSelector:sel] where sel is not implemented by object (and object is an instance not a class)

In the 1.21 runtime this worked fine.
Now this raises an NSInvalidArgumentException saying:

"+[class methodName] unrecognized selector sent to instance ..."

Note it is saying the class doesn't respond to the method - which is correct as its the instance which has the fowarding implemented - but why is it sending methodName to class?

From gdb what happens is

- performSelector calls class_getMethodImplementation passing the class and selector.
From the docs this should return the pointer that would be called if a particular message were sent to an instance of a class.

- class_getMethodImplementation calls get_imp. Not sure what happens in here but I assume the imp isn't found and hence the message forwarding mechanism is entered

- get_imp calls gs_objc_msg_foward2 in GSFFIInvocation.m - passing the class (as the receiver parameter) and the selector

- gs_objc_msg_forward2 calls (line 185)
             sig = [receiver methodSignatureForSelector: sel];

Which returns nil as receiver is the class not the instance.
This causes the exception to be raised a couple of lines later.

Not sure if the problem lies with get_imp calling gs_objc_msg_foward2 with a class (which doesn't seem to make sense), or if class_getMethodImplementation can be expected to forward a message at all.

Any help with above appreciated.







Michael Johnston <mjohnston>

 

(Note: upload size limit is set to 16384 kB, after insertion of the required escape characters.)

Attach Files:
   
   
Comment:
   

Attached Files
file #26153:  test.m added by mjohnston (2KiB - text/x-objcsrc - Code illustrating the problem and its output for three different cases)
file #26154:  output2.txt added by mjohnston (2KiB - text/plain - Code illustrating the problem and its output for three different cases)
file #26155:  output1.txt added by mjohnston (1KiB - text/plain - Code illustrating the problem and its output for three different cases)
file #26156:  output3.txt added by mjohnston (1KiB - text/plain - Code illustrating the problem and its output for three different cases)
file #26124:  performSel.patch added by thebeing (1KiB - application/octet-stream)

 

Depends on the following items: None found

Items that depend on this one: None found

 

Carbon-Copy List
  • -email is unavailable- added by CaS (Posted a comment)
  • -email is unavailable- added by thebeing (Posted a comment)
  • -email is unavailable- added by FredKiefer (Posted a comment)
  • -email is unavailable- added by mjohnston (Submitted the item)
  •  

    There are 0 votes so far. Votes easily highlight which items people would like to see resolved in priority, independently of the priority of the item set by tracker managers.

    Only logged-in users can vote.

     

    Follow 8 latest changes.

    Date Changed by Updated Field Previous Value => Replaced by
    2012-07-09 CaS StatusNone Fixed
        Open/ClosedOpen Closed
    2012-07-06 mjohnston Attached File- Added test.m, #26153
        Attached File- Added output2.txt, #26154
        Attached File- Added output1.txt, #26155
        Attached File- Added output3.txt, #26156
    2012-06-29 thebeing Attached File- Added performSel.patch, #26124
    2012-06-22 FredKiefer Item GroupNone Bug

    Back to the top

    Powered by Savane 3.13-758e.
    Corresponding source code