dart-lang / native

Dart packages related to FFI and native assets bundling.
BSD 3-Clause "New" or "Revised" License
157 stars 44 forks source link

Missing symbols in static Flutter plugin builds #1475

Open stuartmorgan opened 3 months ago

stuartmorgan commented 3 months ago

When adding some new usage of .listener for blocks, I started hitting this error at runtime:

Unhandled Exception: Invalid argument(s): Couldn't resolve native function 'disposeObjCBlockWithClosure' in 'objective_c.framework/objective_c' : No asset with id 'objective_c.framework/objective_c' found. No available native assets. Attempted to fallback to process lookup. dlsym(RTLD_DEFAULT, disposeObjCBlockWithClosure): symbol not found.

It looks like there's nothing guaranteeing that this symbol is preserved, so in certain build configurations it's being dead-code stripped. For some important context here, CocoaPod-based plugins can be built in two configurations: as frameworks that are bundled into the app bundle, or as static libraries that are linked directly into the Runner. This depends on both plugin-level settings and app-level settings. flutter create using a Swift app template defaults to forcing framework builds (for somewhat complicated and mostly legacy reasons), while an Obj-C app template does not. In the new SPM-based system Flutter is moving to, there is a similar divergence in build types depending on whether it's a release build.

I'm currently working on a plugin old enough that its example app is Obj-C, so it uses static library builds. In that app, disposeObjCBlockWithClosure is not present anywhere in my final build; the plugin is being built statically and linked in, but the symbol isn't in the resulting binary, so presumably it's been stripped. I was able to fix my app by adding use_frameworks!, but plugins need to support both modes.

I assume this will be migrating to native assets at some point; I'm not sure how far out that is, and if we need another workaround in the short term.

mkustermann commented 3 months ago

The error message indicates it's based on our declarative FFI (e.g. @Native annotations) which works only with the native code asset experiment flag turned on and a hook/build.dart was adding the "native code asset" with an id.

(It does has a fallback path - if nohook/build.dart was used - to look the symbol in the process namespace - so @Native works e.g. for binding with malloc(). On some platforms DynamicLibrary.open(<foo>') will make the symbols available in process namespace - which means a manual open of a library will make declarative API work without hook/build.dart)

/cc @dcharkes

dcharkes commented 2 months ago

It does have a fallback path

It's using the fallback path. package:objective_c is not using a build hook. It's using DynamicLibrary.open on the dylib and subsequently uses @Natives relying on the fact that on iOS and MacOS the symbols are loaded with global visibility.