diff --git a/docs/website/mtouch-errors.md b/docs/website/mtouch-errors.md index 7faab8252854..034dd76d5bd9 100644 --- a/docs/website/mtouch-errors.md +++ b/docs/website/mtouch-errors.md @@ -3749,3 +3749,28 @@ This exception will have an inner exception which gives the reason for the failu ### MT8036: Failed to convert the value at index {index} from {type} to {type}. This exception will have an inner exception which gives the reason for the failure. + + + +### MX8056: Failed to marshal the Objective-C object {handle} (type: {objc_type}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance of generic type {managed_type}. + +This occurs when the Xamarin.iOS runtime finds an Objective-C object without a +corresponding managed wrapper object, and when trying to create that managed +wrapper, it turns out it's not possible. This error is specific to the managed +static registrar. + +There are a few reasons this may happen: + +* A managed wrapper existed at some point, but was collected by the GC. If the + native object is still alive, and later resurfaces to managed code, the + Xamarin.iOS runtime will try to re-create a managed wrapper instance. In + most cases the problem here is that the managed wrapper shouldn't have been + collected by the GC in the first place. + + Possible causes include: + + * Manually calling Dispose too early on the managed wrapper. + * Incorrect bindings for third-party libraries. + * Reference-counting bugs in third-party libraries. + +* This indicates a bug in Xamarin.iOS. Please file a new issue on [github](https://github.com/xamarin/xamarin-macios/issues/new). diff --git a/src/Foundation/NSObject2.cs b/src/Foundation/NSObject2.cs index cce28c98309a..271971ec8e06 100644 --- a/src/Foundation/NSObject2.cs +++ b/src/Foundation/NSObject2.cs @@ -69,6 +69,16 @@ public class NSObjectFlag { } #endif +#if NET + // This interface will be made public when the managed static registrar is used. + internal interface INSObjectFactory { + // The method will be implemented via custom linker step if the managed static registrar is used + // for NSObject subclasses which have an (NativeHandle) or (IntPtr) constructor. + [MethodImpl(MethodImplOptions.NoInlining)] + virtual static NSObject _Xamarin_ConstructNSObject (NativeHandle handle) => null; + } +#endif + #if NET && !COREBUILD [ObjectiveCTrackedType] [SupportedOSPlatform ("ios")] @@ -81,6 +91,9 @@ public partial class NSObject : INativeObject #if !COREBUILD , IEquatable , IDisposable +#endif +#if NET + , INSObjectFactory #endif { #if !COREBUILD diff --git a/src/ObjCRuntime/IManagedRegistrar.cs b/src/ObjCRuntime/IManagedRegistrar.cs index 1c2c370d78f5..426fdf9563e1 100644 --- a/src/ObjCRuntime/IManagedRegistrar.cs +++ b/src/ObjCRuntime/IManagedRegistrar.cs @@ -6,7 +6,6 @@ // // Copyright 2023 Microsoft Corp - #if NET #nullable enable @@ -34,6 +33,10 @@ interface IManagedRegistrar { // This method will be called once per assembly, and the implementation has to // add all the interface -> wrapper type mappings to the dictionary. void RegisterWrapperTypes (Dictionary type); + // Create an instance of a managed NSObject subclass for an existing Objective-C object. + INativeObject? ConstructNSObject (RuntimeTypeHandle typeHandle, NativeHandle nativeHandle); + // Create an instance of a managed NSObject subclass for an existing Objective-C object. + INativeObject? ConstructINativeObject (RuntimeTypeHandle typeHandle, NativeHandle nativeHandle, bool owns); } } diff --git a/src/ObjCRuntime/INativeObject.cs b/src/ObjCRuntime/INativeObject.cs index 022b1ac868b5..58c085400740 100644 --- a/src/ObjCRuntime/INativeObject.cs +++ b/src/ObjCRuntime/INativeObject.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using Foundation; #if !NET @@ -15,6 +16,14 @@ NativeHandle Handle { get; } #endif + +#if NET + // The method will be implemented via custom linker step if the managed static registrar is used + // for classes which have an (NativeHandle, bool) or (IntPtr, bool) constructor. + // This method will be made public when the managed static registrar is used. + [MethodImpl(MethodImplOptions.NoInlining)] + internal static virtual INativeObject? _Xamarin_ConstructINativeObject (NativeHandle handle, bool owns) => null; +#endif } #if !COREBUILD diff --git a/src/ObjCRuntime/RegistrarHelper.cs b/src/ObjCRuntime/RegistrarHelper.cs index 6b764a7dfe1f..a790af23ec24 100644 --- a/src/ObjCRuntime/RegistrarHelper.cs +++ b/src/ObjCRuntime/RegistrarHelper.cs @@ -208,6 +208,22 @@ internal static uint LookupRegisteredTypeId (Type type) return entry.Registrar.LookupTypeId (type.TypeHandle); } + internal static T? ConstructNSObject (Type type, NativeHandle nativeHandle) + where T : class, INativeObject + { + if (!TryGetMapEntry (type.Assembly.GetName ().Name!, out var entry)) + return null; + return (T?) entry.Registrar.ConstructNSObject (type.TypeHandle, nativeHandle); + } + + internal static T? ConstructINativeObject (Type type, NativeHandle nativeHandle, bool owns) + where T : class, INativeObject + { + if (!TryGetMapEntry (type.Assembly.GetName ().Name!, out var entry)) + return null; + return (T?) entry.Registrar.ConstructINativeObject (type.TypeHandle, nativeHandle, owns); + } + // helper functions for converting between native and managed objects static NativeHandle ManagedArrayToNSArray (object array, bool retain) { diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index 34916f7e1611..3db1cc5e3b48 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -1260,38 +1260,65 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut } msg.Append (")."); + if (sel != IntPtr.Zero || method_handle.Value != IntPtr.Zero) { - msg.AppendLine (); - msg.AppendLine ("Additional information:"); - if (sel != IntPtr.Zero) - msg.Append ("\tSelector: ").Append (Selector.GetName (sel)).AppendLine (); - if (method_handle.Value != IntPtr.Zero) { - try { - var method = MethodBase.GetMethodFromHandle (method_handle); - msg.Append ($"\tMethod: "); - if (method is not null) { - // there's no good built-in function to format a MethodInfo :/ - msg.Append (method.DeclaringType?.FullName ?? string.Empty); - msg.Append ("."); - msg.Append (method.Name); - msg.Append ("("); - var parameters = method.GetParameters (); - for (var i = 0; i < parameters.Length; i++) { - if (i > 0) - msg.Append (", "); - msg.Append (parameters [i].ParameterType.FullName); - } - msg.Append (")"); - } else { - msg.Append ($"Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}"); + AppendAdditionalInformation (msg, sel, method_handle); + } + + throw ErrorHelper.CreateError (8027, msg.ToString ()); + } + +#if NET + static void CannotCreateManagedInstanceOfGenericType (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolution resolution, IntPtr sel, RuntimeMethodHandle method_handle) + { + if (resolution == MissingCtorResolution.Ignore) + return; + + if (klass == IntPtr.Zero) + klass = Class.GetClassForObject (ptr); + + var msg = new StringBuilder (); + msg.AppendFormat (Xamarin.Bundler.Errors.MX8056 /* Failed to marshal the Objective-C object 0x{0} (type: {1}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance of generic type {2}. */, ptr.ToString ("x"), new Class (klass).Name, type.FullName); + + if (sel != IntPtr.Zero || method_handle.Value != IntPtr.Zero) { + AppendAdditionalInformation (msg, sel, method_handle); + } + + throw ErrorHelper.CreateError (8056, msg.ToString ()); + } +#endif + + static void AppendAdditionalInformation (StringBuilder msg, IntPtr sel, RuntimeMethodHandle method_handle) + { + msg.AppendLine (); + msg.AppendLine ("Additional information:"); + if (sel != IntPtr.Zero) + msg.Append ("\tSelector: ").Append (Selector.GetName (sel)).AppendLine (); + if (method_handle.Value != IntPtr.Zero) { + try { + var method = MethodBase.GetMethodFromHandle (method_handle); + msg.Append ($"\tMethod: "); + if (method is not null) { + // there's no good built-in function to format a MethodInfo :/ + msg.Append (method.DeclaringType?.FullName ?? string.Empty); + msg.Append ("."); + msg.Append (method.Name); + msg.Append ("("); + var parameters = method.GetParameters (); + for (var i = 0; i < parameters.Length; i++) { + if (i > 0) + msg.Append (", "); + msg.Append (parameters [i].ParameterType.FullName); } - msg.AppendLine (); - } catch (Exception ex) { - msg.Append ($"\tMethod: Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}: {ex.Message}"); + msg.Append (")"); + } else { + msg.Append ($"Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}"); } + msg.AppendLine (); + } catch (Exception ex) { + msg.Append ($"\tMethod: Unable to resolve RuntimeMethodHandle 0x{method_handle.Value.ToString ("x")}: {ex.Message}"); } } - throw ErrorHelper.CreateError (8027, msg.ToString ()); } static NSObject? ConstructNSObject (IntPtr ptr, IntPtr klass, MissingCtorResolution missingCtorResolution) @@ -1310,19 +1337,61 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut return ConstructNSObject (ptr, typeof (T), MissingCtorResolution.ThrowConstructor1NotFound); } - // The generic argument T is only used to cast the return value. // The 'selector' and 'method' arguments are only used in error messages. - static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution) where T : class, INativeObject + static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution) where T : NSObject { return ConstructNSObject (ptr, type, missingCtorResolution, IntPtr.Zero, default (RuntimeMethodHandle)); } - // The generic argument T is only used to cast the return value. // The 'selector' and 'method' arguments are only used in error messages. +#if NET + static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle) where T : NSObject, INSObjectFactory +#else static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle) where T : class, INativeObject +#endif { if (type is null) throw new ArgumentNullException (nameof (type)); +#if NET + if (Runtime.IsManagedStaticRegistrar) { + T? instance = default; + var nativeHandle = new NativeHandle (ptr); + + // We want to create an instance of `type` and if we have the chance to use the factory method + // on the generic type, we will prefer it to using the lookup table. + if (typeof (T) == type + && typeof (T) != typeof (NSObject) + && !(typeof (T).IsInterface || typeof (T).IsAbstract)) { + instance = ConstructNSObjectViaFactoryMethod (nativeHandle); + } + + // Generic types can only be instantiated through the factory method and if that failed, we can't + // fall back to the lookup tables and we need to stop here. + if (type.IsGenericType && instance is null) { + CannotCreateManagedInstanceOfGenericType (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle); + return null; + } + + // If we couldn't create an instance of T through the factory method, we'll use the lookup table + // based on the RuntimeTypeHandle. + // + // This isn't possible for generic types - we don't know the type arguments at compile time + // (otherwise we would be able to create an instance of T through the factory method). + // For non-generic types, we can call the NativeHandle constructor based on the RuntimeTypeHandle. + + if (instance is null) { + instance = RegistrarHelper.ConstructNSObject (type, nativeHandle); + } + + if (instance is null) { + // If we couldn't create an instance using the lookup table either, it means `type` doesn't contain + // a suitable constructor. + MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle); + } + + return instance; + } +#endif var ctor = GetIntPtrConstructor (type); @@ -1343,6 +1412,17 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut #endif return (T) ctor.Invoke (ctorArguments); + +#if NET + // It isn't possible to call T._Xamarin_ConstructNSObject (...) directly from the parent function. For some + // types, the app crashes with a SIGSEGV: + // + // error: * Assertion at /Users/runner/work/1/s/src/mono/mono/mini/mini-generic-sharing.c:2283, condition `m_class_get_vtable (info->klass)' not met + // + // When the same call is made from a separate function, it works fine. + static T? ConstructNSObjectViaFactoryMethod (NativeHandle handle) + => T._Xamarin_ConstructNSObject (handle) as T; +#endif } // The generic argument T is only used to cast the return value. @@ -1354,6 +1434,56 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut if (type.IsByRef) type = type.GetElementType ()!; +#if NET + if (Runtime.IsManagedStaticRegistrar) { + var nativeHandle = new NativeHandle (ptr); + T? instance = null; + + // We want to create an instance of `type` and if we have the chance to use the factory method + // on the generic type, we will prefer it to using the lookup table. + if (typeof (T) == type + && typeof (T) != typeof (INativeObject) + && typeof (T) != typeof (NSObject) + && !(typeof (T).IsInterface || typeof (T).IsAbstract)) { + instance = ConstructINativeObjectViaFactoryMethod (nativeHandle, owns); + } + + // Generic types can only be instantiated through the factory method and if that failed, we can't + // fall back to the lookup tables and we need to stop here. + if (type.IsGenericType && instance is null) { + CannotCreateManagedInstanceOfGenericType (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle); + return null; + } + + // If we couldn't create an instance of T through the factory method, we'll use the lookup table + // based on the RuntimeTypeHandle. + // + // This isn't possible for generic types - we don't know the type arguments at compile time + // (otherwise we would be able to create an instance of T through the factory method). + // For non-generic types, we can call the NativeHandle constructor based on the RuntimeTypeHandle. + + // If type is an NSObject, we prefer the NSObject lookup table + if (instance is null && type != typeof (NSObject) && type.IsSubclassOf (typeof (NSObject))) { + instance = (T?)(INativeObject?) RegistrarHelper.ConstructNSObject (type, nativeHandle); + if (instance is not null && owns) { + Runtime.TryReleaseINativeObject (instance); + } + } + + if (instance is null && type != typeof (INativeObject)) { + instance = RegistrarHelper.ConstructINativeObject (type, nativeHandle, owns); + } + + if (instance is null) { + // If we couldn't create an instance using the lookup table either, it means `type` doesn't contain + // a suitable constructor. + MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle); + } + + return instance; + } +#endif + var ctor = GetIntPtr_BoolConstructor (type); if (ctor is null) { @@ -1374,6 +1504,17 @@ static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolut ctorArguments [1] = owns; return (T?) ctor.Invoke (ctorArguments); + +#if NET + // It isn't possible to call T._Xamarin_ConstructINativeObject (...) directly from the parent function. For some + // types, the app crashes with a SIGSEGV: + // + // error: * Assertion at /Users/runner/work/1/s/src/mono/mono/mini/mini-generic-sharing.c:2283, condition `m_class_get_vtable (info->klass)' not met + // + // When the same call is made from a separate function, it works fine. + static T? ConstructINativeObjectViaFactoryMethod (NativeHandle nativeHandle, bool owns) + => T._Xamarin_ConstructINativeObject (nativeHandle, owns) as T; +#endif } static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, NSObject.Flags flags) @@ -1753,7 +1894,7 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ // native objects and NSObject instances. throw ErrorHelper.CreateError (8004, $"Cannot create an instance of {implementation.FullName} for the native object 0x{ptr:x} (of type '{Class.class_getName (Class.GetClassForObject (ptr))}'), because another instance already exists for this native object (of type {o.GetType ().FullName})."); } - return ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle); + return ConstructNSObject (ptr, implementation!, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle); } return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle); @@ -1810,10 +1951,22 @@ static Type LookupINativeObjectImplementation (IntPtr ptr, Type target_type, Typ // native objects and NSObject instances. throw ErrorHelper.CreateError (8004, $"Cannot create an instance of {implementation.FullName} for the native object 0x{ptr:x} (of type '{Class.class_getName (Class.GetClassForObject (ptr))}'), because another instance already exists for this native object (of type {o.GetType ().FullName})."); } - var rv = (T?) ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound); +#if NET + if (!Runtime.IsManagedStaticRegistrar) { + // For other registrars other than managed-static the generic parameter of ConstructNSObject is used + // only to cast the return value so we can safely pass NSObject here to satisfy the constraints of the + // generic parameter. + var rv = (T?)(INativeObject?) ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle); + if (owns) + TryReleaseINativeObject (rv); + return rv; + } +#else + var rv = ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle); if (owns) TryReleaseINativeObject (rv); return rv; +#endif } return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle); diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index 6ce1973a8d9c..5a0bb3144b1c 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -839,6 +839,9 @@ protected override bool ContainsPlatformReference (AssemblyDefinition assembly) } protected override IEnumerable CollectTypes (AssemblyDefinition assembly) + => GetAllTypes (assembly); + + internal static IEnumerable GetAllTypes (AssemblyDefinition assembly) { var queue = new Queue (); diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index 92f6384e547b..3f5ae20bdb5a 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -383,6 +383,12 @@ public TypeReference ObjCRuntime_IManagedRegistrar { } } + public TypeReference Foundation_INSObjectFactory { + get { + return GetTypeReference (PlatformAssembly, "Foundation.INSObjectFactory", out var _); + } + } + public TypeReference ObjCRuntime_INativeObject { get { return GetTypeReference (PlatformAssembly, "ObjCRuntime.INativeObject", out var _); @@ -749,6 +755,49 @@ public MethodReference IManagedRegistrar_LookupTypeId { } } + public MethodReference IManagedRegistrar_ConstructNSObject { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_IManagedRegistrar, "ConstructNSObject", + isStatic: false, + System_RuntimeTypeHandle, + ObjCRuntime_NativeHandle); + } + } + + public MethodReference INSObjectFactory__Xamarin_ConstructNSObject { + get { + return GetMethodReference (PlatformAssembly, + Foundation_INSObjectFactory, "_Xamarin_ConstructNSObject", + nameof (INSObjectFactory__Xamarin_ConstructNSObject), + isStatic: true, + ObjCRuntime_NativeHandle); + } + } + + public MethodReference IManagedRegistrar_ConstructINativeObject { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_IManagedRegistrar, "ConstructINativeObject", + nameof (IManagedRegistrar_ConstructINativeObject), + isStatic: false, + System_RuntimeTypeHandle, + ObjCRuntime_NativeHandle, + System_Boolean); + } + } + + public MethodReference INativeObject__Xamarin_ConstructINativeObject { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_INativeObject, "_Xamarin_ConstructINativeObject", + nameof (INativeObject__Xamarin_ConstructINativeObject), + isStatic: true, + ObjCRuntime_NativeHandle, + System_Boolean); + } + } + public MethodReference IManagedRegistrar_RegisterWrapperTypes { get { return GetMethodReference (PlatformAssembly, ObjCRuntime_IManagedRegistrar, "RegisterWrapperTypes", (v) => @@ -1091,6 +1140,15 @@ public MethodReference Runtime_RetainAndAutoreleaseNativeObject { } } + public MethodReference Runtime_TryReleaseINativeObject { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "TryReleaseINativeObject", + isStatic: true, + ObjCRuntime_INativeObject); + } + } + public MethodReference UnmanagedCallersOnlyAttribute_Constructor { get { return GetMethodReference (CorlibAssembly, "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute", ".ctor", (v) => v.IsDefaultConstructor ()); diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs index 28dc1c22f233..803a063e1de7 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; @@ -123,6 +124,8 @@ void CreateRegistrarType (AssemblyTrampolineInfo info) GenerateLookupType (info, registrarType, types); GenerateLookupTypeId (info, registrarType, types); GenerateRegisterWrapperTypes (registrarType); + GenerateConstructNSObject (registrarType); + GenerateConstructINativeObject (registrarType); // Make sure the linker doesn't sweep away anything we just generated. Annotations.Mark (registrarType); @@ -201,6 +204,13 @@ List GetTypesToRegister (TypeDefinition registrarType, AssemblyTrampol return types; } + IEnumerable GetRelevantTypes (Func isRelevant) + => StaticRegistrar.GetAllTypes (abr.CurrentAssembly) + .Cast () + .Where (type => !IsTrimmed (type)) + .Where (type => type.Module.Assembly == abr.CurrentAssembly) + .Where (isRelevant); + bool IsTrimmed (MemberReference type) { return StaticRegistrar.IsTrimmed (type, Annotations); @@ -281,6 +291,245 @@ void GenerateLookupType (AssemblyTrampolineInfo infos, TypeDefinition registrarT il.Emit (OpCodes.Ret); } + void GenerateConstructNSObject (TypeDefinition registrarType) + { + var createInstanceMethod = registrarType.AddMethod ("ConstructNSObject", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject); + var typeHandleParameter = createInstanceMethod.AddParameter ("typeHandle", abr.System_RuntimeTypeHandle); + var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); + createInstanceMethod.Overrides.Add (abr.IManagedRegistrar_ConstructNSObject); + var body = createInstanceMethod.CreateBody (out var il); + + // We generate something like this: + // if (RuntimeTypeHandle.Equals (typeHandle, typeof (TypeA).TypeHandle)) + // return new TypeA (nativeHandle); + // if (RuntimeTypeHandle.Equals (typeHandle, typeof (TypeB).TypeHandle)) + // return new TypeB (nativeHandle); + // return null; + + var types = GetRelevantTypes (type => type.IsNSObject (DerivedLinkContext) && !type.IsAbstract && !type.IsInterface); + + foreach (var type in types) { + var ctorRef = FindNSObjectConstructor (type); + if (ctorRef is null) { + Driver.Log (9, $"Cannot include {type.FullName} in ConstructNSObject because it doesn't have a suitable constructor"); + continue; + } + + var ctor = abr.CurrentAssembly.MainModule.ImportReference (ctorRef); + if (IsTrimmed (ctor)) + Annotations.Mark (ctor.Resolve ()); + + // We can only add a type to the table if it's not an open type. + if (!ManagedRegistrarStep.IsOpenType (type)) { + EnsureVisible (createInstanceMethod, ctor); + + il.Emit (OpCodes.Ldarga_S, typeHandleParameter); + il.Emit (OpCodes.Ldtoken, type); + il.Emit (OpCodes.Call, abr.RuntimeTypeHandle_Equals); + var falseTarget = il.Create (OpCodes.Nop); + il.Emit (OpCodes.Brfalse_S, falseTarget); + + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Newobj, ctor); + il.Emit (OpCodes.Ret); + + il.Append (falseTarget); + } + + // In addition to the big lookup method, implement the static factory method on the type: + ImplementConstructNSObjectFactoryMethod (type, ctor); + } + + // return default (NSObject); + il.Emit (OpCodes.Ldnull); + il.Emit (OpCodes.Ret); + } + + void GenerateConstructINativeObject (TypeDefinition registrarType) + { + var createInstanceMethod = registrarType.AddMethod ("ConstructINativeObject", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject); + var typeHandleParameter = createInstanceMethod.AddParameter ("typeHandle", abr.System_RuntimeTypeHandle); + var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); + var ownsParameter = createInstanceMethod.AddParameter ("owns", abr.System_Boolean); + createInstanceMethod.Overrides.Add (abr.IManagedRegistrar_ConstructINativeObject); + var body = createInstanceMethod.CreateBody (out var il); + + // We generate something like this: + // if (RuntimeTypeHandle.Equals (typeHandle, typeof (TypeA).TypeHandle)) + // return new TypeA (nativeHandle, owns); + // if (RuntimeTypeHandle.Equals (typeHandle, typeof (TypeB).TypeHandle)) + // return new TypeB (nativeHandle, owns); + // return null; + + var types = GetRelevantTypes (type => type.IsNativeObject () && !type.IsAbstract && !type.IsInterface); + + foreach (var type in types) { + var ctorRef = FindINativeObjectConstructor (type); + + if (ctorRef is not null) { + var ctor = abr.CurrentAssembly.MainModule.ImportReference (ctorRef); + + // we need to preserve the constructor because it might not be used anywhere else + if (IsTrimmed (ctor)) + Annotations.Mark (ctor.Resolve ()); + + if (!ManagedRegistrarStep.IsOpenType (type)) { + EnsureVisible (createInstanceMethod, ctor); + + il.Emit (OpCodes.Ldarga_S, typeHandleParameter); + il.Emit (OpCodes.Ldtoken, type); + il.Emit (OpCodes.Call, abr.RuntimeTypeHandle_Equals); + var falseTarget = il.Create (OpCodes.Nop); + il.Emit (OpCodes.Brfalse_S, falseTarget); + + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Ldarg, ownsParameter); + il.Emit (OpCodes.Newobj, ctor); + il.Emit (OpCodes.Ret); + + il.Append (falseTarget); + } + } + + // In addition to the big lookup method, implement the static factory method on the type: + ImplementConstructINativeObjectFactoryMethod (type, ctorRef); + } + + // return default (NSObject) + il.Emit (OpCodes.Ldnull); + il.Emit (OpCodes.Ret); + } + + void ImplementConstructNSObjectFactoryMethod (TypeDefinition type, MethodReference ctor) + { + // skip creating the factory for NSObject itself + if (type.Is ("Foundation", "NSObject")) + return; + + var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructNSObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.Foundation_NSObject); + var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); + abr.Foundation_INSObjectFactory.Resolve ().IsPublic = true; + createInstanceMethod.Overrides.Add (abr.INSObjectFactory__Xamarin_ConstructNSObject); + var body = createInstanceMethod.CreateBody (out var il); + + if (type.HasGenericParameters) { + ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); + } + + // return new TypeA (nativeHandle); // for NativeHandle ctor + // return new TypeA ((IntPtr) nativeHandle); // for IntPtr ctor + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Newobj, ctor); + il.Emit (OpCodes.Ret); + + Annotations.Mark (createInstanceMethod); + } + + void ImplementConstructINativeObjectFactoryMethod (TypeDefinition type, MethodReference? ctor) + { + // skip creating the factory for NSObject itself + if (type.Is ("Foundation", "NSObject")) + return; + + // If the type is a subclass of NSObject, we prefer the NSObject "IntPtr" constructor + var nsobjectConstructor = type.IsNSObject (DerivedLinkContext) ? FindNSObjectConstructor (type) : null; + if (nsobjectConstructor is null && ctor is null) + return; + + var createInstanceMethod = type.AddMethod ("_Xamarin_ConstructINativeObject", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.ObjCRuntime_INativeObject); + var nativeHandleParameter = createInstanceMethod.AddParameter ("nativeHandle", abr.ObjCRuntime_NativeHandle); + var ownsParameter = createInstanceMethod.AddParameter ("owns", abr.System_Boolean); + abr.INativeObject__Xamarin_ConstructINativeObject.Resolve ().IsPublic = true; + createInstanceMethod.Overrides.Add (abr.INativeObject__Xamarin_ConstructINativeObject); + var body = createInstanceMethod.CreateBody (out var il); + + if (nsobjectConstructor is not null) { + // var instance = new TypeA (nativeHandle); + // // alternatively with a cast: new TypeA ((IntPtr) nativeHandle); + // if (instance is not null && owns) + // Runtime.TryReleaseINativeObject (instance); + // return instance; + + if (type.HasGenericParameters) { + nsobjectConstructor = type.CreateMethodReferenceOnGenericType (nsobjectConstructor, type.GenericParameters.ToArray ()); + } + + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (nsobjectConstructor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Newobj, nsobjectConstructor); + + var falseTarget = il.Create (OpCodes.Nop); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Ldnull); + il.Emit (OpCodes.Cgt_Un); + il.Emit (OpCodes.Ldarg, ownsParameter); + il.Emit (OpCodes.And); + il.Emit (OpCodes.Brfalse_S, falseTarget); + + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Call, abr.Runtime_TryReleaseINativeObject); + + il.Append (falseTarget); + + il.Emit (OpCodes.Ret); + } else if (ctor is not null) { + // return new TypeA (nativeHandle, owns); // for NativeHandle ctor + // return new TypeA ((IntPtr) nativeHandle, owns); // IntPtr ctor + + if (type.HasGenericParameters) { + ctor = type.CreateMethodReferenceOnGenericType (ctor, type.GenericParameters.ToArray ()); + } + + il.Emit (OpCodes.Ldarg, nativeHandleParameter); + if (ctor.Parameters [0].ParameterType.Is ("System", "IntPtr")) + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_IntPtr); + il.Emit (OpCodes.Ldarg, ownsParameter); + il.Emit (OpCodes.Newobj, ctor); + il.Emit (OpCodes.Ret); + } else { + throw new UnreachableException (); + } + + Annotations.Mark (createInstanceMethod); + } + + static MethodReference? FindNSObjectConstructor (TypeDefinition type) + { + return FindConstructorWithOneParameter ("ObjCRuntime", "NativeHandle") + ?? FindConstructorWithOneParameter ("System", "IntPtr"); + + MethodReference? FindConstructorWithOneParameter (string ns, string cls) + => type.Methods.SingleOrDefault (method => + method.IsConstructor + && !method.IsStatic + && method.HasParameters + && method.Parameters.Count == 1 + && method.Parameters [0].ParameterType.Is (ns, cls)); + } + + + static MethodReference? FindINativeObjectConstructor (TypeDefinition type) + { + return FindConstructorWithTwoParameters ("ObjCRuntime", "NativeHandle", "System", "Boolean") + ?? FindConstructorWithTwoParameters ("System", "IntPtr", "System", "Boolean"); + + MethodReference? FindConstructorWithTwoParameters (string ns1, string cls1, string ns2, string cls2) + => type.Methods.SingleOrDefault (method => + method.IsConstructor + && !method.IsStatic + && method.HasParameters + && method.Parameters.Count == 2 + && method.Parameters [0].ParameterType.Is (ns1, cls1) + && method.Parameters [1].ParameterType.Is (ns2, cls2)); + } + void GenerateRegisterWrapperTypes (TypeDefinition type) { var method = type.AddMethod ("RegisterWrapperTypes", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_Void); @@ -473,13 +722,32 @@ static string GetMethodSignature (MethodDefinition method) return $"{method?.ReturnType?.FullName ?? "(null)"} {method?.DeclaringType?.FullName ?? "(null)"}::{method?.Name ?? "(null)"} ({string.Join (", ", method?.Parameters?.Select (v => v?.ParameterType?.FullName + " " + v?.Name) ?? Array.Empty ())})"; } - void EnsureVisible (MethodDefinition caller, TypeDefinition type) + static void EnsureVisible (MethodDefinition caller, MethodReference methodRef) { + var method = methodRef.Resolve (); + var type = method.DeclaringType.Resolve (); if (type.IsNested) { - type.IsNestedPublic = true; + if (!method.IsPublic) { + method.IsFamilyOrAssembly = true; + } + + EnsureVisible (caller, type); + } else if (!method.IsPublic) { + method.IsFamilyOrAssembly = true; + } + } + + static void EnsureVisible (MethodDefinition caller, TypeReference typeRef) + { + var type = typeRef.Resolve (); + if (type.IsNested) { + if (!type.IsNestedPublic) { + type.IsNestedAssembly = true; + } + EnsureVisible (caller, type.DeclaringType); - } else { - type.IsPublic = true; + } else if (!type.IsPublic) { + type.IsNotPublic = true; } } diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index 0e89314fa655..8b459d0cf43c 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -1030,7 +1030,7 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type return false; } - bool IsOpenType (TypeReference tr) + internal static bool IsOpenType (TypeReference tr) { if (tr is GenericParameter) return true; @@ -1052,13 +1052,13 @@ bool IsOpenType (TypeReference tr) return IsOpenType (tr.Resolve ()); } - void EnsureVisible (MethodDefinition caller, FieldDefinition field) + static void EnsureVisible (MethodDefinition caller, FieldDefinition field) { field.IsPublic = true; EnsureVisible (caller, field.DeclaringType); } - void EnsureVisible (MethodDefinition caller, TypeDefinition type) + static void EnsureVisible (MethodDefinition caller, TypeDefinition type) { if (type.IsNested) { type.IsNestedPublic = true; @@ -1068,7 +1068,7 @@ void EnsureVisible (MethodDefinition caller, TypeDefinition type) } } - void EnsureVisible (MethodDefinition caller, MethodReference method) + static void EnsureVisible (MethodDefinition caller, MethodReference method) { var md = method.Resolve (); md.IsPublic = true; diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index d770fa6bddc0..797627573c54 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -4292,5 +4292,14 @@ public static string MX8055 { return ResourceManager.GetString("MX8055", resourceCulture); } } + + /// + /// Looks up a localized string similar to Failed to marshal the Objective-C object 0x{0} (type: {1}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance of generic type {2}.. + /// + public static string MX8056 { + get { + return ResourceManager.GetString("MX8056", resourceCulture); + } + } } } diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index 8ebe9f75b579..e88b0756970f 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -2254,4 +2254,8 @@ Could not find the type 'ObjCRuntime.__Registrar__' in the assembly '{0}'. + + + Failed to marshal the Objective-C object 0x{0} (type: {1}). Could not find an existing managed instance for this object, nor was it possible to create a new managed instance of generic type {2}. +