Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Mono.Android] fix "replaceable" objects in ManagedValueManager #10107

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

Merged
merged 1 commit into from
May 6, 2025

Conversation

jonathanpeppers
Copy link
Member

The following test is failing on NativeAOT as well as any case we'd use ManagedValueManager:

[Test]
public void JnienvCreateInstance_RegistersMultipleInstances ()
{
    using (var adapter = new CreateInstance_OverrideAbsListView_Adapter (Application.Context)) {

        var intermediate  = CreateInstance_OverrideAbsListView_Adapter.Intermediate;
        var registered    = Java.Lang.Object.GetObject<CreateInstance_OverrideAbsListView_Adapter>(adapter.Handle, JniHandleOwnership.DoNotTransfer);

        Assert.AreNotSame (adapter, intermediate); // Passes
        Assert.AreSame (adapter, registered);      // Fails!
    }
}

With the assertion:

Expected: same as <com.xamarin.android.runtimetests.CreateInstance_OverrideAbsListView_Adapter{cbd0e5a V.ED.VC.. ......I. 0,0-0,0}>
But was:  <com.xamarin.android.runtimetests.CreateInstance_OverrideAbsListView_Adapter{cbd0e5a V.ED.VC.. ......I. 0,0-0,0}>

The second assertion fails because registered is the same instance as intermediate. In this example, this is a code path where intermediate should be "replaced" with adapter.

After lots of debugging, I found the problem are these lines in the ManagedValueManager.AddPeer() method:

var o = PeekPeer (value.PeerReference);
if (o != null)
    return;

If we PeekPeer() in the middle of AddPeer() and a type is "replaceable", it would find an instance and not replace it! I did not find equivalent code in AndroidValueManager.AddPeer(), which is what is used in Mono & production today.

This was also addressed in Java.Interop's ManagedValueManager here:

This also solves a test failure in Java.Interop-Tests:

Mono.Android.NET_Tests, Java.InteropTests.InvokeVirtualFromConstructorTests.CreateManagedInstanceFirst_WithNewObject / Release

Error message
    Expected t and registered to be the same instance; t=20dfddd, registered=325d330
    Expected: same as net.dot.jni.test.CallVirtualFromConstructorDerived@564f0f8
    But was:  net.dot.jni.test.CallVirtualFromConstructorDerived@564f0f8
Stack trace
    at Java.InteropTests.InvokeVirtualFromConstructorTests.CreateManagedInstanceFirst_WithNewObject()
    at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack, Void**, ObjectHandleOnStack, BOOL, ObjectHandleOnStack)
    at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack, Void**, ObjectHandleOnStack, BOOL, ObjectHandleOnStack)
    at System.RuntimeMethodHandle.InvokeMethod(Object, Void**, Signature, Boolean)
    at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
    at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object, BindingFlags)

The following test is failing on NativeAOT as well as any case we'd
use `ManagedValueManager`:

    [Test]
    public void JnienvCreateInstance_RegistersMultipleInstances ()
    {
        using (var adapter = new CreateInstance_OverrideAbsListView_Adapter (Application.Context)) {

            var intermediate  = CreateInstance_OverrideAbsListView_Adapter.Intermediate;
            var registered    = Java.Lang.Object.GetObject<CreateInstance_OverrideAbsListView_Adapter>(adapter.Handle, JniHandleOwnership.DoNotTransfer);

            Assert.AreNotSame (adapter, intermediate); // Passes
            Assert.AreSame (adapter, registered);      // Fails!
        }
    }

With the assertion:

    Expected: same as <com.xamarin.android.runtimetests.CreateInstance_OverrideAbsListView_Adapter{cbd0e5a V.ED.VC.. ......I. 0,0-0,0}>
    But was:  <com.xamarin.android.runtimetests.CreateInstance_OverrideAbsListView_Adapter{cbd0e5a V.ED.VC.. ......I. 0,0-0,0}>

The second assertion fails because `registered` is the same instance
as `intermediate`. In this example, this is a code path where
`intermediate` should be "replaced" with `adapter`.

After lots of debugging, I found the problem are these lines in the
`ManagedValueManager.AddPeer()` method:

    var o = PeekPeer (value.PeerReference);
    if (o != null)
        return;

If we `PeekPeer()` in the middle of `AddPeer()` and a type is
"replaceable", it would find an instance and not replace it! I did not
find equivalent code in `AndroidValueManager.AddPeer()`, which is what
is used in Mono & production today.

This was also addressed in Java.Interop's `ManagedValueManager` here:

* dotnet/java-interop@d3d3a1b

This also solves a test failure in `Java.Interop-Tests`:

    Mono.Android.NET_Tests, Java.InteropTests.InvokeVirtualFromConstructorTests.CreateManagedInstanceFirst_WithNewObject / Release

    Error message
        Expected t and registered to be the same instance; t=20dfddd, registered=325d330
        Expected: same as net.dot.jni.test.CallVirtualFromConstructorDerived@564f0f8
        But was:  net.dot.jni.test.CallVirtualFromConstructorDerived@564f0f8
    Stack trace
        at Java.InteropTests.InvokeVirtualFromConstructorTests.CreateManagedInstanceFirst_WithNewObject()
        at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack, Void**, ObjectHandleOnStack, BOOL, ObjectHandleOnStack)
        at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack, Void**, ObjectHandleOnStack, BOOL, ObjectHandleOnStack)
        at System.RuntimeMethodHandle.InvokeMethod(Object, Void**, Signature, Boolean)
        at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
        at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object, BindingFlags)
@jonathanpeppers jonathanpeppers requested a review from jonpryor as a code owner May 5, 2025 17:47
@jonpryor
Copy link
Member

jonpryor commented May 5, 2025

Draft commit message:

Context: d3d35846a6e53b53a3bc944abb1c9e275399449c
Context: https://github.com/dotnet/java-interop/commit/d3d3a1bf8200cbcc545ac438c47abcd158b55a1e

The following test was failing on NativeAOT as well as whenever we'd
use `ManagedValueManager`:

	[Test]
	public void JnienvCreateInstance_RegistersMultipleInstances ()
	{
	  using (var adapter = new CreateInstance_OverrideAbsListView_Adapter (Application.Context)) {

	    var intermediate  = CreateInstance_OverrideAbsListView_Adapter.Intermediate;
	    var registered    = Java.Lang.Object.GetObject<CreateInstance_OverrideAbsListView_Adapter>(adapter.Handle, JniHandleOwnership.DoNotTransfer);

	    Assert.AreNotSame (adapter, intermediate); // Passes
	    Assert.AreSame (adapter, registered);      // Fails!
	  }
	}

With the assertion:

	Expected: same as <com.xamarin.android.runtimetests.CreateInstance_OverrideAbsListView_Adapter{cbd0e5a V.ED.VC.. ......I. 0,0-0,0}>
	But was:  <com.xamarin.android.runtimetests.CreateInstance_OverrideAbsListView_Adapter{cbd0e5a V.ED.VC.. ......I. 0,0-0,0}>

The second assertion fails because `registered` is the same instance
as `intermediate`.  In this example, this is a code path where
`intermediate` should be "replaced" with `adapter`.

After lots of debugging, I found the problem are these lines in the
`ManagedValueManager.AddPeer()` method:

	var o = PeekPeer (value.PeerReference);
	if (o != null)
	    return;

If we `PeekPeer()` in the middle of `AddPeer()` and a type is
"replaceable", it would find an instance and not replace it! I did not
find equivalent code in `AndroidValueManager.AddPeer()`, which is what
is used in Mono & production today.

This was also addressed in Java.Interop's `ManagedValueManager` by
dotnet/java-interop@d3d3a1bf.

This also solves a test failure in `Java.Interop-Tests`:

	Mono.Android.NET_Tests, Java.InteropTests.InvokeVirtualFromConstructorTests.CreateManagedInstanceFirst_WithNewObject / Release
	
	Error message
	    Expected t and registered to be the same instance; t=20dfddd, registered=325d330
	    Expected: same as net.dot.jni.test.CallVirtualFromConstructorDerived@564f0f8
	    But was:  net.dot.jni.test.CallVirtualFromConstructorDerived@564f0f8
	Stack trace
	    at Java.InteropTests.InvokeVirtualFromConstructorTests.CreateManagedInstanceFirst_WithNewObject()
	    at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack, Void**, ObjectHandleOnStack, BOOL, ObjectHandleOnStack)
	    at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack, Void**, ObjectHandleOnStack, BOOL, ObjectHandleOnStack)
	    at System.RuntimeMethodHandle.InvokeMethod(Object, Void**, Signature, Boolean)
	    at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
	    at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object, BindingFlags)

@jonpryor jonpryor merged commit 525f755 into main May 6, 2025
59 checks passed
@jonpryor jonpryor deleted the dev/peppers/ManagedValueManagerPeekPeer branch May 6, 2025 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants