diff --git a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs index cd487e8e3e4..35c8a951877 100644 --- a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs +++ b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs @@ -105,6 +105,7 @@ public static void RaiseThrowable (Java.Lang.Throwable throwable) if (throwable == null) throw new ArgumentNullException ("throwable"); JNIEnv.Throw (throwable.Handle); + GC.KeepAlive (throwable); } internal static void UnhandledException (Exception e) diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 2b014fe3a49..681fbbfb87d 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -506,9 +506,9 @@ public static IntPtr ToLocalJniHandle (IJavaObject? value) if (value == null) return IntPtr.Zero; var ex = value as IJavaObjectEx; - if (ex != null) - return ex.ToLocalJniHandle (); - return NewLocalRef (value.Handle); + IntPtr result = ex != null ? ex.ToLocalJniHandle () : NewLocalRef (value.Handle); + GC.KeepAlive (value); + return result; } public static string? GetCharSequence (IntPtr jobject, JniHandleOwnership transfer) @@ -895,6 +895,7 @@ public static void CopyArray (IJavaObject[] src, IntPtr dest) for (int i = 0; i < src.Length; i++) { IJavaObject o = src [i]; JniEnvironment.Arrays.SetObjectArrayElement (new JniObjectReference (dest), i, new JniObjectReference (o == null ? IntPtr.Zero : o.Handle)); + GC.KeepAlive (o); } } @@ -1545,6 +1546,7 @@ static IntPtr NewArray (Array value, Type elementType, IntPtr elementClass) } }, { typeof (IJavaObject), (dest, index, value) => { SetObjectArrayElement (dest, index, value == null ? IntPtr.Zero : ((IJavaObject) value).Handle); + GC.KeepAlive (value); } }, { typeof (Array), (dest, index, value) => { IntPtr _v = NewArray ((Array) value!); diff --git a/src/Mono.Android/Android.Runtime/JavaObject.cs b/src/Mono.Android/Android.Runtime/JavaObject.cs index ec968131f99..e1ddbbecae3 100644 --- a/src/Mono.Android/Android.Runtime/JavaObject.cs +++ b/src/Mono.Android/Android.Runtime/JavaObject.cs @@ -11,29 +11,7 @@ public static IntPtr GetHandle (object obj) if (obj == null) return IntPtr.Zero; - Type type = obj.GetType (); - if (type == typeof (bool)) - return new Java.Lang.Boolean ((bool)obj).Handle; - else if (type == typeof (sbyte)) - return new Java.Lang.Byte ((sbyte)obj).Handle; - else if (type == typeof (char)) - return new Java.Lang.Character ((char)obj).Handle; - else if (type == typeof (short)) - return new Java.Lang.Short ((short)obj).Handle; - else if (type == typeof (int)) - return new Java.Lang.Integer ((int)obj).Handle; - else if (type == typeof (long)) - return new Java.Lang.Long ((long)obj).Handle; - else if (type == typeof (float)) - return new Java.Lang.Float ((float)obj).Handle; - else if (type == typeof (double)) - return new Java.Lang.Double ((double)obj).Handle; - else if (type == typeof (string)) - return JNIEnv.NewString ((string)obj); - else if (typeof (IJavaObject).IsAssignableFrom (type)) - return ((IJavaObject)obj).Handle; - else - return new JavaObject (obj).Handle; + return Java.Interop.JavaConvert.ToLocalJniHandle (obj); } public static object? GetObject (IntPtr handle, JniHandleOwnership transfer) @@ -112,4 +90,3 @@ public override string ToString () } } } - diff --git a/src/Mono.Android/Android.Runtime/XmlReaderPullParser.cs b/src/Mono.Android/Android.Runtime/XmlReaderPullParser.cs index bcf54a12161..d3b9a53439d 100644 --- a/src/Mono.Android/Android.Runtime/XmlReaderPullParser.cs +++ b/src/Mono.Android/Android.Runtime/XmlReaderPullParser.cs @@ -15,8 +15,11 @@ public class XmlReaderResourceParser : XmlReaderPullParser, IXmlResourceParser return IntPtr.Zero; var xpr = value as XmlResourceParserReader; - if (xpr != null) - return JNIEnv.NewLocalRef (xpr.Handle); + if (xpr != null) { + IntPtr result = JNIEnv.NewLocalRef (xpr.Handle); + GC.KeepAlive (xpr); + return result; + } return JNIEnv.ToLocalJniHandle (new Android.Runtime.XmlReaderResourceParser (value)); } @@ -130,8 +133,11 @@ public static IntPtr ToLocalJniHandle (XmlReader? value) return IntPtr.Zero; var xppr = value as XmlPullParserReader; - if (xppr != null) - return JNIEnv.NewLocalRef (xppr.Handle); + if (xppr != null) { + IntPtr result = JNIEnv.NewLocalRef (xppr.Handle); + GC.KeepAlive (xppr); + return result; + } return JNIEnv.ToLocalJniHandle (new Android.Runtime.XmlReaderPullParser (value)); } diff --git a/src/Mono.Android/Android.Widget/AbsListView.cs b/src/Mono.Android/Android.Widget/AbsListView.cs index dbacdcccc2e..c989aec88dc 100644 --- a/src/Mono.Android/Android.Widget/AbsListView.cs +++ b/src/Mono.Android/Android.Widget/AbsListView.cs @@ -79,6 +79,8 @@ public virtual void SetAdapter (Android.Widget.IListAdapter adapter) JNIEnv.CallVoidMethod (Handle, id_setAdapter_Landroid_widget_ListAdapter_, new JValue (adapter)); else JNIEnv.CallNonvirtualVoidMethod (Handle, ThresholdClass, id_setAdapter_Landroid_widget_ListAdapter_, new JValue (adapter)); + GC.KeepAlive (adapter); + GC.KeepAlive (this); } #endif } diff --git a/src/Mono.Android/Android.Widget/AdapterView.cs b/src/Mono.Android/Android.Widget/AdapterView.cs index ec7948632d3..393f81aebbf 100644 --- a/src/Mono.Android/Android.Widget/AdapterView.cs +++ b/src/Mono.Android/Android.Widget/AdapterView.cs @@ -81,6 +81,7 @@ public AdapterView (Android.Content.Context context) JniHandleOwnership.TransferLocalRef); JNIEnv.FinishCreateInstance (Handle, "(Landroid/content/Context;)V", new JValue (context)); } + GC.KeepAlive (context); } static IntPtr id_ctor_Landroid_content_Context_Landroid_util_AttributeSet_; @@ -104,6 +105,8 @@ public AdapterView (Android.Content.Context context, Android.Util.IAttributeSet JniHandleOwnership.TransferLocalRef); JNIEnv.FinishCreateInstance (Handle, "(Landroid/content/Context;Landroid/util/AttributeSet;)V", new JValue (context), new JValue (attrs)); } + GC.KeepAlive (context); + GC.KeepAlive (attrs); } static IntPtr id_ctor_Landroid_content_Context_Landroid_util_AttributeSet_I; @@ -127,6 +130,8 @@ public AdapterView (Android.Content.Context context, Android.Util.IAttributeSet JniHandleOwnership.TransferLocalRef); JNIEnv.FinishCreateInstance (Handle, "(Landroid/content/Context;Landroid/util/AttributeSet;I)V", new JValue (context), new JValue (attrs), new JValue (defStyle)); } + GC.KeepAlive (context); + GC.KeepAlive (attrs); } protected override Java.Lang.Object? RawAdapter { @@ -140,4 +145,3 @@ public abstract T Adapter { } } } - diff --git a/src/Mono.Android/Android.Widget/AdapterViewAnimator.cs b/src/Mono.Android/Android.Widget/AdapterViewAnimator.cs index 3bc26268995..26670405146 100644 --- a/src/Mono.Android/Android.Widget/AdapterViewAnimator.cs +++ b/src/Mono.Android/Android.Widget/AdapterViewAnimator.cs @@ -86,15 +86,18 @@ public Android.Widget.IAdapter? Adapter { get { if (id_getAdapter == IntPtr.Zero) id_getAdapter = JNIEnv.GetMethodID (class_ref, "getAdapter", "()Landroid/widget/Adapter;"); + Android.Widget.IAdapter? result; if (GetType () == ThresholdType) - return Java.Lang.Object.GetObject (JNIEnv.CallObjectMethod (Handle, id_getAdapter), JniHandleOwnership.TransferLocalRef); + result = Java.Lang.Object.GetObject (JNIEnv.CallObjectMethod (Handle, id_getAdapter), JniHandleOwnership.TransferLocalRef); else - return Java.Lang.Object.GetObject ( + result = Java.Lang.Object.GetObject ( JNIEnv.CallNonvirtualObjectMethod ( Handle, ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, "getAdapter", "()Landroid/widget/Adapter;")), JniHandleOwnership.TransferLocalRef); + GC.KeepAlive (this); + return result; } set { if (id_setAdapter_Landroid_widget_Adapter_ == IntPtr.Zero) @@ -108,6 +111,8 @@ public Android.Widget.IAdapter? Adapter { ThresholdClass, JNIEnv.GetMethodID (ThresholdClass, "setAdapter", "(Landroid/widget/Adapter;)V"), new JValue (JNIEnv.ToJniHandle ((IJavaObject?) value))); + GC.KeepAlive (value); + GC.KeepAlive (this); } } diff --git a/src/Mono.Android/Android.Widget/ArrayAdapter.cs b/src/Mono.Android/Android.Widget/ArrayAdapter.cs index 134ea2ab2b0..d59c1e126b7 100644 --- a/src/Mono.Android/Android.Widget/ArrayAdapter.cs +++ b/src/Mono.Android/Android.Widget/ArrayAdapter.cs @@ -39,6 +39,7 @@ public ArrayAdapter (Android.Content.Context context, int textViewResourceId) JniHandleOwnership.TransferLocalRef); JNIEnv.FinishCreateInstance (Handle, "(Landroid/content/Context;I)V", new JValue (context), new JValue (textViewResourceId)); } + GC.KeepAlive (context); } static IntPtr id_ctor_Landroid_content_Context_II; @@ -62,6 +63,7 @@ public ArrayAdapter (Android.Content.Context context, int resource, int textView JniHandleOwnership.TransferLocalRef); JNIEnv.FinishCreateInstance (Handle, "(Landroid/content/Context;II)V", new JValue (context), new JValue (resource), new JValue (textViewResourceId)); } + GC.KeepAlive (context); } static IntPtr id_ctor_Landroid_content_Context_IarrayLjava_lang_Object_; @@ -87,6 +89,7 @@ public ArrayAdapter (Android.Content.Context context, int textViewResourceId, T[ JNIEnv.FinishCreateInstance (Handle, "(Landroid/content/Context;I[Ljava/lang/Object;)V", new JValue (context), new JValue (textViewResourceId), new JValue (native_objects)); } JNIEnv.DeleteLocalRef (native_objects); + GC.KeepAlive (context); } static IntPtr id_ctor_Landroid_content_Context_IIarrayLjava_lang_Object_; @@ -112,6 +115,7 @@ public ArrayAdapter (Android.Content.Context context, int resource, int textView JNIEnv.FinishCreateInstance (Handle, "(Landroid/content/Context;II[Ljava/lang/Object;)V", new JValue (context), new JValue (resource), new JValue (textViewResourceId), new JValue (native_objects)); } JNIEnv.DeleteLocalRef (native_objects); + GC.KeepAlive (context); } static IntPtr id_ctor_Landroid_content_Context_ILjava_util_List_; @@ -137,6 +141,7 @@ public ArrayAdapter (Android.Content.Context context, int textViewResourceId, Sy JNIEnv.FinishCreateInstance (Handle, "(Landroid/content/Context;ILjava/util/List;)V", new JValue (context), new JValue (textViewResourceId), new JValue (lrefObjects)); } JNIEnv.DeleteLocalRef (lrefObjects); + GC.KeepAlive (context); } static IntPtr id_ctor_Landroid_content_Context_IILjava_util_List_; @@ -162,6 +167,7 @@ public ArrayAdapter (Android.Content.Context context, int resource, int textView JNIEnv.FinishCreateInstance (Handle, "(Landroid/content/Context;IILjava/util/List;)V", new JValue (context), new JValue (resource), new JValue (textViewResourceId), new JValue (lrefObjects)); } JNIEnv.DeleteLocalRef (lrefObjects); + GC.KeepAlive (context); } static IntPtr id_add_Ljava_lang_Object_; @@ -182,9 +188,11 @@ public void Add (T @object) { if (id_createFromResource_Landroid_content_Context_II == IntPtr.Zero) id_createFromResource_Landroid_content_Context_II = JNIEnv.GetStaticMethodID (class_ref, "createFromResource", "(Landroid/content/Context;II)Landroid/widget/ArrayAdapter;"); - return JavaConvert.FromJniHandle> ( + var result = JavaConvert.FromJniHandle> ( JNIEnv.CallStaticObjectMethod (class_ref, id_createFromResource_Landroid_content_Context_II, new JValue (context), new JValue (textArrayResId), new JValue (textViewResId)), - JniHandleOwnership.TransferLocalRef)!; + JniHandleOwnership.TransferLocalRef); + GC.KeepAlive (context); + return result ?? throw new InvalidOperationException ("Unable to marshal the return value to an Android.Widget.ArrayAdapter instance."); } static IntPtr id_getItem_I; @@ -239,6 +247,8 @@ public void Remove (T @object) if (id_sort_Ljava_util_Comparator_ == IntPtr.Zero) id_sort_Ljava_util_Comparator_ = JNIEnv.GetMethodID (class_ref, "sort", "(Ljava/util/Comparator;)V"); JNIEnv.CallNonvirtualVoidMethod (Handle, class_ref, id_sort_Ljava_util_Comparator_, new JValue (comparator)); + GC.KeepAlive (comparator); + GC.KeepAlive (this); } } } diff --git a/src/Mono.Android/Android.Widget/TextView.cs b/src/Mono.Android/Android.Widget/TextView.cs index cef5e050088..5465af9f4b4 100644 --- a/src/Mono.Android/Android.Widget/TextView.cs +++ b/src/Mono.Android/Android.Widget/TextView.cs @@ -13,6 +13,8 @@ void AddTextChangedListener (TextWatcherImplementor watcher) if (id_addTextChangedListener == IntPtr.Zero) id_addTextChangedListener = JNIEnv.GetMethodID (class_ref, "addTextChangedListener", "(Landroid/text/TextWatcher;)V"); JNIEnv.CallVoidMethod (Handle, id_addTextChangedListener, new JValue (watcher)); + GC.KeepAlive (watcher); + GC.KeepAlive (this); } WeakReference? implementor_TextWatcher; @@ -69,4 +71,3 @@ public event EventHandler TextChanged { } } } - diff --git a/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs b/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs index a992e187779..dd154087d0e 100644 --- a/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs +++ b/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs @@ -81,7 +81,9 @@ internal static TResult? _JavaCast< if (instance.Handle == IntPtr.Zero) throw new ObjectDisposedException (instance.GetType ().FullName); - return (TResult?) Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer, typeof (TResult)) ?? + var result = (TResult?) Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer, typeof (TResult)); + GC.KeepAlive (instance); + return result ?? throw new InvalidCastException ( FormattableString.Invariant ($"Unable to convert instance of type '{instance.GetType ().FullName}' to type '{typeof (TResult).FullName}'.")); } @@ -100,7 +102,9 @@ internal static TResult? _JavaCast< if (resultType.IsAssignableFrom (instance.GetType ())) return instance; - return (IJavaObject?) Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer, resultType) ?? + var result = (IJavaObject?) Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer, resultType); + GC.KeepAlive (instance); + return result ?? throw new InvalidCastException ( FormattableString.Invariant ($"Unable to convert instance of type '{instance.GetType ().FullName}' to type '{resultType.FullName}'.")); } diff --git a/src/Mono.Android/Java.Interop/Runtime.cs b/src/Mono.Android/Java.Interop/Runtime.cs index 314815a9edb..71e7d018163 100644 --- a/src/Mono.Android/Java.Interop/Runtime.cs +++ b/src/Mono.Android/Java.Interop/Runtime.cs @@ -40,7 +40,9 @@ public static bool IsGCUserPeer (IJavaObject value) { if (value == null) return false; - return IsGCUserPeer (value.Handle); + bool result = IsGCUserPeer (value.Handle); + GC.KeepAlive (value); + return result; } public static bool IsGCUserPeer (IntPtr value) diff --git a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs index 08e8e25dffc..67183644780 100644 --- a/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs +++ b/src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs @@ -1746,6 +1746,7 @@ KeyStore GetConfiguredKeyStoreInstance () var keyFactory = KeyFactory.GetInstance (algorithmName) ?? throw new InvalidOperationException ($"Failed to get the KeyFactory instance for algorithm {algorithmName}"); var privateKey = keyFactory.GeneratePrivate (new Java.Security.Spec.PKCS8EncodedKeySpec (key.ExportPkcs8PrivateKey ())); var certificate = Java.Lang.Object.GetObject (clientCertificate.Handle, JniHandleOwnership.DoNotTransfer); + GC.KeepAlive (clientCertificate); if (privateKey is null || certificate is null) { return null; diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs index 917710ab797..409a03ec1f4 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs @@ -506,6 +506,42 @@ public void ManagedToJavaTypeMapping () Assert.AreEqual (null, m, "`JnienvTest` does *not* subclass Java.Lang.Object, it should *not* be in the typemap!"); } + [Test] + [Category ("GCBridge")] + [Category ("NativeAOTIgnore")] + public void ToLocalJniHandleSurvivesConcurrentCollection () + { + const int iterations = 1000; + using var done = new CancellationTokenSource (); + var collector = new Thread (() => { + while (!done.IsCancellationRequested) { + GC.Collect (); + GC.WaitForPendingFinalizers (); + Thread.Yield (); + } + }); + collector.Start (); + + try { + for (int i = 0; i < iterations; i++) { + IntPtr handle = JNIEnv.ToLocalJniHandle (new FinalizableHandleOwner ()); + try { + Assert.AreNotEqual (IntPtr.Zero, handle, $"No local reference was returned during iteration {i}."); + Assert.IsNotEmpty (JNIEnv.GetClassNameFromInstance (handle), $"The local reference was invalid during iteration {i}."); + } finally { + JNIEnv.DeleteLocalRef (handle); + } + } + } finally { + done.Cancel (); + collector.Join (); + } + } + + sealed class FinalizableHandleOwner : Java.Lang.Object + { + } + [Test, Category ("GCBridge")] [Ignore ("Failing in NativeAOT: https://github.com/dotnet/android/issues/11690")] public void DoNotLeakWeakReferences ()