diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index d9901e64db0..a5383701a1e 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -310,7 +310,7 @@ protected virtual bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (tr public JniValueMarshaler GetValueMarshaler () => GetValueMarshalerCore (); protected abstract JniValueMarshaler GetValueMarshalerCore (); - internal JniObjectReference CreateLocalObjectReferenceArgument (Type type, object? value) + public JniObjectReference CreateLocalObjectReferenceArgument (Type type, object? value) { EnsureNotDisposed (); diff --git a/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt b/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt index c4b72718507..2088a07dc59 100644 --- a/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt @@ -8,6 +8,7 @@ virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeForSimpleReference(string! virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignatureCore(System.Type! type) -> Java.Interop.JniTypeSignature virtual Java.Interop.JniRuntime.JniTypeManager.GetTypeSignaturesCore(System.Type! type) -> System.Collections.Generic.IEnumerable! abstract Java.Interop.JniRuntime.JniValueManager.CreateLocalObjectReferenceArgumentCore(System.Type! type, object? value) -> Java.Interop.JniObjectReference +Java.Interop.JniRuntime.JniValueManager.CreateLocalObjectReferenceArgument(System.Type! type, object? value) -> Java.Interop.JniObjectReference Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void Java.Interop.JavaException.SetJavaStackTrace(Java.Interop.JniObjectReference peerReferenceOverride = default(Java.Interop.JniObjectReference)) -> void Java.Interop.JniRuntime.ReflectionJniTypeManager diff --git a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs index 9c2909e7c74..04163ac243f 100644 --- a/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs +++ b/src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs @@ -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.")] diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs index 2c457e43149..c1701cb1df0 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs @@ -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; diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 7f5507cb396..cb05c9da9b1 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -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 (reference.Handle, JniHandleOwnership.DoNotTransfer); @@ -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; @@ -329,7 +336,6 @@ protected override IEnumerable 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); @@ -346,21 +352,22 @@ protected override IEnumerable 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 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? GetStaticMethodFallbackTypesCore (string jniSimpleReference) @@ -368,7 +375,7 @@ protected override IEnumerable GetSimpleReferences (Type type) return JniRemappingLookup.GetStaticMethodFallbackTypes (jniSimpleReference, useReplacementTypes: true); } - protected override string? GetReplacementTypeCore (string jniSimpleReference) + protected override string? GetReplacementTypeCore (string? jniSimpleReference) { return JniRemappingLookup.GetReplacementType (jniSimpleReference); } @@ -393,8 +400,6 @@ protected override IEnumerable 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) { @@ -489,20 +494,10 @@ static bool CallRegisterMethodByIndex (JniNativeMethodRegistrationArguments argu } [Obsolete ("Use RegisterNativeMembers(JniType, Type, ReadOnlySpan) 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 methods) + public override void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) { try { if (methods.IsEmpty) { @@ -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 methodsSpan) @@ -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 instances = new Dictionary (); @@ -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); } diff --git a/src/Mono.Android/Android.Runtime/IJavaObjectValueMarshaler.cs b/src/Mono.Android/Android.Runtime/IJavaObjectValueMarshaler.cs index bd7d8942936..443b4e5890b 100644 --- a/src/Mono.Android/Android.Runtime/IJavaObjectValueMarshaler.cs +++ b/src/Mono.Android/Android.Runtime/IJavaObjectValueMarshaler.cs @@ -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 ( diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 6b2fa369ad0..433d1a0d53e 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -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; diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index ce424298645..cefbc6c694e 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -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, @@ -158,10 +153,14 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) args->propagateUncaughtExceptionFn = (IntPtr)(delegate* unmanaged)&PropagateUncaughtException; if (!RuntimeFeature.TrimmableTypeMap) { - args->registerJniNativesFn = (IntPtr)(delegate* unmanaged)&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)&RegisterJniNatives; } [LibraryImport (RuntimeConstants.InternalDllName)] @@ -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) { @@ -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) diff --git a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs index 10e38fcb236..84d7de20f76 100644 --- a/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs +++ b/src/Mono.Android/Android.Runtime/JNINativeWrapper.cs @@ -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) diff --git a/src/Mono.Android/Android.Runtime/JavaCollection.cs b/src/Mono.Android/Android.Runtime/JavaCollection.cs index 48bdcd6abd2..f7962057d9a 100644 --- a/src/Mono.Android/Android.Runtime/JavaCollection.cs +++ b/src/Mono.Android/Android.Runtime/JavaCollection.cs @@ -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 @@ -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)); if (inst == null) inst = new JavaCollection (handle, transfer); else diff --git a/src/Mono.Android/Android.Runtime/JavaDictionary.cs b/src/Mono.Android/Android.Runtime/JavaDictionary.cs index 3ca9fdfea17..9556c0989ee 100644 --- a/src/Mono.Android/Android.Runtime/JavaDictionary.cs +++ b/src/Mono.Android/Android.Runtime/JavaDictionary.cs @@ -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 @@ -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)); if (inst == null) inst = new JavaDictionary (handle, transfer); else diff --git a/src/Mono.Android/Android.Runtime/JavaList.cs b/src/Mono.Android/Android.Runtime/JavaList.cs index 73323903d7e..4abc0877e3c 100644 --- a/src/Mono.Android/Android.Runtime/JavaList.cs +++ b/src/Mono.Android/Android.Runtime/JavaList.cs @@ -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 diff --git a/src/Mono.Android/Android.Runtime/JavaSet.cs b/src/Mono.Android/Android.Runtime/JavaSet.cs index eec208f08ce..c768f320a7e 100644 --- a/src/Mono.Android/Android.Runtime/JavaSet.cs +++ b/src/Mono.Android/Android.Runtime/JavaSet.cs @@ -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 @@ -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)); if (inst == null) inst = new JavaSet (handle, transfer); else diff --git a/src/Mono.Android/Java.Interop/JavaConvert.cs b/src/Mono.Android/Java.Interop/JavaConvert.cs index 9fb9de7429a..8b8184f9e2a 100644 --- a/src/Mono.Android/Java.Interop/JavaConvert.cs +++ b/src/Mono.Android/Java.Interop/JavaConvert.cs @@ -12,8 +12,9 @@ namespace Java.Interop { static class JavaConvert { const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; + const JniObjectReferenceOptions DisposeSource = JniObjectReferenceOptions.CopyAndDispose & ~JniObjectReferenceOptions.Copy; - static Dictionary> JniHandleConverters = new Dictionary>() { + static Dictionary> JniHandleConverters = new Dictionary>() { { typeof (bool), (handle, transfer) => { using (var value = new Java.Lang.Boolean (handle, transfer | JniHandleOwnership.DoNotRegister)) return value.BooleanValue (); @@ -71,55 +72,61 @@ static class JavaConvert { static Func? GetJniHandleConverter (Type? target) { - // FIXME: https://github.com/xamarin/xamarin-android/issues/8724 - // Might cause an issue in the future for NativeAOT - [UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = "We don't think the IDictionary, IList, or ICollection code paths occur if JavaDictionary<,>, JavaList<>, and JavaCollection<> do not exist.")] - [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] - static Type MakeGenericType ( - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] - Type type, - params Type [] typeArguments - ) => - // FIXME: https://github.com/xamarin/xamarin-android/issues/8724 - // IL3050 disabled in source: if someone uses NativeAOT, they will get the warning. - #pragma warning disable IL3050 - type.MakeGenericType (typeArguments); - #pragma warning restore IL3050 - if (target == null) return null; if (JniHandleConverters.TryGetValue (target, out var converter)) return converter; + + // For Nullable, look up the converter for the underlying element type and + // wrap it so that a null Java reference maps to a null value. + var underlyingType = Nullable.GetUnderlyingType (target); + if (underlyingType != null && JniHandleConverters.TryGetValue (underlyingType, out var underlyingConverter)) + return (h, t) => h == IntPtr.Zero ? null : underlyingConverter (h, t); + if (target.IsArray) return (h, t) => JNIEnv.GetArray (h, t, target.GetElementType ()); - if (RuntimeFeature.TrimmableTypeMap) { - var factoryConverter = TryGetFactoryBasedConverter (target); - if (factoryConverter != null) - return factoryConverter; + if (target.IsGenericType && !target.IsGenericTypeDefinition) { + if (RuntimeFeature.TrimmableTypeMap) { + var factoryConverter = TryGetFactoryBasedConverter (target); + if (factoryConverter != null) + return factoryConverter; + } else { + var factoryConverter = TryMakeGenericCollectionTypeFactory (target); + if (factoryConverter != null) + return factoryConverter; + } } - if (target.IsGenericType && target.GetGenericTypeDefinition() == typeof (IDictionary<,>)) { - Type t = MakeGenericType (typeof (JavaDictionary<,>), target.GetGenericArguments ()); - return GetJniHandleConverterForType (t); - } if (typeof (IDictionary).IsAssignableFrom (target)) return (h, t) => JavaDictionary.FromJniHandle (h, t); - if (target.IsGenericType && target.GetGenericTypeDefinition() == typeof (IList<>)) { - Type t = MakeGenericType (typeof (JavaList<>), target.GetGenericArguments ()); - return GetJniHandleConverterForType (t); - } if (typeof (IList).IsAssignableFrom (target)) return (h, t) => JavaList.FromJniHandle (h, t); - if (target.IsGenericType && target.GetGenericTypeDefinition() == typeof (ICollection<>)) { - Type t = MakeGenericType (typeof (JavaCollection<>), target.GetGenericArguments ()); - return GetJniHandleConverterForType (t); - } if (typeof (ICollection).IsAssignableFrom (target)) return (h, t) => JavaCollection.FromJniHandle (h, t); return null; + + [UnconditionalSuppressMessage ("ReflectionAnalysis", "IL2055:RequiresUnreferencedCode", + Justification = "The target generic type is expected to be preserved by the trimmer as the target type in marshaling.")] + static Func? TryMakeGenericCollectionTypeFactory (Type target) + { + if (target.GetGenericTypeDefinition() == typeof (IDictionary<,>)) { + Type t = typeof (JavaDictionary<,>).MakeGenericType (target.GetGenericArguments ()); + return GetJniHandleConverterForType (t); + } + if (target.GetGenericTypeDefinition() == typeof (IList<>)) { + Type t = typeof (JavaList<>).MakeGenericType (target.GetGenericArguments ()); + return GetJniHandleConverterForType (t); + } + if (target.GetGenericTypeDefinition() == typeof (ICollection<>)) { + Type t = typeof (JavaCollection<>).MakeGenericType (target.GetGenericArguments ()); + return GetJniHandleConverterForType (t); + } + + return null; + } } /// @@ -199,7 +206,7 @@ static Func GetJniHandleConverterForType ([D internal readonly struct ArrayElementConverter { readonly Type? elementType; - readonly Func? converter; + readonly Func? converter; readonly bool useRuntimeTypeMapping; public ArrayElementConverter (Array array) @@ -234,7 +241,7 @@ public ArrayElementConverter (Array array) if (elementType != null && typeof (IJavaPeerable).IsAssignableFrom (elementType)) { if (RuntimeFeature.TrimmableTypeMap) return FromJniHandleWithTrimmableTypeMapping (handle, transfer, elementType); - return Java.Lang.Object.GetObject (handle, transfer, elementType); + return GetObjectWithSuppression (handle, transfer, elementType); } var value = FromJniHandleWithRuntimeTypeMapping (handle, transfer); @@ -242,6 +249,13 @@ public ArrayElementConverter (Array array) return value; return Convert.ChangeType (value, elementType, CultureInfo.InvariantCulture); } + + [UnconditionalSuppressMessage ("ReflectionAnalysis", "IL2067:RequiresUnreferencedCode", + Justification = "Custom trimmer steps marks the activation constructors on IJavaPeerable types.")] + static object? GetObjectWithSuppression (IntPtr handle, JniHandleOwnership transfer, Type? elementType) + { + return Java.Lang.Object.GetObject (handle, transfer, elementType); + } } static object? FromJniHandleWithRuntimeTypeMapping (IntPtr handle, JniHandleOwnership transfer) @@ -329,6 +343,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, @@ -382,7 +421,9 @@ public static T? FromJniHandle< { var lref = JniEnvironment.Types.GetObjectClass (new JniObjectReference (handle)); try { - string className = JniEnvironment.Types.GetJniTypeNameFromClass (lref); + string? className = JniEnvironment.Types.GetJniTypeNameFromClass (lref); + if (className is null) + return null; if (TypeMappings.TryGetValue (className, out var match)) return match; if (JniEnvironment.Types.IsAssignableFrom (lref, new JniObjectReference (JavaDictionary.map_class))) @@ -571,6 +612,10 @@ public static T? FromJavaObject< static Func GetLocalJniHandleConverter (object value) { Type sourceType = value.GetType (); + Type? underlyingType = Nullable.GetUnderlyingType (sourceType); + if (underlyingType != null) + sourceType = underlyingType; + Func? converter; if (LocalJniHandleConverters.TryGetValue (sourceType, out converter)) return converter; @@ -593,6 +638,37 @@ internal static IntPtr ToLocalJniHandle (object? value) return converter (value); } + // Converts values that map to a well-known Java peer (IJavaObject instances, boxed + // primitives/strings, and arrays) to a local JNI handle. Unlike ToLocalJniHandle, this + // does NOT fall back to wrapping unknown objects in Android.Runtime.JavaObject; instead it + // returns false so callers (e.g. the trimmable typemap value manager) can provide their own + // proxy for arbitrary .NET objects. + internal static bool TryConvertKnownValueToLocalJniHandle (object? value, out IntPtr handle) + { + if (value == null) { + handle = IntPtr.Zero; + return true; + } + if (value is IJavaObject v) { + handle = JNIEnv.ToLocalJniHandle (v); + return true; + } + + Type sourceType = value.GetType (); + Func? converter; + if (LocalJniHandleConverters.TryGetValue (sourceType, out converter)) { + handle = converter (value); + return true; + } + if (sourceType.IsArray) { + handle = LocalJniHandleConverters [typeof (Array)] (value); + return true; + } + + handle = IntPtr.Zero; + return false; + } + public static TReturn WithLocalJniHandle(TValue value, Func action) { IntPtr lref = ToLocalJniHandle (value); diff --git a/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs b/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs index a3e817facb9..a992e187779 100644 --- a/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs +++ b/src/Mono.Android/Java.Interop/JavaObjectExtensions.cs @@ -81,7 +81,7 @@ internal static TResult? _JavaCast< if (instance.Handle == IntPtr.Zero) throw new ObjectDisposedException (instance.GetType ().FullName); - return (TResult) Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer, typeof (TResult)) ?? + return (TResult?) Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer, typeof (TResult)) ?? throw new InvalidCastException ( FormattableString.Invariant ($"Unable to convert instance of type '{instance.GetType ().FullName}' to type '{typeof (TResult).FullName}'.")); } @@ -108,42 +108,27 @@ 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); + string? definitionFullName = definition.FullName; + if (definitionFullName == null) + throw new NotSupportedException ("Generic type doesn't have a full name! " + type.FullName); + int bt = definitionFullName.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, - definition.FullName.Substring (0, bt) + suffix + definition.FullName.Substring (bt)); + Type? suffixDefinition = definition.Assembly.GetType ( + definitionFullName.Substring (0, bt) + suffix + definitionFullName.Substring (bt)); if (suffixDefinition == null) return null; - return MakeGenericType (suffixDefinition, arguments); + return suffixDefinition.MakeGenericType (arguments); } } } diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index f5fb4c90d38..494f0e304f4 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -42,14 +42,13 @@ public sealed class JavaPeerAliasesAttribute : Attribute [AttributeUsage (AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] public abstract class JavaPeerProxy : Attribute { - protected JavaPeerProxy ( - string jniName, - Type targetType, - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type? invokerType) + private protected JavaPeerProxy (string jniName, Type targetType, Type? invokerType) { - JniName = jniName ?? throw new ArgumentNullException (nameof (jniName)); - TargetType = targetType ?? throw new ArgumentNullException (nameof (targetType)); + ArgumentNullException.ThrowIfNull (jniName); + ArgumentNullException.ThrowIfNull (targetType); + + JniName = jniName; + TargetType = targetType; InvokerType = invokerType; } @@ -76,7 +75,6 @@ protected JavaPeerProxy ( /// Gets the invoker type for interfaces and abstract classes. /// Returns null for concrete types that can be directly instantiated. /// - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] public Type? InvokerType { get; } /// @@ -141,20 +139,17 @@ static bool IsActivationPeer (IJavaPeerable peer) /// /// The target .NET peer type this proxy represents. [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)] - T - > : JavaPeerProxy where T : class, IJavaPeerable + public abstract class JavaPeerProxy<[DynamicallyAccessedMembers (Constructors)] T> + : JavaPeerProxy where T : class, IJavaPeerable { - protected JavaPeerProxy ( - string jniName, - [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type? invokerType) : base (jniName, typeof (T), invokerType) + const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; + + protected JavaPeerProxy (string jniName, Type? invokerType) + : base (jniName, typeof (T), invokerType) { } - public override JavaPeerContainerFactory GetContainerFactory () + public override JavaPeerContainerFactory? GetContainerFactory () => JavaPeerContainerFactory.Instance; } diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index b00ea6056b0..f856818e09f 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -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; diff --git a/src/Mono.Android/Java.Lang/Object.cs b/src/Mono.Android/Java.Lang/Object.cs index 814d4c3b277..f404349c0fc 100644 --- a/src/Mono.Android/Java.Lang/Object.cs +++ b/src/Mono.Android/Java.Lang/Object.cs @@ -141,20 +141,23 @@ static JniObjectReferenceOptions FromJniHandleOwnership (JniHandleOwnership tran return (T?)PeekObject (handle, typeof (T)); } - public static T? GetObject (IntPtr jnienv, IntPtr handle, JniHandleOwnership transfer) + public static T? GetObject<[DynamicallyAccessedMembers (Constructors)] T> ( + IntPtr jnienv, IntPtr handle, JniHandleOwnership transfer) where T : class, IJavaObject { JNIEnv.CheckHandle (jnienv); return GetObject (handle, transfer); } - public static T? GetObject (IntPtr handle, JniHandleOwnership transfer) + public static T? GetObject<[DynamicallyAccessedMembers (Constructors)] T> ( + IntPtr handle, JniHandleOwnership transfer) where T : class, IJavaObject { return _GetObject(handle, transfer); } - internal static T? _GetObject (IntPtr handle, JniHandleOwnership transfer) + internal static T? _GetObject<[DynamicallyAccessedMembers (Constructors)] T> ( + IntPtr handle, JniHandleOwnership transfer) { if (handle == IntPtr.Zero) return default (T); @@ -165,19 +168,15 @@ static JniObjectReferenceOptions FromJniHandleOwnership (JniHandleOwnership tran internal static IJavaPeerable? GetObject ( IntPtr handle, JniHandleOwnership transfer, + [DynamicallyAccessedMembers (Constructors)] Type? type = null) { if (handle == IntPtr.Zero) return null; - var r = GetPeer (handle, type); + var r = JniEnvironment.Runtime.ValueManager.GetPeer (new JniObjectReference (handle), type); JNIEnv.DeleteRef (handle, transfer); return r; - - // FIXME: should use [DynamicallyAccessedMembers (Constructors)] in the future - [UnconditionalSuppressMessage ("Trimming", "IL2067:'targetType' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors', 'DynamicallyAccessedMemberTypes.NonPublicConstructors' in call to 'Java.Interop.JniRuntime.JniValueManager.GetPeer(JniObjectReference, Type)'.", Justification = "The MarkJavaObjects step preserves ctors on Java.Lang.Object subclasses.")] - static IJavaPeerable? GetPeer (IntPtr handle, Type? type) => - JniEnvironment.Runtime.ValueManager.GetPeer (new JniObjectReference (handle), type); } [EditorBrowsable (EditorBrowsableState.Never)] diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs index a584a991e0d..e83d20fd046 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs @@ -56,128 +56,11 @@ public override void FinalizePeer (IJavaPeerable value) JavaMarshalRegisteredPeers.FinalizePeer (value); } - public override void ActivatePeer (JniObjectReference reference, Type type, ConstructorInfo cinfo, object?[]? argumentValues) - { - if (RuntimeFeature.TrimmableTypeMap) - throw new PlatformNotSupportedException ("Activating Java peers is not supported when TrimmableTypeMap is enabled."); - - base.ActivatePeer (reference, type, cinfo, argumentValues); - } - public override List GetSurfacedPeers () { return JavaMarshalRegisteredPeers.GetSurfacedPeers (); } - public override IJavaPeerable? CreatePeer ( - ref JniObjectReference reference, - JniObjectReferenceOptions transfer, - [DynamicallyAccessedMembers (Constructors)] - Type? targetType) - { - EnsureNotDisposed (); - - if (!reference.IsValid) { - return null; - } - - if (RuntimeFeature.TrimmableTypeMap) { - try { - // Mirror legacy GetPeerType: callers commonly request universal - // interfaces / boxes (IJavaPeerable, object, Exception) — map these - // to a concrete peer type so the proxy lookup can succeed. - var resolvedTargetType = ResolvePeerType (targetType); - - var typeMap = TrimmableTypeMap.Instance; - var peer = typeMap.CreateInstance (reference.Handle, resolvedTargetType); - if (peer is not null) { - return peer; - } - - // Disambiguate the failure — match the contract of the base - // JniRuntime.JniValueManager.CreatePeer so JavaCast / JavaAs - // surface the right exception (or null) to callers: - // - // (a) target type has no Java mapping at all → ArgumentException - // (b) Java instance is not assignable to the target's Java class - // → return null (JavaAs returns null; JavaCast wraps to - // InvalidCastException via its `??` clause) - // (c) classes are compatible but no proxy / activation failed - // → NotSupportedException (genuine generator gap) - if (resolvedTargetType is not null && - IsIncompatibleCast (typeMap, ref reference, resolvedTargetType)) { - return null; - } - - var targetName = resolvedTargetType?.AssemblyQualifiedName ?? ""; - var javaType = JniEnvironment.Types.GetJniTypeNameFromInstance (reference); - - throw new NotSupportedException ( - $"No generated {nameof (JavaPeerProxy)} was found for Java type '{javaType}' " + - $"with targetType '{targetName}' while {nameof (RuntimeFeature.TrimmableTypeMap)} is enabled. " + - $"This indicates a missing trimmable typemap proxy or association and should be fixed in the generator."); - } finally { - JniObjectReference.Dispose (ref reference, transfer); - } - } - - return base.CreatePeer (ref reference, transfer, targetType); - } - - [return: DynamicallyAccessedMembers (Constructors)] - static Type? ResolvePeerType ([DynamicallyAccessedMembers (Constructors)] Type? type) - { - if (type is null) { - return null; - } - if (type == typeof (object) || type == typeof (IJavaPeerable)) { - return typeof (global::Java.Interop.JavaObject); - } - if (type == typeof (Exception)) { - return typeof (JavaException); - } - return type; - } - - /// - /// Returns true when 's Java class is not assignable from - /// . Throws when has no usable mapping. - /// - static bool IsIncompatibleCast ( - TrimmableTypeMap typeMap, - ref JniObjectReference reference, - Type targetType) - { - if (!typeMap.TryGetJniNameForManagedType (targetType, out var targetJniName)) { - throw new ArgumentException ( - $"Could not determine Java type corresponding to '{targetType.AssemblyQualifiedName}'.", - nameof (targetType)); - } - - var instanceClass = JniEnvironment.Types.GetObjectClass (reference); - JniObjectReference targetClass = default; - try { - try { - targetClass = JniEnvironment.Types.FindClass (targetJniName); - } catch (Java.Lang.ClassNotFoundException e) { - throw new ArgumentException ( - $"Could not find Java class '{targetJniName}'.", - nameof (targetType), e); - } - - if (!JniEnvironment.Types.IsAssignableFrom (instanceClass, targetClass)) { - // Bad cast: callers translate null to the expected result. - return true; - } - } finally { - JniObjectReference.Dispose (ref instanceClass); - JniObjectReference.Dispose (ref targetClass); - } - - // Compatible classes mean a proxy/activation gap. - return false; - } - protected override bool TryConstructPeer ( IJavaPeerable self, ref JniObjectReference reference, diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index 145d253050d..7eb2257be59 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -203,7 +203,7 @@ JavaPeerProxy[] GetProxiesForJniName (string jniName) if (proxies.Length == 0) { return null; } - if (proxies.Length == 1 || targetType is null) { + if (targetType is null) { return proxies [0]; } foreach (var proxy in proxies) { @@ -241,154 +241,154 @@ internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true) return null; } - return TryGetProxyFromHierarchy (this, handle, targetType) ?? - TryGetProxyFromTargetType (this, handle, targetType); + return TryGetProxyFromHierarchy (handle, targetType) ?? + TryGetProxyFromTargetType (handle, targetType); + } - static JavaPeerProxy? TryGetProxyFromHierarchy (TrimmableTypeMap self, IntPtr handle, Type? targetType) - { - var selfRef = new JniObjectReference (handle); - var jniClass = JniEnvironment.Types.GetObjectClass (selfRef); + JavaPeerProxy? TryGetProxyFromHierarchy (IntPtr handle, Type? targetType) + { + var selfRef = new JniObjectReference (handle); + var jniClass = JniEnvironment.Types.GetObjectClass (selfRef); - try { - while (jniClass.IsValid) { - var className = JniEnvironment.Types.GetJniTypeNameFromClass (jniClass); - if (className != null) { - var proxy = self.GetProxyForJniClass (className, targetType); - if (proxy != null && (targetType is null || TargetTypeMatches (targetType, proxy.TargetType))) { - return proxy; - } + try { + while (jniClass.IsValid) { + var className = JniEnvironment.Types.GetJniTypeNameFromClass (jniClass); + if (className is not null) { + var proxy = GetProxyForJniClass (className, targetType); + if (proxy is not null) { + return proxy; } + } - // When targetType is an interface, also check the Java interfaces - // at each level. getInterfaces() only returns directly declared - // interfaces so we must call it at each class in the hierarchy. - // This handles the case where an intermediate class entry (e.g., - // X509ExtendedTrustManager) was trimmed but the Java interface - // entry (e.g., X509TrustManager) survives. - if (targetType is { IsInterface: true } && className != null) { - var result = GetProxyForJavaInterfaces (self, jniClass, className, targetType); - if (result != null) { - return result; - } + // When targetType is an interface, also check the Java interfaces + // at each level. getInterfaces() only returns directly declared + // interfaces so we must call it at each class in the hierarchy. + // This handles the case where an intermediate class entry (e.g., + // X509ExtendedTrustManager) was trimmed but the Java interface + // entry (e.g., X509TrustManager) survives. + if (targetType is { IsInterface: true } && className != null) { + var result = GetProxyForJavaInterfaces (jniClass, className, targetType); + if (result != null) { + return result; } - - var super = JniEnvironment.Types.GetSuperclass (jniClass); - JniObjectReference.Dispose (ref jniClass); - jniClass = super; } - } finally { + + var super = JniEnvironment.Types.GetSuperclass (jniClass); JniObjectReference.Dispose (ref jniClass); + jniClass = super; } - - return null; + } finally { + JniObjectReference.Dispose (ref jniClass); } - static JavaPeerProxy? GetProxyForJavaInterfaces (TrimmableTypeMap self, JniObjectReference jniClass, string className, Type targetType) - { - var proxy = self._interfaceProxyCache.GetOrAdd ( - (className, targetType), - _ => TryMatchInterfaces (self, jniClass, targetType) ?? s_noPeerSentinel); - return ReferenceEquals (proxy, s_noPeerSentinel) ? null : proxy; - } + return null; + } - // getInterfaces() returns only directly declared interfaces (not transitive), - // so we recurse into super-interfaces to find the matching TypeMap entry. - static JavaPeerProxy? TryMatchInterfaces (TrimmableTypeMap self, JniObjectReference jniClass, Type targetType) - { - var interfaces = JniEnvironment.InstanceMethods.CallObjectMethod (jniClass, GetClassGetInterfacesMethod ()); - try { - if (!interfaces.IsValid) { - return null; - } + JavaPeerProxy? GetProxyForJavaInterfaces (JniObjectReference jniClass, string className, Type targetType) + { + var proxy = _interfaceProxyCache.GetOrAdd ( + (className, targetType), + _ => TryMatchInterfaces (jniClass, targetType) ?? s_noPeerSentinel); + return ReferenceEquals (proxy, s_noPeerSentinel) ? null : proxy; + } - int count = JniEnvironment.Arrays.GetArrayLength (interfaces); - for (int i = 0; i < count; i++) { - var iface = JniEnvironment.Arrays.GetObjectArrayElement (interfaces, i); - try { - var ifaceName = JniEnvironment.Types.GetJniTypeNameFromClass (iface); - if (ifaceName != null) { - var proxy = self.GetProxyForJniClass (ifaceName, targetType); - if (proxy != null && TargetTypeMatches (targetType, proxy.TargetType)) { - return proxy; - } - } + // getInterfaces() returns only directly declared interfaces (not transitive), + // so we recurse into super-interfaces to find the matching TypeMap entry. + JavaPeerProxy? TryMatchInterfaces (JniObjectReference jniClass, Type targetType) + { + var interfaces = JniEnvironment.InstanceMethods.CallObjectMethod (jniClass, GetClassGetInterfacesMethod ()); + try { + if (!interfaces.IsValid) { + return null; + } - // Recurse into super-interfaces - var result = TryMatchInterfaces (self, iface, targetType); - if (result != null) { - return result; + int count = JniEnvironment.Arrays.GetArrayLength (interfaces); + for (int i = 0; i < count; i++) { + var iface = JniEnvironment.Arrays.GetObjectArrayElement (interfaces, i); + try { + var ifaceName = JniEnvironment.Types.GetJniTypeNameFromClass (iface); + if (ifaceName is not null) { + var proxy = GetProxyForJniClass (ifaceName, targetType); + if (proxy is not null) { + return proxy; } - } finally { - JniObjectReference.Dispose (ref iface); } + + // Recurse into super-interfaces + var result = TryMatchInterfaces (iface, targetType); + if (result is not null) { + return result; + } + } finally { + JniObjectReference.Dispose (ref iface); } - } finally { - JniObjectReference.Dispose (ref interfaces); } - - return null; + } finally { + JniObjectReference.Dispose (ref interfaces); } - static JniMethodInfo GetClassGetInterfacesMethod () - { - var method = s_classGetInterfacesMethod; - if (method != null) { - return method; - } + return null; + } - var classClass = JniEnvironment.Types.FindClass ("java/lang/Class"); - try { - method = JniEnvironment.InstanceMethods.GetMethodID (classClass, "getInterfaces", "()[Ljava/lang/Class;"); - } finally { - JniObjectReference.Dispose (ref classClass); - } + static JniMethodInfo GetClassGetInterfacesMethod () + { + var method = s_classGetInterfacesMethod; + if (method != null) { + return method; + } - var previous = Interlocked.CompareExchange (ref s_classGetInterfacesMethod, method, null); - return previous ?? method; + var classClass = JniEnvironment.Types.FindClass ("java/lang/Class"); + try { + method = JniEnvironment.InstanceMethods.GetMethodID (classClass, "getInterfaces", "()[Ljava/lang/Class;"); + } finally { + JniObjectReference.Dispose (ref classClass); } - static JavaPeerProxy? TryGetProxyFromTargetType (TrimmableTypeMap self, IntPtr handle, Type? targetType) - { - if (targetType is null) { - return null; - } + var previous = Interlocked.CompareExchange (ref s_classGetInterfacesMethod, method, null); + return previous ?? method; + } - var proxy = self.GetProxyForManagedType (targetType); - // Verify the Java object is actually assignable to the target Java type - // before returning the fallback proxy. Without this, we'd create invalid peers - // (e.g., IAppendableInvoker wrapping a java.lang.Integer). - if (proxy is null || !self.TryGetJniNameForManagedType (targetType, out var targetJniName)) { - return null; - } + JavaPeerProxy? TryGetProxyFromTargetType (IntPtr handle, Type? targetType) + { + if (targetType is null) { + return null; + } - var selfRef = new JniObjectReference (handle); - var objClass = default (JniObjectReference); - var targetClass = default (JniObjectReference); + var proxy = GetProxyForManagedType (targetType); + // Verify the Java object is actually assignable to the target Java type + // before returning the fallback proxy. Without this, we'd create invalid peers + // (e.g., IAppendableInvoker wrapping a java.lang.Integer). + if (proxy is null || !TryGetJniNameForManagedType (targetType, out var targetJniName)) { + return null; + } + + var selfRef = new JniObjectReference (handle); + var objClass = default (JniObjectReference); + var targetClass = default (JniObjectReference); + try { + objClass = JniEnvironment.Types.GetObjectClass (selfRef); try { - objClass = JniEnvironment.Types.GetObjectClass (selfRef); - try { - targetClass = JniEnvironment.Types.FindClass (targetJniName); - } catch (Java.Lang.ClassNotFoundException) { - // FindClass throws for managed types whose Java peer class is - // not present in the APK (e.g. test types annotated with - // [JniTypeSignature("__missing__")]). Treat as "no match" so - // JavaMarshalValueManager.CreatePeer can surface the correct - // ArgumentException instead of leaking ClassNotFoundException. - return null; - } - var isAssignable = JniEnvironment.Types.IsAssignableFrom (objClass, targetClass); - return isAssignable ? proxy : null; - } finally { - JniObjectReference.Dispose (ref objClass); - JniObjectReference.Dispose (ref targetClass); + targetClass = JniEnvironment.Types.FindClass (targetJniName); + } catch (Java.Lang.ClassNotFoundException) { + // FindClass throws for managed types whose Java peer class is + // not present in the APK (e.g. test types annotated with + // [JniTypeSignature("__missing__")]). Treat as "no match" so + // TrimmableTypeMapValueManager.CreatePeer can surface the correct + // ArgumentException instead of leaking ClassNotFoundException. + return null; } + var isAssignable = JniEnvironment.Types.IsAssignableFrom (objClass, targetClass); + return isAssignable ? proxy : null; + } finally { + JniObjectReference.Dispose (ref objClass); + JniObjectReference.Dispose (ref targetClass); } } internal IJavaPeerable? CreateInstance ( - IntPtr handle, - [DynamicallyAccessedMembers (Constructors)] - Type? targetType = null) + IntPtr handle, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType = null) { var proxy = GetProxyForJavaObject (handle, targetType); @@ -496,7 +496,6 @@ internal static bool TargetTypeMatches (Type targetType, Type proxyTargetType) /// /// Gets the invoker type for an interface or abstract class from the proxy attribute. /// - [return: DynamicallyAccessedMembers (Constructors)] internal Type? GetInvokerType (Type type) { return GetProxyForManagedType (type)?.InvokerType; diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs index 5ff5f6d4a9d..e7a73de4e35 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Reflection; using Java.Interop; namespace Microsoft.Android.Runtime; @@ -14,16 +13,127 @@ namespace Microsoft.Android.Runtime; /// Type manager for the trimmable typemap path. Delegates type lookups /// to . /// -[UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Temporary suppression for Java.Interop reflection manager base.")] -class TrimmableTypeMapTypeManager : JniRuntime.ReflectionJniTypeManager +class TrimmableTypeMapTypeManager : JniRuntime.JniTypeManager { - const string NoSimpleReference = "\0"; - readonly ConcurrentDictionary _simpleReferenceCache = new (); + readonly ConcurrentDictionary _typeSignatureCache = new (); + + // This type manager has 2 core APIs: GetTypeSignatureCore for managed-to-Java lookups, and GetTypeForSimpleReference for Java-to-managed lookups. + // The rest of the APIs are unsupported and will throw if called, as they are not needed internally anywhere. + + public override IEnumerable GetTypes (JniTypeSignature typeSignature) + { + if (typeSignature.SimpleReference is null) { + return []; + } + + var simpleReference = typeSignature.SimpleReference ?? throw new InvalidOperationException ("Should not be reached"); + var simpleTypes = GetTypesForSimpleReference (simpleReference); + + if (typeSignature.ArrayRank == 0) { + return simpleTypes; + } + + return GetFlattenedArrayTypes (typeSignature, simpleTypes); + + // Multiple managed types can map to a single JNI type and a single managed type can map to multiple array types. + IEnumerable GetFlattenedArrayTypes (JniTypeSignature typeSignature, IEnumerable elementTypes) + { + Debug.Assert (typeSignature.ArrayRank > 0, "Should not be reached"); + + foreach (var elementType in elementTypes) { + foreach (var arrayType in GetArrayTypes (typeSignature, elementType)) { + yield return arrayType; + } + } + } + + // A single managed type can map to multiple array types, e.g., JavaArray, JavaPrimitiveArray, and T[]. + static IEnumerable GetArrayTypes (JniTypeSignature typeSignature, Type elementType) + { + Debug.Assert (elementType != typeof (void), "Cannot create an array of void"); + + // We only pre-generate the array types proxy map for Native AOT because we can't manipulate types at runtime. + // For CoreCLR, we take advantage of the dynamic runtime and we save app size by not pre-generating the array types proxy map. + if (RuntimeFeature.IsNativeAotRuntime) { + return TrimmableTypeMap.Instance.TryGetArrayProxy (elementType, typeSignature.ArrayRank, out var arrayProxy) + ? arrayProxy.GetArrayTypes () + : []; + } + + if (RuntimeFeature.IsCoreClrRuntime) { + return GetArrayTypesForCoreClr (typeSignature, elementType); + } + + throw new NotSupportedException ("Unsupported runtime."); + + [UnconditionalSuppressMessage ("Trimming", "IL2026:RequiresUnreferencedCode", + Justification = "This API is called as part of Java to .NET type marshalling when the target type is expected as the input " + + "parameter of the target method, so it must be seen by the IL trimmer. This justification would not hold for Native AOT " + + "but this codepath is only reachable on CoreCLR.")] + static IEnumerable GetArrayTypesForCoreClr (JniTypeSignature typeSignature, Type elementType) + { + if (IsKeyword (typeSignature)) { + return GetPrimitiveArrayTypes (elementType, typeSignature.ArrayRank); + } + + return MakeArrayTypes (elementType, typeSignature.ArrayRank); + } + + static bool IsKeyword (JniTypeSignature typeSignature) + { + // typeSignature.IsKeyword is not public so we're using this workaround + var keywordTypeSignature = new JniTypeSignature (typeSignature.SimpleReference, typeSignature.ArrayRank, keyword: true); + return typeSignature.Equals (keywordTypeSignature); + } + + [RequiresDynamicCode ("This API uses reflection to create generic types at runtime, which is not supported in AOT scenarios.")] + [RequiresUnreferencedCode ("This API uses reflection to create array types at runtime, which is not supported in trimming scenarios.")] + static IEnumerable GetPrimitiveArrayTypes (Type elementType, int rank) + { + Debug.Assert (elementType != typeof (void), "Cannot create an array of void"); + Debug.Assert (rank > 0, "At least one array rank is expected"); + + if (!PrimitiveArrayInfo.TryGetArrayTypes (elementType, out var types)) { + throw new InvalidOperationException ($"Cannot create an array of type '{elementType.FullName}'"); + } + + foreach (var type in types) { + if (rank == 1) { + yield return type; + } else { + foreach (var arrayType in MakeArrayTypes (type, rank - 1)) { + yield return arrayType; + } + } + } + } + + [RequiresDynamicCode ("This API uses reflection to create generic types at runtime, which is not supported in AOT scenarios.")] + [RequiresUnreferencedCode ("This API uses reflection to create array types at runtime, which is not supported in trimming scenarios.")] + static IEnumerable MakeArrayTypes (Type elementType, int rank) + { + Debug.Assert (rank > 0, "At least one array rank is expected"); + + var javaObjectArrayType = elementType; + for (int i = 0; i < rank; i++) { + javaObjectArrayType = typeof (JavaObjectArray<>).MakeGenericType (javaObjectArrayType); + } + + var arrayType = elementType; + for (int i = 0; i < rank; i++) { + arrayType = arrayType.MakeArrayType (); + } + + return [javaObjectArrayType, arrayType]; + } + } + } protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference) { - foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) { - yield return t; + var builtInType = GetBuiltInTypeForSimpleReference (jniSimpleReference); + if (builtInType is not null) { + yield return builtInType; } if (TrimmableTypeMap.Instance.TryGetTargetTypes (jniSimpleReference, out var types)) { @@ -35,97 +145,209 @@ protected override IEnumerable GetTypesForSimpleReference (string jniSimpl protected override Type? GetTypeForSimpleReference (string jniSimpleReference) { - var type = base.GetTypeForSimpleReference (jniSimpleReference); - if (type != null) { - return type; + // Return the first match without allocating the GetTypesForSimpleReference iterator; + // this is a hot Java-to-managed lookup path. Keep the lookup order in sync with it. + var builtInType = GetBuiltInTypeForSimpleReference (jniSimpleReference); + if (builtInType is not null) { + return builtInType; } + // TryGetTargetTypes returns a non-empty array when it succeeds. if (TrimmableTypeMap.Instance.TryGetTargetTypes (jniSimpleReference, out var types)) { return types [0]; } + return null; } - protected override string? GetSimpleReference (Type type) + // Lookup of the built-in managed type for a JNI simple reference, e.g., string, bool?, int?, etc. + static Type? GetBuiltInTypeForSimpleReference (string jniSimpleReference) { - var simpleReference = _simpleReferenceCache.GetOrAdd (type, GetSimpleReferenceUncached); - return simpleReference == NoSimpleReference ? null : simpleReference; + return jniSimpleReference switch { + "java/lang/String" => typeof (string), + "V" => typeof (void), + "Z" => typeof (bool), + "java/lang/Boolean" => typeof (bool?), + "B" => typeof (sbyte), + "java/lang/Byte" => typeof (sbyte?), + "C" => typeof (char), + "java/lang/Character" => typeof (char?), + "S" => typeof (short), + "java/lang/Short" => typeof (short?), + "I" => typeof (int), + "java/lang/Integer" => typeof (int?), + "J" => typeof (long), + "java/lang/Long" => typeof (long?), + "F" => typeof (float), + "java/lang/Float" => typeof (float?), + "D" => typeof (double), + "java/lang/Double" => typeof (double?), + _ => null, + }; } - string GetSimpleReferenceUncached (Type type) + protected override JniTypeSignature GetTypeSignatureCore (Type type) { - if (TrimmableTypeMap.Instance.TryGetJniNameForManagedType (type, out var jniName)) { - return jniName; - } + return _typeSignatureCache.GetOrAdd (type, GetTypeSignatureUncached); - foreach (var r in base.GetSimpleReferences (type)) { - return r; - } + static JniTypeSignature GetTypeSignatureUncached (Type type) + { + type = GetUnderlyingType (type, out int rank); - // Walk the base type chain for managed-only subclasses (e.g., JavaProxyThrowable - // extends Java.Lang.Error but has no [Register] attribute itself). - for (var baseType = type.BaseType; baseType is not null; baseType = baseType.BaseType) { - if (TrimmableTypeMap.Instance.TryGetJniNameForManagedType (baseType, out var baseJniName)) { - return baseJniName; + if (TryGetBuiltInTypeSignature (type, out var signature)) { + return signature.AddArrayRank (rank); } - } - return NoSimpleReference; - } + if (type.IsGenericType) { + var genericDefinition = type.GetGenericTypeDefinition (); + if (genericDefinition == typeof (JavaArray<>) + || genericDefinition == typeof (JavaObjectArray<>) + || genericDefinition == typeof (JavaPrimitiveArray<>)) { + var elementSignature = GetTypeSignatureUncached (type.GenericTypeArguments [0]); + return elementSignature.AddArrayRank (rank + 1); + } + } - protected override IEnumerable GetSimpleReferences (Type type) - { - if (TrimmableTypeMap.Instance.TryGetJniNameForManagedType (type, out var jniName)) { - yield return jniName; - yield break; - } + // Walk the base type chain for managed-only subclasses (e.g., JavaProxyThrowable + // extends Java.Lang.Error but has no [Register] attribute itself). + Type? currentType = type; - foreach (var r in base.GetSimpleReferences (type)) { - yield return r; - } + while (currentType is not null) { + if (TrimmableTypeMap.Instance.TryGetJniNameForManagedType (currentType, out var jniName)) { + return new (jniName, rank, keyword: false); + } + + currentType = currentType.BaseType; + } + + return default; + + static Type GetUnderlyingType (Type type, out int rank) + { + rank = 0; + var originalType = type; + while (type.IsArray) { + if (type.GetArrayRank () > 1) + throw new ArgumentException ($"Multidimensional array '{originalType.FullName}' is not supported.", nameof (type)); + rank++; + type = type.GetElementType () ?? throw new InvalidOperationException ("Array type has no element type."); + } + + if (type.IsEnum) + type = Enum.GetUnderlyingType (type); + + return type; + } + + static bool TryGetBuiltInTypeSignature (Type type, out JniTypeSignature signature) + { + // Keep the hybrid Type.GetTypeCode + explicit nullable checks. Nullable.GetUnderlyingType () + // allocates a Type[] via GetGenericArguments (), and this path is otherwise allocation-free. + if (GetPrimitiveTypeJniName (type) is string primitiveJniTypeName) { + signature = new JniTypeSignature (primitiveJniTypeName, keyword: true); + return true; + } + + if (type == typeof (void)) { + signature = new JniTypeSignature ("V", keyword: true); + return true; + } + + if (TryGetBuiltInReferenceJniName (type, out var jniName)) { + signature = new JniTypeSignature (jniName); + return true; + } + + if (PrimitiveArrayInfo.TryGetTypeSignature (type, out signature)) { + return true; + } + + signature = default; + return false; + + static string? GetPrimitiveTypeJniName (Type type) + { + return Type.GetTypeCode (type) switch { + TypeCode.Boolean => "Z", + TypeCode.Byte => "B", + TypeCode.SByte => "B", + TypeCode.Char => "C", + TypeCode.Int16 => "S", + TypeCode.UInt16 => "S", + TypeCode.Int32 => "I", + TypeCode.UInt32 => "I", + TypeCode.Int64 => "J", + TypeCode.UInt64 => "J", + TypeCode.Single => "F", + TypeCode.Double => "D", + _ => null, + }; + } + + /// + /// Lookup of the JNI type signature for a built-in reference type, e.g., string, bool?, int?, etc. + /// + static bool TryGetBuiltInReferenceJniName (Type type, [NotNullWhen (true)] out string? jni) + { + if (type == typeof (string)) { jni = "java/lang/String"; return true; } + if (type == typeof (bool?)) { jni = "java/lang/Boolean"; return true; } + if (type == typeof (sbyte?)) { jni = "java/lang/Byte"; return true; } + if (type == typeof (char?)) { jni = "java/lang/Character"; return true; } + if (type == typeof (short?)) { jni = "java/lang/Short"; return true; } + if (type == typeof (int?)) { jni = "java/lang/Integer"; return true; } + if (type == typeof (long?)) { jni = "java/lang/Long"; return true; } + if (type == typeof (float?)) { jni = "java/lang/Float"; return true; } + if (type == typeof (double?)) { jni = "java/lang/Double"; return true; } + jni = null; + return false; + } - // Walk the base type chain for managed-only subclasses (e.g., JavaProxyThrowable - // extends Java.Lang.Error but has no [Register] attribute itself). - for (var baseType = type.BaseType; baseType is not null; baseType = baseType.BaseType) { - if (TrimmableTypeMap.Instance.TryGetJniNameForManagedType (baseType, out var baseJniName)) { - yield return baseJniName; - yield break; } } } - protected override Type? GetInvokerTypeCore (Type type) + protected override IEnumerable GetTypeSignaturesCore (Type type) { - var invokerType = TrimmableTypeMap.Instance.GetInvokerType (type); - if (invokerType != null) { - return invokerType; - } - - return base.GetInvokerTypeCore (type); + var signature = GetTypeSignatureCore (type); + return signature.IsValid ? [signature] : []; } + // Remapping APIs for InTune support + protected override IReadOnlyList? GetStaticMethodFallbackTypesCore (string jniSimpleReference) - { - return JniRemappingLookup.GetStaticMethodFallbackTypes (jniSimpleReference, useReplacementTypes: true); - } + => JniRemappingLookup.GetStaticMethodFallbackTypes (jniSimpleReference, useReplacementTypes: true); protected override string? GetReplacementTypeCore (string jniSimpleReference) - { - return JniRemappingLookup.GetReplacementType (jniSimpleReference); - } + => JniRemappingLookup.GetReplacementType (jniSimpleReference); protected override JniRuntime.ReplacementMethodInfo? GetReplacementMethodInfoCore (string jniSourceType, string jniMethodName, string jniMethodSignature) - { - return JniRemappingLookup.GetReplacementMethodInfo (jniSourceType, jniMethodName, jniMethodSignature); - } + => JniRemappingLookup.GetReplacementMethodInfo (jniSourceType, jniMethodName, jniMethodSignature); - public override void RegisterNativeMembers ( - JniType nativeClass, - Type type, - ReadOnlySpan methods) - { - throw new UnreachableException ( + // The rest of the APIs are unsupported - they are not needed internally anywhere anyway + + protected override Type? GetInvokerTypeCore (Type type) + => throw new UnreachableException ( + $"{nameof (GetInvokerTypeCore)} should not be called in the trimmable typemap path. " + + $"Invoker types should use generated {nameof (JavaPeerProxy)} instances."); + + protected override string? GetSimpleReference (Type type) + => throw new UnreachableException ( + $"{nameof (GetSimpleReference)} should not be called in the trimmable typemap path. " + + $"Simple reference lookup should use {nameof (GetTypeSignatureCore)} to get the full type signature, including simple reference."); + + protected override IEnumerable GetSimpleReferences (Type type) + => throw new UnreachableException ( + $"{nameof (GetSimpleReferences)} should not be called in the trimmable typemap path. " + + $"Simple reference lookup should use {nameof (GetTypeSignatureCore)} to get the full type signature, including simple reference."); + + public override void RegisterNativeMembers (JniType nativeClass, Type type, ReadOnlySpan methods) + => throw new UnreachableException ( + $"RegisterNativeMembers should not be called in the trimmable typemap path. " + + $"Native methods for '{type.FullName}' should be registered by JCW static initializer blocks."); + + [Obsolete ("Use RegisterNativeMembers(JniType, Type, ReadOnlySpan)")] + public override void RegisterNativeMembers (JniType nativeClass, Type type, string? methods) + => throw new UnreachableException ( $"RegisterNativeMembers should not be called in the trimmable typemap path. " + $"Native methods for '{type.FullName}' should be registered by JCW static initializer blocks."); - } } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs new file mode 100644 index 00000000000..b2344f8fd2a --- /dev/null +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs @@ -0,0 +1,428 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Reflection; +using System.Runtime.CompilerServices; +using Android.Runtime; +using Java.Interop; + +namespace Microsoft.Android.Runtime; + +sealed partial class TrimmableTypeMapValueManager : JniRuntime.JniValueManager +{ + const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; + const JniObjectReferenceOptions DoNotRegisterTarget = JniObjectReferenceOptions.CopyAndDoNotRegister & ~JniObjectReferenceOptions.Copy; + + public TrimmableTypeMapValueManager () + { + JavaMarshalRegisteredPeers.InitializeIfNeeded (); + } + + public override void WaitForGCBridgeProcessing () + { + // Intentionally empty. The Mono runtime's own implementation acknowledges this + // pattern is fundamentally flawed (see FIXME in sgen-bridge.c): a thread that + // passes the check can still race with bridge processing that starts immediately + // after. The wait cannot prevent the race, only reduce its window. On CoreCLR, + // JNI wrapper threads hold their own handle copies via JniObjectReference, so + // they are not affected by the bridge swapping control_block handles. + } + + public override void CollectPeers () + { + JavaMarshalRegisteredPeers.CollectPeers (); + } + + public override void AddPeer (IJavaPeerable value) + { + JavaMarshalRegisteredPeers.AddPeer (value); + } + + public override IJavaPeerable? PeekPeer (JniObjectReference reference) + { + return JavaMarshalRegisteredPeers.PeekPeer (reference); + } + + public override void RemovePeer (IJavaPeerable value) + { + JavaMarshalRegisteredPeers.RemovePeer (value); + } + + public override void FinalizePeer (IJavaPeerable value) + { + JavaMarshalRegisteredPeers.FinalizePeer (value); + } + + public override List GetSurfacedPeers () + { + return JavaMarshalRegisteredPeers.GetSurfacedPeers (); + } + + public override void ActivatePeer (JniObjectReference reference, Type type, ConstructorInfo cinfo, object?[]? argumentValues) + { + throw new PlatformNotSupportedException ("Activating Java peers through the value manager is not supported when TrimmableTypeMap is enabled."); + } + + protected override void ConstructPeerCore ( + IJavaPeerable peer, + ref JniObjectReference reference, + JniObjectReferenceOptions options) + { + ArgumentNullException.ThrowIfNull (peer); + + var newRef = peer.PeerReference; + if (newRef.IsValid) { + JniObjectReference.Dispose (ref reference, options); + + // Instance was already added, don't add again + if (peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Activatable)) { + return; + } + var orig = newRef; + newRef = orig.NewGlobalRef (); + JniObjectReference.Dispose (ref orig); + } else if (options == JniObjectReferenceOptions.None) { + // `reference` is likely *InvalidJniObjectReference, and can't be touched + return; + } else if (!reference.IsValid) { + throw new ArgumentException ("JNI Object Reference is invalid.", nameof (reference)); + } else { + newRef = reference; + + if ((options & JniObjectReferenceOptions.Copy) == JniObjectReferenceOptions.Copy) { + newRef = reference.NewGlobalRef (); + } + + JniObjectReference.Dispose (ref reference, options); + } + + peer.SetPeerReference (newRef); + peer.SetJniIdentityHashCode (JniEnvironment.References.GetIdentityHashCode (newRef)); + + var o = Runtime.ObjectReferenceManager; + if (o.LogGlobalReferenceMessages) { + o.WriteGlobalReferenceLine ("Created PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}, Java.Type={4}", + newRef.ToString (), + peer.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture), + RuntimeHelpers.GetHashCode (peer).ToString ("x", CultureInfo.InvariantCulture), + peer.GetType ().FullName, + JniEnvironment.Types.GetJniTypeNameFromInstance (newRef)); + } + + if ((options & DoNotRegisterTarget) != DoNotRegisterTarget) { + AddPeer (peer); + } + } + + public override IJavaPeerable? CreatePeer ( + ref JniObjectReference reference, + JniObjectReferenceOptions transfer, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType) + { + EnsureNotDisposed (); + + if (!reference.IsValid) { + return null; + } + + try { + var resolvedTargetType = ResolvePeerType (targetType); + return TrimmableTypeMap.Instance.CreateInstance (reference.Handle, resolvedTargetType) + ?? NotFoundFallback (ref reference, targetType, resolvedTargetType); + } finally { + JniObjectReference.Dispose (ref reference, transfer); + } + + [return: DynamicallyAccessedMembers (Constructors)] + static Type? ResolvePeerType ([DynamicallyAccessedMembers (Constructors)] Type? type) + { + if (type is null) { + return null; + } + if (type == typeof (object) || type == typeof (IJavaPeerable)) { + return typeof (global::Java.Interop.JavaObject); + } + if (type == typeof (Exception)) { + return typeof (JavaException); + } + return type; + } + + static IJavaPeerable? NotFoundFallback (ref JniObjectReference reference, Type? targetType, Type? resolvedTargetType) + { + // Disambiguate the failure — match the contract of the base + // JniRuntime.JniValueManager.CreatePeer so JavaCast / JavaAs + // surface the right exception (or null) to callers: + // + // (a) target type has no Java mapping at all → ArgumentException + // (b) Java instance is not assignable to the target's Java class + // → return null (JavaAs returns null; JavaCast wraps to + // InvalidCastException via its `??` clause) + // (c) classes are compatible but no proxy / activation failed + // → NotSupportedException (genuine generator gap) + if (targetType is not null && resolvedTargetType is not null) { + if (!TrimmableTypeMap.Instance.TryGetJniNameForManagedType (resolvedTargetType, out var targetJniName)) { + throw new ArgumentException ( + $"Could not determine Java type corresponding to '{targetType.AssemblyQualifiedName}'.", + nameof (targetType)); + } + + if (IsIncompatibleCast (targetJniName, ref reference, resolvedTargetType)) { + return null; + } + } + + var targetName = resolvedTargetType?.AssemblyQualifiedName ?? ""; + var javaType = JniEnvironment.Types.GetJniTypeNameFromInstance (reference); + + throw new NotSupportedException ( + $"No generated {nameof (JavaPeerProxy)} was found for Java type '{javaType}' " + + $"with targetType '{targetName}' while {nameof (RuntimeFeature.TrimmableTypeMap)} is enabled. " + + $"This indicates a missing trimmable typemap proxy or association and should be fixed in the generator."); + } + + static bool IsIncompatibleCast ( + string targetJniName, + ref JniObjectReference reference, + Type targetType) + { + var instanceClass = JniEnvironment.Types.GetObjectClass (reference); + JniObjectReference targetClass = default; + try { + targetClass = JniEnvironment.Types.FindClass (targetJniName); + + if (!JniEnvironment.Types.IsAssignableFrom (instanceClass, targetClass)) { + // Match the legacy cast diagnostic when assembly logging is enabled. + if (Logger.LogAssembly) { + var targetSig = JniRuntime.CurrentRuntime.TypeManager.GetTypeSignature (targetType); + var message = $"Handle 0x{reference.Handle:x} is of type '{JNIEnv.GetClassNameFromInstance (reference.Handle)}' which is not assignable to '{targetSig.SimpleReference}'"; + Logger.Log (LogLevel.Debug, "monodroid-assembly", message); + } + + if (RuntimeFeature.IsAssignableFromCheck) { + return true; + } + } + } catch (Java.Lang.ClassNotFoundException e) { + throw new ArgumentException ( + $"Could not find Java class '{targetJniName}'.", + nameof (targetType), e); + } finally { + JniObjectReference.Dispose (ref instanceClass); + JniObjectReference.Dispose (ref targetClass); + } + + // Compatible classes mean a proxy/activation gap. + return false; + } + + } + + [return: MaybeNull] + protected override T CreateValueCore<[DynamicallyAccessedMembers (Constructors)] T> ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType = null) + { + EnsureNotDisposed (); + if (!reference.IsValid) { + return default; + } + + if (targetType != null && !typeof (T).IsAssignableFrom (targetType)) { + throw new ArgumentException ( + string.Format (CultureInfo.InvariantCulture, "Requested runtime type '{0}' is not compatible with requested compile-time type T of '{1}'.", + targetType, + typeof (T)), + nameof (targetType)); + } + + var boxed = PeekBoxedObject (reference); + if (boxed != null) { + JniObjectReference.Dispose (ref reference, options); + return (T) Convert.ChangeType (boxed, targetType ?? typeof (T), CultureInfo.InvariantCulture); + } + + targetType ??= typeof (T); + + if (typeof (IJavaPeerable).IsAssignableFrom (targetType)) { + return (T?) CreatePeer (ref reference, options, targetType); + } + + if (PrimitiveArrayInfo.TryCreateWrapper (ref reference, options, targetType, out var arrayWrapper)) { + return (T) arrayWrapper; + } + + var value = JavaConvert.FromObjectReference (ref reference, options, targetType); + if (value is null) { + return default; + } + return (T) value; + } + + protected override object? CreateValueCore ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType = null) + { + EnsureNotDisposed (); + if (!reference.IsValid) { + return null; + } + + if (targetType != null && typeof (IJavaPeerable).IsAssignableFrom (targetType)) { + return CreatePeer (ref reference, options, targetType); + } + + var boxed = PeekBoxedObject (reference); + if (boxed != null) { + JniObjectReference.Dispose (ref reference, options); + if (targetType != null) { + return Convert.ChangeType (boxed, targetType, CultureInfo.InvariantCulture); + } + return boxed; + } + + if (targetType != null && PrimitiveArrayInfo.TryCreateWrapper (ref reference, options, targetType, out var arrayWrapper)) { + return arrayWrapper; + } + + return JavaConvert.FromObjectReference (ref reference, options, targetType); + } + + [return: MaybeNull] + protected override T GetValueCore<[DynamicallyAccessedMembers (Constructors)] T> ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType = null) + { + EnsureNotDisposed (); + if (!reference.IsValid) { + return default; + } + + if (targetType != null && !typeof (T).IsAssignableFrom (targetType)) { + throw new ArgumentException ( + $"Requested runtime type '{targetType}' is not compatible with requested compile-time type T of '{typeof (T)}'.", + nameof (targetType)); + } + + targetType ??= typeof (T); + + var existing = PeekValue (reference); + if (existing != null && targetType.IsAssignableFrom (existing.GetType ())) { + JniObjectReference.Dispose (ref reference, options); + return (T) existing; + } + + var value = CreateValueCore (ref reference, options, targetType); + if (value is null) { + return default; + } + return value; + } + + protected override object? GetValueCore ( + ref JniObjectReference reference, + JniObjectReferenceOptions options, + [DynamicallyAccessedMembers (Constructors)] + Type? targetType = null) + { + EnsureNotDisposed (); + if (!reference.IsValid) { + return null; + } + + var existing = PeekValue (reference); + if (existing != null && (targetType == null || targetType.IsAssignableFrom (existing.GetType ()))) { + JniObjectReference.Dispose (ref reference, options); + return existing; + } + + return CreateValueCore (ref reference, options, targetType); + } + + object? PeekBoxedObject (JniObjectReference reference) + { + var peer = PeekPeer (reference); + if (peer == null) { + return null; + } + return TryUnboxPeerObject (peer, out var result) ? result : null; + } + + protected override bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (true)] out object? result) + { + if (value is TrimmableJavaProxyObject proxy) { + result = proxy.Value; + return true; + } + + return base.TryUnboxPeerObject (value, out result); + } + + protected override JniObjectReference CreateLocalObjectReferenceArgumentCore (Type type, object? value) + { + if (value == null) { + return new JniObjectReference (); + } + + if (PrimitiveArrayInfo.TryCreateObjectReference (value, out var primitiveArrayReference)) { + return primitiveArrayReference; + } + + if (value is IJavaPeerable peerable) { + return peerable.PeerReference.IsValid + ? peerable.PeerReference.NewLocalRef () + : new JniObjectReference (); + } + + if (JavaConvert.TryConvertKnownValueToLocalJniHandle (value, out var handle)) { + return handle == IntPtr.Zero + ? new JniObjectReference () + : new JniObjectReference (handle, JniObjectReferenceType.Local); + } + + var proxy = TrimmableJavaProxyObject.GetProxy (value); + return proxy.PeerReference.NewLocalRef (); + } + + protected override JniValueMarshaler GetValueMarshalerCore (Type type) + => throw new NotSupportedException ($"{nameof (GetValueMarshalerCore)} should not be called in the trimmable typemap path."); + + protected override JniValueMarshaler GetValueMarshalerCore () + => throw new NotSupportedException ($"{nameof (GetValueMarshalerCore)} should not be called in the trimmable typemap path."); + + // Trimmable proxies use Java identity semantics: equals/hashCode/toString are NOT overridden + // and therefore do not delegate to the wrapped .NET object. This matches the trimmable Java + // runtime copy of JavaProxyObject and avoids the reflection-based native method registration + // that is unsupported in the trimmable typemap path. + [Register ("net/dot/jni/internal/TrimmableJavaProxyObject")] + private sealed class TrimmableJavaProxyObject : Java.Lang.Object + { + static readonly ConditionalWeakTable CachedValues = new (); + + private TrimmableJavaProxyObject (object value) => Value = value; + + // This class is not meant to be instantiated from the Java side, so make the parameterless constructor + // private to prevent the generator from generating the default Java ctor. + private TrimmableJavaProxyObject () => throw new UnreachableException (); + + public object Value { get; } + + public static TrimmableJavaProxyObject GetProxy (object value) + { + ArgumentNullException.ThrowIfNull (value); + + lock (CachedValues) { + return CachedValues.GetOrAdd (value, static (value) => new TrimmableJavaProxyObject (value)); + } + } + } +} diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj index 9b90b93ce48..c8c2c87a071 100644 --- a/src/Mono.Android/Mono.Android.csproj +++ b/src/Mono.Android/Mono.Android.csproj @@ -372,6 +372,7 @@ + diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs index 4f3cfb71bbb..7cb25bd7540 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs @@ -482,15 +482,15 @@ public void BuildHasNoWarnings (bool isRelease, bool multidex, string packageFor Assert.IsTrue (b.Build (proj), "Build should have succeeded."); if (runtime == AndroidRuntime.NativeAOT) { - // NativeAOT currently (Jun 2026) produces 4 `ILC : AOT analysis warning IL3050` - // warnings: two distinct warnings (the reflection-backed ManagedTypeManager - // generic ctor and JNINativeWrapper.CreateDelegate), each surfaced twice in the - // MSBuild summary (once per publish target context). #11753 replaced the JNIEnv - // array path with JavaArrayProxy, removing the previous JNIEnv.MakeArrayType - // warning. Even though this test expects no warnings and the above likely make - // the app not work correctly at run time, it is still worth running this test - // under NativeAOT to test for the absence of other warnings. - int numberOfExpectedWarnings = 4; + // NativeAOT currently (Jul 2026) produces 2 `ILC : AOT analysis warning IL3050` + // warnings: a single distinct warning (the reflection-backed ManagedTypeManager + // constructor, which chains to the [RequiresDynamicCode] ReflectionJniTypeManager + // base and is therefore not Native AOT compatible), surfaced twice in the MSBuild + // summary (once per publish target context). Even though this test expects no + // warnings and the above likely make the app not work correctly at run time, it is + // still worth running this test under NativeAOT to test for the absence of other + // warnings. + int numberOfExpectedWarnings = 2; // MSBuild prints a " N Warning(s)" summary line near the end of the build; parse N so the // assertion can report the actual count instead of a bare "Expected: True But was: False". diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.apkdesc index 042ee6a45db..eb6cece9dff 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.apkdesc +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.CoreCLR.apkdesc @@ -5,10 +5,10 @@ "Size": 6652 }, "classes.dex": { - "Size": 9452072 + "Size": 9413828 }, "classes2.dex": { - "Size": 108080 + "Size": 158204 }, "kotlin/annotation/annotation.kotlin_builtins": { "Size": 928 @@ -2235,4 +2235,4 @@ } }, "PackageSize": 20786765 -} \ No newline at end of file +} diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs index 9c1a9f9865c..f527953d850 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs @@ -345,6 +345,27 @@ public void Build_SinglePeer_HasAssociation () Assert.Single (model.Associations); } + [Fact] + public void Build_ConcreteSelfPeerWithoutActivation_CreatesProxyAndAssociation () + { + // A concrete type that supplies its own Java peer ([JniTypeSignature(GenerateJavaPeer=false)], + // modeled here as DoNotGenerateAcw=true with no activation ctor / invoker) is constructed + // managed-side via `new`. Its managed→Java JNI name must be resolvable, otherwise the runtime + // falls back to the generic mono.android.runtime.JavaObject peer and throws ArrayStoreException + // when the instance is stored into a typed Java array (e.g. CrossReferenceBridge[]). + var peer = MakeMcwPeer ("net/dot/jni/test/CrossReferenceBridge", "Java.InteropTests.CrossReferenceBridge", "Java.Interop-Tests") + with { DoNotGenerateAcw = true }; + var model = BuildModel (new [] { peer }, "MyTypeMap"); + + var proxy = Assert.Single (model.ProxyTypes); + Assert.Equal ("net/dot/jni/test/CrossReferenceBridge", proxy.JniName); + Assert.Equal ("Java.InteropTests.CrossReferenceBridge", proxy.TargetType.ManagedTypeName); + Assert.False (proxy.HasActivation); + + var association = Assert.Single (model.Associations); + Assert.Contains ("Java.InteropTests.CrossReferenceBridge, Java.Interop-Tests", association.SourceTypeReference); + } + [Fact] public void Build_PeerWithInvoker_CreatesProxy () { diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs index 766ae9f42f5..8b4abf0d84f 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs @@ -180,23 +180,22 @@ public void RegisteredPeer_CanCreateGenericHolder () } [Test] - public void JavaProxyObject_ValueMarshalerUsesProxyType () + public void TrimmableJavaProxyObject_CreateLocalObjectReferenceArgumentUsesProxyType () { AssumeTrimmableTypeMapEnabled (); var value = new object (); - var marshaler = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (typeof (object)); - var state = marshaler.CreateObjectReferenceArgumentState (value); + var reference = JniEnvironment.Runtime.ValueManager.CreateLocalObjectReferenceArgument (typeof (object), value); try { - Assert.AreEqual ("net/dot/jni/internal/JavaProxyObject", JNIEnv.GetClassNameFromInstance (state.ReferenceValue.Handle)); + Assert.AreEqual ("net/dot/jni/internal/TrimmableJavaProxyObject", JNIEnv.GetClassNameFromInstance (reference.Handle)); } finally { - marshaler.DestroyArgumentState (value, ref state); + JniObjectReference.Dispose (ref reference); } } [Test] - public void JavaProxyObject_CanBeUsedInObjectArray () + public void TrimmableJavaProxyObject_CanBeUsedInObjectArray () { AssumeTrimmableTypeMapEnabled (); @@ -207,19 +206,18 @@ public void JavaProxyObject_CanBeUsedInObjectArray () } [Test] - public void JavaProxyObject_ObjectMethodsUseJavaIdentitySemantics () + public void TrimmableJavaProxyObject_ObjectMethodsUseJavaIdentitySemantics () { AssumeTrimmableTypeMapEnabled (); var value = new object (); var other = new object (); - var marshaler = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (typeof (object)); - var state = marshaler.CreateObjectReferenceArgumentState (value); - var otherState = marshaler.CreateObjectReferenceArgumentState (other); + var reference = JniEnvironment.Runtime.ValueManager.CreateLocalObjectReferenceArgument (typeof (object), value); + var otherReference = JniEnvironment.Runtime.ValueManager.CreateLocalObjectReferenceArgument (typeof (object), other); try { - var localProxy = state.ReferenceValue.NewLocalRef (); - var localOtherProxy = otherState.ReferenceValue.NewLocalRef (); + var localProxy = reference.NewLocalRef (); + var localOtherProxy = otherReference.NewLocalRef (); try { IntPtr proxyClass = JNIEnv.GetObjectClass (localProxy.Handle); @@ -239,7 +237,7 @@ public void JavaProxyObject_ObjectMethodsUseJavaIdentitySemantics () JNIEnv.CallIntMethod (localProxy.Handle, hashCode)); var proxyString = JNIEnv.GetString (JNIEnv.CallObjectMethod (localProxy.Handle, toString), JniHandleOwnership.TransferLocalRef); Assert.IsTrue ( - proxyString.StartsWith ("net.dot.jni.internal.JavaProxyObject@", StringComparison.Ordinal), + proxyString.StartsWith ("net.dot.jni.internal.TrimmableJavaProxyObject@", StringComparison.Ordinal), proxyString); } finally { JniObjectReference.Dispose (ref systemClass); @@ -252,8 +250,8 @@ public void JavaProxyObject_ObjectMethodsUseJavaIdentitySemantics () JniObjectReference.Dispose (ref localOtherProxy); } } finally { - marshaler.DestroyArgumentState (other, ref otherState); - marshaler.DestroyArgumentState (value, ref state); + JniObjectReference.Dispose (ref otherReference); + JniObjectReference.Dispose (ref reference); } } diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs index 99cde29dcd3..315bb04bac6 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.Net/AndroidMessageHandlerTests.cs @@ -137,6 +137,7 @@ public async Task DoesNotDisposeContentStream() } [Test] + [Ignore ("Crashes the test process with a native SIGSEGV: https://github.com/dotnet/android/issues/8608")] public async Task ServerCertificateCustomValidationCallback_ApproveRequest () { bool callbackHasBeenCalled = false; @@ -163,6 +164,7 @@ public async Task ServerCertificateCustomValidationCallback_ApproveRequest () } [Test] + [Ignore ("Crashes the test process with a native SIGSEGV: https://github.com/dotnet/android/issues/8608")] public async Task ServerCertificateCustomValidationCallback_RejectRequest () { bool callbackHasBeenCalled = false; @@ -181,6 +183,7 @@ public async Task ServerCertificateCustomValidationCallback_RejectRequest () } [Test] + [Ignore ("Crashes the test process with a native SIGSEGV: https://github.com/dotnet/android/issues/8608")] public async Task ServerCertificateCustomValidationCallback_ApprovesRequestWithInvalidCertificate () { bool callbackHasBeenCalled = false; @@ -208,6 +211,7 @@ public async Task NoServerCertificateCustomValidationCallback_ThrowsWhenThereIsC } [Test] + [Ignore ("Crashes the test process with a native SIGSEGV: https://github.com/dotnet/android/issues/8608")] public async Task ServerCertificateCustomValidationCallback_IgnoresCertificateHostnameMismatch () { bool callbackHasBeenCalled = false; @@ -229,6 +233,7 @@ public async Task ServerCertificateCustomValidationCallback_IgnoresCertificateHo } [Test] + [Ignore ("Crashes the test process with a native SIGSEGV: https://github.com/dotnet/android/issues/8608")] public async Task ServerCertificateCustomValidationCallback_Redirects () { int callbackCounter = 0; diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/TestInstrumentation.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/TestInstrumentation.cs index da97716db5d..8970d10ed5b 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/TestInstrumentation.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/TestInstrumentation.cs @@ -29,7 +29,7 @@ protected override IEnumerable? ExcludedCategories { if (Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) { categories.Add ("NativeTypeMap"); - categories.Add ("Export"); + categories.Add ("TrimmableTypeMapUnsupported"); } // Build-time flags flow in via runtimeconfig.json properties @@ -39,7 +39,10 @@ protected override IEnumerable? ExcludedCategories { categories.Add ("NativeAOTIgnore"); categories.Add ("SSL"); categories.Add ("NTLM"); - categories.Add ("Export"); + + if (!Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) { + categories.Add ("Export"); + } } if (HasAppContextSwitch ("EnableLLVM")) {