Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
3fc399e
[TrimmableTypeMap] CoreCLR JavaMarshal value-manager split + JI bump
simonrozsival Jun 29, 2026
3dc1557
Address build failures
simonrozsival Jun 29, 2026
82060ad
Suppress more warnings
simonrozsival Jun 29, 2026
681bc29
[NativeAOT] Use CoreClrJavaMarshalValueManager for default value manager
simonrozsival Jun 29, 2026
f2af1f4
[Mono.Android] Resolve derived interface peers in CoreClrJavaMarshalV…
simonrozsival Jun 29, 2026
7799cbe
[Mono.Android] Share Java interface-hierarchy walk between value/type…
simonrozsival Jun 29, 2026
da1cbe6
[Tests] Update NativeAOT BuildHasNoWarnings baseline 10 -> 6
simonrozsival Jun 29, 2026
f4c9dc9
[Mono.Android] Add JavaInterfaceHierarchy.cs to the compile list
simonrozsival Jun 29, 2026
f0a4bee
Change name back to JavaMarshalValueManager
simonrozsival Jun 30, 2026
255cb33
Minimize PR: drop interface-resolution sub-feature, restore Trimmable…
simonrozsival Jun 30, 2026
26a3e1b
Undo unnecessary changes
simonrozsival Jun 30, 2026
49d36eb
Renive AndroidReflectionJniValueManager (again?)
simonrozsival Jun 30, 2026
1ae71e6
Remove unnecessary changes from JavaMarshalValueManager
simonrozsival Jun 30, 2026
1b002ce
Undo a few more changes
simonrozsival Jun 30, 2026
572badb
Bring back ActivatePeer method
simonrozsival Jun 30, 2026
398e2c7
[TrimmableTypeMap] Add reflection-free TrimmableTypeMapType/ValueManager
simonrozsival Jun 29, 2026
77802e6
Drop formatting/gate/array-API changes; keep managers minimal
simonrozsival Jun 29, 2026
a965153
Include JavaConvert collection factory refactor
simonrozsival Jun 29, 2026
92818fa
Suppress warnings in non-trimmable-typemap codepaths
simonrozsival Jun 29, 2026
cbe18a5
Remove dead TrimmableTypeMap branch from JavaMarshalValueManager
simonrozsival Jun 30, 2026
080b04d
Remove unnecessary overrides from JavaMarshalValueManager
simonrozsival Jun 30, 2026
e97aee4
Remove unnecessary helper class
simonrozsival Jun 30, 2026
e15e6b9
Fix rebase merge artifacts in JavaPeerProxy
simonrozsival Jun 30, 2026
111d1d6
Restore #nullable enable on IntentFilterAttribute.Partial.cs
simonrozsival Jun 30, 2026
a2739ce
Address review suggestions in trimmable managers
simonrozsival Jun 30, 2026
7f5dfa3
Derive JniObjectReferenceOptions bits from public ABI members
simonrozsival Jun 30, 2026
506f7a1
Simplify nullable value type converters
simonrozsival Jun 30, 2026
2e74b42
Remove extra dead code
simonrozsival Jun 30, 2026
1ea9e9b
Add suppressions where appropriate
simonrozsival Jun 30, 2026
35e1244
Suppress more warnings
simonrozsival Jun 30, 2026
dd86e6f
Switch the default typemap for nativeaot
simonrozsival Jun 30, 2026
3920f4a
Emit managed→Java typemap for self-peer types (GenerateJavaPeer=false)
simonrozsival Jun 30, 2026
47a26b3
[tests] Ignore crashing ServerCertificateCustomValidationCallback tests
simonrozsival Jul 1, 2026
36a34d5
Merge branch 'main' into dev/simonrozsival/trimmable-managers
simonrozsival Jul 1, 2026
7cf8de4
Revert "Switch the default typemap for nativeaot"
simonrozsival Jul 1, 2026
f1462fb
Fix managed typemap NativeAOT build: restore ReflectionJniTypeManager…
simonrozsival Jul 1, 2026
005e5b3
Revert all changes to ManagedTypeManager.cs (keep it identical to main)
simonrozsival Jul 1, 2026
8c004a3
Update NativeAOT warning count and CoreCLR apkdiff baseline
simonrozsival Jul 1, 2026
9b6d092
Fix NativeAOT generic-collection marshalling (JavaDictionary<,>.Get I…
simonrozsival Jul 1, 2026
3d0c61e
Exclude TrimmableTypeMapUnsupported tests; try enabling Export tests
simonrozsival Jul 1, 2026
f18ab18
enable export tests for the trimmable typemap
simonrozsival Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ static JniRuntime.JniTypeManager CreateDefaultTypeManager ()
return new TrimmableTypeMapTypeManager ();
}

return new ManagedTypeManager ();
return CreateManagedTypeManager ();

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Managed type manager is preserved by the MarkJavaObjects trimmer step.")]
[UnconditionalSuppressMessage ("Trimming", "IL3050", Justification = "This type manager won't be used in Native AOT builds in the future.")]
static JniRuntime.JniTypeManager CreateManagedTypeManager () => new ManagedTypeManager ();
}

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "CoreCLR value manager is preserved by the MarkJavaObjects trimmer step.")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,14 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName,
if (!isAliasGroup) {
// Single peer — no aliases needed, emit directly with the base JNI name
var peer = peersForName [0];
bool hasProxy = peer.ActivationCtor != null || peer.InvokerTypeName != null;
// A concrete type that supplies its own Java peer ([JniTypeSignature(GenerateJavaPeer=false)]
// or an MCW binding without an activation ctor) is constructed managed-side via `new`, so its
// managed→Java JNI name must still be resolvable in order to instantiate the correct Java class.
// Such types have neither an activation ctor nor an invoker; without a proxy + association they
// fall back to the generic mono.android.runtime.JavaObject peer and throw ArrayStoreException
// when stored into a typed Java array (e.g. CrossReferenceBridge[]).
bool needsManagedToJavaName = peer.DoNotGenerateAcw && !peer.IsInterface && !peer.IsAbstract;
bool hasProxy = peer.ActivationCtor != null || peer.InvokerTypeName != null || needsManagedToJavaName;
bool isAcw = !peer.DoNotGenerateAcw && !peer.IsInterface && peer.MarshalMethods.Count > 0;

JavaPeerProxyData? proxy = null;
Expand Down
61 changes: 22 additions & 39 deletions src/Mono.Android/Android.Runtime/AndroidRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public override string GetCurrentManagedThreadStackTrace (int skipFrames, bool f
if (!reference.IsValid)
return null;
var peeked = JniEnvironment.Runtime.ValueManager.PeekPeer (reference);
if (peeked is JavaProxyThrowable proxyThrowable) {
JniObjectReference.Dispose (ref reference, options);
return proxyThrowable.InnerException;
}
var peekedExc = peeked as Exception;
if (peekedExc == null) {
var throwable = Java.Lang.Object.GetObject<Java.Lang.Throwable> (reference.Handle, JniHandleOwnership.DoNotTransfer);
Expand Down Expand Up @@ -310,10 +314,13 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value)
}
}

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Temporary suppression for Java.Interop reflection manager base.")]
[RequiresDynamicCode ("This type manager is reflection-backed and is not compatible with Native AOT.")]
[RequiresUnreferencedCode ("This type manager is reflection-backed and is not trimming-compatible.")]
class AndroidTypeManager : JniRuntime.ReflectionJniTypeManager {
bool jniAddNativeMethodRegistrationAttributePresent;

const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;

public AndroidTypeManager (bool jniAddNativeMethodRegistrationAttributePresent)
{
this.jniAddNativeMethodRegistrationAttributePresent = jniAddNativeMethodRegistrationAttributePresent;
Expand All @@ -329,7 +336,6 @@ protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpl
yield return t;
}

[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "Temporary suppression until legacy typemap entries carry DAM annotations.")]
protected override Type? GetTypeForSimpleReference (string jniSimpleReference)
{
var type = base.GetTypeForSimpleReference (jniSimpleReference);
Expand All @@ -346,29 +352,30 @@ protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpl
if (j != null) {
return GetReplacementTypeCore (j) ?? j;
}
return base.GetSimpleReference (type);
// Intentionally don't call base.GetSimpleReference(type): Android's
// non-trimmable runtime uses the generated/registered typemap, not
// Java.Interop's JniTypeSignatureAttribute fallback.
return null;
}

protected override IEnumerable<string> GetSimpleReferences (Type type)
{
string? j = JNIEnv.TypemapManagedToJava (type);
j = GetReplacementTypeCore (j) ?? j;
j = GetReplacementTypeCore (j) ?? j;

if (j != null) {
yield return j;
yield break;
}
foreach (var r in base.GetSimpleReferences (type)) {
yield return r;
return [j];
}
// Keep this in sync with GetSimpleReference(): no base fallback.
return [];
}

protected override IReadOnlyList<string>? GetStaticMethodFallbackTypesCore (string jniSimpleReference)
{
return JniRemappingLookup.GetStaticMethodFallbackTypes (jniSimpleReference, useReplacementTypes: true);
}

protected override string? GetReplacementTypeCore (string jniSimpleReference)
protected override string? GetReplacementTypeCore (string? jniSimpleReference)
{
return JniRemappingLookup.GetReplacementType (jniSimpleReference);
}
Expand All @@ -393,8 +400,6 @@ protected override IEnumerable<string> GetSimpleReferences (Type type)
static MethodInfo? dynamic_callback_gen;

// See ExportAttribute.cs
[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Mono.Android.Export.dll is preserved when [Export] is used via [DynamicDependency].")]
[UnconditionalSuppressMessage ("Trimming", "IL2075", Justification = "Mono.Android.Export.dll is preserved when [Export] is used via [DynamicDependency].")]
static Delegate CreateDynamicCallback (MethodInfo method)
{
if (dynamic_callback_gen == null) {
Expand Down Expand Up @@ -489,20 +494,10 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu
}

[Obsolete ("Use RegisterNativeMembers(JniType, Type, ReadOnlySpan<char>) instead.")]
public override void RegisterNativeMembers (
JniType nativeClass,
Type type,
string? methods) =>
public override void RegisterNativeMembers (JniType nativeClass, Type type, string? methods) =>
RegisterNativeMembers (nativeClass, type, methods.AsSpan ());

[UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Type.GetType() can never statically know the string value parsed from parameter 'methods'.")]
[UnconditionalSuppressMessage ("Trimming", "IL2067", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")]
[UnconditionalSuppressMessage ("Trimming", "IL2070", Justification = "GetMethods can never statically know the string value parsed from parameter 'methods'.")]
[UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")]
public override void RegisterNativeMembers (
JniType nativeClass,
Type type,
ReadOnlySpan<char> methods)
public override void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan<char> methods)
{
try {
if (methods.IsEmpty) {
Expand Down Expand Up @@ -586,15 +581,6 @@ public override void RegisterNativeMembers (
} catch (Exception e) {
JniEnvironment.Runtime.RaisePendingException (e);
}

bool ShouldRegisterDynamically (string callbackTypeName, string callbackString, string typeName, string callbackName)
{
if (String.Compare (typeName, callbackTypeName, StringComparison.Ordinal) != 0) {
return false;
}

return String.Compare (callbackName, callbackString, StringComparison.Ordinal) == 0;
}
}

static int CountMethods (ReadOnlySpan<char> methodsSpan)
Expand Down Expand Up @@ -631,7 +617,8 @@ static void SplitMethodLine (
}
}

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Temporary suppression for Java.Interop reflection manager base.")]
[RequiresDynamicCode ("This value manager is reflection-backed and is not compatible with Native AOT.")]
[RequiresUnreferencedCode ("This value manager is reflection-backed and is not trimming-compatible.")]
class AndroidValueManager : JniRuntime.ReflectionJniValueManager {

Dictionary<IntPtr, IdentityHashTargets> instances = new Dictionary<IntPtr, IdentityHashTargets> ();
Expand Down Expand Up @@ -841,11 +828,7 @@ internal void RemovePeer (IJavaPeerable value, IntPtr hash)
return null;
}

public override void ActivatePeer (
JniObjectReference reference,
Type type,
ConstructorInfo cinfo,
object?[]? argumentValues)
public override void ActivatePeer (JniObjectReference reference, Type type, ConstructorInfo cinfo, object?[]? argumentValues)
{
Java.Interop.TypeManager.Activate (reference.Handle, cinfo, argumentValues);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public override Expression CreateReturnValueFromManagedExpression (JniValueMarsh
[RequiresUnreferencedCode (ExpressionRequiresUnreferencedCode)]
public override Expression CreateParameterToManagedExpression (JniValueMarshalerContext context, ParameterExpression sourceValue, ParameterAttributes synchronize, Type? targetType)
{
ArgumentNullException.ThrowIfNull (targetType);

var r = Expression.Variable (targetType, sourceValue.Name + "_val");
context.LocalVariables.Add (r);
context.CreationStatements.Add (
Expand Down
5 changes: 1 addition & 4 deletions src/Mono.Android/Android.Runtime/JNIEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,7 @@ public static IntPtr FindClass (System.Type type)
}
sig = sig.AddArrayRank (rank);

JniObjectReference local_ref = JniEnvironment.Types.FindClass (sig.Name);
IntPtr global_ref = local_ref.NewGlobalRef ().Handle;
JniObjectReference.Dispose (ref local_ref);
return global_ref;
return FindClass (sig.Name);
} catch (Java.Lang.Throwable e) {
if (!((e is Java.Lang.NoClassDefFoundError) || (e is Java.Lang.ClassNotFoundException)))
throw;
Expand Down
42 changes: 27 additions & 15 deletions src/Mono.Android/Android.Runtime/JNIEnvInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,11 @@ static void PropagateUncaughtException (IntPtr env, IntPtr javaThread, IntPtr ja
}

[UnmanagedCallersOnly]
[RequiresUnreferencedCode ("Uses reflection to access System.StartupHookProvider.")]
static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len)
{
// FIXME: https://github.com/xamarin/xamarin-android/issues/8724
[UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Type should be preserved by the MarkJavaObjects trimmer step.")]
[return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes)]
static Type TypeGetType (string typeName) =>
Type.GetType (typeName, throwOnError: false);

string typeName = new string ((char*) typeName_ptr, 0, typeName_len);
var type = TypeGetType (typeName);
var type = Type.GetType (typeName, throwOnError: false);
if (type == null) {
RuntimeNativeMethods.monodroid_log (LogLevel.Error,
LogCategories.Default,
Expand Down Expand Up @@ -158,10 +153,14 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
args->propagateUncaughtExceptionFn = (IntPtr)(delegate* unmanaged<IntPtr, IntPtr, IntPtr, void>)&PropagateUncaughtException;

if (!RuntimeFeature.TrimmableTypeMap) {
args->registerJniNativesFn = (IntPtr)(delegate* unmanaged<IntPtr, int, IntPtr, IntPtr, int, void>)&RegisterJniNatives;
args->registerJniNativesFn = GetRegisterJniNativesFnPtr ();
}
RunStartupHooksIfNeeded ();
SetSynchronizationContext ();

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "This method is never used with the trimmable type map.")]
IntPtr GetRegisterJniNativesFnPtr () =>
(IntPtr)(delegate* unmanaged<IntPtr, int, IntPtr, IntPtr, int, void>)&RegisterJniNatives;
}

[LibraryImport (RuntimeConstants.InternalDllName)]
Expand All @@ -175,16 +174,28 @@ internal static JniRuntime.JniTypeManager CreateTypeManager (JnienvInitializeArg
}

if (RuntimeFeature.IsNativeAotRuntime || RuntimeFeature.ManagedTypeMap) {
return new ManagedTypeManager ();
return CreateManagedTypeManager ();
}

return new AndroidTypeManager (args.jniAddNativeMethodRegistrationAttributePresent != 0);
return CreateAndroidTypeManager (args);

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Managed type manager is preserved by the MarkJavaObjects trimmer step.")]
[UnconditionalSuppressMessage ("Trimming", "IL3050", Justification = "This type manager won't be used in Native AOT builds in the future.")]
static JniRuntime.JniTypeManager CreateManagedTypeManager () => new ManagedTypeManager ();

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "This type manager won't be used in Native AOT builds.")]
[UnconditionalSuppressMessage ("Trimming", "IL3050", Justification = "This type manager won't be used in Native AOT builds.")]
static JniRuntime.JniTypeManager CreateAndroidTypeManager (JnienvInitializeArgs args) => new AndroidTypeManager (args.jniAddNativeMethodRegistrationAttributePresent != 0);
}

internal static JniRuntime.JniValueManager CreateValueManager ()
{
if (RuntimeFeature.TrimmableTypeMap) {
return new TrimmableTypeMapValueManager ();
}

if (RuntimeFeature.IsMonoRuntime) {
return new AndroidValueManager ();
return CreateAndroidValueManager ();
}

if (RuntimeFeature.IsCoreClrRuntime) {
Expand All @@ -199,10 +210,11 @@ internal static JniRuntime.JniValueManager CreateValueManager ()

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "CoreCLR value manager is preserved by the MarkJavaObjects trimmer step.")]
[UnconditionalSuppressMessage ("Trimming", "IL3050", Justification = "This value manager won't be used in Native AOT builds in the future.")]
JniRuntime.JniValueManager CreateJavaMarshalValueManager ()
{
return new JavaMarshalValueManager ();
}
JniRuntime.JniValueManager CreateJavaMarshalValueManager () => new JavaMarshalValueManager ();

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Mono value manager is preserved by the MarkJavaObjects trimmer step.")]
[UnconditionalSuppressMessage ("Trimming", "IL3050", Justification = "This value manager won't be used in Native AOT builds in the future.")]
JniRuntime.JniValueManager CreateAndroidValueManager () => new AndroidValueManager ();
}

static void InitializeCommonState (JnienvInitializeArgs args)
Expand Down
1 change: 1 addition & 0 deletions src/Mono.Android/Android.Runtime/JNINativeWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ static void get_runtime_types ()
AndroidEnvironment.FailFast ("Cannot find AndroidRuntimeInternal.WaitForBridgeProcessing");
}

[RequiresDynamicCode ("This method uses System.Reflection.Emit to create a delegate at runtime.")]
public static Delegate CreateDelegate (Delegate dlg)
{
if (dlg == null)
Expand Down
4 changes: 2 additions & 2 deletions src/Mono.Android/Android.Runtime/JavaCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public IEnumerator GetEnumerator ()
if (handle == IntPtr.Zero)
return null;

var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle);
var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle, typeof (ICollection));
if (inst == null)
inst = new JavaCollection (handle, transfer);
else
Expand Down Expand Up @@ -399,7 +399,7 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
if (handle == IntPtr.Zero)
return null;

var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle);
var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle, typeof (ICollection<T>));
if (inst == null)
inst = new JavaCollection<T> (handle, transfer);
else
Expand Down
4 changes: 2 additions & 2 deletions src/Mono.Android/Android.Runtime/JavaDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public void Remove (object key)
if (handle == IntPtr.Zero)
return null;

var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle);
var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle, typeof (IDictionary));
if (inst == null)
inst = new JavaDictionary (handle, transfer);
else
Expand Down Expand Up @@ -645,7 +645,7 @@ public bool TryGetValue (K key, out V value)
if (handle == IntPtr.Zero)
return null;

var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle);
var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle, typeof (IDictionary<K, V>));
if (inst == null)
inst = new JavaDictionary<K, V> (handle, transfer);
else
Expand Down
2 changes: 1 addition & 1 deletion src/Mono.Android/Android.Runtime/JavaList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ public virtual unsafe JavaList SubList (int start, int end)
if (handle == IntPtr.Zero)
return null;

var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle);
var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle, typeof (IList));
if (inst == null)
inst = new JavaList (handle, transfer);
else
Expand Down
4 changes: 2 additions & 2 deletions src/Mono.Android/Android.Runtime/JavaSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public void Remove (object? item)
if (handle == IntPtr.Zero)
return null;

var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle);
var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle, typeof (ICollection));
if (inst == null)
inst = new JavaSet (handle, transfer);
else
Expand Down Expand Up @@ -431,7 +431,7 @@ public bool Remove (T item)
if (handle == IntPtr.Zero)
return null;

var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle);
var inst = (IJavaObject?) Java.Lang.Object.PeekObject (handle, typeof (ICollection<T>));
if (inst == null)
inst = new JavaSet<T> (handle, transfer);
else
Expand Down
Loading
Loading