Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions src/Mono.Android/Java.Interop/TypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,14 +268,26 @@ public override void ActivatePeer (IJavaPeerable? self, JniObjectReference refer
void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues)
{
var declType = GetDeclaringType (cinfo);
var self = GetUninitializedObject (declType);

#pragma warning disable IL2072
var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType);
#pragma warning restore IL2072
self.SetPeerReference (reference);
// ConstructPeer BEFORE the constructor to create a proper
// global ref and eliminate the race window where bridge
// processing could see a raw local ref.
// See: https://github.com/dotnet/android/issues/11101
JniEnvironment.Runtime.ValueManager.ConstructPeer (
self, ref reference, JniObjectReferenceOptions.Copy);

cinfo.Invoke (self, argumentValues);

[UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Activation constructors are preserved by the runtime typemap.")]
static IJavaPeerable GetUninitializedObject (
[DynamicallyAccessedMembers (Constructors)] Type type)
{
var value = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type);
value.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable);
return value;
}

[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "🤷‍♂️")]
[return: DynamicallyAccessedMembers (Constructors)]
Type GetDeclaringType (ConstructorInfo cinfo) =>
Expand Down
20 changes: 16 additions & 4 deletions src/Mono.Android/Microsoft.Android.Runtime/SimpleValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,26 @@ public override void ActivatePeer (IJavaPeerable? self, JniObjectReference refer
void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues)
{
var declType = GetDeclaringType (cinfo);
var self = GetUninitializedObject (declType);

#pragma warning disable IL2072
var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType);
#pragma warning restore IL2072
self.SetPeerReference (reference);
// ConstructPeer BEFORE the constructor to create a proper
// global ref and eliminate the race window where bridge
// processing could see a raw local ref.
// See: https://github.com/dotnet/android/issues/11101
JniEnvironment.Runtime.ValueManager.ConstructPeer (
self, ref reference, JniObjectReferenceOptions.Copy);

cinfo.Invoke (self, argumentValues);

[UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Activation constructors are preserved by the runtime typemap.")]
static IJavaPeerable GetUninitializedObject (
[DynamicallyAccessedMembers (Constructors)] Type type)
{
var value = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (type);
value.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable);
return value;
}

[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "🤷‍♂️")]
[return: DynamicallyAccessedMembers (Constructors)]
Type GetDeclaringType (ConstructorInfo cinfo) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Android.App;
using System;
using Android.App;
using Android.Content;
using Android.Util;
using Android.Views;
Expand Down Expand Up @@ -44,6 +45,44 @@ 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
Expand Down