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

Callback with ObjCBlock.listener crashed with EXC_BAD_ACCESS error #1017

Closed
yanshouwang opened this issue Mar 15, 2024 · 10 comments
Closed

Callback with ObjCBlock.listener crashed with EXC_BAD_ACCESS error #1017

yanshouwang opened this issue Mar 15, 2024 · 10 comments

Comments

@yanshouwang
Copy link

yanshouwang commented Mar 15, 2024

App crashed when the callback method executed with EXC_BAD_ACCESS error.

Launching lib/main.dart on iPhone 12 in debug mode...
Automatically signing iOS for device deployment using specified development team in Xcode project: PT2F4U6696
Xcode build done.                                           12.3s
(lldb) 2024-03-15 16:13:22.718904+0800 Runner[3232:1441346] [ERROR:flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalImpeller.mm(42)] Using the Impeller rendering backend.
Connecting to VM Service at ws://127.0.0.1:55859/eyAmJlDrOlc=/ws
Initialized TensorFlow Lite runtime.
INFO: Initialized TensorFlow Lite runtime.
Created TensorFlow Lite XNNPACK delegate for CPU.
INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
[core] "Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)""
[PAAccessLogger] Failed to log access with error: access=<PATCCAccess 0x283de1450> accessor:<<PAApplication 0x283df2c10 identifierType:auditToken identifier:{pid:3232, version:8201}>> identifier:B3419EA5-9019-4910-A61D-955CE4F35F9D kind:intervalEvent timestampAdjustment:0 visibilityState:0 assetIdentifierCount:0 tccService:kTCCServicePhotos, error=Error Domain=NSCocoaErrorDomain Code=4097 "connection to service with pid 226 named com.apple.privacyaccountingd" UserInfo={NSDebugDescription=connection to service with pid 226 named com.apple.privacyaccountingd}
* thread #9, name = 'io.flutter.1.ui', stop reason = EXC_BAD_ACCESS (code=1, address=0x166f994f0)
    frame #0: 0x00000001b85e7a7c libobjc.A.dylib`objc_retain + 16
libobjc.A.dylib`objc_retain:
->  0x1b85e7a7c <+16>: ldr    x17, [x17, #0x20]
    0x1b85e7a80 <+20>: tbz    w17, #0x2, 0x1b85e7a38    ; ___lldb_unnamed_symbol1366
    0x1b85e7a84 <+24>: tbz    w16, #0x0, 0x1b85e7a60    ; ___lldb_unnamed_symbol1366 + 40
    0x1b85e7a88 <+28>: lsr    x17, x16, #55
Target 0: (Runner) stopped.
Lost connection to device.

Exited.

Related code:

  @override
  Future<List<Barcode>> process(InputImage image) {
    final completer = Completer<List<Barcode>>();
    final mlkImage = image is BitmapInputImageImpl
        ? image.toMLKVisionImage()
        : throw TypeError();
    final completion = ffi.ObjCBlock_ffiVoid_NSArray_NSError.listener(
      lib,
      (nsBarcodes, nsError) {
        final barcodes = nsBarcodes?.toBarcodeList() ?? [];
        final error = nsError?.toError();
        if (error == null) {
          completer.complete(barcodes);
        } else {
          completer.completeError(error);
        }
      },
    );
    mlkScanner.processImage_completion_(mlkImage, completion);
    return completer.future;
  }

The source repo is here: https://github.com/yanshouwang/hymir.dev/tree/hymir_mlkit/hymir_mlkit/hymir_mlkit_darwin

@liamappelbe
Copy link
Contributor

When you're interacting with native code, you need to think carefully about memory management. My guess is that your ObjCBlock is being garbage collected, because you're not holding a reference to it.

Try sticking the block in a static variable. I know that's not the cleanest solution, but it's just to confirm that this is the bug. If that fixes it, you can come up with a cleaner way of holding on to the reference, but the details will depend on your use case (eg, can you share an single block instance for the whole app? Does the block need to fire only once?).

@yanshouwang
Copy link
Author

I noticed here is an related issue about ObjCBlock.lisenter, Do we need some extra code to make it work when the callback has arguments?
#835

@liamappelbe
Copy link
Contributor

Maybe. #835 is about the callback arguments. If you've tried what I suggested before, and are holding a reference to the ObjCBlock to make sure it's not GC'd, and are still seeing the crash, then yeah the other possibility is that the arguments are being GC'd. Specifically, I sometimes see crashes when the trampoline wraps an NSObject argument, because the wrapper tries to retain a reference to a dead object.

So the next thing to figure out is where exactly the crash is happening. If you aren't getting useful stack traces, you can try print debugging.

For example, here's an ObjCBlock.listener constructor, from the AVF audio example:

  ObjCBlock_ffiVoid_ObjCObject_bool.listener(
      AVFAudio lib, void Function(NSObject, ffi.Pointer<ffi.Bool>) fn)
      : this._(
            lib._newBlock1(
                (_dartFuncListenerTrampoline ??= ffi.NativeCallable<
                            ffi.Void Function(
                                ffi.Pointer<_ObjCBlock>,
                                ffi.Pointer<ObjCObject>,
                                ffi.Pointer<ffi.Bool>)>.listener(
                        _ObjCBlock_ffiVoid_ObjCObject_bool_closureTrampoline)
                      ..keepIsolateAlive = false)
                    .nativeFunction
                    .cast(),
                _ObjCBlock_ffiVoid_ObjCObject_bool_registerClosure(
                    (ffi.Pointer<ObjCObject> arg0, ffi.Pointer<ffi.Bool> arg1) =>
                        fn(NSObject._(arg0, lib, retain: true, release: true),
                            arg1))),
            lib);

The NSObject._(arg0, lib, retain: true, release: true) part will crash if arg0 has already been GC'd. So you can try rewriting that closure like this:

(ffi.Pointer<ObjCObject> arg0, ffi.Pointer<ffi.Bool> arg1) {
  print("Creating wrapper object...");
  final arg0Wrap = NSObject._(arg0, lib, retain: true, release: true);
  print("Calling the function...");
  return fn(arg0Wrap, arg1);
},

If this is where the crash is, then the fix is to retain a reference on the ObjC side, before the block is invoked. This is what #835 plans to do automatically, but you can do this manually if you don't want to wait. Just write an ObjC function that takes a block, and returns a block that retains a ref to the arg before calling the given block. Then you can pass your Dart block through that function to get a block that won't crash.

One AI I already have from this issue is to make this more debuggable. It'd be nice if we got a clear error message if the arg has been GC'd, rather than just crashing.

@yanshouwang
Copy link
Author

There is the logs, I think this crash is caused by the arguments

Connecting to VM Service at ws://127.0.0.1:62999/vXMMscuo9pw=/ws
Initialized TensorFlow Lite runtime.
INFO: Initialized TensorFlow Lite runtime.
Created TensorFlow Lite XNNPACK delegate for CPU.
INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
[core] "Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)""
[PAAccessLogger] Failed to log access with error: access=<PATCCAccess 0x28035a8a0> accessor:<<PAApplication 0x2803423a0 identifierType:auditToken identifier:{pid:3771, version:9471}>> identifier:846B9D65-4D81-475F-90C7-9E0FE9E552FA kind:intervalEvent timestampAdjustment:0 visibilityState:0 assetIdentifierCount:0 tccService:kTCCServicePhotos, error=Error Domain=NSCocoaErrorDomain Code=4097 "connection to service with pid 226 named com.apple.privacyaccountingd" UserInfo={NSDebugDescription=connection to service with pid 226 named com.apple.privacyaccountingd}
flutter: Creating wrapper object...
* thread #9, name = 'io.flutter.1.ui', stop reason = EXC_BAD_ACCESS (code=1, address=0x5874a90d0)
    frame #0: 0x00000001b85e7a7c libobjc.A.dylib`objc_retain + 16
libobjc.A.dylib`objc_retain:
->  0x1b85e7a7c <+16>: ldr    x17, [x17, #0x20]
    0x1b85e7a80 <+20>: tbz    w17, #0x2, 0x1b85e7a38    ; ___lldb_unnamed_symbol1366
    0x1b85e7a84 <+24>: tbz    w16, #0x0, 0x1b85e7a60    ; ___lldb_unnamed_symbol1366 + 40
    0x1b85e7a88 <+28>: lsr    x17, x16, #55
Target 0: (Runner) stopped.
Lost connection to device.

Exited.

I tried the pedometer example, it works fine, but if I add s.static_framework = true to the pedometer.podspec file, it will throw this error:

ArgumentError (Invalid argument(s): Failed to load dynamic library 'pedometer.framework/pedometer': dlopen(pedometer.framework/pedometer, 0x0001): tried: 'pedometer.framework/pedometer' (no such file), '/private/preboot/Cryptexes/OSpedometer.framework/pedometer' (no such file), '/usr/lib/swift/pedometer.framework/pedometer' (no such file, not in dyld cache), '/private/preboot/Cryptexes/OS/usr/lib/swift/pedometer.framework/pedometer' (no such file), '/private/var/containers/Bundle/Application/1A282E66-6B9D-4DA6-ADC4-12727DBCE47A/Runner.app/Frameworks/pedometer.framework/pedometer' (no such file), '/private/var/containers/Bundle/Application/1A282E66-6B9D-4DA6-ADC4-12727DBCE47A/Runner.app/Frameworks/pedometer.framework/pedometer' (no such file), '/usr/lib/pedometer.framework/pedometer' (no such file, not in dyld cache), 'pedometer.framework/pedometer' (no such file), '/System/Library/Frameworks/pedometer.framework/pedometer' (no such file, not in dyld cache))

I add s.static_framework = true because I add s.dependency 'GoogleMLKit/BarcodeScanning', '~> 4.0.0' as a dependency.

Is there anything I can do to make it work?

@liamappelbe
Copy link
Contributor

The pedometer example is unrelated. To fix the argument ref counting issue, you'll need to retain a reference to the argument before sending it to Dart. To do this, you'll need to write a bit of ObjC code to wrap your callback in another callback that retains the arg then calls the original callback.

@yanshouwang
Copy link
Author

I did write the objc code to wrap the callback like this

#import <Foundation/Foundation.h>
#import <MLKitBarcodeScanning/MLKitBarcodeScanning.h>

#import "hymir_mlkit_ios.h"

MLKBarcodeScanningCallback wrapCallback(MLKBarcodeScanningCallback callback) {
  return [^(NSArray<MLKBarcode *> *_Nullable barcodes, NSError *_Nullable error) {
    return callback([barcodes retain], [error retain]);
  } copy];
}

It throws when I use the wrapCallback method, I think this is caused by the s.static_library = true declaration in the podspec.
截屏2024-03-18 18 09 46

I also write another implementation use the Vision framework instead of MLKit, the callback all works fine with or without the wrapCallback method.

But I encountered another issue is that when I call an property with NSData type, the app crash with messages:

Connecting to VM Service at ws://127.0.0.1:64705/LdHuae_xu34=/ws
[core] "Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)""
[PAAccessLogger] Failed to log access with error: access=<PATCCAccess 0x282ac9810> accessor:<<PAApplication 0x282af27b0 identifierType:auditToken identifier:{pid:4130, version:10391}>> identifier:676D0FB7-760D-4345-82E6-98601BE4EC50 kind:intervalEvent timestampAdjustment:0 visibilityState:0 assetIdentifierCount:0 tccService:kTCCServicePhotos, error=Error Domain=NSCocoaErrorDomain Code=4097 "connection to service with pid 4035 named com.apple.privacyaccountingd" UserInfo={NSDebugDescription=connection to service with pid 4035 named com.apple.privacyaccountingd}
-[VNBarcodeObservation payloadData]: unrecognized selector sent to instance 0x100fb8960
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[VNBarcodeObservation payloadData]: unrecognized selector sent to instance 0x100fb8960'
*** First throw call stack:
(0x195bc6d94 0x18ec7c3d0 0x195d3bb14 0x195bdd1c8 0x195c439f0 0x109587a48 0x11675fbc8 0x11675f5cc 0x11675f148 0x116757a8c 0x116755908 0x116754950 0x116754778 0x11675426c 0x116754094 0x116753f88 0x114cab168 0x116753dd4 0x10f73cf60 0x10f73c358 0x109583424 0x104055d18 0x104075b80 0x1040a3d90 0x10443e080 0x103e42710 0x103fd9aec 0x103d0ad8c 0x103d0f0e4 0x195c88704 0x195c4603c 0x195beb7fc 0x195c375c4 0x195c3c4dc 0x103d0f1d0 0x103d0dca4 0x1f55546b8 0x1f5553b88)
libc++abi: terminating due to uncaught exception of type NSException
* thread #10, name = 'io.flutter.1.ui', stop reason = signal SIGABRT
    frame #0: 0x00000001d48dc558 libsystem_kernel.dylib`__pthread_kill + 8
libsystem_kernel.dylib`:
->  0x1d48dc558 <+8>:  b.lo   0x1d48dc578               ; <+40>
    0x1d48dc55c <+12>: pacibsp 
    0x1d48dc560 <+16>: stp    x29, x30, [sp, #-0x10]!
    0x1d48dc564 <+20>: mov    x29, sp
Target 0: (Runner) stopped.
Lost connection to device.

Exited.

The example with MLKit framework:
https://github.com/yanshouwang/hymir.dev/tree/hymir_mlkit/hymir_mlkit/hymir_mlkit_ios

The example with Vision framework:
https://github.com/yanshouwang/hymir.dev/tree/vision/hymir_mlkit/hymir_mlkit_ios

@liamappelbe
Copy link
Contributor

It throws when I use the wrapCallback method, I think this is caused by the s.static_library = true declaration in the podspec.
截屏2024-03-18 18 09 46

That's a linker issue. Your wrapCallback function isn't being linked in to the app.

But I encountered another issue is that when I call an property with NSData type, the app crash with messages:

Connecting to VM Service at ws://127.0.0.1:64705/LdHuae_xu34=/ws
[core] "Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)""
[PAAccessLogger] Failed to log access with error: access=<PATCCAccess 0x282ac9810> accessor:<<PAApplication 0x282af27b0 identifierType:auditToken identifier:{pid:4130, version:10391}>> identifier:676D0FB7-760D-4345-82E6-98601BE4EC50 kind:intervalEvent timestampAdjustment:0 visibilityState:0 assetIdentifierCount:0 tccService:kTCCServicePhotos, error=Error Domain=NSCocoaErrorDomain Code=4097 "connection to service with pid 4035 named com.apple.privacyaccountingd" UserInfo={NSDebugDescription=connection to service with pid 4035 named com.apple.privacyaccountingd}
-[VNBarcodeObservation payloadData]: unrecognized selector sent to instance 0x100fb8960
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[VNBarcodeObservation payloadData]: unrecognized selector sent to instance 0x100fb8960'
*** First throw call stack:
(0x195bc6d94 0x18ec7c3d0 0x195d3bb14 0x195bdd1c8 0x195c439f0 0x109587a48 0x11675fbc8 0x11675f5cc 0x11675f148 0x116757a8c 0x116755908 0x116754950 0x116754778 0x11675426c 0x116754094 0x116753f88 0x114cab168 0x116753dd4 0x10f73cf60 0x10f73c358 0x109583424 0x104055d18 0x104075b80 0x1040a3d90 0x10443e080 0x103e42710 0x103fd9aec 0x103d0ad8c 0x103d0f0e4 0x195c88704 0x195c4603c 0x195beb7fc 0x195c375c4 0x195c3c4dc 0x103d0f1d0 0x103d0dca4 0x1f55546b8 0x1f5553b88)
libc++abi: terminating due to uncaught exception of type NSException
* thread #10, name = 'io.flutter.1.ui', stop reason = signal SIGABRT
    frame #0: 0x00000001d48dc558 libsystem_kernel.dylib`__pthread_kill + 8
libsystem_kernel.dylib`:
->  0x1d48dc558 <+8>:  b.lo   0x1d48dc578               ; <+40>
    0x1d48dc55c <+12>: pacibsp 
    0x1d48dc560 <+16>: stp    x29, x30, [sp, #-0x10]!
    0x1d48dc564 <+20>: mov    x29, sp
Target 0: (Runner) stopped.
Lost connection to device.

Exited.

That error means the object isn't actually an NSData. Either you're casting a different object pointer to NSData, or the NSData is being GC'd and another different type of object is being put at that same memory location.

@yanshouwang
Copy link
Author

That error means the object isn't actually an NSData. Either you're casting a different object pointer to NSData, or the NSData is being GC'd and another different type of object is being put at that same memory location.

Is this a bug or I missed something? The payloadData should be a NSData property

@liamappelbe
Copy link
Contributor

Oh sorry, I misspoke. It's not that payloadData is returning something that's not an NSData, it's that your barcodes argument isn't really a VNBarcodeObservation.

2 more ideas to help you debug this:

  1. On the Dart side, try calling VNBarcodeObservation.isInstance(barcodes).
  2. On the ObjC side, in wrapCallback, try to test if barcodes is a valid VNBarcodeObservation at that point.

If 1 and 2 both work, then your VNBarcodeObservation object is just missing that method (could be a versioning issue). If 2 works but 1 doesn't, then the object is still being GC'd before being handled in Dart. If 1 and 2 both fail, then your object isn't really a VNBarcodeObservation, and you'll need to double check the API you're using.

@yanshouwang
Copy link
Author

Oh sorry, I misspoke. It's not that payloadData is returning something that's not an NSData, it's that your barcodes argument isn't really a VNBarcodeObservation.

2 more ideas to help you debug this:

  1. On the Dart side, try calling VNBarcodeObservation.isInstance(barcodes).
  2. On the ObjC side, in wrapCallback, try to test if barcodes is a valid VNBarcodeObservation at that point.

If 1 and 2 both work, then your VNBarcodeObservation object is just missing that method (could be a versioning issue). If 2 works but 1 doesn't, then the object is still being GC'd before being handled in Dart. If 1 and 2 both fail, then your object isn't really a VNBarcodeObservation, and you'll need to double check the API you're using.

Ah, you are right, according the Apple's docs, the payloadData is only available on iOS 17+, which I run the code on an iOS 16 device. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants