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..ce45e0be648 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs @@ -540,7 +540,125 @@ void ProcessContext (HandleContext* context) } } - return base.CreatePeer (ref reference, transfer, targetType); + var peerTargetType = ResolvePeerType (targetType ?? typeof (global::Java.Interop.JavaObject)) + ?? typeof (global::Java.Interop.JavaObject); + + if (!typeof (IJavaPeerable).IsAssignableFrom (peerTargetType)) { + throw new ArgumentException ($"targetType `{peerTargetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType)); + } + + var targetSig = Runtime.TypeManager.GetTypeSignature (peerTargetType); + if (!targetSig.IsValid || targetSig.SimpleReference == null) { + throw new ArgumentException ($"Could not determine Java type corresponding to `{peerTargetType.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 (RuntimeFeature.IsAssignableFromCheck) { + if (!JniEnvironment.Types.IsAssignableFrom (refClass, targetClass)) { + JniObjectReference.Dispose (ref refClass); + JniObjectReference.Dispose (ref targetClass); + return null; + } + } + + JniObjectReference.Dispose (ref targetClass); + + 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), peerTargetType)); + } + 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); + + 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)] @@ -567,30 +685,36 @@ static bool IsIncompatibleCast ( ref JniObjectReference reference, Type targetType) { - 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)) { - // Bad cast: callers translate null to the expected result. - return true; + 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; + } + } 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. diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index 915daa0e248..269ab592766 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -374,8 +374,12 @@ static JniMethodInfo GetClassGetInterfacesMethod () // ArgumentException instead of leaking ClassNotFoundException. return null; } - var isAssignable = JniEnvironment.Types.IsAssignableFrom (objClass, targetClass); - return isAssignable ? proxy : null; + if (RuntimeFeature.IsAssignableFromCheck) { + if (!JniEnvironment.Types.IsAssignableFrom (objClass, targetClass)) { + return null; + } + } + 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); + } }