Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
579ea8a
Bump external/Java.Interop from `b881d21` to `d7dbad5`
dependabot[bot] Jun 10, 2026
f79c0cc
Implement Android JavaMarshal value manager split
simonrozsival Jun 9, 2026
676dca4
Use pure trimmable typemap value manager
simonrozsival Jun 9, 2026
01dfd04
Use pure trimmable typemap type manager
simonrozsival Jun 9, 2026
b77c841
Propagate trimmable typemap DAM annotations
simonrozsival Jun 9, 2026
0554d57
Prefer Requires annotations for reflection managers
simonrozsival Jun 9, 2026
7d81397
Remove manager suppression attributes
simonrozsival Jun 9, 2026
1fe2ef5
Remove invoker lookup suppression attributes
simonrozsival Jun 9, 2026
7228982
Use feature guards for runtime manager selection
simonrozsival Jun 9, 2026
326f0fd
Inline CoreCLR JavaMarshal peer delegation
simonrozsival Jun 9, 2026
6a8ec68
Throw unreachable from trimmable native registration
simonrozsival Jun 9, 2026
7d3d1e3
Remove SimpleValueManager
simonrozsival Jun 10, 2026
725d433
Simplify changes to the ManagedTypeManager
simonrozsival Jun 10, 2026
caaea85
Remove the option to use ManagedTypeManager
simonrozsival Jun 10, 2026
b489d1f
Cleanup value managers
simonrozsival Jun 10, 2026
f1ce2a2
Refine trimmable typemap runtime paths
simonrozsival Jun 10, 2026
0a1072a
Reuse Java.Interop value marshalers in trimmable runtime
simonrozsival Jun 11, 2026
78cb7d6
Support JavaObjectArray in trimmable value marshaling
simonrozsival Jun 11, 2026
169bc8f
Generalize trimmable primitive value marshaling
simonrozsival Jun 11, 2026
869f183
Apply trimmable test exclusions to new runner
simonrozsival Jun 11, 2026
93ddc73
Remove LayoutInflater test contamination
simonrozsival Jun 11, 2026
933a38d
Address trimmable review cleanup
simonrozsival Jun 11, 2026
65a2cf5
Flow excluded NUnit categories from MSBuild
simonrozsival Jun 11, 2026
6b4aedb
Extract trimmable primitive marshaler helpers
simonrozsival Jun 11, 2026
80997b8
Address trimmable review feedback
simonrozsival Jun 11, 2026
6071fd0
Support trimmable custom value marshalers
simonrozsival Jun 11, 2026
d45a519
Unwrap trimmable proxy throwables
simonrozsival Jun 11, 2026
4282882
Support trimmable type manager lookups
simonrozsival Jun 11, 2026
152845b
Use trimmable CrossReferenceBridge fixture
simonrozsival Jun 11, 2026
8105789
Use trimmable method binding fixtures
simonrozsival Jun 11, 2026
7a7ca23
Support trimmable marshaler expressions
simonrozsival Jun 11, 2026
fece7d7
Remove generated value marshaler registry
simonrozsival Jun 11, 2026
b134621
Address trimmable test review feedback
simonrozsival Jun 11, 2026
6e1a69a
Fix trimmable typemap CI failures
simonrozsival Jun 12, 2026
8447f63
Simplify generated proxy trim suppression
simonrozsival Jun 12, 2026
18f39bc
Address trimmable runtime review feedback
simonrozsival Jun 12, 2026
7496a32
Simplify trimmable marshaler integration
simonrozsival Jun 12, 2026
f2c59bd
Re-enable trimmable GC bridge coverage
simonrozsival Jun 12, 2026
298f262
Re-enable trimmable replacement method lookup test
simonrozsival Jun 12, 2026
7ab9659
Re-enable trimmable method binding coverage
simonrozsival Jun 12, 2026
12946a7
Remove JavaProxyThrowable Exception wrapper
simonrozsival Jun 12, 2026
1cae90b
Remove ManagedPeer workarounds from trimmable PR
simonrozsival Jun 12, 2026
a53fb70
Document built-in type signature mapping choice
simonrozsival Jun 12, 2026
25a0526
Split Java marshal value manager types
simonrozsival Jun 12, 2026
a5b39d1
Simplify trimmable object array marshaling
simonrozsival Jun 12, 2026
5dff3a8
Return object references from trimmable marshaling
simonrozsival Jun 12, 2026
f928f99
Clarify trimmable local reference ownership
simonrozsival Jun 12, 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
2 changes: 1 addition & 1 deletion external/Java.Interop
Submodule Java.Interop updated 144 files
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<type fullname="Android.Runtime.RaiseThrowableEventArgs" />
<type fullname="Android.Runtime.RegisterAttribute" />
<type fullname="Android.Runtime.TypeManager" />
<type fullname="Microsoft.Android.Runtime.ManagedTypeMapping" preserve="all" />
<!--
<type fullname="Android.Runtime.XmlResourceParserReader" />
<type fullname="Android.Runtime.XmlPullParserReader" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,17 +208,17 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A
continue;
}

// [JniAddNativeMethodRegistrationAttribute] is not supported by the trimmable typemap
// by design (see XA4251). Detect the attribute *before* any per-type filters below
// (array type, no JNI name, etc.) so the diagnostic fires uniformly regardless of
// whether the type would otherwise have ended up in the typemap.
//
// Skip the per-method walk entirely for the overwhelmingly common case where
// the assembly doesn't even reference the attribute type — the per-assembly
// flag was computed cheaply in AssemblyIndex.Build.
if (index.MayUseJniAddNativeMethodRegistrationAttribute &&
var fullName = MetadataTypeNameResolver.GetFullName (typeDef, index.Reader);

// Framework/runtime assemblies contain internal [JniAddNativeMethodRegistration]
// users such as Java.Interop.JavaProxyObject and Java.Interop.ManagedPeer.
// The diagnostic is for user assemblies because the trimmable runtime either
// has generated replacements for framework registration or intentionally
// disables unsupported runtime paths.
if (!frameworkAssemblyNames.Contains (index.AssemblyName) &&
index.MayUseJniAddNativeMethodRegistrationAttribute &&
HasJniAddNativeMethodRegistrationAttribute (typeDef, index)) {
logger?.LogJniAddNativeMethodRegistrationAttributeError (MetadataTypeNameResolver.GetFullName (typeDef, index.Reader));
logger?.LogJniAddNativeMethodRegistrationAttributeError (fullName);
}

// Determine the JNI name and whether this is a known Java peer.
Expand Down Expand Up @@ -261,8 +261,6 @@ void ScanAssembly (AssemblyIndex index, Dictionary<(string ManagedName, string A
}
}

var fullName = MetadataTypeNameResolver.GetFullName (typeDef, index.Reader);

var isInterface = (typeDef.Attributes & TypeAttributes.Interface) != 0;
var isAbstract = (typeDef.Attributes & TypeAttributes.Abstract) != 0;
var isGenericDefinition = typeDef.GetGenericParameters ().Count > 0;
Expand Down
17 changes: 10 additions & 7 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,7 +314,9 @@ public override void DeleteWeakGlobalReference (ref JniObjectReference value)
}
}

class AndroidTypeManager : JniRuntime.JniTypeManager {
[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;
Expand Down Expand Up @@ -385,8 +391,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 @@ -488,9 +492,6 @@ public override void RegisterNativeMembers (
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", "IL2072", Justification = "Delegate.CreateDelegate() can never statically know the string value parsed from parameter 'methods'.")]
public override void RegisterNativeMembers (
JniType nativeClass,
[DynamicallyAccessedMembers (MethodsAndPrivateNested)] Type type,
Expand Down Expand Up @@ -623,7 +624,9 @@ static void SplitMethodLine (
}
}

class AndroidValueManager : JniRuntime.JniValueManager {
[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
28 changes: 20 additions & 8 deletions src/Mono.Android/Android.Runtime/JNIEnvInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,25 +174,37 @@ internal static JniRuntime.JniTypeManager CreateTypeManager (JnienvInitializeArg
return new TrimmableTypeMapTypeManager ();
}

if (RuntimeFeature.IsNativeAotRuntime || RuntimeFeature.ManagedTypeMap) {
return new ManagedTypeManager ();
if (RuntimeFeature.IsNativeAotRuntime) {
throw new NotSupportedException ($"{nameof (RuntimeFeature.IsNativeAotRuntime)} requires {nameof (RuntimeFeature.TrimmableTypeMap)}.");
}

return new AndroidTypeManager (args.jniAddNativeMethodRegistrationAttributePresent != 0);
if (RuntimeFeature.IsMonoRuntime) {
return new AndroidTypeManager (args.jniAddNativeMethodRegistrationAttributePresent != 0);
}

if (RuntimeFeature.IsCoreClrRuntime) {
return new AndroidTypeManager (args.jniAddNativeMethodRegistrationAttributePresent != 0);
}

throw new NotSupportedException ("Internal error: unknown runtime not supported");
}

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

if (RuntimeFeature.IsNativeAotRuntime) {
throw new NotSupportedException ($"Native AOT builds require using {nameof (RuntimeFeature.TrimmableTypeMap)}.");
}

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

if (RuntimeFeature.IsCoreClrRuntime) {
return new JavaMarshalValueManager ();
}

if (RuntimeFeature.IsNativeAotRuntime) {
return new JavaMarshalValueManager ();
return new CoreClrJavaMarshalValueManager ();
}

throw new NotSupportedException ("Internal error: unknown runtime not supported");
Expand Down
1 change: 0 additions & 1 deletion src/Mono.Android/Android.Runtime/JavaProxyThrowable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
namespace Android.Runtime {

sealed class JavaProxyThrowable : Java.Lang.Error {

public readonly Exception InnerException;

JavaProxyThrowable (string message, Exception innerException)
Expand Down
27 changes: 27 additions & 0 deletions src/Mono.Android/Java.Interop/JavaConvert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace Java.Interop {

static class JavaConvert {
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
// Mirrors JniObjectReference.DisposeSource; JniObjectReferenceOptions only exposes it through CopyAndDispose.
const JniObjectReferenceOptions DisposeSource = (JniObjectReferenceOptions)(1 << 1);

static Dictionary<Type, Func<IntPtr, JniHandleOwnership, object>> JniHandleConverters = new Dictionary<Type, Func<IntPtr, JniHandleOwnership, object>>() {
{ typeof (bool), (handle, transfer) => {
Expand Down Expand Up @@ -295,6 +297,31 @@ public static T? FromJniHandle<
return (T?) Convert.ChangeType (v, typeof (T), CultureInfo.InvariantCulture);
}

internal static object? FromObjectReference (
ref JniObjectReference reference,
JniObjectReferenceOptions options,
[DynamicallyAccessedMembers (Constructors)]
Type? targetType = null)
{
JniHandleOwnership transfer;
if ((options & DisposeSource) != DisposeSource) {
transfer = JniHandleOwnership.DoNotTransfer;
} else {
transfer = reference.Type switch {
JniObjectReferenceType.Local => JniHandleOwnership.TransferLocalRef,
JniObjectReferenceType.Global => JniHandleOwnership.TransferGlobalRef,
_ => JniHandleOwnership.DoNotTransfer,
};
}

var value = FromJniHandle (reference.Handle, transfer, targetType);
if (transfer != JniHandleOwnership.DoNotTransfer) {
reference = default;
}

return value;
}

public static object? FromJniHandle (
IntPtr handle,
JniHandleOwnership transfer,
Expand Down
28 changes: 5 additions & 23 deletions src/Mono.Android/Java.Interop/JavaObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,42 +108,24 @@ internal static TResult? _JavaCast<
// typeof(Foo) -> FooInvoker
// typeof(Foo<>) -> FooInvoker`1
[return: DynamicallyAccessedMembers (Constructors)]
[RequiresDynamicCode ("Invoker lookup can construct generic invoker types.")]
[RequiresUnreferencedCode ("Invoker lookup uses reflection over preserved Java peer types.")]
internal static Type? GetInvokerType (Type type)
{
const string InvokerTypes = "*Invoker types are preserved by the MarkJavaObjects linker step.";

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = InvokerTypes)]
[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = InvokerTypes)]
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = InvokerTypes)]
[return: DynamicallyAccessedMembers (Constructors)]
static Type? AssemblyGetType (Assembly assembly, string typeName) =>
assembly.GetType (typeName);

// FIXME: https://github.com/xamarin/xamarin-android/issues/8724
// IL3050 disabled in source: if someone uses NativeAOT, they will get the warning.
[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = InvokerTypes)]
[UnconditionalSuppressMessage ("Trimming", "IL2068", Justification = InvokerTypes)]
[return: DynamicallyAccessedMembers (Constructors)]
static Type MakeGenericType (Type type, params Type [] typeArguments) =>
#pragma warning disable IL3050
type.MakeGenericType (typeArguments);
#pragma warning restore IL3050

const string suffix = "Invoker";

Type[] arguments = type.GetGenericArguments ();
if (arguments.Length == 0)
return AssemblyGetType (type.Assembly, type + suffix);
return type.Assembly.GetType (type + suffix);
Type definition = type.GetGenericTypeDefinition ();
int bt = definition.FullName!.IndexOf ("`", StringComparison.Ordinal);
if (bt == -1)
throw new NotSupportedException ("Generic type doesn't follow generic type naming convention! " + type.FullName);
Type? suffixDefinition = AssemblyGetType (
definition.Assembly,
Type? suffixDefinition = definition.Assembly.GetType (
definition.FullName.Substring (0, bt) + suffix + definition.FullName.Substring (bt));
if (suffixDefinition == null)
return null;
return MakeGenericType (suffixDefinition, arguments);
return suffixDefinition.MakeGenericType (arguments);
}
}
}
30 changes: 26 additions & 4 deletions src/Mono.Android/Java.Interop/JavaPeerProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,24 @@ public sealed class JavaPeerAliasesAttribute : Attribute
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
public abstract class JavaPeerProxy : Attribute
{
const DynamicallyAccessedMemberTypes MethodsConstructors =
DynamicallyAccessedMemberTypes.PublicMethods |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicConstructors |
DynamicallyAccessedMemberTypes.NonPublicConstructors;

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

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Generated proxy constructors pass compile-time-known type tokens to the proxy base constructor.")]
[UnconditionalSuppressMessage ("Trimming", "IL2111", Justification = "Generated proxy constructors pass compile-time-known type tokens to the proxy base constructor.")]
protected JavaPeerProxy (
string jniName,
[DynamicallyAccessedMembers (MethodsConstructors)]
Type targetType,
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
[DynamicallyAccessedMembers (Constructors)]
Type? invokerType)
{
JniName = jniName ?? throw new ArgumentNullException (nameof (jniName));
Expand All @@ -70,13 +84,14 @@ protected JavaPeerProxy (
/// <summary>
/// Gets the target .NET type that this proxy represents.
/// </summary>
[DynamicallyAccessedMembers (MethodsConstructors)]
public Type TargetType { get; }

/// <summary>
/// Gets the invoker type for interfaces and abstract classes.
/// Returns null for concrete types that can be directly instantiated.
/// </summary>
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
[DynamicallyAccessedMembers (Constructors)]
public Type? InvokerType { get; }

/// <summary>
Expand Down Expand Up @@ -143,13 +158,20 @@ static bool IsActivationPeer (IJavaPeerable peer)
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
public abstract class JavaPeerProxy<
// TODO (https://github.com/dotnet/android/issues/10794): Remove this DAM annotation
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
[DynamicallyAccessedMembers (
DynamicallyAccessedMemberTypes.PublicMethods |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.NonPublicNestedTypes |
DynamicallyAccessedMemberTypes.PublicConstructors |
DynamicallyAccessedMemberTypes.NonPublicConstructors)]
T
> : JavaPeerProxy where T : class, IJavaPeerable
{
[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Generated proxy constructors pass compile-time-known type tokens to the proxy base constructor.")]
[UnconditionalSuppressMessage ("Trimming", "IL2111", Justification = "Generated proxy constructors pass compile-time-known type tokens to the proxy base constructor.")]
protected JavaPeerProxy (
string jniName,
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
[DynamicallyAccessedMembers (Constructors)]
Type? invokerType) : base (jniName, typeof (T), invokerType)
{
}
Expand Down
6 changes: 4 additions & 2 deletions src/Mono.Android/Java.Interop/TypeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,15 @@ static Type monovm_typemap_java_to_managed (string java_type_name)
return null;
}

[RequiresDynamicCode ("Legacy type manager peer creation can construct generic invoker types.")]
[RequiresUnreferencedCode ("Legacy type manager peer creation uses reflection over preserved Java peer types.")]
internal static IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer)
{
return CreateInstance (handle, transfer, null);
}

[UnconditionalSuppressMessage ("Trimming", "IL2067", Justification = "TypeManager.CreateProxy() does not statically know the value of the 'type' local variable.")]
[UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = "TypeManager.CreateProxy() does not statically know the value of the 'type' local variable.")]
[RequiresDynamicCode ("Legacy type manager peer creation can construct generic invoker types.")]
[RequiresUnreferencedCode ("Legacy type manager peer creation uses reflection over preserved Java peer types.")]
internal static IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer, Type? targetType)
{
Type? type = null;
Expand Down
Loading
Loading