[Java.Interop] Map nullable byte jagged arrays to java.lang.Byte - #12113
[Java.Interop] Map nullable byte jagged arrays to java.lang.Byte#12113simonrozsival wants to merge 28 commits into
Conversation
Centralize safe runtime construction of arrays and generic Java collection wrappers so JavaConvert and JNIEnv no longer carry the trimmable-specific construction policy inline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix nullable annotations on factory Try methods and keep mixed dictionary creation from bypassing the explicit value-type support map. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Expand suppression justifications and comments with the source-grounded NativeAOT behavior for array and generic type construction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Consolidate primitive and nullable value-type rooting for safe arrays and Java collection wrappers into ValueTypeFactory<T>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update the suppression text to say first-rank value vectors bypass CreateInstanceFromArrayType and are allocated through ValueTypeFactory. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…elines The new nullable/collection marshaling paths made JavaConvert.GetJniHandleConverter AOT-reachable, exposing the legacy TryMakeGenericCollectionTypeFactory helper whose MakeGenericType() calls only suppressed IL2055 (trimming) and not IL3050 (AOT), leaking 6 NativeAOT warnings. Add the matching IL3050 suppression; that branch only runs when !RuntimeFeature.TrimmableTypeMap (Mono/CoreCLR), never under NativeAOT. Update BuildReleaseArm64 SimpleDotNet CoreCLR/NativeAOT apkdesc baselines to accept the expected size increase from the rooted array/collection typemap entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d API - Remove the unused SafeJavaCollectionFactory.TryCreateInstance(Type, object?, out IJavaObject?) speculative overload (no call sites). - Convert the three new factory files to file-scoped namespaces per repo conventions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Check the value-type factory locals directly instead of via intermediate bools so the compiler tracks the non-null flow, clearing CS8602/CS8604 in the dictionary path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… API, avoid per-element closure - Add FromJniHandle_IDictionaryInt32Int64 to exercise the value/value dictionary branch (JavaDictionary<int,long> via the generic-virtual CreateDictionaryWithKey<TKey>), confirming that value-over-value instantiation is rooted under the trimmable/NativeAOT config. - Remove the unused throwing SafeArrayFactory.GetArrayType(Type,int) overload (only TryGetArrayType/CreateInstance are called). - Inline JavaConvert.WithLocalJniHandle in CopyManagedObjectArray to avoid allocating a capturing closure per array element. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ctions Resolve conflicts from main's IsDynamicCodeSupported [FeatureGuard] refactor and its generated-proxy collection factory, which landed in the same regions this branch rewrites to runtime Safe*Factory construction: - JNIEnv.ArrayCreateInstance / TrimmableTypeMapTypeManager.GetArrayTypes: keep main's IsDynamicCodeSupported dynamic-code fast path; route the AOT path through SafeArrayFactory (generated proxy first, then runtime construction). - JavaConvert.GetJniHandleConverter: replace main's TryGetFactoryBasedConverter (generated-proxy path, removed on this branch) with SafeJavaCollectionFactory; keep main's IsDynamicCodeSupported/ManagedTypeMap else-chain and [RequiresDynamicCode] on TryMakeGenericCollectionTypeFactory. - BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc: kept the branch baseline; MUST be regenerated from a validation build (sizes shift after the merge). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: fddff664-4661-4c06-8922-316731aa2b8d
…roid-trimmable-revisit-arrays-generic-collections
Addresses the two remaining PR review comments: - Use `nameof (elementType)` instead of the string literal in `CreateManagedArrayFromObjectArray`. - Gate the trimmable generic-collection converter on a new `SafeJavaCollectionFactory.IsSupportedCollectionType ()` check that does not resolve (or root) the closed collection type. Previously the gate called `TryGetCollectionType ()` (running `MakeGenericType ()`) purely as a boolean probe, and `TryCreateFromJniHandle ()` then resolved the type again, so `MakeGenericType ()` ran twice per uncached `FromJniHandle ()` marshal for reference generic collections. The type is now resolved at most once (reference collections), or not at all (value collections handled by the rooted `ValueTypeFactory` path). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 155743e0-3f7e-489b-b351-80da73edc7d0
The NativeAOT Java-to-managed array reverse lookup (TrimmableTypeMapTypeManager.GetArrayTypes) fell back to returning only the `T[]` vector when no generated array proxy was present. That drops the JavaObjectArray<T>/JavaArray<T> (reference) and JavaArray<T>/JavaPrimitiveArray<T>/ concrete-primitive (e.g. JavaSByteArray) wrapper types that both the generated proxy and the CoreCLR path return, so marshaling a Java array to those wrapper types would fail to resolve once array proxies are no longer pre-generated. Replace the fallback with BuildRuntimeArrayTypes, which reproduces the generated JavaArrayProxy.GetArrayTypes() contract (see the generator's ModelBuilder.GetArrayTypeReferences) at runtime and AOT-safely: - Primitive leaves use PrimitiveArrayInfo's closed `typeof` tokens. - Reference leaves build [JavaObjectArray<T>, JavaArray<T>, T[]] via canonical (__Canon) MakeGenericType/MakeArrayType, the same rooting the trimmable typemap already relies on for reference generics. - Jagged (rank > 1) mirrors ExpandRankOneTypes. Behavioral note: non-primitive value-type leaves (e.g. Nullable<int>) return only the exact rooted vector (int?[]); JavaObjectArray<valueType> would require an unrooted value-type generic instantiation and the generator never emitted one. Why no test caught this: the existing TryGetArrayProxy_* tests only assert the *proxy* path (gated on generated proxies), and the marshaling round-trip tests only need `T[]`, so the no-proxy runtime fallback was never asserted. BuildRuntimeArrayTypes is a pure function, so the added TrimmableTypeMapTypeManagerTests exercise it directly on every config. Also inline JavaConvert.WithLocalJniHandle in JNIEnv.SetObjectArrayElementFromManagedValue to avoid allocating a capturing closure per element on the SetArrayItem path (matching CopyManagedObjectArray). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 155743e0-3f7e-489b-b351-80da73edc7d0
Address review: the removed ScalarContainerFactories supported byte-element collections (IList<byte>, IDictionary<byte,…>), but ValueTypeFactory only mapped sbyte, so IsSupportedCollectionType returned false for byte and the trimmable GetJniHandleConverter fell through to a silent null — a regression versus the reflection paths (which still handle byte via dynamic MakeGenericType). Add byte and byte? to ValueTypeFactory.PrimitiveTypeFactories. byte marshals to java.lang.Byte bitwise, identical to sbyte, and JavaConvert already has the byte scalar converters, so JavaList<byte>/JavaDictionary<byte,…> round-trip correctly (including values > 127). byte[] arrays are unaffected: they are handled by JNIEnv's dedicated primitive-array path (new byte[len]), which never reaches SafeArrayFactory. Add a FromJniHandle_IListByte round-trip test (with an unsigned-range value). Also document, in ValueTypeFactory.CreateDictionaryWithKey, why value/value dictionaries need no dedicated rooting token: every ValueTypeFactory<X> is statically constructed, CreateDictionary is virtually reachable for all of them (instantiating the GVM CreateDictionaryWithKey<X> for every key X), so NativeAOT's generic-virtual-method dependency analysis roots the full JavaDictionary<X,Y> cross-product over the fixed primitive/nullable set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 155743e0-3f7e-489b-b351-80da73edc7d0
Add complete nullable byte array marshaling, parse generic collection shapes once per converter, and add focused NativeAOT trimmable coverage for value/value dictionary rooting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 98b572c2-766d-43a8-8f1f-3c39df991541
Assert complete runtime array type contracts and exercise collection conversion through JniValueManager without reflection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 98b572c2-766d-43a8-8f1f-3c39df991541
Resolve APK size references using the lower values. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bec709dd-9aac-4675-91e0-b263414a86c3
Fix nullable byte jagged-array JNI signatures, cover the path with runtime tests, and run the full eligible NativeAOT trimmable suite. Stop consuming generated array proxies and the legacy container-factory accessor while retaining generator compatibility for follow-up cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bec709dd-9aac-4675-91e0-b263414a86c3
Use the measurements produced by Azure DevOps build 1508177 after the runtime factory changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bec709dd-9aac-4675-91e0-b263414a86c3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0cae6439-30ce-4c1e-b8f6-b64f3d77d4ad
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
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
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds nullable byte JNI mapping to java.lang.Byte, preserving sbyte? canonicalization and expanding jagged-array coverage.
Changes:
- Adds legacy nullable-byte signature support.
- Adds rank-2/rank-3 type-manager and device tests.
- Adds a NativeAOT trimmable test lane.
Show a summary per file
| File | Summary |
|---|---|
tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs |
Adds jagged-array signature coverage. |
tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/JnienvArrayMarshaling.cs |
Adds nullable-byte array round-trip coverage; legacy-path coverage remains needed. |
external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/JniTypeManagerTests.cs |
Tests nullable-byte signatures and canonicalization. |
external/Java.Interop/src/Java.Interop/Java.Interop/JniBuiltinMarshalers.cs |
Adds nullable-byte mapping; the source template must also be updated. |
build-tools/automation/yaml-templates/stage-package-tests.yaml |
Adds the trimmable test lane; the property name should explicitly select the trimmable typemap. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 3
- Review effort level: Lite
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
The nullable-byte signature mapping and rank-two/rank-three device coverage are directionally good, and all 44 reported checks are green. However, the new reflection type mapping is not paired with a byte? value marshaler, so JavaObjectArray<byte?> now allocates java.lang.Byte[] but attempts to populate it with proxy objects, causing an array-store failure. The inline comment describes the required mapping and regression coverage.
Generated by Android PR Reviewer for #12113 · gpt56 · 143.8 AIC · ⌖ 9.13 AIC · ⊞ 25.7K
Comment /review to run again
Keep the reflection type and value mappings consistent for byte? and preserve unsigned byte values through Java's signed byte representation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9d3a4906-44f6-4c84-8a2f-d56df4c399fe
| configuration: $(XA.Build.Configuration) | ||
| testName: Mono.Android.NET_Tests-NativeAOTTrimmable | ||
| project: tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj | ||
| extraBuildArgs: -p:TestsFlavor=NativeAOTTrimmable -p:PublishAot=true -p:AndroidTypeMapImplementation=trimmable |
There was a problem hiding this comment.
Are these settings already the default for NativeAOT? If so, this is a duplicate test run?
Summary
byte?as a managed-to-JNI signature alias ofsbyte?forjava/lang/Bytein the reflection type manager built-insjava/lang/Byteassbyte?Experiment evidence
Before this change, legacy reverse signature lookup had no built-in entry for
System.Byte?. Looking upbyte?[][]orbyte?[][][]therefore failed, andJNIEnv.NewArray()fell back tojava.lang.Object[][]/java.lang.Object[][][]. The element values still round-tripped. The trimmable manager already returned the intended[[Ljava/lang/Byte;and[[[Ljava/lang/Byte;signatures.Behavior change
Legacy
byte?reverse lookup now shares the existing nullable signed-byte JNI signature, producing exactjava.lang.Bytearray classes at ranks 2 and 3. Primitivebyte/sbytebehavior, Java-to-managedsbyte?canonicalization, and collection roots are unchanged.Tests
dotnet test tests/Java.Interop-Tests/Java.Interop-Tests.csproj --filter FullyQualifiedName~JniTypeManagerTests(6 passed; supplied installed JDK paths)