From b16cfe40833c78f2dfba6d2d431acac6cc35083c Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 8 Jun 2026 15:15:23 +0200 Subject: [PATCH 1/4] Broaden Android assignability check switch Honor Microsoft.Android.Runtime.RuntimeFeature.IsAssignableFromCheck in CoreCLR peer creation paths so remapped Java types can still be wrapped when the escape hatch is disabled. Add coverage for wrapping a remapped LayoutInflater service and run the Intune assignability tests under CoreCLR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../yaml-templates/stage-package-tests.yaml | 10 ++ .../JavaMarshalValueManager.cs | 140 +++++++++++++++++- .../TrimmableTypeMap.cs | 9 +- .../Android.Views/LayoutInflaterTest.cs | 15 ++ 4 files changed, 171 insertions(+), 3 deletions(-) diff --git a/build-tools/automation/yaml-templates/stage-package-tests.yaml b/build-tools/automation/yaml-templates/stage-package-tests.yaml index d68f11e2b13..64d550fbcf1 100644 --- a/build-tools/automation/yaml-templates/stage-package-tests.yaml +++ b/build-tools/automation/yaml-templates/stage-package-tests.yaml @@ -195,6 +195,16 @@ stages: artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab artifactFolder: $(DotNetTargetFramework)-IsAssignableFrom + - template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml + parameters: + configuration: $(XA.Build.Configuration) + testName: Mono.Android.NET_Tests-CoreCLR-IsAssignableFrom + project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj + testResultsFiles: TestResult-Mono.Android.NET_Tests-$(XA.Build.Configuration)CoreCLRIsAssignableFrom.xml + extraBuildArgs: -p:TestsFlavor=CoreCLRIsAssignableFrom -p:IncludeCategories=Intune -p:_AndroidIsAssignableFromCheck=false -p:UseMonoRuntime=false + artifactSource: bin/Test$(XA.Build.Configuration)/$(DotNetTargetFramework)-android/Mono.Android.NET_Tests-Signed.aab + artifactFolder: $(DotNetTargetFramework)-CoreCLR-IsAssignableFrom + - template: /build-tools/automation/yaml-templates/apk-instrumentation.yaml parameters: configuration: $(XA.Build.Configuration) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs index 458ad77202a..1c7f3ee768f 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs @@ -540,9 +540,141 @@ void ProcessContext (HandleContext* context) } } + if (!RuntimeFeature.IsAssignableFromCheck) { + return CreatePeerAllowingIncompatibleJavaType (ref reference, transfer, targetType); + } + return base.CreatePeer (ref reference, transfer, targetType); } + IJavaPeerable? CreatePeerAllowingIncompatibleJavaType ( + ref JniObjectReference reference, + JniObjectReferenceOptions transfer, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType) + { + // Mirrors Java.Interop's CreatePeer path, but lets the Android assignability + // feature switch decide whether a Java type mismatch rejects peer creation. + var resolvedTargetType = ResolvePeerType (targetType ?? typeof (global::Java.Interop.JavaObject)); + if (resolvedTargetType is null) { + return null; + } + + if (!typeof (IJavaPeerable).IsAssignableFrom (resolvedTargetType)) { + throw new ArgumentException ($"targetType `{resolvedTargetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType)); + } + + var targetSig = Runtime.TypeManager.GetTypeSignature (resolvedTargetType); + if (!targetSig.IsValid || targetSig.SimpleReference == null) { + throw new ArgumentException ($"Could not determine Java type corresponding to `{resolvedTargetType.AssemblyQualifiedName}`.", nameof (targetType)); + } + + var refClass = JniEnvironment.Types.GetObjectClass (reference); + JniObjectReference targetClass; + try { + targetClass = JniEnvironment.Types.FindClass (targetSig.SimpleReference); + } catch (Exception e) { + JniObjectReference.Dispose (ref refClass); + throw new ArgumentException ($"Could not find Java class `{targetSig.SimpleReference}`.", + nameof (targetType), + e); + } + + if (!JniEnvironment.Types.IsAssignableFrom (refClass, targetClass) && Logger.LogAssembly) { + var message = $"Handle 0x{reference.Handle:x} is of type '{JniEnvironment.Types.GetJniTypeNameFromInstance (reference)}' which is not assignable to '{targetSig.SimpleReference}'"; + Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + } + + JniObjectReference.Dispose (ref targetClass); + + var peer = CreatePeerInstance (ref refClass, resolvedTargetType, ref reference, transfer); + if (peer == null) { + throw new NotSupportedException (string.Format (CultureInfo.InvariantCulture, "Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.", + JniEnvironment.Types.GetJniTypeNameFromInstance (reference), resolvedTargetType)); + } + peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); + return peer; + } + + IJavaPeerable? CreatePeerInstance ( + ref JniObjectReference klass, + [DynamicallyAccessedMembers (Constructors)] + Type targetType, + ref JniObjectReference reference, + JniObjectReferenceOptions transfer) + { + var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass); + + while (jniTypeName != null) { + if (!JniTypeSignature.TryParse (jniTypeName, out var sig)) { + return null; + } + + Type? type = GetTypeAssignableTo (sig, targetType); + if (type != null) { + var peer = TryCreatePeerInstance (ref reference, transfer, type); + + if (peer != null) { + JniObjectReference.Dispose (ref klass); + return peer; + } + } + + var super = JniEnvironment.Types.GetSuperclass (klass); + jniTypeName = super.IsValid + ? JniEnvironment.Types.GetJniTypeNameFromClass (super) + : null; + + JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); + klass = super; + } + JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); + + return TryCreatePeerInstance (ref reference, transfer, targetType); + + [UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "Types returned here should be preserved via other means.")] + [return: DynamicallyAccessedMembers (Constructors)] + Type? GetTypeAssignableTo (JniTypeSignature sig, Type targetType) + { + foreach (var type in Runtime.TypeManager.GetTypes (sig)) { + if (targetType.IsAssignableFrom (type)) { + return type; + } + } + return null; + } + } + + IJavaPeerable? TryCreatePeerInstance ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + type = Runtime.TypeManager.GetInvokerType (type) ?? type; + + var self = GetUninitializedObject (type); + var constructed = false; + try { + constructed = TryConstructPeer (self, ref reference, options, type); + } finally { + if (!constructed) { + GC.SuppressFinalize (self); + self = null; + } + } + return self; + + static IJavaPeerable GetUninitializedObject ( + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + var value = (IJavaPeerable) RuntimeHelpers.GetUninitializedObject (type); + value.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + return value; + } + } + [return: DynamicallyAccessedMembers (Constructors)] static Type? ResolvePeerType ([DynamicallyAccessedMembers (Constructors)] Type? type) { @@ -585,8 +717,12 @@ static bool IsIncompatibleCast ( } if (!JniEnvironment.Types.IsAssignableFrom (instanceClass, targetClass)) { - // Bad cast: callers translate null to the expected result. - return true; + if (Logger.LogAssembly) { + var message = $"Handle 0x{reference.Handle:x} is of type '{JniEnvironment.Types.GetJniTypeNameFromInstance (reference)}' which is not assignable to '{targetJniName}'"; + Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + } + // Bad casts translate to null unless the assignability check is explicitly disabled. + return RuntimeFeature.IsAssignableFromCheck; } } finally { JniObjectReference.Dispose (ref instanceClass); diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index 915daa0e248..833c98732c4 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -375,7 +375,14 @@ static JniMethodInfo GetClassGetInterfacesMethod () return null; } var isAssignable = JniEnvironment.Types.IsAssignableFrom (objClass, targetClass); - return isAssignable ? proxy : null; + if (!isAssignable && RuntimeFeature.IsAssignableFromCheck) { + return null; + } + if (!isAssignable && Logger.LogAssembly) { + var message = $"Handle 0x{handle:x} is of type '{JniEnvironment.Types.GetJniTypeNameFromInstance (selfRef)}' which is not assignable to '{targetJniName}'"; + Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + } + return proxy; } finally { JniObjectReference.Dispose (ref objClass); JniObjectReference.Dispose (ref targetClass); diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Views/LayoutInflaterTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Views/LayoutInflaterTest.cs index 20b1f72f8be..88e022d109b 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Views/LayoutInflaterTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Views/LayoutInflaterTest.cs @@ -1,5 +1,7 @@ using System; using Android.App; +using Android.Content; +using Android.Runtime; using Android.Views; using Microsoft.Android.Runtime; using NUnit.Framework; @@ -20,4 +22,17 @@ public void From () var from = LayoutInflater.From (Application.Context); Assert.IsNotNull (from); } + + [Test] + [Category ("Intune")] + public void FromSystemService () + { + Console.WriteLine ($"{nameof (LayoutInflaterTest)}: RuntimeFeature.IsAssignableFromCheck={RuntimeFeature.IsAssignableFromCheck}"); + + var service = Application.Context.GetSystemService (Context.LayoutInflaterService); + Assert.IsNotNull (service); + + var inflater = Java.Lang.Object.GetObject (service.Handle, JniHandleOwnership.DoNotTransfer); + Assert.IsNotNull (inflater); + } } From e4face541b0c0b1c80b30ed9672463303100813d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 13:57:18 +0000 Subject: [PATCH 2/4] Refactor assignability switch handling in peer creation Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- .../JavaMarshalValueManager.cs | 178 +++++++++--------- .../TrimmableTypeMap.cs | 12 +- 2 files changed, 90 insertions(+), 100 deletions(-) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs index 1c7f3ee768f..18f060cbe15 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs @@ -540,33 +540,16 @@ void ProcessContext (HandleContext* context) } } - if (!RuntimeFeature.IsAssignableFromCheck) { - return CreatePeerAllowingIncompatibleJavaType (ref reference, transfer, targetType); - } - - return base.CreatePeer (ref reference, transfer, targetType); - } + targetType = targetType ?? typeof (global::Java.Interop.JavaObject); + targetType = ResolvePeerType (targetType); - IJavaPeerable? CreatePeerAllowingIncompatibleJavaType ( - ref JniObjectReference reference, - JniObjectReferenceOptions transfer, - [DynamicallyAccessedMembers (Constructors)] - Type? targetType) - { - // Mirrors Java.Interop's CreatePeer path, but lets the Android assignability - // feature switch decide whether a Java type mismatch rejects peer creation. - var resolvedTargetType = ResolvePeerType (targetType ?? typeof (global::Java.Interop.JavaObject)); - if (resolvedTargetType is null) { - return null; - } - - if (!typeof (IJavaPeerable).IsAssignableFrom (resolvedTargetType)) { - throw new ArgumentException ($"targetType `{resolvedTargetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType)); + if (!typeof (IJavaPeerable).IsAssignableFrom (targetType)) { + throw new ArgumentException ($"targetType `{targetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType)); } - var targetSig = Runtime.TypeManager.GetTypeSignature (resolvedTargetType); + var targetSig = Runtime.TypeManager.GetTypeSignature (targetType); if (!targetSig.IsValid || targetSig.SimpleReference == null) { - throw new ArgumentException ($"Could not determine Java type corresponding to `{resolvedTargetType.AssemblyQualifiedName}`.", nameof (targetType)); + throw new ArgumentException ($"Could not determine Java type corresponding to `{targetType.AssemblyQualifiedName}`.", nameof (targetType)); } var refClass = JniEnvironment.Types.GetObjectClass (reference); @@ -580,98 +563,101 @@ void ProcessContext (HandleContext* context) e); } - if (!JniEnvironment.Types.IsAssignableFrom (refClass, targetClass) && Logger.LogAssembly) { - var message = $"Handle 0x{reference.Handle:x} is of type '{JniEnvironment.Types.GetJniTypeNameFromInstance (reference)}' which is not assignable to '{targetSig.SimpleReference}'"; - Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + if (RuntimeFeature.IsAssignableFromCheck) { + if (!JniEnvironment.Types.IsAssignableFrom (refClass, targetClass)) { + JniObjectReference.Dispose (ref refClass); + JniObjectReference.Dispose (ref targetClass); + return null; + } } JniObjectReference.Dispose (ref targetClass); - var peer = CreatePeerInstance (ref refClass, resolvedTargetType, ref reference, transfer); - if (peer == null) { + var createdPeer = CreatePeerInstance (ref refClass, targetType, ref reference, transfer); + if (createdPeer == null) { throw new NotSupportedException (string.Format (CultureInfo.InvariantCulture, "Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.", - JniEnvironment.Types.GetJniTypeNameFromInstance (reference), resolvedTargetType)); + JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType)); } - peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); - return peer; - } + createdPeer.SetJniManagedPeerState (createdPeer.JniManagedPeerState | JniManagedPeerStates.Replaceable); + return createdPeer; - IJavaPeerable? CreatePeerInstance ( - ref JniObjectReference klass, - [DynamicallyAccessedMembers (Constructors)] - Type targetType, - ref JniObjectReference reference, - JniObjectReferenceOptions transfer) - { - var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass); + IJavaPeerable? CreatePeerInstance ( + ref JniObjectReference klass, + [DynamicallyAccessedMembers (Constructors)] + Type targetType, + ref JniObjectReference reference, + JniObjectReferenceOptions transfer) + { + var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass); - while (jniTypeName != null) { - if (!JniTypeSignature.TryParse (jniTypeName, out var sig)) { - return null; - } + while (jniTypeName != null) { + if (!JniTypeSignature.TryParse (jniTypeName, out var sig)) { + return null; + } - Type? type = GetTypeAssignableTo (sig, targetType); - if (type != null) { - var peer = TryCreatePeerInstance (ref reference, transfer, type); + Type? type = GetTypeAssignableTo (sig, targetType); + if (type != null) { + var createdPeer = TryCreatePeerInstance (ref reference, transfer, type); - if (peer != null) { - JniObjectReference.Dispose (ref klass); - return peer; + if (createdPeer != null) { + JniObjectReference.Dispose (ref klass); + return createdPeer; + } } - } - var super = JniEnvironment.Types.GetSuperclass (klass); - jniTypeName = super.IsValid - ? JniEnvironment.Types.GetJniTypeNameFromClass (super) - : null; + var super = JniEnvironment.Types.GetSuperclass (klass); + jniTypeName = super.IsValid + ? JniEnvironment.Types.GetJniTypeNameFromClass (super) + : null; + JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); + klass = super; + } JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); - klass = super; - } - JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose); - return TryCreatePeerInstance (ref reference, transfer, targetType); + return TryCreatePeerInstance (ref reference, transfer, targetType); - [UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "Types returned here should be preserved via other means.")] - [return: DynamicallyAccessedMembers (Constructors)] - Type? GetTypeAssignableTo (JniTypeSignature sig, Type targetType) - { - foreach (var type in Runtime.TypeManager.GetTypes (sig)) { - if (targetType.IsAssignableFrom (type)) { - return type; + [UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "Types returned here should be preserved via other means.")] + [return: DynamicallyAccessedMembers (Constructors)] + Type? GetTypeAssignableTo (JniTypeSignature sig, Type targetType) + { + foreach (var type in Runtime.TypeManager.GetTypes (sig)) { + if (targetType.IsAssignableFrom (type)) { + return type; + } } - } - return null; - } - } - - IJavaPeerable? TryCreatePeerInstance ( - ref JniObjectReference reference, - JniObjectReferenceOptions options, - [DynamicallyAccessedMembers (Constructors)] - Type type) - { - type = Runtime.TypeManager.GetInvokerType (type) ?? type; - - var self = GetUninitializedObject (type); - var constructed = false; - try { - constructed = TryConstructPeer (self, ref reference, options, type); - } finally { - if (!constructed) { - GC.SuppressFinalize (self); - self = null; + return null; } } - return self; - static IJavaPeerable GetUninitializedObject ( + IJavaPeerable? TryCreatePeerInstance ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, [DynamicallyAccessedMembers (Constructors)] Type type) { - var value = (IJavaPeerable) RuntimeHelpers.GetUninitializedObject (type); - value.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); - return value; + type = Runtime.TypeManager.GetInvokerType (type) ?? type; + + var self = GetUninitializedObject (type); + var constructed = false; + try { + constructed = TryConstructPeer (self, ref reference, options, type); + } finally { + if (!constructed) { + GC.SuppressFinalize (self); + self = null; + } + } + return self; + + static IJavaPeerable GetUninitializedObject ( + [DynamicallyAccessedMembers (Constructors)] + Type type) + { + var value = (IJavaPeerable) RuntimeHelpers.GetUninitializedObject (type); + value.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable); + return value; + } } } @@ -699,6 +685,10 @@ static bool IsIncompatibleCast ( ref JniObjectReference reference, Type targetType) { + if (!RuntimeFeature.IsAssignableFromCheck) { + return false; + } + if (!typeMap.TryGetJniNameForManagedType (targetType, out var targetJniName)) { throw new ArgumentException ( $"Could not determine Java type corresponding to '{targetType.AssemblyQualifiedName}'.", @@ -721,8 +711,8 @@ static bool IsIncompatibleCast ( var message = $"Handle 0x{reference.Handle:x} is of type '{JniEnvironment.Types.GetJniTypeNameFromInstance (reference)}' which is not assignable to '{targetJniName}'"; Logger.Log (LogLevel.Debug, "monodroid-assembly", message); } - // Bad casts translate to null unless the assignability check is explicitly disabled. - return RuntimeFeature.IsAssignableFromCheck; + // Bad casts translate to null. + return true; } } finally { JniObjectReference.Dispose (ref instanceClass); diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index 833c98732c4..1206117f57d 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -374,12 +374,12 @@ static JniMethodInfo GetClassGetInterfacesMethod () // ArgumentException instead of leaking ClassNotFoundException. return null; } - var isAssignable = JniEnvironment.Types.IsAssignableFrom (objClass, targetClass); - if (!isAssignable && RuntimeFeature.IsAssignableFromCheck) { - return null; - } - if (!isAssignable && Logger.LogAssembly) { - var message = $"Handle 0x{handle:x} is of type '{JniEnvironment.Types.GetJniTypeNameFromInstance (selfRef)}' which is not assignable to '{targetJniName}'"; + if (RuntimeFeature.IsAssignableFromCheck) { + if (!JniEnvironment.Types.IsAssignableFrom (objClass, targetClass)) { + return null; + } + } else if (Logger.LogAssembly) { + var message = $"Skipping Java assignability check for handle 0x{handle:x} targeting '{targetJniName}' because {nameof (RuntimeFeature.IsAssignableFromCheck)} is disabled."; Logger.Log (LogLevel.Debug, "monodroid-assembly", message); } return proxy; From b6c4ac565983e084aad5cbe0f380096729dbb2cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 14:02:01 +0000 Subject: [PATCH 3/4] Polish assignability review feedback updates Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- .../Microsoft.Android.Runtime/JavaMarshalValueManager.cs | 6 +++--- .../Microsoft.Android.Runtime/TrimmableTypeMap.cs | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs index 18f060cbe15..df914ce9ddc 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs @@ -597,11 +597,11 @@ void ProcessContext (HandleContext* context) Type? type = GetTypeAssignableTo (sig, targetType); if (type != null) { - var createdPeer = TryCreatePeerInstance (ref reference, transfer, type); + var peer = TryCreatePeerInstance (ref reference, transfer, type); - if (createdPeer != null) { + if (peer != null) { JniObjectReference.Dispose (ref klass); - return createdPeer; + return peer; } } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index 1206117f57d..269ab592766 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -378,9 +378,6 @@ static JniMethodInfo GetClassGetInterfacesMethod () if (!JniEnvironment.Types.IsAssignableFrom (objClass, targetClass)) { return null; } - } else if (Logger.LogAssembly) { - var message = $"Skipping Java assignability check for handle 0x{handle:x} targeting '{targetJniName}' because {nameof (RuntimeFeature.IsAssignableFromCheck)} is disabled."; - Logger.Log (LogLevel.Debug, "monodroid-assembly", message); } return proxy; } finally { From 01852f92b3d2298a692473fa4fc67c937e0f1bbd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Jun 2026 14:10:35 +0000 Subject: [PATCH 4/4] Address validation findings in assignability refactor Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- .../JavaMarshalValueManager.cs | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs index df914ce9ddc..ce45e0be648 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs @@ -540,16 +540,16 @@ void ProcessContext (HandleContext* context) } } - targetType = targetType ?? typeof (global::Java.Interop.JavaObject); - targetType = ResolvePeerType (targetType); + var peerTargetType = ResolvePeerType (targetType ?? typeof (global::Java.Interop.JavaObject)) + ?? typeof (global::Java.Interop.JavaObject); - if (!typeof (IJavaPeerable).IsAssignableFrom (targetType)) { - throw new ArgumentException ($"targetType `{targetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType)); + if (!typeof (IJavaPeerable).IsAssignableFrom (peerTargetType)) { + throw new ArgumentException ($"targetType `{peerTargetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType)); } - var targetSig = Runtime.TypeManager.GetTypeSignature (targetType); + var targetSig = Runtime.TypeManager.GetTypeSignature (peerTargetType); if (!targetSig.IsValid || targetSig.SimpleReference == null) { - throw new ArgumentException ($"Could not determine Java type corresponding to `{targetType.AssemblyQualifiedName}`.", nameof (targetType)); + throw new ArgumentException ($"Could not determine Java type corresponding to `{peerTargetType.AssemblyQualifiedName}`.", nameof (targetType)); } var refClass = JniEnvironment.Types.GetObjectClass (reference); @@ -573,10 +573,10 @@ void ProcessContext (HandleContext* context) JniObjectReference.Dispose (ref targetClass); - var createdPeer = CreatePeerInstance (ref refClass, targetType, ref reference, transfer); + var createdPeer = CreatePeerInstance (ref refClass, peerTargetType, ref reference, transfer); if (createdPeer == null) { throw new NotSupportedException (string.Format (CultureInfo.InvariantCulture, "Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.", - JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType)); + JniEnvironment.Types.GetJniTypeNameFromInstance (reference), peerTargetType)); } createdPeer.SetJniManagedPeerState (createdPeer.JniManagedPeerState | JniManagedPeerStates.Replaceable); return createdPeer; @@ -685,38 +685,36 @@ static bool IsIncompatibleCast ( ref JniObjectReference reference, Type targetType) { - if (!RuntimeFeature.IsAssignableFromCheck) { - return false; - } - - if (!typeMap.TryGetJniNameForManagedType (targetType, out var targetJniName)) { - throw new ArgumentException ( - $"Could not determine Java type corresponding to '{targetType.AssemblyQualifiedName}'.", - nameof (targetType)); - } - - var instanceClass = JniEnvironment.Types.GetObjectClass (reference); - JniObjectReference targetClass = default; - try { - try { - targetClass = JniEnvironment.Types.FindClass (targetJniName); - } catch (Java.Lang.ClassNotFoundException e) { + if (RuntimeFeature.IsAssignableFromCheck) { + if (!typeMap.TryGetJniNameForManagedType (targetType, out var targetJniName)) { throw new ArgumentException ( - $"Could not find Java class '{targetJniName}'.", - nameof (targetType), e); + $"Could not determine Java type corresponding to '{targetType.AssemblyQualifiedName}'.", + nameof (targetType)); } - if (!JniEnvironment.Types.IsAssignableFrom (instanceClass, targetClass)) { - if (Logger.LogAssembly) { - var message = $"Handle 0x{reference.Handle:x} is of type '{JniEnvironment.Types.GetJniTypeNameFromInstance (reference)}' which is not assignable to '{targetJniName}'"; - Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + var instanceClass = JniEnvironment.Types.GetObjectClass (reference); + JniObjectReference targetClass = default; + try { + try { + targetClass = JniEnvironment.Types.FindClass (targetJniName); + } catch (Java.Lang.ClassNotFoundException e) { + throw new ArgumentException ( + $"Could not find Java class '{targetJniName}'.", + nameof (targetType), e); + } + + if (!JniEnvironment.Types.IsAssignableFrom (instanceClass, targetClass)) { + if (Logger.LogAssembly) { + var message = $"Handle 0x{reference.Handle:x} is of type '{JniEnvironment.Types.GetJniTypeNameFromInstance (reference)}' which is not assignable to '{targetJniName}'"; + Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + } + // Bad casts translate to null. + return true; } - // Bad casts translate to null. - return true; + } finally { + JniObjectReference.Dispose (ref instanceClass); + JniObjectReference.Dispose (ref targetClass); } - } finally { - JniObjectReference.Dispose (ref instanceClass); - JniObjectReference.Dispose (ref targetClass); } // Compatible classes mean a proxy/activation gap.