Skip to content

interop.FunctionReference function passed as a block, then as a function pointer, asserts #503

Description

@edusperoni

Summary

A function wrapped with interop.FunctionReference and then passed to a native API as a block loses its FunctionReference identity. A later attempt to pass it as a function pointer then hits tns::Assert(false) in Interop::SetFFIParams, which crashes the app.

Repro

const fn = function () {};
const ref = new interop.FunctionReference(fn); // returns fn itself

// Any API taking a block, e.g.:
NSOperationQueue.mainQueue.addOperationWithBlock(ref);

// Any API taking a C function pointer, e.g. a struct field or a C function parameter
// typed as a function pointer:
someFunctionTakingAFunctionPointer(ref); // -> tns::Assert(false)

Cause

Both wrapper kinds live in the same per-object slot (tns::SetValue / tns::GetValue):

  • FunctionReference's constructor stores a FunctionReferenceWrapper on fn and registers fn with ObjectManager (FunctionReference.cpp).
  • The block branch of Interop::SetFFIParams doesn't recognise a FunctionReferenceWrapper as a cached block. It builds a JSBlock and overwrites the slot with its BlockWrapper. tns::SetValue doesn't free the previous wrapper, so the FunctionReferenceWrapper leaks, along with the trampoline it may have cached.
  • The function-pointer branch accepts only Pointer, AnonymousFunction and FunctionReference wrappers. With a BlockWrapper in the slot it falls through to tns::Assert(false, isolate) (NativeScript/runtime/Interop.mm, function-pointer branch of SetFFIParams).

In the opposite order (block first, then new interop.FunctionReference(fn)), the constructor overwrites the block's wrapper. Every later block marshal of fn then builds a new block instead of reusing the cached one, and the next one overwrites the FunctionReferenceWrapper again.

Expected

One function can be used both as a block and as a function pointer. Possible directions:

  • keep the block cache and the FunctionReference state in separate slots;
  • let the block path recognise a FunctionReferenceWrapper and keep the block alongside it.

Either way, neither marshal should evict the other's wrapper, and the function-pointer path should never assert on a wrapper type it can explain to the user. At minimum it should throw a JS error instead.

Context

Found while reviewing #500, which makes JS block wrappers owned by their JSBlock. The worker test added there (blockFunctionReferenceWorker.js) relies on the current overwrite behaviour to reach the teardown path.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions