From 5530ef4aa87835a4c4f0d7b9c4d8e3cd64280e46 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Apr 2026 14:04:29 -0500 Subject: [PATCH 1/8] [Mono.Android] fix global ref leak in TypeManager.Activate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/dotnet/android/issues/11101 Fixes: https://github.com/dotnet/android/issues/10989 Context: https://github.com/dotnet/android/commit/5c23bcda8 (PR #9640) The Java.Interop Unification (5c23bcda8) changed `Object` to extend `JavaObject`, introducing an additional `ConstructPeer` call in the constructor chain. `TypeManager.Activate` was updated to use `SetPeerReference` but never set the `Activatable` state flag. Without it, each `ConstructPeer` call in the constructor chain (`JavaObject()` and `Object.SetHandle()`) creates a new JNI global ref, overwriting the previous one without deleting it — leaking 3 global refs per `LayoutInflater.Inflate` call. Before the fix, the new test fails with: Global reference leak detected: 30 extra global refs after inflating/GC'ing 10 custom views. Before=207, After=237 This went unnoticed because the `Activate` path is only triggered when Java creates .NET objects (not the other way around). The two main scenarios are Activity recreation and custom C# views in Android XML layouts. Most developers use .NET MAUI, which has a single Activity and does not use custom C# views in Android layout XML files, so neither scenario was commonly hit. Changes: - Promote `GetUninitializedObject` from a local function in `CreateProxy` to a shared static method. It sets `Activatable | Replaceable`, which tells `ConstructPeer` to return early and not create duplicate global refs. - Have `Activate` call `GetUninitializedObject` then `ConstructPeer` to create one global ref while `jobject` is still a valid JNI local ref. The `Activatable` flag then prevents duplicates during the constructor chain. - Add regression test that inflates custom views and asserts JNI global reference count does not grow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Mono.Android/Java.Interop/TypeManager.cs | 28 ++++++------- .../Android.Widget/CustomWidgetTests.cs | 40 ++++++++++++++++++- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index cc0b22936bd..528534a4b13 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -181,12 +181,12 @@ static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPt internal static void Activate (IntPtr jobject, ConstructorInfo cinfo, object? []? parms) { try { - var newobj = RuntimeHelpers.GetUninitializedObject (cinfo.DeclaringType!); - if (newobj is IJavaPeerable peer) { - peer.SetPeerReference (new JniObjectReference (jobject)); - } else { - throw new InvalidOperationException ($"Unsupported type: '{newobj}'"); - } + var newobj = GetUninitializedObject (cinfo.DeclaringType!); + var reference = new JniObjectReference (jobject); + JniEnvironment.Runtime.ValueManager.ConstructPeer ( + newobj, + ref reference, + JniObjectReferenceOptions.Copy); cinfo.Invoke (newobj, parms); } catch (Exception e) { var m = FormattableString.Invariant ( @@ -425,15 +425,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; - } + 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/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs index 78d62576a0e..98893bc9f20 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs @@ -1,4 +1,5 @@ -using Android.App; +using System; +using Android.App; using Android.Content; using Android.Util; using Android.Views; @@ -44,6 +45,43 @@ public void UpperAndLowerCaseCustomWidget_FromLibrary_ShouldNotThrowInflateExcep inflater.Inflate (Resource.Layout.upper_lower_custom, null); }, "Regression test for widgets with uppercase and lowercase namespace (bug #23880) failed."); } + + // https://github.com/dotnet/android/issues/11101 + [Test] + public void InflateCustomView_ShouldNotLeakGlobalRefs () + { + var inflater = (LayoutInflater) Application.Context.GetSystemService (Context.LayoutInflaterService)!; + + // Warm up: inflate once to ensure all caches and type mappings are populated + inflater.Inflate (Resource.Layout.lowercase_custom, null); + + CollectGarbage (times: 3); + + int grefBefore = Java.Interop.Runtime.GlobalReferenceCount; + + for (int i = 0; i < 10; i++) { + inflater.Inflate (Resource.Layout.lowercase_custom, null); + } + + CollectGarbage (times: 3); + + int grefAfter = Java.Interop.Runtime.GlobalReferenceCount; + int delta = grefAfter - grefBefore; + + // Each inflate creates a LinearLayout + CustomButton via TypeManager.Activate. + // If global refs are leaking during activation, delta will be >= 10. + // Allow a small delta for noise (cached objects, etc.) + Assert.IsTrue (delta <= 5, + $"Global reference leak detected: {delta} extra global refs after inflating/GC'ing 10 custom views. Before={grefBefore}, After={grefAfter}"); + + static void CollectGarbage (int times) + { + for (int i = 0; i < times; i++) { + GC.Collect (); + GC.WaitForPendingFinalizers (); + } + } + } } public class CustomButton : Button From 17c104cf1a67466ebf033387a95c092358c2085e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Apr 2026 17:02:40 -0500 Subject: [PATCH 2/8] Address PR feedback: use Assert.IsNotNull instead of ! Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs index 98893bc9f20..bcefa10a8c7 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs @@ -50,7 +50,8 @@ public void UpperAndLowerCaseCustomWidget_FromLibrary_ShouldNotThrowInflateExcep [Test] public void InflateCustomView_ShouldNotLeakGlobalRefs () { - var inflater = (LayoutInflater) Application.Context.GetSystemService (Context.LayoutInflaterService)!; + var inflater = (LayoutInflater) Application.Context.GetSystemService (Context.LayoutInflaterService); + Assert.IsNotNull (inflater); // Warm up: inflate once to ensure all caches and type mappings are populated inflater.Inflate (Resource.Layout.lowercase_custom, null); From 20779467108f8660090fbe0f2273eaaa8af37fb1 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Apr 2026 11:02:12 +0200 Subject: [PATCH 3/8] Fix NativeAOT inherited activation leak\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Generator/TypeMapAssemblyEmitter.cs | 28 +++++++++++++++++++ .../Java.Interop/JavaPeerProxy.cs | 11 ++++++++ .../TypeMapAssemblyGeneratorTests.cs | 2 ++ 3 files changed, 41 insertions(+) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 70521a1473f..42d9120c68a 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -68,6 +68,7 @@ sealed class TypeMapAssemblyEmitter AssemblyReferenceHandle _javaInteropRef; + TypeReferenceHandle _javaPeerProxyBaseRef; TypeReferenceHandle _javaPeerProxyRef; TypeReferenceHandle _iJavaPeerableRef; TypeReferenceHandle _jniHandleOwnershipRef; @@ -83,6 +84,7 @@ sealed class TypeMapAssemblyEmitter MemberReferenceHandle _getTypeFromHandleRef; MemberReferenceHandle _getUninitializedObjectRef; + MemberReferenceHandle _constructActivatedPeerRef; MemberReferenceHandle _notSupportedExceptionCtorRef; MemberReferenceHandle _jniObjectReferenceCtorRef; MemberReferenceHandle _jniEnvDeleteRefRef; @@ -163,6 +165,8 @@ void EmitCore (TypeMapAssemblyData model) void EmitTypeReferences () { var metadata = _pe.Metadata; + _javaPeerProxyBaseRef = metadata.AddTypeReference (_pe.MonoAndroidRef, + metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy")); _javaPeerProxyRef = metadata.AddTypeReference (_pe.MonoAndroidRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JavaPeerProxy`1")); _iJavaPeerableRef = metadata.AddTypeReference (_javaInteropRef, @@ -213,6 +217,14 @@ void EmitMemberReferences () rt => rt.Type ().Object (), p => p.AddParameter ().Type ().Type (_systemTypeRef, false))); + _constructActivatedPeerRef = _pe.AddMemberRef (_javaPeerProxyBaseRef, "ConstructActivatedPeer", + sig => sig.MethodSignature (isInstanceMethod: false).Parameters (2, + rt => rt.Void (), + p => { + p.AddParameter ().Type ().Type (_iJavaPeerableRef, false); + p.AddParameter ().Type ().IntPtr (); + })); + _notSupportedExceptionCtorRef = _pe.AddMemberRef (_notSupportedExceptionRef, ".ctor", sig => sig.MethodSignature (isInstanceMethod: true).Parameters (1, rt => rt.Void (), @@ -497,6 +509,10 @@ void EmitCreateInstanceInheritedCtor (EntityHandle targetTypeRef, ActivationCtor encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); + encoder.OpCode (ILOpCode.Dup); + encoder.OpCode (ILOpCode.Ldarg_1); + encoder.Call (_constructActivatedPeerRef); + encoder.OpCode (ILOpCode.Dup); encoder.OpCode (ILOpCode.Ldarg_1); encoder.OpCode (ILOpCode.Ldarg_2); @@ -563,6 +579,10 @@ void EmitCreateInstanceInheritedJavaInteropCtor (EntityHandle targetTypeRef, Act encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); + encoder.OpCode (ILOpCode.Dup); + encoder.OpCode (ILOpCode.Ldarg_1); // handle + encoder.Call (_constructActivatedPeerRef); + // dup obj (one copy for the call, one for the return) encoder.OpCode (ILOpCode.Dup); @@ -746,6 +766,10 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy encoder.Call (_getUninitializedObjectRef); encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); + + encoder.OpCode (ILOpCode.Dup); + encoder.LoadArgument (1); // self + encoder.Call (_constructActivatedPeerRef); } encoder.LoadLocalAddress (0); @@ -796,6 +820,10 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); + encoder.OpCode (ILOpCode.Dup); + encoder.LoadArgument (1); // self + encoder.Call (_constructActivatedPeerRef); + encoder.LoadArgument (1); // self encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer encoder.Call (ctorRef); diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index c4309d78f85..95327dba246 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -61,6 +61,17 @@ protected JavaPeerProxy ( /// /// A factory for creating containers of the target type, or null if not supported. public virtual JavaPeerContainerFactory? GetContainerFactory () => null; + + protected static void ConstructActivatedPeer (IJavaPeerable peer, IntPtr handle) + { + if (peer == null) + throw new ArgumentNullException (nameof (peer)); + + peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + + var reference = new JniObjectReference (handle); + JniEnvironment.Runtime.ValueManager.ConstructPeer (peer, ref reference, JniObjectReferenceOptions.Copy); + } } /// diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index a99d95bfca0..0d937ffff30 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -191,6 +191,7 @@ public void Generate_SimpleActivity_UsesGetUninitializedObject () var memberNames = GetMemberRefNames (reader); Assert.DoesNotContain ("CreateManagedPeer", memberNames); + Assert.Contains ("ConstructActivatedPeer", memberNames); Assert.Contains ("GetUninitializedObject", memberNames); } @@ -229,6 +230,7 @@ public void Generate_InheritedCtor_UcoUsesGuardAndInlinedActivation () var reader = pe.GetMetadataReader (); var memberNames = GetMemberRefNames (reader); + Assert.Contains ("ConstructActivatedPeer", memberNames); Assert.Contains ("get_WithinNewObjectScope", memberNames); Assert.Contains ("GetUninitializedObject", memberNames); Assert.DoesNotContain ("ActivateInstance", memberNames); From 8471eabf055bc2b18f38ebbc083f77f044331c99 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Apr 2026 13:19:31 +0200 Subject: [PATCH 4/8] [TrimmableTypeMap] Share inherited activation helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Generator/TypeMapAssemblyEmitter.cs | 59 ++++++------------- .../Java.Interop/JavaPeerProxy.cs | 12 ++++ .../TypeMapAssemblyGeneratorTests.cs | 12 ++-- 3 files changed, 34 insertions(+), 49 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 42d9120c68a..f4255c25ec8 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -27,7 +27,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// // Creates the managed peer when Java calls into .NET /// public override IJavaPeerable CreateInstance(IntPtr handle, JniHandleOwnership ownership) /// => new Activity(handle, ownership); // leaf ctor -/// // or: (Activity)RuntimeHelpers.GetUninitializedObject(typeof(Activity)); +/// // or: (Activity)CreateUninitializedInstance(typeof(Activity).TypeHandle, handle); /// // obj.BaseCtor(handle, ownership); // inherited ctor /// // or: new IOnClickListenerInvoker(handle, ownership); // interface invoker /// // or: null; // no activation @@ -43,7 +43,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// [UnmanagedCallersOnly] /// public static void nctor_0_uco(IntPtr jnienv, IntPtr self) /// => new Activity(self, JniHandleOwnership.DoNotTransfer); -/// // or: var obj = (Activity)RuntimeHelpers.GetUninitializedObject(typeof(Activity)); +/// // or: var obj = (Activity)CreateUninitializedInstance(typeof(Activity).TypeHandle, self); /// // obj.BaseCtor(self, JniHandleOwnership.DoNotTransfer); /// /// // Registers JNI native methods (ACWs only): @@ -80,11 +80,9 @@ sealed class TypeMapAssemblyEmitter TypeReferenceHandle _runtimeTypeHandleRef; TypeReferenceHandle _jniTypeRef; TypeReferenceHandle _notSupportedExceptionRef; - TypeReferenceHandle _runtimeHelpersRef; MemberReferenceHandle _getTypeFromHandleRef; - MemberReferenceHandle _getUninitializedObjectRef; - MemberReferenceHandle _constructActivatedPeerRef; + MemberReferenceHandle _createUninitializedInstanceRef; MemberReferenceHandle _notSupportedExceptionCtorRef; MemberReferenceHandle _jniObjectReferenceCtorRef; MemberReferenceHandle _jniEnvDeleteRefRef; @@ -189,8 +187,6 @@ void EmitTypeReferences () metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniType")); _notSupportedExceptionRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, metadata.GetOrAddString ("System"), metadata.GetOrAddString ("NotSupportedException")); - _runtimeHelpersRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, - metadata.GetOrAddString ("System.Runtime.CompilerServices"), metadata.GetOrAddString ("RuntimeHelpers")); _jniNativeMethodRef = metadata.AddTypeReference (_javaInteropRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniNativeMethod")); @@ -212,16 +208,11 @@ void EmitMemberReferences () rt => rt.Type ().Type (_systemTypeRef, false), p => p.AddParameter ().Type ().Type (_runtimeTypeHandleRef, true))); - _getUninitializedObjectRef = _pe.AddMemberRef (_runtimeHelpersRef, "GetUninitializedObject", - sig => sig.MethodSignature ().Parameters (1, - rt => rt.Type ().Object (), - p => p.AddParameter ().Type ().Type (_systemTypeRef, false))); - - _constructActivatedPeerRef = _pe.AddMemberRef (_javaPeerProxyBaseRef, "ConstructActivatedPeer", - sig => sig.MethodSignature (isInstanceMethod: false).Parameters (2, - rt => rt.Void (), + _createUninitializedInstanceRef = _pe.AddMemberRef (_javaPeerProxyBaseRef, "CreateUninitializedInstance", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().Type (_iJavaPeerableRef, false), p => { - p.AddParameter ().Type ().Type (_iJavaPeerableRef, false); + p.AddParameter ().Type ().Type (_runtimeTypeHandleRef, true); p.AddParameter ().Type ().IntPtr (); })); @@ -504,15 +495,11 @@ void EmitCreateInstanceInheritedCtor (EntityHandle targetTypeRef, ActivationCtor EmitCreateInstanceBody (encoder => { encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); - encoder.Call (_getTypeFromHandleRef); - encoder.Call (_getUninitializedObjectRef); + encoder.OpCode (ILOpCode.Ldarg_1); + encoder.Call (_createUninitializedInstanceRef); encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); - encoder.OpCode (ILOpCode.Dup); - encoder.OpCode (ILOpCode.Ldarg_1); - encoder.Call (_constructActivatedPeerRef); - encoder.OpCode (ILOpCode.Dup); encoder.OpCode (ILOpCode.Ldarg_1); encoder.OpCode (ILOpCode.Ldarg_2); @@ -559,7 +546,7 @@ void EmitCreateInstanceViaJavaInteropNewobj (EntityHandle typeRef) /// /// Emits CreateInstance for JavaInterop-style activation (inherited ctor): - /// var obj = (TargetType)RuntimeHelpers.GetUninitializedObject(typeof(TargetType)); + /// var obj = (TargetType)CreateUninitializedInstance(typeof(TargetType).TypeHandle, handle); /// var jniRef = new JniObjectReference(handle); /// obj.BaseCtor(ref jniRef, JniObjectReferenceOptions.Copy); /// JNIEnv.DeleteRef(handle, ownership); @@ -571,18 +558,14 @@ void EmitCreateInstanceInheritedJavaInteropCtor (EntityHandle targetTypeRef, Act EmitCreateInstanceBodyWithLocals ( EncodeJniObjectReferenceLocal, encoder => { - // var obj = (TargetType)RuntimeHelpers.GetUninitializedObject(typeof(TargetType)); + // var obj = (TargetType)CreateUninitializedInstance(typeof(TargetType).TypeHandle, handle); encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); - encoder.Call (_getTypeFromHandleRef); - encoder.Call (_getUninitializedObjectRef); + encoder.OpCode (ILOpCode.Ldarg_1); // handle + encoder.Call (_createUninitializedInstanceRef); encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); - encoder.OpCode (ILOpCode.Dup); - encoder.OpCode (ILOpCode.Ldarg_1); // handle - encoder.Call (_constructActivatedPeerRef); - // dup obj (one copy for the call, one for the return) encoder.OpCode (ILOpCode.Dup); @@ -762,14 +745,10 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy if (!activationCtor.IsOnLeafType) { encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); - encoder.Call (_getTypeFromHandleRef); - encoder.Call (_getUninitializedObjectRef); + encoder.LoadArgument (1); // self + encoder.Call (_createUninitializedInstanceRef); encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); - - encoder.OpCode (ILOpCode.Dup); - encoder.LoadArgument (1); // self - encoder.Call (_constructActivatedPeerRef); } encoder.LoadLocalAddress (0); @@ -815,15 +794,11 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy } else { encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); - encoder.Call (_getTypeFromHandleRef); - encoder.Call (_getUninitializedObjectRef); + encoder.LoadArgument (1); // self + encoder.Call (_createUninitializedInstanceRef); encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); - encoder.OpCode (ILOpCode.Dup); - encoder.LoadArgument (1); // self - encoder.Call (_constructActivatedPeerRef); - encoder.LoadArgument (1); // self encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer encoder.Call (ctorRef); diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index 95327dba246..195d4b8269f 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -61,6 +61,18 @@ protected JavaPeerProxy ( /// /// A factory for creating containers of the target type, or null if not supported. public virtual JavaPeerContainerFactory? GetContainerFactory () => null; + /// + /// Creates an uninitialized managed peer for inherited Java activation paths and + /// binds it to the provided JNI handle before the activation constructor runs. + /// + [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Generated proxy activation passes the runtime handle for the peer type that must be allocated uninitialized.")] + protected static IJavaPeerable CreateUninitializedInstance (RuntimeTypeHandle typeHandle, IntPtr handle) + { + var type = Type.GetTypeFromHandle (typeHandle); + var peer = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type); + ConstructActivatedPeer (peer, handle); + return peer; + } protected static void ConstructActivatedPeer (IJavaPeerable peer, IntPtr handle) { diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index 0d937ffff30..2661025f35d 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -176,7 +176,7 @@ public void Generate_EmptyPeerList_ProducesValidAssembly () } [Fact] - public void Generate_SimpleActivity_UsesGetUninitializedObject () + public void Generate_SimpleActivity_UsesSharedActivationHelper () { var peers = ScanFixtures (); var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity"); @@ -186,13 +186,11 @@ public void Generate_SimpleActivity_UsesGetUninitializedObject () using var stream = GenerateAssembly (new [] { simpleActivity }, "InheritedCtorTest"); using var pe = new PEReader (stream); var reader = pe.GetMetadataReader (); - var typeNames = GetTypeRefNames (reader); - Assert.Contains ("RuntimeHelpers", typeNames); var memberNames = GetMemberRefNames (reader); Assert.DoesNotContain ("CreateManagedPeer", memberNames); - Assert.Contains ("ConstructActivatedPeer", memberNames); - Assert.Contains ("GetUninitializedObject", memberNames); + Assert.Contains ("CreateUninitializedInstance", memberNames); + Assert.DoesNotContain ("GetUninitializedObject", memberNames); } [Fact] @@ -230,9 +228,9 @@ public void Generate_InheritedCtor_UcoUsesGuardAndInlinedActivation () var reader = pe.GetMetadataReader (); var memberNames = GetMemberRefNames (reader); - Assert.Contains ("ConstructActivatedPeer", memberNames); Assert.Contains ("get_WithinNewObjectScope", memberNames); - Assert.Contains ("GetUninitializedObject", memberNames); + Assert.Contains ("CreateUninitializedInstance", memberNames); + Assert.DoesNotContain ("GetUninitializedObject", memberNames); Assert.DoesNotContain ("ActivateInstance", memberNames); Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames); } From 19c0bc7e0a8183e4258f84a586604f25d3367d97 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Apr 2026 14:33:15 +0200 Subject: [PATCH 5/8] [TrimmableTypeMap] Inline shared activation helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Mono.Android/Java.Interop/JavaPeerProxy.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index 195d4b8269f..9d30f2eb72a 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -70,19 +70,12 @@ protected static IJavaPeerable CreateUninitializedInstance (RuntimeTypeHandle ty { var type = Type.GetTypeFromHandle (typeHandle); var peer = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type); - ConstructActivatedPeer (peer, handle); - return peer; - } - - protected static void ConstructActivatedPeer (IJavaPeerable peer, IntPtr handle) - { - if (peer == null) - throw new ArgumentNullException (nameof (peer)); peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); var reference = new JniObjectReference (handle); JniEnvironment.Runtime.ValueManager.ConstructPeer (peer, ref reference, JniObjectReferenceOptions.Copy); + return peer; } } From 699c8566b4b0ea10ac676c4f0fc3bdad814ce2ec Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Apr 2026 14:39:25 +0200 Subject: [PATCH 6/8] [TrimmableTypeMap] Drop overlapping leak checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Mono.Android/Java.Interop/TypeManager.cs | 28 ++++++------- .../Android.Widget/CustomWidgetTests.cs | 41 +------------------ 2 files changed, 15 insertions(+), 54 deletions(-) diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 528534a4b13..cc0b22936bd 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -181,12 +181,12 @@ static void n_Activate (IntPtr jnienv, IntPtr jclass, IntPtr typename_ptr, IntPt internal static void Activate (IntPtr jobject, ConstructorInfo cinfo, object? []? parms) { try { - var newobj = GetUninitializedObject (cinfo.DeclaringType!); - var reference = new JniObjectReference (jobject); - JniEnvironment.Runtime.ValueManager.ConstructPeer ( - newobj, - ref reference, - JniObjectReferenceOptions.Copy); + var newobj = RuntimeHelpers.GetUninitializedObject (cinfo.DeclaringType!); + if (newobj is IJavaPeerable peer) { + peer.SetPeerReference (new JniObjectReference (jobject)); + } else { + throw new InvalidOperationException ($"Unsupported type: '{newobj}'"); + } cinfo.Invoke (newobj, parms); } catch (Exception e) { var m = FormattableString.Invariant ( @@ -425,15 +425,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; + 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/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs index bcefa10a8c7..78d62576a0e 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Widget/CustomWidgetTests.cs @@ -1,5 +1,4 @@ -using System; -using Android.App; +using Android.App; using Android.Content; using Android.Util; using Android.Views; @@ -45,44 +44,6 @@ public void UpperAndLowerCaseCustomWidget_FromLibrary_ShouldNotThrowInflateExcep inflater.Inflate (Resource.Layout.upper_lower_custom, null); }, "Regression test for widgets with uppercase and lowercase namespace (bug #23880) failed."); } - - // https://github.com/dotnet/android/issues/11101 - [Test] - public void InflateCustomView_ShouldNotLeakGlobalRefs () - { - var inflater = (LayoutInflater) Application.Context.GetSystemService (Context.LayoutInflaterService); - Assert.IsNotNull (inflater); - - // Warm up: inflate once to ensure all caches and type mappings are populated - inflater.Inflate (Resource.Layout.lowercase_custom, null); - - CollectGarbage (times: 3); - - int grefBefore = Java.Interop.Runtime.GlobalReferenceCount; - - for (int i = 0; i < 10; i++) { - inflater.Inflate (Resource.Layout.lowercase_custom, null); - } - - CollectGarbage (times: 3); - - int grefAfter = Java.Interop.Runtime.GlobalReferenceCount; - int delta = grefAfter - grefBefore; - - // Each inflate creates a LinearLayout + CustomButton via TypeManager.Activate. - // If global refs are leaking during activation, delta will be >= 10. - // Allow a small delta for noise (cached objects, etc.) - Assert.IsTrue (delta <= 5, - $"Global reference leak detected: {delta} extra global refs after inflating/GC'ing 10 custom views. Before={grefBefore}, After={grefAfter}"); - - static void CollectGarbage (int times) - { - for (int i = 0; i < times; i++) { - GC.Collect (); - GC.WaitForPendingFinalizers (); - } - } - } } public class CustomButton : Button From 3a8f9ece3559debc4ec1e82bb7d76163a67e3a75 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Apr 2026 14:44:41 +0200 Subject: [PATCH 7/8] [TrimmableTypeMap] Rename activation helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Generator/TypeMapAssemblyEmitter.cs | 20 +++++++++---------- .../Java.Interop/JavaPeerProxy.cs | 2 +- .../TypeMapAssemblyGeneratorTests.cs | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index f4255c25ec8..cd05affffba 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -27,7 +27,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// // Creates the managed peer when Java calls into .NET /// public override IJavaPeerable CreateInstance(IntPtr handle, JniHandleOwnership ownership) /// => new Activity(handle, ownership); // leaf ctor -/// // or: (Activity)CreateUninitializedInstance(typeof(Activity).TypeHandle, handle); +/// // or: (Activity)CreateActivatedPeer(typeof(Activity).TypeHandle, handle); /// // obj.BaseCtor(handle, ownership); // inherited ctor /// // or: new IOnClickListenerInvoker(handle, ownership); // interface invoker /// // or: null; // no activation @@ -43,7 +43,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// [UnmanagedCallersOnly] /// public static void nctor_0_uco(IntPtr jnienv, IntPtr self) /// => new Activity(self, JniHandleOwnership.DoNotTransfer); -/// // or: var obj = (Activity)CreateUninitializedInstance(typeof(Activity).TypeHandle, self); +/// // or: var obj = (Activity)CreateActivatedPeer(typeof(Activity).TypeHandle, self); /// // obj.BaseCtor(self, JniHandleOwnership.DoNotTransfer); /// /// // Registers JNI native methods (ACWs only): @@ -82,7 +82,7 @@ sealed class TypeMapAssemblyEmitter TypeReferenceHandle _notSupportedExceptionRef; MemberReferenceHandle _getTypeFromHandleRef; - MemberReferenceHandle _createUninitializedInstanceRef; + MemberReferenceHandle _createActivatedPeerRef; MemberReferenceHandle _notSupportedExceptionCtorRef; MemberReferenceHandle _jniObjectReferenceCtorRef; MemberReferenceHandle _jniEnvDeleteRefRef; @@ -208,7 +208,7 @@ void EmitMemberReferences () rt => rt.Type ().Type (_systemTypeRef, false), p => p.AddParameter ().Type ().Type (_runtimeTypeHandleRef, true))); - _createUninitializedInstanceRef = _pe.AddMemberRef (_javaPeerProxyBaseRef, "CreateUninitializedInstance", + _createActivatedPeerRef = _pe.AddMemberRef (_javaPeerProxyBaseRef, "CreateActivatedPeer", sig => sig.MethodSignature ().Parameters (2, rt => rt.Type ().Type (_iJavaPeerableRef, false), p => { @@ -496,7 +496,7 @@ void EmitCreateInstanceInheritedCtor (EntityHandle targetTypeRef, ActivationCtor encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); encoder.OpCode (ILOpCode.Ldarg_1); - encoder.Call (_createUninitializedInstanceRef); + encoder.Call (_createActivatedPeerRef); encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); @@ -546,7 +546,7 @@ void EmitCreateInstanceViaJavaInteropNewobj (EntityHandle typeRef) /// /// Emits CreateInstance for JavaInterop-style activation (inherited ctor): - /// var obj = (TargetType)CreateUninitializedInstance(typeof(TargetType).TypeHandle, handle); + /// var obj = (TargetType)CreateActivatedPeer(typeof(TargetType).TypeHandle, handle); /// var jniRef = new JniObjectReference(handle); /// obj.BaseCtor(ref jniRef, JniObjectReferenceOptions.Copy); /// JNIEnv.DeleteRef(handle, ownership); @@ -558,11 +558,11 @@ void EmitCreateInstanceInheritedJavaInteropCtor (EntityHandle targetTypeRef, Act EmitCreateInstanceBodyWithLocals ( EncodeJniObjectReferenceLocal, encoder => { - // var obj = (TargetType)CreateUninitializedInstance(typeof(TargetType).TypeHandle, handle); + // var obj = (TargetType)CreateActivatedPeer(typeof(TargetType).TypeHandle, handle); encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); encoder.OpCode (ILOpCode.Ldarg_1); // handle - encoder.Call (_createUninitializedInstanceRef); + encoder.Call (_createActivatedPeerRef); encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); @@ -746,7 +746,7 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); encoder.LoadArgument (1); // self - encoder.Call (_createUninitializedInstanceRef); + encoder.Call (_createActivatedPeerRef); encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); } @@ -795,7 +795,7 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); encoder.LoadArgument (1); // self - encoder.Call (_createUninitializedInstanceRef); + encoder.Call (_createActivatedPeerRef); encoder.OpCode (ILOpCode.Castclass); encoder.Token (targetTypeRef); diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index 9d30f2eb72a..76d76dd0917 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -66,7 +66,7 @@ protected JavaPeerProxy ( /// binds it to the provided JNI handle before the activation constructor runs. /// [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Generated proxy activation passes the runtime handle for the peer type that must be allocated uninitialized.")] - protected static IJavaPeerable CreateUninitializedInstance (RuntimeTypeHandle typeHandle, IntPtr handle) + protected static IJavaPeerable CreateActivatedPeer (RuntimeTypeHandle typeHandle, IntPtr handle) { var type = Type.GetTypeFromHandle (typeHandle); var peer = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type); diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index 2661025f35d..0d59f3c642c 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -189,7 +189,7 @@ public void Generate_SimpleActivity_UsesSharedActivationHelper () var memberNames = GetMemberRefNames (reader); Assert.DoesNotContain ("CreateManagedPeer", memberNames); - Assert.Contains ("CreateUninitializedInstance", memberNames); + Assert.Contains ("CreateActivatedPeer", memberNames); Assert.DoesNotContain ("GetUninitializedObject", memberNames); } @@ -229,7 +229,7 @@ public void Generate_InheritedCtor_UcoUsesGuardAndInlinedActivation () var memberNames = GetMemberRefNames (reader); Assert.Contains ("get_WithinNewObjectScope", memberNames); - Assert.Contains ("CreateUninitializedInstance", memberNames); + Assert.Contains ("CreateActivatedPeer", memberNames); Assert.DoesNotContain ("GetUninitializedObject", memberNames); Assert.DoesNotContain ("ActivateInstance", memberNames); Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames); From 5a49637f28b2f32f72d35a63e82051a21c6826aa Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Apr 2026 14:58:40 +0200 Subject: [PATCH 8/8] [TrimmableTypeMap] Use annotated type in activation helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Generator/TypeMapAssemblyEmitter.cs | 14 +++++++++----- src/Mono.Android/Java.Interop/JavaPeerProxy.cs | 16 +++++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index cd05affffba..149d13cfa0b 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -27,7 +27,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// // Creates the managed peer when Java calls into .NET /// public override IJavaPeerable CreateInstance(IntPtr handle, JniHandleOwnership ownership) /// => new Activity(handle, ownership); // leaf ctor -/// // or: (Activity)CreateActivatedPeer(typeof(Activity).TypeHandle, handle); +/// // or: (Activity)CreateActivatedPeer(typeof(Activity), handle); /// // obj.BaseCtor(handle, ownership); // inherited ctor /// // or: new IOnClickListenerInvoker(handle, ownership); // interface invoker /// // or: null; // no activation @@ -43,7 +43,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// [UnmanagedCallersOnly] /// public static void nctor_0_uco(IntPtr jnienv, IntPtr self) /// => new Activity(self, JniHandleOwnership.DoNotTransfer); -/// // or: var obj = (Activity)CreateActivatedPeer(typeof(Activity).TypeHandle, self); +/// // or: var obj = (Activity)CreateActivatedPeer(typeof(Activity), self); /// // obj.BaseCtor(self, JniHandleOwnership.DoNotTransfer); /// /// // Registers JNI native methods (ACWs only): @@ -212,7 +212,7 @@ void EmitMemberReferences () sig => sig.MethodSignature ().Parameters (2, rt => rt.Type ().Type (_iJavaPeerableRef, false), p => { - p.AddParameter ().Type ().Type (_runtimeTypeHandleRef, true); + p.AddParameter ().Type ().Type (_systemTypeRef, false); p.AddParameter ().Type ().IntPtr (); })); @@ -495,6 +495,7 @@ void EmitCreateInstanceInheritedCtor (EntityHandle targetTypeRef, ActivationCtor EmitCreateInstanceBody (encoder => { encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); + encoder.Call (_getTypeFromHandleRef); encoder.OpCode (ILOpCode.Ldarg_1); encoder.Call (_createActivatedPeerRef); encoder.OpCode (ILOpCode.Castclass); @@ -546,7 +547,7 @@ void EmitCreateInstanceViaJavaInteropNewobj (EntityHandle typeRef) /// /// Emits CreateInstance for JavaInterop-style activation (inherited ctor): - /// var obj = (TargetType)CreateActivatedPeer(typeof(TargetType).TypeHandle, handle); + /// var obj = (TargetType)CreateActivatedPeer(typeof(TargetType), handle); /// var jniRef = new JniObjectReference(handle); /// obj.BaseCtor(ref jniRef, JniObjectReferenceOptions.Copy); /// JNIEnv.DeleteRef(handle, ownership); @@ -558,9 +559,10 @@ void EmitCreateInstanceInheritedJavaInteropCtor (EntityHandle targetTypeRef, Act EmitCreateInstanceBodyWithLocals ( EncodeJniObjectReferenceLocal, encoder => { - // var obj = (TargetType)CreateActivatedPeer(typeof(TargetType).TypeHandle, handle); + // var obj = (TargetType)CreateActivatedPeer(typeof(TargetType), handle); encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); + encoder.Call (_getTypeFromHandleRef); encoder.OpCode (ILOpCode.Ldarg_1); // handle encoder.Call (_createActivatedPeerRef); encoder.OpCode (ILOpCode.Castclass); @@ -745,6 +747,7 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy if (!activationCtor.IsOnLeafType) { encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); + encoder.Call (_getTypeFromHandleRef); encoder.LoadArgument (1); // self encoder.Call (_createActivatedPeerRef); encoder.OpCode (ILOpCode.Castclass); @@ -794,6 +797,7 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy } else { encoder.OpCode (ILOpCode.Ldtoken); encoder.Token (targetTypeRef); + encoder.Call (_getTypeFromHandleRef); encoder.LoadArgument (1); // self encoder.Call (_createActivatedPeerRef); encoder.OpCode (ILOpCode.Castclass); diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index 76d76dd0917..a2dc89ec4ef 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -18,10 +18,12 @@ namespace Java.Interop [AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] public abstract class JavaPeerProxy : Attribute { + const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; + protected JavaPeerProxy ( string jniName, Type targetType, - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + [DynamicallyAccessedMembers (Constructors)] Type? invokerType) { JniName = jniName ?? throw new ArgumentNullException (nameof (jniName)); @@ -52,7 +54,7 @@ protected JavaPeerProxy ( /// Gets the invoker type for interfaces and abstract classes. /// Returns null for concrete types that can be directly instantiated. /// - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + [DynamicallyAccessedMembers (Constructors)] public Type? InvokerType { get; } /// @@ -62,13 +64,13 @@ protected JavaPeerProxy ( /// A factory for creating containers of the target type, or null if not supported. public virtual JavaPeerContainerFactory? GetContainerFactory () => null; /// - /// Creates an uninitialized managed peer for inherited Java activation paths and - /// binds it to the provided JNI handle before the activation constructor runs. + /// Creates the managed peer for inherited Java activation paths and binds it + /// to the provided JNI handle before the activation constructor runs. /// - [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Generated proxy activation passes the runtime handle for the peer type that must be allocated uninitialized.")] - protected static IJavaPeerable CreateActivatedPeer (RuntimeTypeHandle typeHandle, IntPtr handle) + protected static IJavaPeerable CreateActivatedPeer ( + [DynamicallyAccessedMembers (Constructors)] Type type, + IntPtr handle) { - var type = Type.GetTypeFromHandle (typeHandle); var peer = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type); peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable);