Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c1f136f
[Mono.Android] Move Mono.Android.Export rooting off the shared Export…
simonrozsival Jun 29, 2026
35489d9
Drop now-redundant IL2026 suppression on AndroidTypeManager
simonrozsival Jun 29, 2026
98e0128
Address review: cover [ExportField], remove stale apicompat file, cle…
simonrozsival Jun 29, 2026
18db45c
[tests] Update BuildReleaseArm64SimpleDotNet.CoreCLR.apkdesc
simonrozsival Jun 29, 2026
002093e
Merge remote-tracking branch 'origin/main' into dev/simonrozsival/exp…
simonrozsival Jun 30, 2026
c1214ef
[tests] Update BuildReleaseArm64 CoreCLR apkdesc for Export rooting
simonrozsival Jun 30, 2026
ff1b627
[Mono.Android] Pin only DynamicCallbackCodeGenerator.Create, not All
simonrozsival Jun 30, 2026
52f2ca4
[Mono.Android] Use trimmer-analyzable Type.GetType, drop [DynamicDepe…
simonrozsival Jun 30, 2026
d4c3b16
[Mono.Android] Chain Type.GetType()?.GetMethod() for trimmer dataflow
simonrozsival Jun 30, 2026
117fc46
Simplify
simonrozsival Jun 30, 2026
ffeaf45
Try to allow trimming Mono.Android.Export when it's not needed
simonrozsival Jul 1, 2026
1863854
Change exception message
simonrozsival Jul 1, 2026
a02928f
Merge branch 'main' into dev/simonrozsival/export-dynamic-dependency
simonrozsival Jul 1, 2026
058569e
Merge branch 'main' into dev/simonrozsival/export-dynamic-dependency
simonrozsival Jul 1, 2026
d99cf6a
Make CreateDynamicDelegate properly trimmable
simonrozsival Jul 2, 2026
558f094
Merge branch 'dev/simonrozsival/export-dynamic-dependency' of github.…
simonrozsival Jul 2, 2026
a3cb051
Merge remote-tracking branch 'origin/main' into dev/simonrozsival/exp…
simonrozsival Jul 2, 2026
ca5dc52
[tests] Use smaller apkdesc sizes for SimpleDotNet.CoreCLR
simonrozsival Jul 3, 2026
1c94329
Code cleanup
simonrozsival Jul 3, 2026
debe97a
Extend support to ExportFieldAttribute
simonrozsival Jul 3, 2026
26aab7d
Clarify Mono.Android.Export load failure diagnostic
simonrozsival Jul 3, 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
25 changes: 5 additions & 20 deletions src/Mono.Android/Android.Runtime/AndroidRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -397,25 +397,6 @@ protected override IEnumerable<string> GetSimpleReferences (Type type)

delegate Delegate GetCallbackHandler ();

static MethodInfo? dynamic_callback_gen;

// See ExportAttribute.cs
static Delegate CreateDynamicCallback (MethodInfo method)
{
if (dynamic_callback_gen == null) {
var assembly = Assembly.Load ("Mono.Android.Export");
if (assembly == null)
throw new InvalidOperationException ("To use methods marked with ExportAttribute, Mono.Android.Export.dll needs to be referenced in the application");
var type = assembly.GetType ("Java.Interop.DynamicCallbackCodeGenerator");
if (type == null)
throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required type was not found.");
dynamic_callback_gen = type.GetMethod ("Create");
if (dynamic_callback_gen == null)
throw new InvalidOperationException ("The referenced Mono.Android.Export.dll does not match the expected version. The required method was not found.");
}
return (Delegate)dynamic_callback_gen.Invoke (null, new object [] { method })!;
}

// [Export] callback delegates are created dynamically via DynamicCallbackCodeGenerator and are not
// cached in static fields (unlike non-[Export] connector delegates). Without rooting them here,
// CoreCLR's GC can collect them between JNI registration and first invocation, causing a crash.
Expand Down Expand Up @@ -547,7 +528,11 @@ public override void RegisterNativeMembers (JniType nativeClass, Type type, Read

if (minfo == null)
throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' was not found. Signature: {signature.ToString ()}"));
callback = CreateDynamicCallback (minfo);

var exportAttribute = minfo.GetCustomAttribute<BaseExportAttribute> ()
?? throw new InvalidOperationException (FormattableString.Invariant ($"Specified managed method '{mname.ToString ()}' does not have [Export] attribute. Signature: {signature.ToString ()}"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 💡 Error handling — This guard fires for both [Export] and [ExportField] methods (the __export__ connector is emitted for both, and BaseExportAttribute is their shared base), but the message only names [Export]. BaseExportAttribute.CreateDynamicCallbackCore already phrases its diagnostic as "[Export] or [ExportField]"; consider matching that here so an [ExportField] failure isn't misdiagnosed.

Rule: Keep error diagnostics accurate and consistent


callback = exportAttribute.CreateDynamicCallback (minfo);
lock (prevent_delegate_gc_lock) {
prevent_delegate_gc.Add (callback);
}
Expand Down
1 change: 1 addition & 0 deletions src/Mono.Android/Android.Runtime/JNIEnvInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ IntPtr GetRegisterJniNativesFnPtr () =>
[UnmanagedCallConv (CallConvs = new[] { typeof (CallConvCdecl) })]
private static unsafe partial void xamarin_app_init (IntPtr env, delegate* unmanaged <int, int, int, IntPtr*, void> get_function_pointer);

[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "The AndroidTypeManager branch is only reached when RuntimeFeature.TrimmableTypeMap is false; the linker substitutes the feature switch and trims this branch in trimmable apps.")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 💡 Trimmer/AOT — This method-level IL2026 suppression looks redundant. The AndroidTypeManager branch it justifies is reached only through the CreateAndroidTypeManager local function below (line ~189), which already carries its own IL2026 suppression — and the parallel CreateValueManager method uses the same local-function-with-suppression pattern without a method-level attribute. Since this PR doesn't change CreateTypeManager's body or introduce a new [RequiresUnreferencedCode] call reachable outside those local functions, is the outer suppression actually necessary? If the local-function suppression turned out to be insufficient here, a one-line note would help; otherwise consider dropping it for consistency with CreateValueManager.

Rule: Trimming suppressions should be minimal and scoped to the actual warning site

internal static JniRuntime.JniTypeManager CreateTypeManager (JnienvInitializeArgs args)
{
if (RuntimeFeature.TrimmableTypeMap) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net
REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool
Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void
Java.Interop.BaseExportAttribute
Java.Interop.JavaArrayProxy
abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array!
abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]!
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net
REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool
Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void
Java.Interop.BaseExportAttribute
Java.Interop.JavaArrayProxy
abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array!
abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]!
Original file line number Diff line number Diff line change
Expand Up @@ -4401,6 +4401,7 @@ virtual Java.Util.IdentityHashMap.Remove(Java.Lang.Object? key, Java.Lang.Object
virtual Java.Util.IdentityHashMap.Replace(Java.Lang.Object? key, Java.Lang.Object? oldValue, Java.Lang.Object? newValue) -> bool
Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool
Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void
Java.Interop.BaseExportAttribute
Java.Interop.JavaArrayProxy
abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array!
abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]!
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.SetupRequest(System.Net
REMOVED virtual Xamarin.Android.Net.AndroidClientHandler.WriteRequestContentToOutput(System.Net.Http.HttpRequestMessage! request, Java.Net.HttpURLConnection! httpConnection, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!
Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.get -> bool
Android.App.ApplicationAttribute.EnableOnBackInvokedCallback.set -> void
Java.Interop.BaseExportAttribute
Java.Interop.JavaArrayProxy
abstract Java.Interop.JavaArrayProxy.CreateManagedArray(int length) -> System.Array!
abstract Java.Interop.JavaArrayProxy.GetArrayTypes() -> System.Type![]!
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
"Size": 2717688
},
"lib/arm64-v8a/libclrjit.so": {
"Size": 2836936
"Size": 2835128
},
"lib/arm64-v8a/libcoreclr.so": {
"Size": 4890752
"Size": 4891056
},
"lib/arm64-v8a/libmonodroid.so": {
"Size": 1264112
"Size": 1324320
},
"lib/arm64-v8a/libSystem.Globalization.Native.so": {
"Size": 72112
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Reflection;

namespace Java.Interop;

#if !JCW_ONLY_TYPE_NAMES
public
#endif // !JCW_ONLY_TYPE_NAMES
abstract class BaseExportAttribute : Attribute
{
internal abstract Delegate CreateDynamicCallback (MethodInfo method);

static MethodInfo? dynamic_callback_gen;

private protected static Delegate CreateDynamicCallbackCore (MethodInfo method)
{
// We're loading the Mono.Android.Export assembly dynamically to avoid problems with circular dependencies.
// The Type.GetType (...)?.GetMethod (...) call is chained intentionally: the trimmer only recognizes the
// GetMethod intrinsic when the constant Type value flows directly into it, so routing it through a local breaks dataflow.
dynamic_callback_gen ??= Type.GetType ("Java.Interop.DynamicCallbackCodeGenerator, Mono.Android.Export")?.GetMethod ("Create")
?? throw new InvalidOperationException ("To use methods marked with [Export] or [ExportField], Mono.Android.Export.dll must be referenced by the application and contain a matching Java.Interop.DynamicCallbackCodeGenerator.Create method.");

return (Delegate?)dynamic_callback_gen.Invoke (null, [method])
?? throw new InvalidOperationException (FormattableString.Invariant ($"Unable to create dynamic callback for method '{method.Name}' on type '{method.DeclaringType?.FullName}'"));
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace Java.Interop {

[Serializable]
[AttributeUsage (AttributeTargets.Method | AttributeTargets.Constructor,
AllowMultiple=false,
Inherited=false)]
#if !NETSTANDARD2_0
[RequiresUnreferencedCode ("[ExportAttribute] uses dynamic features.")]
#endif
#if !JCW_ONLY_TYPE_NAMES
public
#endif // !JCW_ONLY_TYPE_NAMES
partial class ExportAttribute : Attribute {
partial class ExportAttribute : BaseExportAttribute {

[DynamicDependency (DynamicallyAccessedMemberTypes.All, "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")]
public ExportAttribute ()
{
}

[DynamicDependency (DynamicallyAccessedMemberTypes.All, "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")]
public ExportAttribute (string name)
{
Name = name;
Expand All @@ -30,6 +25,11 @@ public ExportAttribute (string name)
public string? SuperArgumentsString {get; set;}
public Type []? Throws {get; set;}
internal string []? ThrownNames {get; set;} // msbuild internal use

internal override Delegate CreateDynamicCallback (MethodInfo method)
{
return CreateDynamicCallbackCore (method);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace Java.Interop {

Expand All @@ -10,16 +10,18 @@ namespace Java.Interop {
#if !JCW_ONLY_TYPE_NAMES
public
#endif // !JCW_ONLY_TYPE_NAMES
partial class ExportFieldAttribute : Attribute {
partial class ExportFieldAttribute : BaseExportAttribute {

[DynamicDependency (DynamicallyAccessedMemberTypes.All, "Java.Interop.DynamicCallbackCodeGenerator", "Mono.Android.Export")]
public ExportFieldAttribute (string name)
{
Name = name;
}

public string Name {get; set;}

internal override Delegate CreateDynamicCallback (MethodInfo method)
{
return CreateDynamicCallbackCore (method);
}
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Android.Content\ContentProviderAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Android.Content\ContentProviderAttribute.Partial.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Android.Runtime\RegisterAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\BaseExportAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\ExportAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\ExportFieldAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Java.Interop\ExportParameterAttribute.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ MembersMustExist : Member 'public void Android.Telecom.CallControl.RequestVideoS
TypesMustExist : Type 'Android.Text.IInputType' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Xamarin.Android.Net.AndroidClientHandler' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Android.Views.InputMethods.EditorInfo' does not implement interface 'Android.Text.IInputType' in the implementation but it does in the contract.
CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute' exists on 'Java.Interop.ExportAttribute' in the contract but not the implementation.
CannotRemoveAttribute : Attribute 'System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute' exists on parameter 'targetType' on member 'Android.Graphics.ColorValueMarshaler.CreateGenericValue(Java.Interop.JniObjectReference, Java.Interop.JniObjectReferenceOptions, System.Type)' in the contract but not the implementation.
Loading