diff --git a/src/Java.Interop/Java.Interop/JniObjectReference.cs b/src/Java.Interop/Java.Interop/JniObjectReference.cs index cb92a8364..9048b2370 100644 --- a/src/Java.Interop/Java.Interop/JniObjectReference.cs +++ b/src/Java.Interop/Java.Interop/JniObjectReference.cs @@ -197,6 +197,8 @@ public static void Dispose (ref JniObjectReference reference) case JniObjectReferenceType.WeakGlobal: JniEnvironment.Runtime.ObjectReferenceManager.DeleteWeakGlobalReference (ref reference); break; + case JniObjectReferenceType.Invalid: + break; default: throw new NotImplementedException ("Do not know how to dispose: " + reference.Type.ToString () + "."); } diff --git a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index 1ce3e6543..869243854 100644 --- a/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -104,8 +104,9 @@ public void ConstructPeer (IJavaPeerable peer, ref JniObjectReference reference, if (peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Activatable)) { return; } - JniObjectReference.Dispose (ref reference, options); - newRef = newRef.NewGlobalRef (); + var orig = newRef; + newRef = orig.NewGlobalRef (); + JniObjectReference.Dispose (ref orig); } else if (options == JniObjectReferenceOptions.None) { // `reference` is likely *InvalidJniObjectReference, and can't be touched return; diff --git a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs index 475403875..385bef168 100644 --- a/tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs +++ b/tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs @@ -129,6 +129,46 @@ public void ConstructPeer_ImplicitViaBindingMethod_PeerIsInSurfacedPeers () JniObjectReference.Dispose (ref localRef); } + // https://github.com/dotnet/android/issues/11101 + [Test] + public void ConstructPeer_CalledMultipleTimes_ShouldNotLeakGlobalRefs () + { + // Simulate what TypeManager.Activate does in dotnet/android: + // 1. Create an uninitialized JavaObject + // 2. Set a peer reference (storing the raw handle) + // 3. The constructor chain then calls ConstructPeer multiple times + // + // This should not leak JNI global references. + + using (var original = new MyDisposableObject ()) { + // Get the jobject handle, simulating the IntPtr jobject param in Activate + var handle = original.PeerReference; + + // Create an uninitialized peer and set its reference (simulating Activate) + var peer = (IJavaPeerable) RuntimeHelpers.GetUninitializedObject (typeof (MyDisposableObject)); + peer.SetPeerReference (new JniObjectReference (handle.Handle)); + + try { + // Now simulate the constructor chain calling ConstructPeer multiple times + var ref1 = new JniObjectReference (handle.Handle); + valueManager.ConstructPeer (peer, ref ref1, JniObjectReferenceOptions.Copy); + + int grefAfterFirst = JniEnvironment.Runtime.GlobalReferenceCount; + + var ref2 = new JniObjectReference (handle.Handle); + valueManager.ConstructPeer (peer, ref ref2, JniObjectReferenceOptions.Copy); + + int grefAfterSecond = JniEnvironment.Runtime.GlobalReferenceCount; + + // The second ConstructPeer should NOT create an additional global ref + Assert.AreEqual (grefAfterFirst, grefAfterSecond, + "Second ConstructPeer call should not create an additional global ref"); + } finally { + peer.Dispose (); + } + } + } + [Test] public void CollectPeers ()