diff --git a/src/Mono.Android/Android.Runtime/JNIEnv.cs b/src/Mono.Android/Android.Runtime/JNIEnv.cs index 8b004855ba8..8104ee308a2 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnv.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnv.cs @@ -442,6 +442,12 @@ static unsafe IntPtr monovm_typemap_managed_to_java (Type type, byte* mvidptr) internal static unsafe string? TypemapManagedToJava (Type type) { + if (RuntimeFeature.TrimmableTypeMap) { + // The trimmable typemap doesn't use the native typemap tables. + // Delegate to the managed TrimmableTypeMap instead. + return TrimmableTypeMap.Instance.TryGetJniNameForManagedType (type, out var jniName) ? jniName : null; + } + if (mvid_bytes == null) mvid_bytes = new byte[16]; diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs index 097c8e5d0ab..8bb98c4a993 100644 --- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs +++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs @@ -110,6 +110,7 @@ internal static void InitializeJniRuntimeEarly (JnienvInitializeArgs args) internal static void InitializeJniRuntime (JniRuntime runtime, JnienvInitializeArgs args) { androidRuntime = runtime; + JniRuntime.SetCurrent (runtime); SetSynchronizationContext (); } @@ -159,6 +160,7 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args) valueManager, args->jniAddNativeMethodRegistrationAttributePresent != 0 ); + JniRuntime.SetCurrent (androidRuntime); if (RuntimeFeature.TrimmableTypeMap) { TrimmableTypeMap.Initialize (); diff --git a/src/Mono.Android/Java.Interop/TypeManager.cs b/src/Mono.Android/Java.Interop/TypeManager.cs index 379558e3265..cc0b22936bd 100644 --- a/src/Mono.Android/Java.Interop/TypeManager.cs +++ b/src/Mono.Android/Java.Interop/TypeManager.cs @@ -268,7 +268,12 @@ static Type monovm_typemap_java_to_managed (string java_type_name) return type; } - if (RuntimeFeature.IsMonoRuntime) { + if (RuntimeFeature.TrimmableTypeMap) { + throw new System.Diagnostics.UnreachableException ( + $"{nameof (TypeManager)}.{nameof (GetJavaToManagedTypeCore)} should not be used when " + + $"{nameof (RuntimeFeature.TrimmableTypeMap)} is enabled. The trimmable path should resolve " + + $"types through {nameof (TrimmableTypeMapTypeManager)}."); + } else if (RuntimeFeature.IsMonoRuntime) { type = monovm_typemap_java_to_managed (class_name); } else if (RuntimeFeature.IsCoreClrRuntime) { type = clr_typemap_java_to_managed (class_name); diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs index e1b7e975059..2278e272461 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalValueManager.cs @@ -507,18 +507,35 @@ void ProcessContext (HandleContext* context) [DynamicallyAccessedMembers (Constructors)] Type? targetType) { + ThrowIfDisposed (); + + if (!reference.IsValid) { + return null; + } + if (RuntimeFeature.TrimmableTypeMap) { - var typeMap = TrimmableTypeMap.Instance; - if (typeMap is not null && targetType is not null) { - var proxy = typeMap.GetProxyForManagedType (targetType); - if (proxy is not null) { - var peer = proxy.CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer); - if (peer is not null) { - peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); - JniObjectReference.Dispose (ref reference, transfer); - return peer; + try { + var typeMap = TrimmableTypeMap.Instance; + var proxy = typeMap.GetProxyForJavaObject (reference.Handle, targetType); + var peer = proxy?.CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer); + if (peer is not null) { + var peerState = peer.JniManagedPeerState | JniManagedPeerStates.Replaceable; + if (global::Java.Interop.Runtime.IsGCUserPeer (peer.PeerReference.Handle)) { + peerState |= JniManagedPeerStates.Activatable; } + peer.SetJniManagedPeerState (peerState); + return peer; } + + var targetName = targetType?.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); } } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index b1c2b945429..6c5b51fef0d 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -20,6 +20,7 @@ namespace Microsoft.Android.Runtime; class TrimmableTypeMap { static readonly Lock s_initLock = new (); + static readonly JavaPeerProxy s_noPeerSentinel = new MissingJavaPeerProxy (); static TrimmableTypeMap? s_instance; internal static TrimmableTypeMap Instance => @@ -28,7 +29,8 @@ class TrimmableTypeMap readonly IReadOnlyDictionary _typeMap; readonly IReadOnlyDictionary _proxyTypeMap; - readonly ConcurrentDictionary _proxyCache = new (); + readonly ConcurrentDictionary _proxyCache = new (); + readonly ConcurrentDictionary _peerProxyCache = new (StringComparer.Ordinal); TrimmableTypeMap () { @@ -55,9 +57,6 @@ internal static void Initialize () } } - /// - /// Registers the mono.android.Runtime.registerNatives JNI native method. - /// unsafe void RegisterNatives () { using var runtimeClass = new JniType ("mono/android/Runtime"u8); @@ -68,49 +67,111 @@ unsafe void RegisterNatives () } } - internal bool TryGetType (string jniSimpleReference, [NotNullWhen (true)] out Type? type) - => _typeMap.TryGetValue (jniSimpleReference, out type); + internal bool TryGetTargetType (string jniSimpleReference, [NotNullWhen (true)] out Type? type) + { + type = GetProxyForJavaType (jniSimpleReference)?.TargetType; + return type is not null; + } - /// - /// Finds the proxy for a managed type using the generated proxy type map. - /// Results are cached per type. - /// - internal JavaPeerProxy? GetProxyForManagedType (Type managedType) - => _proxyCache.GetOrAdd (managedType, static (type, self) => self.ResolveProxyForManagedType (type), this); + JavaPeerProxy? GetProxyForManagedType (Type managedType) + { + var proxy = _proxyCache.GetOrAdd (managedType, static (type, self) => { + if (!self._proxyTypeMap.TryGetValue (type, out var proxyType)) { + return s_noPeerSentinel; + } + + return proxyType.GetCustomAttribute (inherit: false) ?? s_noPeerSentinel; + }, this); + return ReferenceEquals (proxy, s_noPeerSentinel) ? null : proxy; + } - JavaPeerProxy? ResolveProxyForManagedType (Type managedType) + JavaPeerProxy? GetProxyForJavaType (string className) { - if (!_proxyTypeMap.TryGetValue (managedType, out var proxyType)) { - return null; - } + var proxy = _peerProxyCache.GetOrAdd (className, static (name, self) => { + if (!self._typeMap.TryGetValue (name, out var mappedType)) { + return s_noPeerSentinel; + } - return proxyType.GetCustomAttribute (inherit: false); + var proxy = mappedType.GetCustomAttribute (inherit: false); + if (proxy is null) { + // Alias typemap entries (for example "jni/name[1]") are not implemented yet. + // Support for them will be added in a follow-up for https://github.com/dotnet/android/issues/10788. + throw new NotImplementedException ( + $"Trimmable typemap alias handling is not implemented yet for '{name}'."); + } + + return proxy; + }, this); + return ReferenceEquals (proxy, s_noPeerSentinel) ? null : proxy; } internal bool TryGetJniNameForManagedType (Type managedType, [NotNullWhen (true)] out string? jniName) { - var proxy = GetProxyForManagedType (managedType); - if (proxy is not null) { - jniName = proxy.JniName; - return true; - } - - jniName = null; - return false; + jniName = GetProxyForManagedType (managedType)?.JniName; + return jniName is not null; } - /// - /// Creates a peer instance using the proxy's CreateInstance method. - /// Given a managed type, resolves the JNI name, finds the proxy, and calls CreateInstance. - /// - internal bool TryCreatePeer (Type type, IntPtr handle, JniHandleOwnership transfer) + internal JavaPeerProxy? GetProxyForJavaObject (IntPtr handle, Type? targetType = null) { - var proxy = GetProxyForManagedType (type); - if (proxy is null) { - return false; + if (handle == IntPtr.Zero) { + return null; } - return proxy.CreateInstance (handle, transfer) != null; + return TryGetProxyFromHierarchy (this, handle, targetType) ?? + TryGetProxyFromTargetType (this, handle, targetType); + + static JavaPeerProxy? TryGetProxyFromHierarchy (TrimmableTypeMap self, 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.GetProxyForJavaType (className); + if (proxy != null && (targetType is null || targetType.IsAssignableFrom (proxy.TargetType))) { + return proxy; + } + } + + var super = JniEnvironment.Types.GetSuperclass (jniClass); + JniObjectReference.Dispose (ref jniClass); + jniClass = super; + } + } finally { + JniObjectReference.Dispose (ref jniClass); + } + + return null; + } + + static JavaPeerProxy? TryGetProxyFromTargetType (TrimmableTypeMap self, IntPtr handle, Type? targetType) + { + if (targetType is null) { + return null; + } + + 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; + } + + var selfRef = new JniObjectReference (handle); + var objClass = default (JniObjectReference); + var targetClass = default (JniObjectReference); + try { + objClass = JniEnvironment.Types.GetObjectClass (selfRef); + targetClass = JniEnvironment.Types.FindClass (targetJniName); + return JniEnvironment.Types.IsAssignableFrom (objClass, targetClass) ? proxy : null; + } finally { + JniObjectReference.Dispose (ref objClass); + JniObjectReference.Dispose (ref targetClass); + } + } } const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; @@ -162,4 +223,13 @@ static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHa } } + sealed class MissingJavaPeerProxy : JavaPeerProxy + { + public MissingJavaPeerProxy () : base ("", typeof (Java.Lang.Object), null) + { + } + + public override IJavaPeerable? CreateInstance (IntPtr handle, JniHandleOwnership transfer) => null; + } + } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs index 3f34ab3c62a..50fb402b91f 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapTypeManager.cs @@ -21,7 +21,7 @@ protected override IEnumerable GetTypesForSimpleReference (string jniSimpl yield return t; } - if (TrimmableTypeMap.Instance.TryGetType (jniSimpleReference, out var type)) { + if (TrimmableTypeMap.Instance.TryGetTargetType (jniSimpleReference, out var type)) { yield return type; } }