Skip to content
Draft
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
10 changes: 10 additions & 0 deletions build-tools/automation/yaml-templates/stage-package-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
166 changes: 145 additions & 21 deletions src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<LayoutInflater> (service.Handle, JniHandleOwnership.DoNotTransfer);
Assert.IsNotNull (inflater);
}
}