Skip to content

[TrimmableTypeMap] Binding/AndroidX interface listener callbacks don't work under NativeAOT (direct-dispatch caused recursion, reverted) #11773

Description

@simonrozsival

Summary

In the trimmable typemap (NativeAOT), Java interface peer types (e.g. listener Implementors) declare their n_* native callback as a private static method on the interface type. For interfaces that live in a binding/AndroidX assembly, ILC trims that callback (nothing references it within its own assembly in the trimmable path), so the generated proxy forwarder "will always throw":

ILC: Method '..._IOnBackStackChangedListenerImplementor_Proxy.n_onBackStackChangeStarted_uco_*' will always throw
     because: Missing method 'Void IOnBackStackChangedListener.n_OnBackStackChangeStarted_...'

This means interface listener callbacks from binding/AndroidX libraries don't work under the trimmable typemap.

What we tried (and reverted)

PR #11769 originally switched interface-implementation marshal methods to direct managed dispatch (ShouldCallManagedMethodDirectly returned true for isInterfaceImplementation), so the generated UCO would do GetObject<TInterface>(handle).Method(...) itself instead of forwarding to the (trimmed) static n_*.

This caused a runtime regression: infinite recursion / stack overflow for all interface listeners, including built-in Mono.Android ones (ViewTreeObserver.GlobalLayout, etc.). Reproduced on an arm64 emulator with a minimal app:

button.ViewTreeObserver.GlobalLayout += (s, e) =>
    Android.Util.Log.Debug("BugzillaTests", "Bug 29730: GlobalLayout event handler called!");

Fatal signal 11 (SIGSEGV) ... Cause: stack pointer is not in a rw map; likely due to stack overflow. The callback log never appears. Disabling the direct-dispatch change makes the handler fire correctly.

The direct-dispatch UCO resolved the peer as the Invoker (GetObject(handle, typeof(TInterface)) creates the invoker when the user's Implementor peer isn't returned), and the invoker forwards the call back to Java → native → UCO → invoker → … until the stack overflows. So direct dispatch is strictly worse than the original static-n_* forwarding, which dispatches correctly for Mono.Android interfaces.

The direct-dispatch change has been reverted (the non-generic JavaPeerProxy base fix for interface proxies in #11769 is kept — that one is correct and fixes a separate TypeLoadException). This unblocks GlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunch and avoids breaking every interface listener.

What still needs solving (this issue)

Binding/AndroidX interface listener callbacks still don't work under the trimmable typemap because the interface's private static n_* callback is trimmed → "will always throw". We need a correct fix that does not reintroduce the recursion, e.g.:

  • Preserve / root the interface n_* callbacks so ILC keeps them (the forwarder then resolves), rather than bypassing them with direct dispatch; or
  • A direct-dispatch variant that provably resolves the user Implementor peer (never the Invoker) — i.e. understand why GetObject returns the invoker here and ensure the UCO dispatches to the already-registered managed peer; or
  • Generate the JCW/registration so the binding's n_* is reachable in the trimmable assembly.

Acceptance criteria

  • Binding/AndroidX interface listener callbacks fire at runtime under NativeAOT (e.g. IOnBackStackChangedListener), with a device test.
  • No regression for built-in Mono.Android interface listeners (GlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunch stays green).
  • No "will always throw" ILC diagnostics for the generated interface proxies.

References

  • Reverted in/around [TrimmableTypeMap] Fix interface-peer proxy generation for NativeAOT #11769 (interface-peer proxy generation for NativeAOT).
  • Repro test: GlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunch (NativeAOT), tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs.
  • Code: src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs (ShouldCallManagedMethodDirectly), Generator/ExportMethodDispatchEmitter.cs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssues that need to be assigned.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions