Skip to content

[JNIEnv] Support sbyte jagged arrays - #12115

Open
simonrozsival wants to merge 3 commits into
mainfrom
dev/simonrozsival/fix-sbyte-jagged-arrays
Open

[JNIEnv] Support sbyte jagged arrays#12115
simonrozsival wants to merge 3 commits into
mainfrom
dev/simonrozsival/fix-sbyte-jagged-arrays

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

Summary

  • add explicit sbyte converters beside the existing byte converters used by shared JNIEnv array creation, copying, reading, and element access
  • reinterpret sbyte[] as JNI byte-array storage bit-for-bit, so negative managed values retain their two's-complement byte representation
  • keep JNI [B fallback routing mapped to managed byte, avoiding any canonicalization of existing byte requests to sbyte

This affects generic and non-generic JNIEnv.NewArray, JNIEnv.GetArray, and JNIEnv.CopyArray paths, including recursive jagged-array handling.

Experiment evidence

Both legacy and trimmable typemap experiments produced the expected JNI signatures ([[B and [[[B) for sbyte jagged arrays. Creation then failed during recursive leaf conversion with:

Don't know how to convert type 'System.SByte' to an Android.Runtime.IJavaObject.

The signatures were therefore already correct; the shared converter dictionaries omitted sbyte while containing byte.

Tests

Added on-device regression coverage for generic and non-generic NewArray/GetArray/CopyArray at ranks 1, 2, and 3:

  • sbyte: minimum, negative, zero, and maximum values
  • byte: values with high bits set, confirming existing unsigned managed behavior remains unchanged
  • JNI signatures asserted as [B, [[B, and [[[B

Validation:

  • dotnet build src/Mono.Android/Mono.Android.csproj --no-restore ... — passed (15 existing warnings)
  • device tests not run because this worktree has no locally built SDK; direct test-project compilation is blocked by missing Java.Interop reflection test types in the unprepared checkout

simonrozsival and others added 3 commits July 15, 2026 11:01
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0e043be9-9070-43bc-aea4-a48438690bdf
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0e043be9-9070-43bc-aea4-a48438690bdf
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@simonrozsival
simonrozsival marked this pull request as ready for review August 21, 2026 10:11
Copilot AI lite review requested due to automatic review settings August 21, 2026 10:12
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12115

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to extend Android.Runtime.JNIEnv array marshaling to support sbyte (including jagged arrays) by treating managed sbyte[] as JNI byte[] ([B) storage bit-for-bit, and it adds on-device regression tests for array creation, reading, and copying at ranks 1–3.

Changes:

  • Add sbyte handling to shared JNIEnv converter tables used by NewArray, GetArray, CopyArray, and element access.
  • Add new on-device tests validating sbyte/byte jagged-array round-trips and expected JNI signatures ([B, [[B, [[[B).
Show a summary per file
File Description
tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs Adds regression tests for sbyte jagged arrays and high-bit byte preservation across ranks 1–3.
src/Mono.Android/Android.Runtime/JNIEnv.cs Introduces sbyte entries in marshaling/converter dictionaries used by array creation/copying/reading and element access.

Review details

Suppressed comments (4)

src/Mono.Android/Android.Runtime/JNIEnv.cs:934

  • ❌ error JNI interop(byte[])(object)(sbyte[])source will throw InvalidCastException at runtime; arrays of sbyte are not interchangeable with arrays of byte. This path needs a real conversion or an sbyte[]-aware JNI region helper instead of casting.
				{ typeof (sbyte),       (source, dest) => CopyArray ((byte[]) (object) (sbyte[]) source, dest) },

src/Mono.Android/Android.Runtime/JNIEnv.cs:1057

  • ❌ error JNI interopCopyArray (source, (byte[])(object)r) will throw InvalidCastException because r is an sbyte[]. This will break JNIEnv.GetArray<sbyte> and jagged-array recursion for sbyte.
				{ typeof (sbyte), (type, source, len) => {
					var r = new sbyte [len];
					CopyArray (source, (byte[]) (object) r);
					return r;

src/Mono.Android/Android.Runtime/JNIEnv.cs:1393

  • ❌ error JNI interopNewArray ((byte[])(object)(sbyte[])source) relies on an invalid runtime cast between sbyte[] and byte[]. This will throw and prevent creating JNI arrays from managed sbyte[].
				{ typeof (sbyte),         (source) => NewArray ((byte[]) (object) (sbyte[]) source) },

src/Mono.Android/Android.Runtime/JNIEnv.cs:1531

  • ❌ error JNI interop(byte[])(object)_value will throw InvalidCastException because _value is an sbyte[]. This breaks setting individual sbyte array elements (and any recursive array creation path that uses this dictionary).
				{ typeof (sbyte), (dest, index, value) => {
					var _value = new[]{(sbyte) value!};
					_SetByteArrayRegion (dest, index, _value.Length, (byte[]) (object) _value);
				} },
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines +660 to +663
{ typeof (sbyte), (type, source, index) => {
var r = new sbyte [1];
_GetByteArrayRegion (source, index, 1, (byte[]) (object) r);
return r [0];
Comment on lines +179 to +186
[TestCase (false)]
[TestCase (true)]
public void NewAndGetArray_SByteJaggedArrays (bool generic)
{
AssertArrayRoundTrip (
new sbyte[]{sbyte.MinValue, -1, 0, sbyte.MaxValue},
new sbyte[]{1, 2, -2, -3},
generic);

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Needs Changes

Findings: 0 errors · 0 warnings · 1 suggestion

The sbyte converter additions are otherwise consistent with the existing signed/unsigned primitive-array reinterpretation pattern, and the rank 1–3 round-trip tests verify both signed values and unchanged high-bit byte behavior. Please avoid introducing the new null-forgiving operator noted inline; the focused element-access coverage requested in the existing review thread would also complete coverage of the changed converter tables.

The latest dotnet-android build (#1562046) is still in progress with no failures reported so far, so the PR is not yet fully green.

Generated by Android PR Reviewer for #12115 · gpt56 · 100.7 AIC · ⌖ 8.98 AIC · ⊞ 25.7K
Comment /review to run again

_SetByteArrayRegion (dest, index, _value.Length, _value);
} },
{ typeof (sbyte), (dest, index, value) => {
var _value = new[]{(sbyte) value!};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 💡 Nullable — This new entry introduces another null-forgiving operator even though value is declared object?. Please pattern-match it to an sbyte (and throw a contextual exception for null or the wrong type) before creating _value; that preserves the invariant instead of suppressing nullable analysis.

Rule: Never use the null-forgiving operator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants