From c4910453c6551285ae7cc3bfe9b9bff127b3ff5d Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 11:01:39 +0200 Subject: [PATCH 1/2] [JNIEnv] Support sbyte jagged arrays Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e043be9-9070-43bc-aea4-a48438690bdf --- src/Mono.Android/Android.Runtime/JNIEnv.cs | 16 +++++ .../Android.Runtime/JnienvArrayMarshaling.cs | 68 +++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index b8a4d73914e..33c53dcca78 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -662,6 +662,11 @@ public static void CopyArray (IntPtr src, string[] dest) _GetByteArrayRegion (source, index, 1, r); return r [0]; } }, + { typeof (sbyte), (type, source, index) => { + var r = new sbyte [1]; + _GetByteArrayRegion (source, index, 1, (byte[]) (object) r); + return r [0]; + } }, { typeof (char), (type, source, index) => { var r = new char [1]; _GetCharArrayRegion (source, index, 1, r); @@ -915,6 +920,7 @@ static Dictionary> CreateCopyManagedToNativeArray () return new Dictionary> () { { typeof (bool), (source, dest) => CopyArray ((bool[]) source, dest) }, { typeof (byte), (source, dest) => CopyArray ((byte[]) source, dest) }, + { typeof (sbyte), (source, dest) => CopyArray ((byte[]) (object) (sbyte[]) source, dest) }, { typeof (char), (source, dest) => CopyArray ((char[]) source, dest) }, { typeof (short), (source, dest) => CopyArray ((short[]) source, dest) }, { typeof (int), (source, dest) => CopyArray ((int[]) source, dest) }, @@ -1008,6 +1014,11 @@ public static void CopyArray (T[] src, IntPtr dest) CopyArray (source, r); return r; } }, + { typeof (sbyte), (type, source, len) => { + var r = new sbyte [len]; + CopyArray (source, (byte[]) (object) r); + return r; + } }, { typeof (char), (type, source, len) => { var r = new char [len]; CopyArray (source, r); @@ -1323,6 +1334,7 @@ static Dictionary> CreateCreateManagedToNativeArray () return new Dictionary> () { { typeof (bool), (source) => NewArray ((bool[]) source) }, { typeof (byte), (source) => NewArray ((byte[]) source) }, + { typeof (sbyte), (source) => NewArray ((byte[]) (object) (sbyte[]) source) }, { typeof (char), (source) => NewArray ((char[]) source) }, { typeof (short), (source) => NewArray ((short[]) source) }, { typeof (int), (source) => NewArray ((int[]) source) }, @@ -1420,6 +1432,10 @@ static IntPtr NewArray (Array value, Type elementType, IntPtr elementClass) var _value = new[]{(byte) value!}; _SetByteArrayRegion (dest, index, _value.Length, _value); } }, + { typeof (sbyte), (dest, index, value) => { + var _value = new[]{(sbyte) value!}; + _SetByteArrayRegion (dest, index, _value.Length, (byte[]) (object) _value); + } }, { typeof (char), (dest, index, value) => { var _value = new[]{(char) value!}; _SetCharArrayRegion (dest, index, _value.Length, _value); diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs index 247027d1ab8..515d7a0abbc 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs @@ -176,6 +176,74 @@ public void GetArray_ByteArrayArray () } } + [TestCase (false)] + [TestCase (true)] + public void NewAndGetArray_SByteJaggedArrays (bool generic) + { + AssertArrayRoundTrip (new sbyte[]{sbyte.MinValue, -1, 0, sbyte.MaxValue}, generic); + AssertArrayRoundTrip (new [] { + new sbyte[]{sbyte.MinValue, -1}, + new sbyte[]{0, sbyte.MaxValue}, + }, generic); + AssertArrayRoundTrip (new [] { + new [] { + new sbyte[]{sbyte.MinValue, -1}, + new sbyte[]{0, sbyte.MaxValue}, + }, + new [] { + new sbyte[]{sbyte.MaxValue, 0}, + new sbyte[]{-1, sbyte.MinValue}, + }, + }, generic); + } + + [TestCase (false)] + [TestCase (true)] + public void NewAndGetArray_ByteJaggedArraysPreserveHighBits (bool generic) + { + AssertArrayRoundTrip (new byte[]{0, 127, 128, byte.MaxValue}, generic); + AssertArrayRoundTrip (new [] { + new byte[]{0, 127}, + new byte[]{128, byte.MaxValue}, + }, generic); + AssertArrayRoundTrip (new [] { + new [] { + new byte[]{0, 127}, + new byte[]{128, byte.MaxValue}, + }, + new [] { + new byte[]{byte.MaxValue, 128}, + new byte[]{127, 0}, + }, + }, generic); + } + + static void AssertArrayRoundTrip (T[] expected, bool generic) + { + int rank = 1; + Type elementType = typeof (T); + while (elementType.IsArray) { + rank++; + elementType = elementType.GetElementType (); + } + + IntPtr array = generic ? JNIEnv.NewArray (expected) : JNIEnv.NewArray ((Array) expected); + + try { + Assert.AreEqual (new string ('[', rank) + "B", JNIEnv.GetClassNameFromInstance (array)); + if (generic) + JNIEnv.CopyArray (expected, array); + else + JNIEnv.CopyArray (expected, typeof (T), array); + Array actual = generic + ? JNIEnv.GetArray (array) + : JNIEnv.GetArray (array, JniHandleOwnership.DoNotTransfer, typeof (T)); + Assert.AreEqual (expected, actual); + } finally { + JNIEnv.DeleteLocalRef (array); + } + } + [Test] public void GetArray_JavaLangByteArrayToSystemByteArray () { From a5bd019b5011efa967c78a0849673a075729050d Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 11:14:28 +0200 Subject: [PATCH 2/2] [tests] Verify JNIEnv array creation before copy Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e043be9-9070-43bc-aea4-a48438690bdf --- .../Android.Runtime/JnienvArrayMarshaling.cs | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs index 515d7a0abbc..028077bca62 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs @@ -180,10 +180,16 @@ public void GetArray_ByteArrayArray () [TestCase (true)] public void NewAndGetArray_SByteJaggedArrays (bool generic) { - AssertArrayRoundTrip (new sbyte[]{sbyte.MinValue, -1, 0, sbyte.MaxValue}, generic); + AssertArrayRoundTrip ( + new sbyte[]{sbyte.MinValue, -1, 0, sbyte.MaxValue}, + new sbyte[]{1, 2, -2, -3}, + generic); AssertArrayRoundTrip (new [] { new sbyte[]{sbyte.MinValue, -1}, new sbyte[]{0, sbyte.MaxValue}, + }, new [] { + new sbyte[]{1, -2}, + new sbyte[]{3, -4}, }, generic); AssertArrayRoundTrip (new [] { new [] { @@ -194,6 +200,15 @@ public void NewAndGetArray_SByteJaggedArrays (bool generic) new sbyte[]{sbyte.MaxValue, 0}, new sbyte[]{-1, sbyte.MinValue}, }, + }, new [] { + new [] { + new sbyte[]{1, -2}, + new sbyte[]{3, -4}, + }, + new [] { + new sbyte[]{5, -6}, + new sbyte[]{7, -8}, + }, }, generic); } @@ -201,10 +216,16 @@ public void NewAndGetArray_SByteJaggedArrays (bool generic) [TestCase (true)] public void NewAndGetArray_ByteJaggedArraysPreserveHighBits (bool generic) { - AssertArrayRoundTrip (new byte[]{0, 127, 128, byte.MaxValue}, generic); + AssertArrayRoundTrip ( + new byte[]{0, 127, 128, byte.MaxValue}, + new byte[]{1, 126, 129, 254}, + generic); AssertArrayRoundTrip (new [] { new byte[]{0, 127}, new byte[]{128, byte.MaxValue}, + }, new [] { + new byte[]{1, 126}, + new byte[]{129, 254}, }, generic); AssertArrayRoundTrip (new [] { new [] { @@ -215,10 +236,19 @@ public void NewAndGetArray_ByteJaggedArraysPreserveHighBits (bool generic) new byte[]{byte.MaxValue, 128}, new byte[]{127, 0}, }, + }, new [] { + new [] { + new byte[]{1, 126}, + new byte[]{129, 254}, + }, + new [] { + new byte[]{253, 130}, + new byte[]{125, 2}, + }, }, generic); } - static void AssertArrayRoundTrip (T[] expected, bool generic) + static void AssertArrayRoundTrip (T[] created, T[] copied, bool generic) { int rank = 1; Type elementType = typeof (T); @@ -227,18 +257,23 @@ static void AssertArrayRoundTrip (T[] expected, bool generic) elementType = elementType.GetElementType (); } - IntPtr array = generic ? JNIEnv.NewArray (expected) : JNIEnv.NewArray ((Array) expected); + IntPtr array = generic ? JNIEnv.NewArray (created) : JNIEnv.NewArray ((Array) created); try { Assert.AreEqual (new string ('[', rank) + "B", JNIEnv.GetClassNameFromInstance (array)); + Array actual = generic + ? JNIEnv.GetArray (array) + : JNIEnv.GetArray (array, JniHandleOwnership.DoNotTransfer, typeof (T)); + Assert.AreEqual (created, actual); + if (generic) - JNIEnv.CopyArray (expected, array); + JNIEnv.CopyArray (copied, array); else - JNIEnv.CopyArray (expected, typeof (T), array); - Array actual = generic + JNIEnv.CopyArray (copied, typeof (T), array); + actual = generic ? JNIEnv.GetArray (array) : JNIEnv.GetArray (array, JniHandleOwnership.DoNotTransfer, typeof (T)); - Assert.AreEqual (expected, actual); + Assert.AreEqual (copied, actual); } finally { JNIEnv.DeleteLocalRef (array); }