Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling performSelector for a @objc_method created by rubicon from Objective C crashes in Python 3.9 #223

Open
yilei opened this issue Jul 7, 2021 · 3 comments
Labels
bug A crash or error in behavior.

Comments

@yilei
Copy link

yilei commented Jul 7, 2021

Describe the bug
In Python 3.9, calling [obj performSelector:NSSelectorFromString(@"name")] crashes with EXC_BAD_ACCESS (code=2, address=0x1056f7f48) if the obj is an instance of a class created by rubicon with @objc_method def name(self): ....

To Reproduce
Steps to reproduce the behavior:

  1. Create a briefcase iOS project:
    $ briefcase new
    # Give it a name `py39crashdemo`
    $ cd py39crashdemo
    $ briefcase create iOS
    
  2. Add the objc class definition using rubicon in app.py:
    $ cat iOS/Xcode/py39crashdemo/py39crashdemo/app/py39crashdemo/app.py
    """
    My first application
    """
    from rubicon import objc
    class Runner(objc.NSObject):
        @objc.objc_method
        def execScript(self):
            pass
    def main():
        # This should start and launch your app!
        Interop = objc.ObjCClass("Interop")  # Interop is a class defined in objc.
        Interop.sharedRunner = Runner.alloc().init()
    
  3. Add the objective code that invokes the execScript method:
     $ cat iOS/Xcode/py39crashdemo/py39crashdemo/main.m
     //
     //  main.m
     //  A main module for starting Python projects under iOS.
     //
     
     #import <Foundation/Foundation.h>
     #import <UIKit/UIKit.h>
     #include <Python.h>
     #include <dlfcn.h>
     
     NSObject *_sharedRunner;
     
     @interface Interop: NSObject
     @property(class) NSObject *sharedRunner;
     @end
     @implementation Interop
     + (void)setSharedRunner:(NSObject *)sharedRunner {
         _sharedRunner = sharedRunner;
     }
     + (NSObject *)sharedRunner {
         return _sharedRunner;
     }
     @end
     
     @interface PythonAppDelegate : NSObject<UIApplicationDelegate>
     @end
     @implementation PythonAppDelegate
     - (void)applicationDidFinishLaunching:(UIApplication *)application {
         [Interop.sharedRunner performSelector:NSSelectorFromString(@"execScript")];  // <---- This is the crash
     }
     @end
     
     int main(int argc, char *argv[]) {
     // ... below are untouched
    
  4. Run the iOS application, and it crashes with EXC_BAD_ACCESS (code=2, address=0x1056f7f48) at [Interop.sharedRunner performSelector:NSSelectorFromString(@"execScript")];

Expected behavior
It should not crash.

Environment:

  • Operating System: macOS 11.3
  • Python version: 3.9.4
  • Software versions:
    • Briefcase: 0.3.5
    • Xcode: 12.5
    • Toga: n/a
    • ...

Additional context
I had an earlier conversion on BeeWare Discoard with @freakboy3742, and I'm instructed to file this bug.

@yilei yilei added the bug A crash or error in behavior. label Jul 7, 2021
@dgelessus
Copy link
Collaborator

dgelessus commented Jul 7, 2021

Thank you for the bug report! (For reference, here is a link to the Discord discussion.)

To check if the crash has anything to do with performSelector:, can you try calling the Python-defined method directly and see if that crashes as well? If you do [Interop.sharedRunner execScript] in the Objective-C code, the compiler will give you a warning (because NSObject doesn't declare an execScript method), but it should still compile and run.

If you want to avoid the warning, you can define a protocol on the Objective-C side for the Python-defined Runner class to implement:

@protocol RunnerProtocol
-(id)execScript;
@end
NSObject<RunnerProtocol> *_sharedRunner;
// and all other declarations rewritten to use NSObject<RunnerProtocol>

And on the Python side:

RunnerProtocol = objc.ObjCProtocol("RunnerProtocol")
class Runner(objc.NSObject, protocols=[RunnerProtocol]):
    # ...

Then you should be able to call [Interop.sharedRunner execScript] without any compiler warnings.

@yilei
Copy link
Author

yilei commented Jul 7, 2021

Thanks. I need to use the protocol approach (otherwise it fails to compile, probably there is a flag to turn it to warning/error?), but calling [Interop.sharedRunner execScript] crashes in the same way.

@freakboy3742 freakboy3742 transferred this issue from beeware/Python-Apple-support May 12, 2022
@freakboy3742
Copy link
Member

Doing some housekeeping; I've transferred this issue to the Rubicon-ObjC repository, since it doesn't appear to be specifically related to the Apple support build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A crash or error in behavior.
Projects
None yet
Development

No branches or pull requests

3 participants