From d6e0e7dcc97803fe28102fdc75d6d38d104696a7 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 4 Apr 2025 15:11:43 -0400 Subject: [PATCH 01/13] Try dotnet/java-interop#1323 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/dotnet/java-interop/pull/1323 Context: https://github.com/dotnet/android/issues/9862#issuecomment-2705027573 Does It Build™? (The expectation is that it *does* build -- only unit tests are changed in dotnet/java-interop#1323 -- but that the new `JniRuntimeJniValueManagerContract.cs.CreatePeer_ReplaceableDoesNotReplace()` test will fail.)` --- .gitmodules | 2 +- external/Java.Interop | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index c5443e9db15..778f1005c57 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,7 +13,7 @@ [submodule "external/Java.Interop"] path = external/Java.Interop url = https://github.com/dotnet/java-interop - branch = main + branch = dev/jonp/jonp-assert-replacable-semantics [submodule "external/libunwind"] path = external/libunwind url = https://github.com/libunwind/libunwind.git diff --git a/external/Java.Interop b/external/Java.Interop index 7b7ae831a76..4b8abce9a4a 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit 7b7ae831a7624c7f5e82abd0d368261ff3d6f1d8 +Subproject commit 4b8abce9a4a04e920023160c8589dd0293d80caf From b6debda7d3c3adc74e0cb6122ccc475c6552ad6d Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 11 Apr 2025 09:01:29 -0400 Subject: [PATCH 02/13] =?UTF-8?q?Fixity=20Fix=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- external/Java.Interop | 2 +- .../Android.Runtime/AndroidRuntime.cs | 15 ++++++++++--- src/Mono.Android/Java.Interop/TypeManager.cs | 22 ++++++++++++++----- .../ManagedValueManager.cs | 9 ++++---- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/external/Java.Interop b/external/Java.Interop index 4b8abce9a4a..bf026607964 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit 4b8abce9a4a04e920023160c8589dd0293d80caf +Subproject commit bf0266079648f91fa03f6944dc22a503475e504b diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index b1390a5f884..b247c2b3e3a 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -689,7 +689,7 @@ internal void AddPeer (IJavaPeerable value, JniObjectReference reference, IntPtr for (int i = 0; i < targets.Count; ++i) { IJavaPeerable? target; var wref = targets [i]; - if (ShouldReplaceMapping (wref!, reference, out target)) { + if (ShouldReplaceMapping (wref!, reference, value, out target)) { found = true; targets [i] = IdentityHashTargets.CreateWeakReference (value); break; @@ -747,7 +747,7 @@ internal void AddPeer (IJavaPeerable value, IntPtr handle, JniHandleOwnership tr } } - bool ShouldReplaceMapping (WeakReference current, JniObjectReference reference, out IJavaPeerable? target) + bool ShouldReplaceMapping (WeakReference current, JniObjectReference reference, IJavaPeerable value, out IJavaPeerable? target) { target = null; @@ -766,17 +766,26 @@ bool ShouldReplaceMapping (WeakReference current, JniObjectRefere if (!JniEnvironment.Types.IsSameObject (target.PeerReference, reference)) return false; + Console.WriteLine ($"# jonp: ShouldReplaceMapping: target={RuntimeHelpers.GetHashCode(target)} {target.JniManagedPeerState}" + + $"value={RuntimeHelpers.GetHashCode (value)} {value.JniManagedPeerState}"); + Console.WriteLine (new System.Diagnostics.StackTrace (true).ToString ()); + // JNIEnv.NewObject/JNIEnv.CreateInstance() compatibility. // When two MCW's are created for one Java instance [0], // we want the 2nd MCW to replace the 1st, as the 2nd is // the one the dev created; the 1st is an implicit intermediary. // + // Meanwhile, a new "replaceable" instance should *not* replace an + // existing "replaceable" instance; see dotnet/android#9862. + // // [0]: If Java ctor invokes overridden virtual method, we'll // transition into managed code w/o a registered instance, and // thus will create an "intermediary" via // (IntPtr, JniHandleOwnership) .ctor. - if ((target.JniManagedPeerState & JniManagedPeerStates.Replaceable) == JniManagedPeerStates.Replaceable) + if (target.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable) && + !value.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable)) { return true; + } return false; } diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 81ee5d6da47..bedac93db40 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -358,10 +358,8 @@ static Type monovm_typemap_java_to_managed (string java_type_name) IJavaPeerable? result = null; try { + Console.WriteLine ($"# jonp: TypeManager.CreateInstance: type={type.FullName} handle={handle:x} transfer={transfer}"); result = (IJavaPeerable) CreateProxy (type, handle, transfer); - if (Runtime.IsGCUserPeer (result.PeerReference.Handle)) { - result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); - } } catch (MissingMethodException e) { var key_handle = JNIEnv.IdentityHash (handle); JNIEnv.DeleteRef (handle, transfer); @@ -385,19 +383,31 @@ internal static object CreateProxy ( BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var c = type.GetConstructor (flags, null, XAConstructorSignature, null); if (c != null) { - return c.Invoke (new object [] { handle, transfer }); + var self = GetUninitializedObject (type); + c.Invoke (self, new object [] { handle, transfer }); + return self; } c = type.GetConstructor (flags, null, JIConstructorSignature, null); if (c != null) { + var self = GetUninitializedObject (type); JniObjectReference r = new JniObjectReference (handle); JniObjectReferenceOptions o = JniObjectReferenceOptions.Copy; - var peer = (IJavaPeerable) c.Invoke (new object [] { r, o }); + c.Invoke (self, new object [] { r, o }); JNIEnv.DeleteRef (handle, transfer); - return peer; + return self; } throw new MissingMethodException ( "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", CreateJavaLocationException ()); + + static IJavaPeerable GetUninitializedObject ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type) + { + var v = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type); + v.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + return v; + } } public static void RegisterType (string java_class, Type t) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs index 616217ca4bc..88600bb0929 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/ManagedValueManager.cs @@ -265,7 +265,8 @@ public override List GetSurfacedPeers () static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) }; - protected override IJavaPeerable? TryCreatePeer ( + protected override bool TryConstructPeer ( + IJavaPeerable self, ref JniObjectReference reference, JniObjectReferenceOptions options, [DynamicallyAccessedMembers (Constructors)] @@ -277,10 +278,10 @@ public override List GetSurfacedPeers () reference.Handle, JniHandleOwnership.DoNotTransfer, }; - var p = (IJavaPeerable) c.Invoke (args); + c.Invoke (self, args); JniObjectReference.Dispose (ref reference, options); - return p; + return true; } - return base.TryCreatePeer (ref reference, options, type); + return base.TryConstructPeer (self, ref reference, options, type); } } From d453845daafd138c08b97ecb16cc18bf00cd0c76 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 15 Apr 2025 10:53:14 -0400 Subject: [PATCH 03/13] Bump to dotnet/java-interop/main@d3d3a1bf Changes: https://github.com/dotnet/java-interop/compare/8221b7d0d8599fc793e3d55ee802c14deb6da07c...d3d3a1bf8200cbcc545ac438c47abcd158b55a1e * dotnet/java-interop@d3d3a1bf: [Java.Interop] JNIEnv::NewObject and Replaceable instances (dotnet/java-interop#1323) --- .gitmodules | 2 +- external/Java.Interop | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 778f1005c57..c5443e9db15 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,7 +13,7 @@ [submodule "external/Java.Interop"] path = external/Java.Interop url = https://github.com/dotnet/java-interop - branch = dev/jonp/jonp-assert-replacable-semantics + branch = main [submodule "external/libunwind"] path = external/libunwind url = https://github.com/libunwind/libunwind.git diff --git a/external/Java.Interop b/external/Java.Interop index 8221b7d0d85..d3d3a1bf820 160000 --- a/external/Java.Interop +++ b/external/Java.Interop @@ -1 +1 @@ -Subproject commit 8221b7d0d8599fc793e3d55ee802c14deb6da07c +Subproject commit d3d3a1bf8200cbcc545ac438c47abcd158b55a1e From d5b102df06374d91a6743c7a79c90e97750b9048 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 15 Apr 2025 14:55:26 -0400 Subject: [PATCH 04/13] Add & remove logging Remove the `Console.WriteLine()`s from `AndroidRuntime.cs` and `TypeManager.cs`. Add an `@(AndroidEnvironment)` to `Mono.Android.NET-Tests.csproj` to set `debug.mono.log=default,gref+`, so that on the next run we can get *some* gref logging to help diagnose an apparent crash: JNI ERROR (app bug): accessed deleted Global 0x3056 --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 4 ---- src/Mono.Android/Java.Interop/TypeManager.cs | 1 - .../Mono.Android-Tests/Mono.Android.NET-Tests.csproj | 7 +++++++ tests/Mono.Android-Tests/Mono.Android-Tests/env.txt | 2 ++ 4 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/env.txt diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index b247c2b3e3a..e5e7eadd11e 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -766,10 +766,6 @@ bool ShouldReplaceMapping (WeakReference current, JniObjectRefere if (!JniEnvironment.Types.IsSameObject (target.PeerReference, reference)) return false; - Console.WriteLine ($"# jonp: ShouldReplaceMapping: target={RuntimeHelpers.GetHashCode(target)} {target.JniManagedPeerState}" + - $"value={RuntimeHelpers.GetHashCode (value)} {value.JniManagedPeerState}"); - Console.WriteLine (new System.Diagnostics.StackTrace (true).ToString ()); - // JNIEnv.NewObject/JNIEnv.CreateInstance() compatibility. // When two MCW's are created for one Java instance [0], // we want the 2nd MCW to replace the 1st, as the 2nd is diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index bedac93db40..a462386d996 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -358,7 +358,6 @@ static Type monovm_typemap_java_to_managed (string java_type_name) IJavaPeerable? result = null; try { - Console.WriteLine ($"# jonp: TypeManager.CreateInstance: type={type.FullName} handle={handle:x} transfer={transfer}"); result = (IJavaPeerable) CreateProxy (type, handle, transfer); } catch (MissingMethodException e) { var key_handle = JNIEnv.IdentityHash (handle); diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj index 8690e4b8e10..7d867216ca1 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj @@ -46,6 +46,13 @@ <_DefaultValueAttributeSupport Condition="'$(TrimMode)' == 'full'">true + + + + + diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt b/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt new file mode 100644 index 00000000000..fb3491e4571 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt @@ -0,0 +1,2 @@ +# Environment Variables and system properties +debug.mono.log gref+,default From 8aaefed1cf008f0362429ed692f15eea86e20c0a Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 16 Apr 2025 15:01:44 -0400 Subject: [PATCH 05/13] Various fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We're seeing a recurrent crash on CI, which is still inexplicable: E droid.NET_Test: JNI ERROR (app bug): accessed deleted Global 0x3056 F droid.NET_Test: java_vm_ext.cc:570] JNI DETECTED ERROR IN APPLICATION: use of deleted global reference 0x3056 The obvious thing to do to track this down is to enable GREF logging, which makes the error disappear. (Figures.) However, attempting to enable GREF logging found *other* issues: The GREF log couldn't be created (!): W monodroid: Failed to create directory '/data/user/0/Mono.Android.NET_Tests/files/.__override__/arm64-v8a'. No such file or directory … E monodroid: fopen failed for file /data/user/0/Mono.Android.NET_Tests/files/.__override__/arm64-v8a/grefs.txt: No such file or directory The apparent cause for this is that the ABI is now part of the path, `…/.__override__/arm64-v8a/grefs.txt` and not `…/.__override__/grefs.txt`. Additionally, `create_public_directory()` was a straight `mkdir()` call, which *does not* create intermediate directories. Update `create_public_directory()` to use `create_directory()` instead, which *does* create intermediate directories. This allows `grefs.txt` to be created. *Next*, I started getting a *bizarre* failure within `MoarThreadingTests()`: I NUnit : 1) Java.InteropTests.JnienvTest.MoarThreadingTests (Mono.Android.NET-Tests) I NUnit : No exception should be thrown [t2]! Got: System.ObjectDisposedException: Cannot access disposed object with JniIdentityHashCode=158880748. I NUnit : Object name: 'Java.Lang.Integer'. I NUnit : at Java.Interop.JniPeerMembers.AssertSelf(IJavaPeerable self) I NUnit : at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeAbstractInt32Method(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters) I NUnit : at Java.Lang.Integer.IntValue() I NUnit : at Java.Lang.Integer.System.IConvertible.ToInt32(IFormatProvider provider) I NUnit : at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider) I NUnit : at Android.Runtime.JNIEnv.GetObjectArray(IntPtr array_ptr, Type[] element_types) I NUnit : at Java.InteropTests.JnienvTest.<>c__DisplayClass26_0.b__1() I NUnit : Expected: null I NUnit : But was: c__DisplayClass26_0.b__1() in /Volumes/Xamarin-Work/src/dotnet/android/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs:line 390 which makes *no* sense, and I'm starting to have flashbacks to issue #9039, which was a GC bug fixed in dotnet/runtime#112825. (Don't we have that fix?! Perhaps not.) Next, the contents of `grefs.txt` occasionally looked "wrong". Turns out, `WriteGlobalReferenceLine()` doesn't consistently append a newline to the message, which impacts e.g. the `Created …` message: Created PeerReference=0x3c06/G IdentityHashCode=0x2e29bd Instance=0xbe0aab36 Instance.Type=Android.OS.Bundle, Java.Type=android/os/Bundle+g+ grefc 19 gwrefc 0 obj-handle 0x706f711035/L -> new-handle 0x3d06/G from thread ''(1) Update `WriteGlobalReferenceLine()` to always append a newline to the message. Finally, update `env.txt` to always set debug.mono.debug=1. Filenames and line numbers in stack traces are handy! --- src/Mono.Android/Android.Runtime/AndroidRuntime.cs | 2 ++ src/Mono.Android/Android.Runtime/JNIEnv.cs | 1 + src/native/mono/runtime-base/util.cc | 4 +--- tests/Mono.Android-Tests/Mono.Android-Tests/env.txt | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index e5e7eadd11e..8af0d4127b4 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -173,11 +173,13 @@ public override IntPtr ReleaseLocalReference (ref JniObjectReference value, ref public override void WriteLocalReferenceLine (string format, params object?[] args) { RuntimeNativeMethods._monodroid_gref_log ("[LREF] " + string.Format (CultureInfo.InvariantCulture, format, args)); + RuntimeNativeMethods._monodroid_gref_log ("\n"); } public override void WriteGlobalReferenceLine (string format, params object?[] args) { RuntimeNativeMethods._monodroid_gref_log (string.Format (CultureInfo.InvariantCulture, format, args)); + RuntimeNativeMethods._monodroid_gref_log ("\n"); } public override JniObjectReference CreateGlobalReference (JniObjectReference value) diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 647acbb5108..65f94b34360 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -1070,6 +1070,7 @@ static int _GetArrayLength (IntPtr array_ptr) ret [i] = targetType == null || targetType.IsInstanceOfType (value) ? value : Convert.ChangeType (value, targetType, CultureInfo.InvariantCulture); + GC.KeepAlive (value); } return ret; diff --git a/src/native/mono/runtime-base/util.cc b/src/native/mono/runtime-base/util.cc index 3e805c34f9b..fda41ecfd88 100644 --- a/src/native/mono/runtime-base/util.cc +++ b/src/native/mono/runtime-base/util.cc @@ -147,12 +147,10 @@ Util::path_combine (const char *path1, const char *path2) void Util::create_public_directory (const char *dir) { - mode_t m = umask (0); - int ret = mkdir (dir, 0777); + int ret = create_directory (dir, 0777); if (ret < 0) { log_warn (LOG_DEFAULT, "Failed to create directory '{}'. {}", dir, std::strerror (errno)); } - umask (m); } int diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt b/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt index fb3491e4571..cb897b79238 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/env.txt @@ -1,2 +1,3 @@ # Environment Variables and system properties -debug.mono.log gref+,default +# debug.mono.log=gref,default +debug.mono.debug=1 From 1e599168986856590040a2848392f057b5ddfc58 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 17 Apr 2025 13:53:46 -0400 Subject: [PATCH 06/13] Reset `TypeManager.cs` to origin/main Re-introduces `CreatePeer_ReplaceableDoesNotReplace()` failure. No crash. --- src/Mono.Android/Java.Interop/TypeManager.cs | 21 ++++++-------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index a462386d996..81ee5d6da47 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -359,6 +359,9 @@ static Type monovm_typemap_java_to_managed (string java_type_name) try { result = (IJavaPeerable) CreateProxy (type, handle, transfer); + if (Runtime.IsGCUserPeer (result.PeerReference.Handle)) { + result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + } } catch (MissingMethodException e) { var key_handle = JNIEnv.IdentityHash (handle); JNIEnv.DeleteRef (handle, transfer); @@ -382,31 +385,19 @@ internal static object CreateProxy ( BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var c = type.GetConstructor (flags, null, XAConstructorSignature, null); if (c != null) { - var self = GetUninitializedObject (type); - c.Invoke (self, new object [] { handle, transfer }); - return self; + return c.Invoke (new object [] { handle, transfer }); } c = type.GetConstructor (flags, null, JIConstructorSignature, null); if (c != null) { - var self = GetUninitializedObject (type); JniObjectReference r = new JniObjectReference (handle); JniObjectReferenceOptions o = JniObjectReferenceOptions.Copy; - c.Invoke (self, new object [] { r, o }); + var peer = (IJavaPeerable) c.Invoke (new object [] { r, o }); JNIEnv.DeleteRef (handle, transfer); - return self; + return peer; } throw new MissingMethodException ( "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", CreateJavaLocationException ()); - - static IJavaPeerable GetUninitializedObject ( - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type type) - { - var v = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type); - v.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); - return v; - } } public static void RegisterType (string java_class, Type t) From c8e45addee1f1aaed7d2e9a2eee2be90f97a25e0 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 17 Apr 2025 13:54:28 -0400 Subject: [PATCH 07/13] Add `GetUnintializedObject()`, but don't call it. `CreatePeer_ReplaceableDoesNotReplace()` still fails. No crash. --- src/Mono.Android/Java.Interop/TypeManager.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 81ee5d6da47..dd7de08c169 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -398,6 +398,15 @@ internal static object CreateProxy ( throw new MissingMethodException ( "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", CreateJavaLocationException ()); + + static IJavaPeerable GetUninitializedObject ( + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type) + { + var v = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type); + v.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + return v; + } } public static void RegisterType (string java_class, Type t) From f520259266bbf5a451c69c53bea8429c4550d454 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 17 Apr 2025 14:03:53 -0400 Subject: [PATCH 08/13] Call GetUninitializedObject(), don't use instance created MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOW the weirdness begins. Remember 8aaefed1cf008f0362429ed692f15eea86e20c0a? > *Next*, I started getting a *bizarre* failure within `MoarThreadingTests()`: > > I NUnit : 1) Java.InteropTests.JnienvTest.MoarThreadingTests (Mono.Android.NET-Tests) > I NUnit : No exception should be thrown [t2]! Got: System.ObjectDisposedException: Cannot access disposed object with JniIdentityHashCode=158880748. I'm seeing it again! E NUnit : [FAIL] E NUnit : : No exception should be thrown [t2]! Got: System.ObjectDisposedException: Cannot access disposed object with JniIdentityHashCode=158880748. E NUnit : Object name: 'Java.Lang.Integer'. E NUnit : at Java.Interop.JniPeerMembers.AssertSelf(IJavaPeerable self) in …/android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.cs:line 153 E NUnit : at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeAbstractInt32Method(String encodedMember, IJavaPeerable self, JniArgumentValue* parameters) in …/android/external/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods_Invoke.cs:line 484 E NUnit : at Java.Lang.Integer.IntValue() in …/android/src/Mono.Android/obj/Debug/net10.0/android-36/mcw/Java.Lang.Integer.cs:line 354 E NUnit : at Java.Lang.Integer.System.IConvertible.ToInt32(IFormatProvider provider) in …/android/src/Mono.Android/Java.Lang/Integer.cs:line 58 E NUnit : at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider) E NUnit : at Android.Runtime.JNIEnv.GetObjectArray(IntPtr array_ptr, Type[] element_types) in …/android/src/Mono.Android/Android.Runtime/JNIEnv.cs:line 1070 E NUnit : at Java.InteropTests.JnienvTest.<>c__DisplayClass26_0.b__1() in …/android/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs:line 389 E NUnit : Expected: null E NUnit : But was: Date: Fri, 25 Apr 2025 15:32:32 -0400 Subject: [PATCH 09/13] =?UTF-8?q?Call=20`MethodBase.Invoke(object,=20objec?= =?UTF-8?q?t[])`=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …and now we crash in `ConversionsAndThreadsAndInstanceMappingsOhMy()`. We crash bigly. 04-25 15:27:37.527 19954 20022 F droid.NET_Tests: java_vm_ext.cc:616] JNI DETECTED ERROR IN APPLICATION: JNI ERROR (app bug): jobject is an invalid global reference: 0x430a (deleted reference at index 536) 04-25 15:27:37.527 19954 20022 F droid.NET_Tests: java_vm_ext.cc:616] in call to NewLocalRef … 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] "Thread-3" prio=10 tid=18 Runnable 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] | group="" sCount=0 ucsCount=0 flags=0 obj=0x1430c000 self=0xb400006eb4f45fe0 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] | sysTid=20022 nice=-8 cgrp=default sched=0/0 handle=0x6d9a5986c0 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] | state=R schedstat=( 6609743 480021 19 ) utm=0 stm=0 core=6 HZ=100 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] | stack=0x6d9a399000-0x6d9a39b000 stackSize=2045KB 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] | held mutexes= "abort lock" "mutator lock"(shared held) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #00 pc 004fc1e0 /apex/com.android.art/lib64/libart.so (art::DumpNativeStack+108) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #01 pc 004fe898 /apex/com.android.art/lib64/libart.so (art::Thread::DumpStack const+376) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #02 pc 0050068c /apex/com.android.art/lib64/libart.so (art::DumpCheckpoint::Run+216) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #03 pc 00553b2c /apex/com.android.art/lib64/libart.so (art::ThreadList::RunCheckpoint+684) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #04 pc 004fffc0 /apex/com.android.art/lib64/libart.so (art::ThreadList::Dump+292) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #05 pc 009377bc /apex/com.android.art/lib64/libart.so (art::AbortState::Dump const+204) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #06 pc 00933c2c /apex/com.android.art/lib64/libart.so (art::Runtime::Abort+712) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #07 pc 000160fc /apex/com.android.art/lib64/libbase.so (android::base::SetAborter::$_0::__invoke+80) (BuildId: 1470f61c05962eb04fafe76bd58bf664) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #08 pc 000156d0 /apex/com.android.art/lib64/libbase.so (android::base::LogMessage::~LogMessage+516) (BuildId: 1470f61c05962eb04fafe76bd58bf664) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #09 pc 0043627c /apex/com.android.art/lib64/libart.so (art::JavaVMExt::JniAbort+1696) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #10 pc 0088eaf4 /apex/com.android.art/lib64/libart.so (art::JavaVMExt::JniAbortV+108) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #11 pc 005a1a88 /apex/com.android.art/lib64/libart.so (art::::ScopedCheck::AbortF +140) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #12 pc 0059b970 /apex/com.android.art/lib64/libart.so (art::::ScopedCheck::Check +1412) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #13 pc 00597784 /apex/com.android.art/lib64/libart.so (art::::CheckJNI::NewRef +196) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #14 pc 001aa580 /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (???) (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #15 pc 001a8d38 /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (???) (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #16 pc 0019e068 /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (???) (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #17 pc 0019bcec /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (???) (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #18 pc 00258434 /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (mono_runtime_invoke_checked+140) (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #19 pc 0026f19c /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (???) (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #20 pc 00070374 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start+180) (BuildId: 3cad38c74be79004f5b2927be886e077) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] native: #21 pc 000620a0 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: 3cad38c74be79004f5b2927be886e077) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:708] (no managed stack frames) … 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:716] JNI DETECTED ERROR IN APPLICATION: JNI ERROR (app bug): jobject is an invalid global reference: 0x430a (deleted reference at index 536) 04-25 15:27:37.625 19954 20022 F droid.NET_Tests: runtime.cc:716] in call to NewLocalRef 04-25 15:27:37.625 19954 20022 F libc : Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 20022 (Thread-3), pid 19954 (droid.NET_Tests) … 04-25 15:27:37.937 20026 20026 F DEBUG : Abort message: 'JNI DETECTED ERROR IN APPLICATION: JNI ERROR (app bug): jobject is an invalid global reference: 0x430a (deleted reference at index 536)\12 in call to NewLocalRef' … 04-25 15:27:37.937 20026 20026 F DEBUG : 17 total frames 04-25 15:27:37.937 20026 20026 F DEBUG : backtrace: 04-25 15:27:37.937 20026 20026 F DEBUG : #00 pc 000000000005e8fc /apex/com.android.runtime/lib64/bionic/libc.so (abort+156) (BuildId: 3cad38c74be79004f5b2927be886e077) 04-25 15:27:37.937 20026 20026 F DEBUG : #01 pc 0000000000933abc /apex/com.android.art/lib64/libart.so (art::Runtime::Abort(char const*)+344) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.937 20026 20026 F DEBUG : #02 pc 00000000000160fc /apex/com.android.art/lib64/libbase.so (android::base::SetAborter(std::__1::function&&)::$_0::__invoke(char const*)+80) (BuildId: 1470f61c05962eb04fafe76bd58bf664) 04-25 15:27:37.937 20026 20026 F DEBUG : #03 pc 00000000000156d0 /apex/com.android.art/lib64/libbase.so (android::base::LogMessage::~LogMessage()+516) (BuildId: 1470f61c05962eb04fafe76bd58bf664) 04-25 15:27:37.937 20026 20026 F DEBUG : #04 pc 000000000043627c /apex/com.android.art/lib64/libart.so (art::JavaVMExt::JniAbort(char const*, char const*)+1696) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.937 20026 20026 F DEBUG : #05 pc 000000000088eaf4 /apex/com.android.art/lib64/libart.so (art::JavaVMExt::JniAbortV(char const*, char const*, std::__va_list)+108) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.937 20026 20026 F DEBUG : #06 pc 00000000005a1a88 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::AbortF(char const*, ...) (.__uniq.99033978352804627313491551960229047428)+140) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.937 20026 20026 F DEBUG : #07 pc 000000000059b970 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::(anonymous namespace)::JniValueType*) (.__uniq.99033978352804627313491551960229047428)+1412) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.937 20026 20026 F DEBUG : #08 pc 0000000000597784 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::CheckJNI::NewRef(char const*, _JNIEnv*, _jobject*, art::IndirectRefKind) (.__uniq.99033978352804627313491551960229047428)+196) (BuildId: 4ccb65ae9ac5ad5da3af5a342d5b0b92) 04-25 15:27:37.937 20026 20026 F DEBUG : #09 pc 00000000001aa580 /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.937 20026 20026 F DEBUG : #10 pc 00000000001a8d38 /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.937 20026 20026 F DEBUG : #11 pc 000000000019e068 /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.937 20026 20026 F DEBUG : #12 pc 000000000019bcec /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.937 20026 20026 F DEBUG : #13 pc 0000000000258434 /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (mono_runtime_invoke_checked+140) (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.937 20026 20026 F DEBUG : #14 pc 000000000026f19c /data/app/~~rrsXQYRBNR6mJq3O2vECbA==/Mono.Android.NET_Tests-5qYa1AD9g6qP-Hr2BrRRnQ==/lib/arm64/libmonosgen-2.0.so (BuildId: dd7058e39c565cec8c5368d64fb956bacc2f52ad) 04-25 15:27:37.937 20026 20026 F DEBUG : #15 pc 0000000000070374 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+180) (BuildId: 3cad38c74be79004f5b2927be886e077) 04-25 15:27:37.937 20026 20026 F DEBUG : #16 pc 00000000000620a0 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: 3cad38c74be79004f5b2927be886e077) *Fortunately*, `adb logcat` output contains the *thread ID*, which shows that it's from thread 20022. What else is from thread 20022? 04-25 15:27:37.527 19954 20022 I DOTNET : # t1 iter: 35 It's `t1` in `ConversionsAndThreadsAndInstanceMappingsOhMy()`, which calls `JNIEnv.CopyObjectArray()`, which uses `JavaConvert.FromJniHandle()`: var t1 = new Thread (() => { int[] output_array1 = new int[1]; for (int i = 0; i < 2000; ++i) { Console.WriteLine ("# t1 iter: {0}", i); try { JNIEnv.CopyObjectArray (grefJliArray, output_array1); } catch (Exception e) { ignore_t1 = e; break; } } }); Time to start investigating `JNIEnv.CopyObjectArray()` and `JavaConvert.FromJniHandle()`… --- src/Mono.Android/Java.Interop/TypeManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 95601b0e76b..d3e5f8be68c 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -386,13 +386,15 @@ internal static object CreateProxy ( BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var c = type.GetConstructor (flags, null, XAConstructorSignature, null); if (c != null) { - return c.Invoke (new object [] { handle, transfer }); + Console.WriteLine ($"# jonp: CreateProxy: before invoking c={c.DeclaringType.FullName}::{c}"); + c.Invoke (peer, new object[] { handle, transfer }); + return peer; } c = type.GetConstructor (flags, null, JIConstructorSignature, null); if (c != null) { JniObjectReference r = new JniObjectReference (handle); JniObjectReferenceOptions o = JniObjectReferenceOptions.Copy; - peer = (IJavaPeerable) c.Invoke (new object [] { r, o }); + c.Invoke (peer, new object [] { r, o }); JNIEnv.DeleteRef (handle, transfer); return peer; } From 75bde5ae394849a43789869a0c88833331447d74 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 28 Apr 2025 13:14:17 -0400 Subject: [PATCH 10/13] SPOOKY ACTION AT A DISTANCE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: 5c23bcda82f1362c8ddd21616b083c4c093bb096 As per 1cb72eb0, we're looking at a JNI error: JNI DETECTED ERROR IN APPLICATION: JNI ERROR (app bug): jobject is an invalid global reference: 0x430a (deleted reference at index 536) in call to NewLocalRef The thread using the invalid global reference is `t1` in `ConversionsAndThreadsAndInstanceMappingsOhMy()`, which uses `JNIEnv.CopyObjectArray(IntPtr, int[])`, which in turn uses `JavaConvert.FromJniHandle()`. Why would that be failing? A plausible answer: consider `JavaConvert.JniHandleConverters`: partial class JavaConvert { static Dictionary> JniHandleConverters = new() { { typeof (int), (handle, transfer) => { using (var value = new Java.Lang.Integer (handle, transfer | JniHandleOwnership.DoNotRegister)) return value.IntValue (); } }, } } Note that we're invoking the `Integer(IntPtr, JniHandleOwnership)` constructor, ensuring that it has `JniHandleOwnership.DoNotRegister`. This *prevents* the instance from being placed into the instance mapping, i.e. `Object.PeekObject()` *will not find the instance*. Next, turn to `Object.SetHandle()` from 5c23bcda: partial class /* Java.Lang. */ Object { protected void SetHandle (IntPtr value, JniHandleOwnership transfer) { var reference = new JniObjectReference (value); JNIEnvInit.ValueManager?.ConstructPeer ( this, ref reference, value == IntPtr.Zero ? JniObjectReferenceOptions.None : JniObjectReferenceOptions.Copy); JNIEnv.DeleteRef (value, transfer); } } What's missing? There is no check for `JniHandleOwnership.DoNotRegister`! Which means that the `new Integer(…)` will be in the instance map, *and replace any existing mappings*, only to be subsequently disposed. This permits the following "total execution order" to happen between threads `t1` and `t2` in `ConversionsAndThreadsAndInstanceMappingsOhMy()`: setup: JNI equivalent to Java `Object[] array = new Object[]{new Integer(1)}; t0: a = new Integer(handle, TransferLocalRef); // via `Object.GetObject()`, via NativeArrayElementToManaged; creates instance map t1: b = new Integer(handle, TransferLocalRef | DoNotRegister) // via `JNIEnv.CopyObjectArray()`, *replaces* instance mapping t2: c = Object.GetObject(handle) // via `JNIEnv.GetArray` -> `JNIEnv.CopyArray` -> `NativeArrayElementToManaged` -> `Object.GetObject()` // finds `b`, not `a`! t1: `b.Dispose()` begins unregistering `b` t2: `c.IntValue()` *boom* "*boom*" can be the previously observed `ObjectDisposedException`, or can be the JNI crash, depending on where things are between t1 and t2. The fix is to correct the oversight from 5c23bcda, and forward the `DoNotRegister` information. --- src/Mono.Android/Java.Lang/Object.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Mono.Android/Java.Lang/Object.cs b/src/Mono.Android/Java.Lang/Object.cs index 9e58768d9ee..58a7e54d88b 100644 --- a/src/Mono.Android/Java.Lang/Object.cs +++ b/src/Mono.Android/Java.Lang/Object.cs @@ -110,13 +110,22 @@ protected override void Dispose (bool disposing) protected void SetHandle (IntPtr value, JniHandleOwnership transfer) { var reference = new JniObjectReference (value); + var options = FromJniHandleOwnership (transfer); JNIEnvInit.ValueManager?.ConstructPeer ( this, ref reference, - value == IntPtr.Zero ? JniObjectReferenceOptions.None : JniObjectReferenceOptions.Copy); + value == IntPtr.Zero ? JniObjectReferenceOptions.None : options); JNIEnv.DeleteRef (value, transfer); } + static JniObjectReferenceOptions FromJniHandleOwnership (JniHandleOwnership transfer) + { + var options = JniObjectReferenceOptions.Copy; + if (transfer.HasFlag (JniHandleOwnership.DoNotRegister)) + options |= JniObjectReferenceOptions.CopyAndDoNotRegister; + return options; + } + internal static IJavaPeerable? PeekObject (IntPtr handle, Type? requiredType = null) { var peeked = JNIEnvInit.ValueManager?.PeekPeer (new JniObjectReference (handle)); From a805f6d75c4c6940b57462da574f472a5df57333 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 29 Apr 2025 13:39:22 -0400 Subject: [PATCH 11/13] Cleanup Remove printfs --- src/Mono.Android/Android.Runtime/JNIEnv.cs | 1 - src/Mono.Android/Java.Interop/TypeManager.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 65f94b34360..647acbb5108 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -1070,7 +1070,6 @@ static int _GetArrayLength (IntPtr array_ptr) ret [i] = targetType == null || targetType.IsInstanceOfType (value) ? value : Convert.ChangeType (value, targetType, CultureInfo.InvariantCulture); - GC.KeepAlive (value); } return ret; diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index d3e5f8be68c..adc07f0edb1 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -386,7 +386,6 @@ internal static object CreateProxy ( BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var c = type.GetConstructor (flags, null, XAConstructorSignature, null); if (c != null) { - Console.WriteLine ($"# jonp: CreateProxy: before invoking c={c.DeclaringType.FullName}::{c}"); c.Invoke (peer, new object[] { handle, transfer }); return peer; } @@ -398,6 +397,7 @@ internal static object CreateProxy ( JNIEnv.DeleteRef (handle, transfer); return peer; } + GC.SuppressFinalize (peer); throw new MissingMethodException ( "No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)", CreateJavaLocationException ()); From c864fea35aab6a1144f61e96a7fff7a8be33f32e Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 29 Apr 2025 15:42:25 -0400 Subject: [PATCH 12/13] Re-enable JnienvCreateInstance_RegistersMultipleInstances() on CoreCLR and NativeAOT. These should be fixed by dotnet/java-interop/main@d3d3a1bf. --- .../Mono.Android-Tests/Java.Lang/ObjectTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs index 875641156d2..e6a051b170a 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs @@ -66,8 +66,6 @@ static MethodInfo MakeGenericMethod (MethodInfo method, Type type) => } [Test] - [Category ("CoreCLRIgnore")] //TODO: https://github.com/dotnet/android/issues/10069 - [Category ("NativeAOTIgnore")] //TODO: https://github.com/dotnet/android/issues/10079 public void JnienvCreateInstance_RegistersMultipleInstances () { using (var adapter = new CreateInstance_OverrideAbsListView_Adapter (Application.Context)) { From 56eb99fdba01cf5a5266803046692c3c92a3a35b Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 29 Apr 2025 18:53:13 -0400 Subject: [PATCH 13/13] Revert "Re-enable JnienvCreateInstance_RegistersMultipleInstances()" This reverts commit c864fea35aab6a1144f61e96a7fff7a8be33f32e. Looks like `JnienvCreateInstance_RegistersMultipleInstances()` is NOT fixed. I'll need to investigate later. --- .../Mono.Android-Tests/Java.Lang/ObjectTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs index e6a051b170a..875641156d2 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs @@ -66,6 +66,8 @@ static MethodInfo MakeGenericMethod (MethodInfo method, Type type) => } [Test] + [Category ("CoreCLRIgnore")] //TODO: https://github.com/dotnet/android/issues/10069 + [Category ("NativeAOTIgnore")] //TODO: https://github.com/dotnet/android/issues/10079 public void JnienvCreateInstance_RegistersMultipleInstances () { using (var adapter = new CreateInstance_OverrideAbsListView_Adapter (Application.Context)) {