diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index 56f836bd2e3..dca2e43a2cf 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -279,7 +279,7 @@ protected virtual bool TryConstructPeer ( return JavaPeerableValueMarshaler.Instance.CreateGenericValue (ref reference, options, targetType); } var marshaler = GetValueMarshaler (targetType); - return marshaler.CreateValue (ref reference, options, targetType); + return CreateMarshaledValue (ref reference, options, targetType, marshaler); } [return: MaybeNull] @@ -319,8 +319,14 @@ protected virtual bool TryConstructPeer ( #pragma warning restore CS8600,CS8601 // Possible null reference assignment. } - var marshaler = GetValueMarshaler (); - return marshaler.CreateGenericValue (ref reference, options, targetType); + var marshaler = GetValueMarshaler (targetType); + var value = CreateMarshaledValue (ref reference, options, targetType, marshaler); + if (value == null) { + #pragma warning disable 8653 + return default (T); + #pragma warning restore 8653 + } + return (T) value; } object? PeekBoxedObject (JniObjectReference reference) @@ -361,7 +367,7 @@ protected virtual bool TryConstructPeer ( return JavaPeerableValueMarshaler.Instance.CreateGenericValue (ref reference, options, targetType); } var marshaler = GetValueMarshaler (targetType); - return marshaler.CreateValue (ref reference, options, targetType); + return CreateMarshaledValue (ref reference, options, targetType, marshaler); } @@ -401,8 +407,74 @@ protected virtual bool TryConstructPeer ( #pragma warning restore CS8600,CS8601 // Possible null reference assignment. } - var marshaler = GetValueMarshaler (); - return marshaler.CreateGenericValue (ref reference, options, targetType); + var marshaler = GetValueMarshaler (targetType); + var value = CreateMarshaledValue (ref reference, options, targetType, marshaler); + if (value == null) { + #pragma warning disable 8653 + return default (T); + #pragma warning restore 8653 + } + return (T) value; + } + + object? CreateMarshaledValue ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + Type targetType, + JniValueMarshaler marshaler) + { + if (!IsPrimitiveArrayMarshaler (marshaler)) + return marshaler.CreateValue (ref reference, options, targetType); + + var jniType = JniEnvironment.Types.GetJniTypeNameFromInstance (reference); + if (IsMatchingPrimitiveArray (marshaler, jniType)) + return marshaler.CreateValue (ref reference, options, targetType); + + if (GetListType (targetType) != null && jniType != null && !jniType.StartsWith ("[", StringComparison.Ordinal)) { + if (JavaListType.IsInstanceOfType (reference)) + return CreateNonArrayListValue (ref reference, options, targetType); + JniObjectReference.Dispose (ref reference, options); + throw new InvalidCastException ($"JNI reference of type '{jniType}' does not implement java.util.List required by managed type '{targetType}'."); + } + + JniObjectReference.Dispose (ref reference, options); + throw new InvalidCastException ($"JNI reference of type '{jniType}' is not the primitive array required by managed type '{targetType}'."); + } + + static JniType? javaListType; + static JniType JavaListType { + get {return JniType.GetCachedJniType (ref javaListType, "java/util/List");} + } + + bool IsMatchingPrimitiveArray (JniValueMarshaler marshaler, string? jniType) + { + if (jniType == null || !JniTypeSignature.TryParse (jniType, out var actualType)) + return false; + + foreach (var candidate in JniPrimitiveArrayMarshalers.Value) { + if (!ReferenceEquals (candidate.Value, marshaler)) + continue; + return Runtime.TypeManager.GetTypeSignature (candidate.Key) == actualType; + } + return false; + } + + static bool IsPrimitiveArrayMarshaler (JniValueMarshaler marshaler) + { + foreach (var candidate in JniPrimitiveArrayMarshalers.Value) { + if (ReferenceEquals (candidate.Value, marshaler)) + return true; + } + return false; + } + + protected virtual object? CreateNonArrayListValue ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + Type targetType) + { + JniObjectReference.Dispose (ref reference, options); + throw new InvalidCastException ($"JNI reference cannot be converted to managed list type '{targetType}'."); } Dictionary Marshalers = new Dictionary (); diff --git a/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt b/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt index 2088a07dc59..48024f01427 100644 --- a/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt @@ -117,3 +117,4 @@ override Java.Interop.JniRuntime.ReflectionJniTypeManager.GetTypesForSimpleRefer override Java.Interop.JniRuntime.ReflectionJniTypeManager.RegisterNativeMembers(Java.Interop.JniType! nativeClass, System.Type! type, string? methods) -> void override Java.Interop.JniRuntime.ReflectionJniTypeManager.RegisterNativeMembers(Java.Interop.JniType! nativeClass, System.Type! type, System.ReadOnlySpan methods) -> void virtual Java.Interop.JniRuntime.ReflectionJniValueManager.TryConstructPeer(Java.Interop.IJavaPeerable! self, ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> bool +virtual Java.Interop.JniRuntime.ReflectionJniValueManager.CreateNonArrayListValue(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! targetType) -> object? diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 34bafabbe58..ee2772e654d 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -630,6 +630,14 @@ public override void WaitForGCBridgeProcessing () return peer; } + protected override object? CreateNonArrayListValue ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + Type targetType) + { + return JavaConvert.FromObjectReference (ref reference, options, targetType); + } + public override void AddPeer (IJavaPeerable value) { if (value == null) diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs index e83d20fd046..1c026b85e9d 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs @@ -61,6 +61,14 @@ public override List GetSurfacedPeers () return JavaMarshalRegisteredPeers.GetSurfacedPeers (); } + protected override object? CreateNonArrayListValue ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + Type targetType) + { + return JavaConvert.FromObjectReference (ref reference, options, targetType); + } + protected override bool TryConstructPeer ( IJavaPeerable self, ref JniObjectReference reference, diff --git a/src/Mono.Android/Microsoft.Android.Runtime/PrimitiveArrayInfo.cs b/src/Mono.Android/Microsoft.Android.Runtime/PrimitiveArrayInfo.cs index 77cd34fc30f..c74b3bdb1af 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/PrimitiveArrayInfo.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/PrimitiveArrayInfo.cs @@ -74,6 +74,16 @@ public override bool TryCreateWrapper ( return false; } + var jniType = JniEnvironment.Types.GetJniTypeNameFromInstance (reference); + if (jniType != "[" + jniSimpleReference) { + if (IsCompatibleListType (targetType) && JavaListType.IsInstanceOfType (reference)) { + value = null; + return false; + } + JniObjectReference.Dispose (ref reference, options); + throw new InvalidCastException ($"JNI reference of type '{jniType}' is not the primitive array required by managed type '{targetType}'."); + } + var array = createFromReference (ref reference, options); if (targetType == typeof (T[]) || IsCompatibleListType (targetType)) { try { @@ -170,6 +180,9 @@ static bool IsCompatibleListType (Type targetType) list => new JavaDoubleArray (list)), ]; + static JniType? javaListType; + static JniType JavaListType => JniType.GetCachedJniType (ref javaListType, "java/util/List"); + public static bool TryGetArrayTypes (Type elementType, [NotNullWhen (true)] out Type[]? arrayTypes) { foreach (var handler in Handlers) { diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaConvertTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaConvertTest.cs index 92552cf6033..8f636bc3297 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaConvertTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JavaConvertTest.cs @@ -127,6 +127,60 @@ public void MarshalInt23Array () } } + [Test] + public void ValueManagerConvertsJavaListToPrimitiveIList () + { + using (var values = new Java.Util.ArrayList ()) + using (var first = new Java.Lang.Boolean (true)) + using (var second = new Java.Lang.Boolean (false)) { + values.Add (first); + values.Add (second); + + var reference = values.PeerReference; + var converted = JniEnvironment.Runtime.ValueManager.GetValue> (ref reference, JniObjectReferenceOptions.Copy); + + CollectionAssert.AreEqual (new [] { true, false }, converted); + } + } + + [Test] + public void ValueManagerConvertsPrimitiveArrayToIList () + { + var reference = new JniObjectReference (JNIEnv.NewArray (new [] { true, false }), JniObjectReferenceType.Local); + var converted = JniEnvironment.Runtime.ValueManager.GetValue> (ref reference, JniObjectReferenceOptions.CopyAndDispose); + + CollectionAssert.AreEqual (new [] { true, false }, converted); + } + + [TestCase (JniObjectReferenceOptions.Copy, true)] + [TestCase (JniObjectReferenceOptions.CopyAndDispose, false)] + public void ValueManagerRejectsNonListForPrimitiveIListAndHonorsOwnership ( + JniObjectReferenceOptions options, + bool remainsValid) + { + var reference = new JniObjectReference (JNIEnv.NewString ("not a list"), JniObjectReferenceType.Local); + try { + Assert.Throws (() => + JniEnvironment.Runtime.ValueManager.GetValue> (ref reference, options)); + Assert.AreEqual (remainsValid, reference.IsValid); + } finally { + JniObjectReference.Dispose (ref reference); + } + } + + [Test] + public void ValueManagerRejectsMismatchedPrimitiveArrayAndDisposesReference () + { + var reference = new JniObjectReference (JNIEnv.NewArray (new [] { 1, 2 }), JniObjectReferenceType.Local); + try { + Assert.Throws (() => + JniEnvironment.Runtime.ValueManager.GetValue> (ref reference, JniObjectReferenceOptions.CopyAndDispose)); + Assert.IsFalse (reference.IsValid); + } finally { + JniObjectReference.Dispose (ref reference); + } + } + static Java.Util.ArrayList CreateList (params int[][] items) { var list = new Java.Util.ArrayList (); @@ -139,5 +193,5 @@ static Java.Util.ArrayList CreateList (params int[][] items) return list; } } -} +}