-
Notifications
You must be signed in to change notification settings - Fork 541
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
[Mono.Android] fix "replaceable" objects in ManagedValueManager
#10004
base: main
Are you sure you want to change the base?
Conversation
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. With these lines removed, the test passes. I will look if we should also update these lines in dotnet/java-interop in a future PR.
Context: dotnet/android#10004 This breaks the "replaceable" logic otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes a bug in ManagedValueManager where "replaceable" objects were not properly replaced, leading to incorrect instance registrations in tests.
- Removed an early return check that prevented the replacement of instances.
- The changes align ManagedValueManager's behavior with AndroidValueManager's implementation.
Comments suppressed due to low confidence (1)
src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs:70
- Removing the PeekPeer check ensures that replaceable objects are correctly updated, but please add a code comment to clarify why this behavior is necessary to prevent future regressions.
var o = PeekPeer (value.PeerReference);
Context: dotnet/android#10004 It looks like dotnet/android#10004 is closely tied to dotnet/android#9862. Might as well update tests to hit this behavior!
What's "funny" is the interaction between #9862, dotnet/java-interop#1323, and this PR #10004. The "problem" is that the current I think that …but at the possible cost of the scenario #10004 is trying to address. Additionally this doesn't "lower" things so that the issue is visible in dotnet/java-interop. I had thought that an existing test case there hit this scenario, but I was wrong; there is partial overlap, but not complete overlap. I've updated dotnet/java-interop#1323 so that |
After more thinking… I think the actual bug is in As per private chat, this doesn't resemble current binding constructors at all. I just didn't think further on it at the time… Aside: dotnet/java-interop@3043d89 Additional aside: We no longer support What the constructor should be is: /* 01 */ public CreateInstance_OverrideAbsListView_Adapter (Context context)
/* 02 */ : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
/* 03 */ {
/* 04 */ const string __id = "(Landroid/content/Context;)V";
/* 05 */
/* 06 */ if (((global::Java.Lang.Object) this).Handle != IntPtr.Zero)
/* 07 */ return;
/* 08 */
/* 09 */ try {
/* 10 */ JniArgumentValue* __args = stackalloc JniArgumentValue [1];
/* 11 */ __args [0] = new JniArgumentValue ((context == null) ? IntPtr.Zero : ((global::Java.Lang.Object) context).Handle);
/* 12 */ var __r = _members.InstanceMethods.StartCreateInstance (__id, ((object) this).GetType (), __args);
/* 13 */ SetHandle (__r.Handle, JniHandleOwnership.TransferLocalRef);
/* 14 */ _members.InstanceMethods.FinishCreateInstance (__id, this, __args);
/* 15 */ } finally {
/* 16 */ global::System.GC.KeepAlive (context);
/* 17 */ }
/* 18 */
/* 19 */ AdapterValue = new ArrayAdapter (context, 0);
/* 20 */ } Line 2: null handle, we're creating a new object below. Line 6-7: If Handle is already set, we don't need to create a new instance; bail. Lines 10-11: JNI argument marshaling Line 12: Line 13: Line 14: This "allocate, create mapping, invoke constructor" pattern allows us to avoid the whole conundrum of "the Java constructor invokes an overridden virtual method! What instance do we invoke it on!" by ensuring that we have the mapping before the Java constructor is even invoked! Which brings us to the "faulty" existing implementation: public CreateInstance_OverrideAbsListView_Adapter (Context context)
: base (
JNIEnv.CreateInstance (
JcwType,
"(Landroid/content/Context;)V",
new JValue (context)),
JniHandleOwnership.TransferLocalRef) The use of
Thus, the question: what is the "fix"? What are we trying to fix? Option 1: Update
Option 2:
Option 3: Add a
|
The following test is failing on NativeAOT as well as any case we'd use
ManagedValueManager
:With the assertion:
The second assertion fails because
registered
is the same instance asintermediate
. In this example, this is a code path whereintermediate
should be "replaced" withadapter
.After lots of debugging, I found the problem are these lines in the
ManagedValueManager.AddPeer()
method:If we
PeekPeer()
in the middle ofAddPeer()
and a type is "replaceable", it would find an instance and not replace it! I did not find equivalent code inAndroidValueManager.AddPeer()
, which is what is used in Mono & production today.With these lines removed, the test passes. I will look if we should also update these lines in dotnet/java-interop in a future PR.