Skip to content

Building hooks that may conflict with embedder library dependencies #2724

@simolus3

Description

@simolus3

I'm migrating package:sqlite3 to use hooks exclusively, and one issue I ran into are challenges loading SQLite with code assets when the app has already loaded SQLite before. I'm filing an issue here because the SQLite demo from this repository has the same issues, so I hope we can come up with possible solutions here.

Anyway, my hooks loading SQLite work just fine in dart test on Linux, but integrating them into a Flutter app causes crashes on Linux. This is reproducible with the SQLite example in this repository by:

  • Adding bindings for sqlite3_initialize(), a supposedly harmless function that initializes the SQLite library if it hasn't already been initialized (typically one wouldn't call this directly, but it serves as a minimal repro and is called by other SQLite functions internally).
  • Noticing that in unit tests, calling sqlite3_initialize() via @Native returns ok.
  • Integrating the sqlite hook demo package into a Flutter app and calling sqlite3_initialize() there crashes the app because sqlite3 calls 0x0 as a function pointer.

You can find a setup ready to reproduce this on my flutter-sqlite-repro branch.

Note that when a Flutter app starts on Linux, the OS will already load libsqlite3.so from the system (on my machine, lddtree reveals libflutter_linux_gtk.so -> libgtk-3.so -> libtinysparql-3.0.so -> libsqlite3.so). So when Dart attempts to load the SQLite library provided by the hook, dlopen docs explain what's going on:

Symbol references in the shared object are resolved using (in order): symbols in the link map of objects loaded for the main program and its dependencies; symbols in shared objects (and their dependencies) that were previously opened with dlopen() using the RTLD_GLOBAL flag; and definitions in the shared object itself (and any dependencies that were loaded for that object).

So when we're loading SQLite, symbol references that have already been loaded (which will be nearly all of them) are pointing at the existing SQLite copy instead of the one we're trying to load. This then causes the new copy to not initialize properly and jump to a null pointer.

It looks like potential ways to fix this could be to

  1. use RTLD_DEEPBIND which makes the loader prefer symbols in that shared object over symbols that have already been found.
  2. (seems much more complicated) using dlmopen() to open the library in an independent namespace.
  3. use static linking, which is what sqlite3_flutter_libs was doing on Linux before migrating to hooks.

Given the complexities of static linking, I wonder if the RTLD_DEEPBIND approach may be an option worth exploring here? Perhaps there could be an opt-in flag on DynamicLoadingBundled indicating that a hook would prefer an asset to get loaded with that flag?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions