[Java.Interop] Map nullable byte jagged arrays to java.lang.Byte#12113
Draft
simonrozsival wants to merge 24 commits into
Draft
[Java.Interop] Map nullable byte jagged arrays to java.lang.Byte#12113simonrozsival wants to merge 24 commits into
simonrozsival wants to merge 24 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
Base automatically changed from
dev/simonrozsival/android-trimmable-revisit-arrays-generic-collections
to
main
July 15, 2026 20:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)