diff --git a/docs/managed-static-registrar.md b/docs/managed-static-registrar.md new file mode 100644 index 0000000000..2860fefc88 --- /dev/null +++ b/docs/managed-static-registrar.md @@ -0,0 +1,227 @@ +# Managed static registrar + +The managed static registrar is a variation of the static registrar where we +don't use features the NativeAOT compiler doesn't support (most notably +metadata tokens). + +It also takes advantage of new features in C# and managed code since the +original static registrar code was written - in particular it tries to do as +much as possible in managed code instead of native code, as well as various +other performance improvements. The actual performance characteristics +compared to the original static registrar will vary between the specific +exported method signatures, but in general it's expected that method calls +from native code to managed code will be faster. + +In order to make the managed static registrar easily testable and debuggable, +it's also implemented for the other runtimes as well (Mono and CoreCLR as +well), as well as when not using AOT in any form. + +## Design + +### Exported methods + +For each method exported to Objective-C, the managed static registrar will +generate a managed method we'll call directly from native code, and which does +all the marshalling. + +This method will have the [UnmanagedCallersOnly] attribute, so that it doesn't +need any additional marshalling from the managed runtime - which makes it +possible to obtain a native function pointer for it. It will also have a +native entry point, which means that for AOT we can just directly call it from +the generated Objective-C code. + +Given the following method: + +```csharp +class AppDelegate : NSObject, IUIApplicationDelegate { + // this method is written by the app developer + public override bool FinishedLaunching (UIApplication app, NSDictionary options) + { + // ... + } +} +``` + +The managed static registrar will add the following method to the `AppDelegate` class: + +```csharp +class AppDelegate { + [UnmanagedCallersOnly (EntryPoint = "__registrar__uiapplicationdelegate_didFinishLaunching")] + static byte __registrar__DidFinishLaunchingWithOptions (IntPtr handle, IntPtr selector, IntPtr p0, IntPtr p1) + { + var obj = Runtime.GetNSObject (handle); + var p0Obj = (UIApplication) Runtime.GetNSObject (p0); + var p1Obj = (NSDictionary) Runtime.GetNSObject (p1); + var rv = obj.DidFinishLaunchingWithOptions (p0Obj, p1Obj); + return rv ? (byte) 1 : (byte) 0; + } +} +``` + +and the generated Objective-C code will look something like this: + +```objective-c +extern BOOL __registrar__uiapplicationdelegate_init (AppDelegate self, SEL _cmd, UIApplication* p0, NSDictionary* p1); + +@interface AppDelegate : NSObject { +} + -(BOOL) application:(UIApplication *)p0 didFinishLaunchingWithOptions:(NSDictionary *)p1; +@end +@implementation AppDelegate { +} + -(BOOL) application:(UIApplication *)p0 didFinishLaunchingWithOptions:(NSDictionary *)p1 + { + return __registrar__uiapplicationdelegate_didFinishLaunching (self, _cmd, p0, p1); + } +@end +``` + +Note: the actual code is somewhat more complex in order to properly support +managed exceptions and a few other corner cases. + +### Type mapping + +The runtime needs to quickly and efficiently do lookups between an Objective-C +type and the corresponding managed type. In order to support this, the managed +static registrar will add lookup tables in each assembly. The managed static +registrar will create a numeric ID for each managed type, which is then +emitted into the generated Objective-C code, and which we can use to look up +the corresponding managed type. There is also a table in Objective-C that maps +between the numeric ID and the corresponding Objective-C type. + +We also need to be able to find the wrapper type for interfaces representing +Objective-C protocols - this is accomplished by generating a table in +unmanaged code that maps the ID for the interface to the ID for the wrapper +type. + +This is all supported by the `ObjCRuntime.IManagedRegistrar.LookupTypeId` and +`ObjCRuntime.IManagedRegistrar.LookupType` methods. + +Note that in many ways the type ID is similar to the metadata token for a type +(and is sometimes referred to as such in the code, especially code that +already existed before the managed static registrar was implemented). + +### Method mapping + +When AOT-compiling code, the generated Objective-C code can call the entry +point for the UnmanagedCallersOnly trampoline directly (the AOT compiler will +emit a native symbol with the name of the entry point). + +However, when not AOT-compiling code, the generated Objective-C code needs to +find the function pointer for the UnmanagedCallersOnly methods. This is +implemented using another lookup table in managed code. + +For technical reasons, this is implemented using multiple levels of functions if +there is a significant number of UnmanagedCallersOnly methods. As it seems +that the JIT will compile the target for every function pointer in a method, +even if the function pointer isn't loaded at runtime. This means that if +there are 1.000 methods in the lookup table and if the lookup was +implemented in a single function, the JIT will have to compile all +the 1.000 methods the first time the lookup method is called, even +if the lookup method will eventually just find a single callback. + +This might be easier to describe with some code. + +Instead of this: + +```csharp +class __Registrar_Callbacks__ { + IntPtr LookupUnmanagedFunction (int id) + { + switch (id) { + case 0: return (IntPtr) (delegate* unmanaged) &Callback0; + case 1: return (IntPtr) (delegate* unmanaged) &Callback1; + ... + case 999: return (IntPtr) (delegate* unmanaged) &Callback999; + } + return (IntPtr) -1); + } +} +``` + +we do this instead: + +```csharp +class __Registrar_Callbacks__ { + IntPtr LookupUnmanagedFunction (int id) + { + if (id < 100) + return LookupUnmanagedFunction_0 (id); + if (id < 200) + return LookupUnmanagedFunction_1 (id); + ... + if (id < 1000) + LookupUnmanagedFunction_9 (id); + return (IntPtr) -1; + } + + IntPtr LookupUnmanagedFunction_0 (int id) + { + switch (id) { + case 0: return (IntPtr) (delegate* unmanaged) &Callback0; + case 1: return (IntPtr) (delegate* unmanaged) &Callback1; + /// ... + case 9: return (IntPtr) (delegate* unmanaged) &Callback9; + } + return (IntPtr) -1; + } + + + IntPtr LookupUnmanagedFunction_1 (int id) + { + switch (id) { + case 10: return (IntPtr) (delegate* unmanaged) &Callback10; + case 11: return (IntPtr) (delegate* unmanaged) &Callback11; + /// ... + case 19: return (IntPtr) (delegate* unmanaged) &Callback19; + } + return (IntPtr) -1; + } +} +``` + + +### Generation + +All the generated IL is done in two separate custom linker steps. The first +one, ManagedRegistrarStep, will generate the UnmanagedCallersOnly trampolines +for every method exported to Objective-C. This happens before the trimmer has +done any work (i.e. before marking), because the generated code will cause +more code to be marked (and this way we don't have to replicate what the +trimmer does when it traverses IL and metadata to figure out what else to +mark). + +The trimmer will then trim away any UnmanagedCallersOnly trampoline that's no +longer needed because the target method has been trimmed away. + +On the other hand, the lookup tables for the type mapping are generated after +trimming, because we only want to add types that aren't trimmed away to the +lookup tables (otherwise we'd end up causing all those types to be kept). + +## Interpreter / JIT + +When not using the AOT compiler, we need to look up the native entry points +for UnmanagedCallersOnly methods at runtime. In order to support this, the +managed static registrar will add lookup tables in each assembly. The managed +static registrar will create a numeric ID for each UnmanagedCallersOnly +method, which is then emitted into the generated Objective-C code, and which +we can use to look up the managed UnmanagedCallersOnly method at runtime (in +the lookup table). + +This is the `ObjCRuntime.IManagedRegistrar.LookupUnmanagedFunction` method. + +## Performance + +Preliminary testing shows the following: + +### macOS + +Calling an exported managed method from Objective-C is 3-6x faster for simple method signatures. + +### Mac Catalyst + +Calling an exported managed method from Objective-C is 30-50% faster for simple method signatures. + +## References + +* https://github.com/dotnet/runtime/issues/80912 diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index eb6a67cfea..70aa437c5f 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -559,6 +559,10 @@ <_ExtraTrimmerArgs Condition="('$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS') And '$(_SdkIsSimulator)' == 'true'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.Arch.IsSimulator true <_ExtraTrimmerArgs Condition="('$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS') And '$(_SdkIsSimulator)' != 'true'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.Arch.IsSimulator false + + <_ExtraTrimmerArgs Condition="'$(Registrar)' == 'managed-static'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.IsManagedStaticRegistrar true + <_ExtraTrimmerArgs Condition="'$(Registrar)' != 'managed-static'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.IsManagedStaticRegistrar false + <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --enable-serialization-discovery @@ -591,6 +595,7 @@ <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreMarkDispatcher" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(Registrar)' == 'managed-static'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="SweepStep" Type="Xamarin.Linker.ManagedRegistrarLookupTablesStep" Condition="'$(Registrar)' == 'managed-static'" /> + @@ -1910,6 +1920,10 @@ global using nfloat = global::System.Runtime.InteropServices.NFloat%3B <_BindingPackagesFromReferencedAssembliesDirectoriesExists Include="@(_BindingPackagesFromReferencedAssembliesDirectoriesCandidates->Distinct())" Condition="Exists('%(Identity)')" /> + + $(BuildSessionId) + + <#+ diff --git a/runtime/runtime.m b/runtime/runtime.m index 3230079307..b38ff6661f 100644 --- a/runtime/runtime.m +++ b/runtime/runtime.m @@ -136,7 +136,7 @@ struct Trampolines { enum InitializationFlags : int { InitializationFlagsIsPartialStaticRegistrar = 0x01, - /* unused = 0x02,*/ + InitializationFlagsIsManagedStaticRegistrar = 0x02, /* unused = 0x04,*/ /* unused = 0x08,*/ InitializationFlagsIsSimulator = 0x10, @@ -2736,6 +2736,30 @@ xamarin_vprintf (const char *format, va_list args) [message release]; } +void +xamarin_registrar_dlsym (void **function_pointer, const char *assembly, const char *symbol, int32_t id) +{ + if (*function_pointer != NULL) + return; + + *function_pointer = dlsym (RTLD_MAIN_ONLY, symbol); + if (*function_pointer != NULL) + return; + + GCHandle exception_gchandle = INVALID_GCHANDLE; + *function_pointer = xamarin_lookup_unmanaged_function (assembly, symbol, id, &exception_gchandle); + if (*function_pointer != NULL) + return; + + if (exception_gchandle != INVALID_GCHANDLE) + xamarin_process_managed_exception_gchandle (exception_gchandle); + + // This shouldn't really happen + NSString *msg = [NSString stringWithFormat: @"Unable to load the symbol '%s' to call managed code: %@", symbol, xamarin_print_all_exceptions (exception_gchandle)]; + NSLog (@"%@", msg); + @throw [[NSException alloc] initWithName: @"SymbolNotFoundException" reason: msg userInfo: NULL]; +} + /* * File/resource lookup for assemblies * @@ -3195,6 +3219,16 @@ xamarin_get_is_debug () return xamarin_debug_mode; } +void +xamarin_set_is_managed_static_registrar (bool value) +{ + if (value) { + options.flags = (InitializationFlags) (options.flags | InitializationFlagsIsManagedStaticRegistrar); + } else { + options.flags = (InitializationFlags) (options.flags & ~InitializationFlagsIsManagedStaticRegistrar); + } +} + bool xamarin_is_managed_exception_marshaling_disabled () { diff --git a/runtime/xamarin/runtime.h b/runtime/xamarin/runtime.h index 09cb68c796..693ef828c1 100644 --- a/runtime/xamarin/runtime.h +++ b/runtime/xamarin/runtime.h @@ -254,6 +254,7 @@ void xamarin_check_objc_type (id obj, Class expected_class, SEL sel, id self, #endif void xamarin_set_gc_pump_enabled (bool value); +void xamarin_set_is_managed_static_registrar (bool value); void xamarin_process_nsexception (NSException *exc); void xamarin_process_nsexception_using_mode (NSException *ns_exception, bool throwManagedAsDefault, GCHandle *output_exception); @@ -295,6 +296,15 @@ void xamarin_printf (const char *format, ...); void xamarin_vprintf (const char *format, va_list args); void xamarin_install_log_callbacks (); +/* + * Looks up a native function pointer for a managed [UnmanagedCallersOnly] method. + * function_pointer: the return value, lookup will only be performed if this points to NULL. + * assembly: the assembly to look in. Might be NULL if the app was not built with support for loading additional assemblies at runtime. + * symbol: the symbol to look up. Can be NULL to save space (this value isn't used except in error messages). + * id: a numerical id for faster lookup (than doing string comparisons on the symbol name). + */ +void xamarin_registrar_dlsym (void **function_pointer, const char *assembly, const char *symbol, int32_t id); + /* * Wrapper GCHandle functions that takes pointer sized handles instead of ints, * so that we can adapt our code incrementally to use pointers instead of ints diff --git a/src/Foundation/NSArray.cs b/src/Foundation/NSArray.cs index ceaa9a57f6..f9b8a8f9be 100644 --- a/src/Foundation/NSArray.cs +++ b/src/Foundation/NSArray.cs @@ -295,6 +295,19 @@ namespace Foundation { return ret; } + static Array ArrayFromHandle (NativeHandle handle, Type elementType) + { + if (handle == NativeHandle.Zero) + return null; + + var c = (int) GetCount (handle); + var rv = Array.CreateInstance (elementType, c); + for (int i = 0; i < c; i++) { + rv.SetValue (UnsafeGetItem (handle, (nuint) i, elementType), i); + } + return rv; + } + static public T [] EnumsFromHandle (NativeHandle handle) where T : struct, IConvertible { if (handle == NativeHandle.Zero) @@ -395,6 +408,18 @@ namespace Foundation { return Runtime.GetINativeObject (val, false); } + static object UnsafeGetItem (NativeHandle handle, nuint index, Type type) + { + var val = GetAtIndex (handle, index); + // A native code could return NSArray with NSNull.Null elements + // and they should be valid for things like T : NSDate so we handle + // them as just null values inside the array + if (val == NSNull.Null.Handle) + return null; + + return Runtime.GetINativeObject (val, false, type); + } + // can return an INativeObject or an NSObject public T GetItem (nuint index) where T : class, INativeObject { diff --git a/src/Foundation/NSObject2.cs b/src/Foundation/NSObject2.cs index 5cb4c6eca4..f88f1a0492 100644 --- a/src/Foundation/NSObject2.cs +++ b/src/Foundation/NSObject2.cs @@ -231,6 +231,14 @@ namespace Foundation { GC.SuppressFinalize (this); } + static T AllocateNSObject (IntPtr handle) where T : NSObject + { + var obj = (T) RuntimeHelpers.GetUninitializedObject (typeof (T)); + obj.handle = handle; + obj.flags = Flags.NativeRef; + return obj; + } + internal static IntPtr CreateNSObject (IntPtr type_gchandle, IntPtr handle, Flags flags) { // This function is called from native code before any constructors have executed. diff --git a/src/ILLink.Substitutions.MacCatalyst.xml b/src/ILLink.Substitutions.MacCatalyst.xml index 0b9d639ef3..82dfcf97ae 100644 --- a/src/ILLink.Substitutions.MacCatalyst.xml +++ b/src/ILLink.Substitutions.MacCatalyst.xml @@ -5,6 +5,8 @@ + + diff --git a/src/ILLink.Substitutions.ios.xml b/src/ILLink.Substitutions.ios.xml index af423bf9d2..00e9e50e50 100644 --- a/src/ILLink.Substitutions.ios.xml +++ b/src/ILLink.Substitutions.ios.xml @@ -7,6 +7,8 @@ + + diff --git a/src/ILLink.Substitutions.macOS.xml b/src/ILLink.Substitutions.macOS.xml index 75d6785fce..a0fd78280a 100644 --- a/src/ILLink.Substitutions.macOS.xml +++ b/src/ILLink.Substitutions.macOS.xml @@ -5,6 +5,8 @@ + + diff --git a/src/ILLink.Substitutions.tvos.xml b/src/ILLink.Substitutions.tvos.xml index 50fcf04fed..e03cc08a2f 100644 --- a/src/ILLink.Substitutions.tvos.xml +++ b/src/ILLink.Substitutions.tvos.xml @@ -7,6 +7,8 @@ + + diff --git a/src/Makefile b/src/Makefile index 4ba6d620ab..34fb28e816 100644 --- a/src/Makefile +++ b/src/Makefile @@ -54,6 +54,7 @@ DOTNET_REFERENCES = \ /r:$(DOTNET_BCL_DIR)/System.Console.dll \ /r:$(DOTNET_BCL_DIR)/System.Diagnostics.Debug.dll \ /r:$(DOTNET_BCL_DIR)/System.Diagnostics.Tools.dll \ + /r:$(DOTNET_BCL_DIR)/System.Diagnostics.StackTrace.dll \ /r:$(DOTNET_BCL_DIR)/System.Drawing.Primitives.dll \ /r:$(DOTNET_BCL_DIR)/System.IO.Compression.dll \ /r:$(DOTNET_BCL_DIR)/System.IO.FileSystem.dll \ diff --git a/src/ObjCRuntime/BindAs.cs b/src/ObjCRuntime/BindAs.cs new file mode 100644 index 0000000000..deaee20088 --- /dev/null +++ b/src/ObjCRuntime/BindAs.cs @@ -0,0 +1,249 @@ +// +// BindAs.cs: Helper code for BindAs support. +// +// Authors: +// Rolf Bjarne Kvinge +// +// Copyright 2023 Microsoft Corp + +#if NET + +#nullable enable + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using CoreFoundation; +using CoreGraphics; +using Foundation; + +using Registrar; + +namespace ObjCRuntime { + // Helper code for BindAs support. + // The managed static registrar will make any API here it uses public. + static class BindAs { + // xamarin_convert_nsarray_to_managed_with_func + static T Identity (T obj) + { + return obj; + } + + unsafe static T[]? ConvertNSArrayToManagedArray (IntPtr nsarray, delegate* convert) where T: struct + { + if (nsarray == IntPtr.Zero) + return null; + + return ConvertNSArrayToManagedArray2 (nsarray, convert, &Identity); + } + + unsafe static IntPtr ConvertManagedArrayToNSArray (T[]? array, delegate* convert) where T: struct + { + if (array is null) + return IntPtr.Zero; + + return ConvertManagedArrayToNSArray2 (array, &Identity, convert); + } + + unsafe static T[]? ConvertNSArrayToManagedArray2 (IntPtr nsarray, delegate* convert1, delegate* convert2) where T: struct + { + if (nsarray == IntPtr.Zero) + return null; + + return NSArray.ArrayFromHandleFunc (nsarray, (ptr) => convert2 (convert1 (ptr))); + } + + unsafe static IntPtr ConvertManagedArrayToNSArray2 (T[]? array, delegate* convert1, delegate* convert2) where T: struct + { + if (array is null) + return IntPtr.Zero; + + NSArray arr; + var count = array.Length; + if (count == 0) { + arr = new NSArray (); + } else { + var ptrs = new IntPtr [count]; + for (nint i = 0; i < count; i++) { + var item = convert2 (convert1 (array [i])); + if (item == IntPtr.Zero) + item = NSNull.Null.Handle; + ptrs [i] = item; + } + fixed (void* ptr = &ptrs[0]) { + arr = Runtime.GetNSObject (NSArray.FromObjects ((IntPtr) ptr, count))!; + } + } + + arr.DangerousRetain (); + arr.DangerousAutorelease (); + var rv = arr.Handle; + arr.Dispose (); + return rv; + } + + unsafe static T? CreateNullable (IntPtr handle, delegate* convert) where T: struct + { + if (handle == IntPtr.Zero) + return null; + return convert (handle); + } + + unsafe static T? CreateNullable2 (IntPtr handle, delegate* convert1, delegate* convert2) where T: struct + { + if (handle == IntPtr.Zero) + return null; + return convert2 (convert1 (handle)); + } + + static Foundation.NSRange xamarin_nsvalue_to_nsrange (IntPtr value) { if (value == IntPtr.Zero) return default (Foundation.NSRange); return Runtime.GetNSObject (value)?.RangeValue ?? default (Foundation.NSRange); } + static CoreGraphics.CGAffineTransform xamarin_nsvalue_to_cgaffinetransform (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGAffineTransform); return Runtime.GetNSObject (value)?.CGAffineTransformValue ?? default (CoreGraphics.CGAffineTransform); } + static CoreGraphics.CGPoint xamarin_nsvalue_to_cgpoint (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGPoint); return Runtime.GetNSObject (value)?.CGPointValue ?? default (CoreGraphics.CGPoint); } + static CoreGraphics.CGRect xamarin_nsvalue_to_cgrect (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGRect); return Runtime.GetNSObject (value)?.CGRectValue ?? default (CoreGraphics.CGRect); } + static CoreGraphics.CGSize xamarin_nsvalue_to_cgsize (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGSize); return Runtime.GetNSObject (value)?.CGSizeValue ?? default (CoreGraphics.CGSize); } +#if !__MACOS__ + static CoreGraphics.CGVector xamarin_nsvalue_to_cgvector (IntPtr value) { if (value == IntPtr.Zero) return default (CoreGraphics.CGVector); return Runtime.GetNSObject (value)?.CGVectorValue ?? default (CoreGraphics.CGVector); } +#endif + static CoreAnimation.CATransform3D xamarin_nsvalue_to_catransform3d (IntPtr value) { if (value == IntPtr.Zero) return default (CoreAnimation.CATransform3D); return Runtime.GetNSObject (value)?.CATransform3DValue ?? default (CoreAnimation.CATransform3D); } + static CoreLocation.CLLocationCoordinate2D xamarin_nsvalue_to_cllocationcoordinate2d (IntPtr value) { if (value == IntPtr.Zero) return default (CoreLocation.CLLocationCoordinate2D); return Runtime.GetNSObject (value)?.CoordinateValue ?? default (CoreLocation.CLLocationCoordinate2D); } + static CoreMedia.CMTime xamarin_nsvalue_to_cmtime (IntPtr value) { if (value == IntPtr.Zero) return default (CoreMedia.CMTime); return Runtime.GetNSObject (value)?.CMTimeValue ?? default (CoreMedia.CMTime); } + static CoreMedia.CMTimeMapping xamarin_nsvalue_to_cmtimemapping (IntPtr value) { if (value == IntPtr.Zero) return default (CoreMedia.CMTimeMapping); return Runtime.GetNSObject (value)?.CMTimeMappingValue ?? default (CoreMedia.CMTimeMapping); } + static CoreMedia.CMTimeRange xamarin_nsvalue_to_cmtimerange (IntPtr value) { if (value == IntPtr.Zero) return default (CoreMedia.CMTimeRange); return Runtime.GetNSObject (value)?.CMTimeRangeValue ?? default (CoreMedia.CMTimeRange); } + static CoreMedia.CMVideoDimensions xamarin_nsvalue_to_cmvideodimensions (IntPtr value) { if (value == IntPtr.Zero) return default (CoreMedia.CMVideoDimensions); return Runtime.GetNSObject (value)?.CMVideoDimensionsValue ?? default (CoreMedia.CMVideoDimensions); } + static MapKit.MKCoordinateSpan xamarin_nsvalue_to_mkcoordinatespan (IntPtr value) { if (value == IntPtr.Zero) return default (MapKit.MKCoordinateSpan); return Runtime.GetNSObject (value)?.CoordinateSpanValue ?? default (MapKit.MKCoordinateSpan); } + static SceneKit.SCNMatrix4 xamarin_nsvalue_to_scnmatrix4 (IntPtr value) { if (value == IntPtr.Zero) return default (SceneKit.SCNMatrix4); return Runtime.GetNSObject (value)?.SCNMatrix4Value ?? default (SceneKit.SCNMatrix4); } + static SceneKit.SCNVector3 xamarin_nsvalue_to_scnvector3 (IntPtr value) { if (value == IntPtr.Zero) return default (SceneKit.SCNVector3); return Runtime.GetNSObject (value)?.Vector3Value ?? default (SceneKit.SCNVector3); } + static SceneKit.SCNVector4 xamarin_nsvalue_to_scnvector4 (IntPtr value) { if (value == IntPtr.Zero) return default (SceneKit.SCNVector4); return Runtime.GetNSObject (value)?.Vector4Value ?? default (SceneKit.SCNVector4); } +#if HAS_UIKIT + static UIKit.UIEdgeInsets xamarin_nsvalue_to_uiedgeinsets (IntPtr value) { if (value == IntPtr.Zero) return default (UIKit.UIEdgeInsets); return Runtime.GetNSObject (value)?.UIEdgeInsetsValue ?? default (UIKit.UIEdgeInsets); } + static UIKit.UIOffset xamarin_nsvalue_to_uioffset (IntPtr value) { if (value == IntPtr.Zero) return default (UIKit.UIOffset); return Runtime.GetNSObject (value)?.UIOffsetValue ?? default (UIKit.UIOffset); } + static UIKit.NSDirectionalEdgeInsets xamarin_nsvalue_to_nsdirectionaledgeinsets (IntPtr value) { if (value == IntPtr.Zero) return default (UIKit.NSDirectionalEdgeInsets); return Runtime.GetNSObject (value)?.DirectionalEdgeInsetsValue ?? default (UIKit.NSDirectionalEdgeInsets); } +#endif + + static IntPtr xamarin_nsrange_to_nsvalue (Foundation.NSRange value) { using var rv = NSValue.FromRange (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cgaffinetransform_to_nsvalue (CoreGraphics.CGAffineTransform value) { using var rv = NSValue.FromCGAffineTransform (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cgpoint_to_nsvalue (CoreGraphics.CGPoint value) { using var rv = NSValue.FromCGPoint (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cgrect_to_nsvalue (CoreGraphics.CGRect value) { using var rv = NSValue.FromCGRect (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cgsize_to_nsvalue (CoreGraphics.CGSize value) { using var rv = NSValue.FromCGSize (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#if !__MACOS__ + static IntPtr xamarin_cgvector_to_nsvalue (CoreGraphics.CGVector value) { using var rv = NSValue.FromCGVector (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#endif + static IntPtr xamarin_catransform3d_to_nsvalue (CoreAnimation.CATransform3D value) { using var rv = NSValue.FromCATransform3D (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cllocationcoordinate2d_to_nsvalue (CoreLocation.CLLocationCoordinate2D value) { using var rv = NSValue.FromMKCoordinate (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cmtime_to_nsvalue (CoreMedia.CMTime value) { using var rv = NSValue.FromCMTime (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cmtimemapping_to_nsvalue (CoreMedia.CMTimeMapping value) { using var rv = NSValue.FromCMTimeMapping (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cmtimerange_to_nsvalue (CoreMedia.CMTimeRange value) { using var rv = NSValue.FromCMTimeRange (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_cmvideodimensions_to_nsvalue (CoreMedia.CMVideoDimensions value) { using var rv = NSValue.FromCMVideoDimensions (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_mkcoordinatespan_to_nsvalue (MapKit.MKCoordinateSpan value) { using var rv = NSValue.FromMKCoordinateSpan (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_scnmatrix4_to_nsvalue (SceneKit.SCNMatrix4 value) { using var rv = NSValue.FromSCNMatrix4 (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_scnvector3_to_nsvalue (SceneKit.SCNVector3 value) { using var rv = NSValue.FromVector (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_scnvector4_to_nsvalue (SceneKit.SCNVector4 value) { using var rv = NSValue.FromVector (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#if HAS_UIKIT + static IntPtr xamarin_uiedgeinsets_to_nsvalue (UIKit.UIEdgeInsets value) { using var rv = NSValue.FromUIEdgeInsets (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_uioffset_to_nsvalue (UIKit.UIOffset value) { using var rv = NSValue.FromUIOffset (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } + static IntPtr xamarin_nsdirectionaledgeinsets_to_nsvalue (UIKit.NSDirectionalEdgeInsets value) { using var rv = NSValue.FromDirectionalEdgeInsets (value); rv.DangerousRetain ().DangerousAutorelease (); return rv.Handle; } +#endif + + static System.SByte xamarin_nsnumber_to_sbyte (IntPtr value) { if (value == IntPtr.Zero) return default (System.SByte); return Runtime.GetNSObject (value)?.SByteValue ?? default (System.SByte); } + static System.Byte xamarin_nsnumber_to_byte (IntPtr value) { if (value == IntPtr.Zero) return default (System.Byte); return Runtime.GetNSObject (value)?.ByteValue ?? default (System.Byte); } + static System.Int16 xamarin_nsnumber_to_short (IntPtr value) { if (value == IntPtr.Zero) return default (System.Int16); return Runtime.GetNSObject (value)?.Int16Value ?? default (System.Int16); } + static System.UInt16 xamarin_nsnumber_to_ushort (IntPtr value) { if (value == IntPtr.Zero) return default (System.UInt16); return Runtime.GetNSObject (value)?.UInt16Value ?? default (System.UInt16); } + static System.Int32 xamarin_nsnumber_to_int (IntPtr value) { if (value == IntPtr.Zero) return default (System.Int32); return Runtime.GetNSObject (value)?.Int32Value ?? default (System.Int32); } + static System.UInt32 xamarin_nsnumber_to_uint (IntPtr value) { if (value == IntPtr.Zero) return default (System.UInt32); return Runtime.GetNSObject (value)?.UInt32Value ?? default (System.UInt32); } + static System.Int64 xamarin_nsnumber_to_long (IntPtr value) { if (value == IntPtr.Zero) return default (System.Int64); return Runtime.GetNSObject (value)?.Int64Value ?? default (System.Int64); } + static System.UInt64 xamarin_nsnumber_to_ulong (IntPtr value) { if (value == IntPtr.Zero) return default (System.UInt64); return Runtime.GetNSObject (value)?.UInt64Value ?? default (System.UInt64); } + static nint xamarin_nsnumber_to_nint (IntPtr value) { if (value == IntPtr.Zero) return default (nint); return Runtime.GetNSObject (value)?.NIntValue ?? default (nint); } + static nuint xamarin_nsnumber_to_nuint (IntPtr value) { if (value == IntPtr.Zero) return default (nuint); return Runtime.GetNSObject (value)?.NUIntValue ?? default (nuint); } + static System.Single xamarin_nsnumber_to_float (IntPtr value) { if (value == IntPtr.Zero) return default (System.Single); return Runtime.GetNSObject (value)?.FloatValue ?? default (System.Single); } + static System.Double xamarin_nsnumber_to_double (IntPtr value) { if (value == IntPtr.Zero) return default (System.Double); return Runtime.GetNSObject (value)?.DoubleValue ?? default (System.Double); } + static System.Boolean xamarin_nsnumber_to_bool (IntPtr value) { if (value == IntPtr.Zero) return default (System.Boolean); return Runtime.GetNSObject (value)?.BoolValue ?? default (System.Boolean); } + static nfloat xamarin_nsnumber_to_nfloat (IntPtr value) + { + if (value == IntPtr.Zero) + return default (nfloat); + var number = Runtime.GetNSObject (value); + if (number is null) + return default (nfloat); + if (IntPtr.Size == 4) + return (nfloat) number.FloatValue; + return (nfloat) number.DoubleValue; + } + + static System.SByte? xamarin_nsnumber_to_nullable_sbyte (IntPtr value) { return Runtime.GetNSObject (value)?.SByteValue ?? null; } + static System.Byte? xamarin_nsnumber_to_nullable_byte (IntPtr value) { return Runtime.GetNSObject (value)?.ByteValue ?? null; } + static System.Int16? xamarin_nsnumber_to_nullable_short (IntPtr value) { return Runtime.GetNSObject (value)?.Int16Value ?? null; } + static System.UInt16? xamarin_nsnumber_to_nullable_ushort (IntPtr value) { return Runtime.GetNSObject (value)?.UInt16Value ?? null; } + static System.Int32? xamarin_nsnumber_to_nullable_int (IntPtr value) { return Runtime.GetNSObject (value)?.Int32Value ?? null; } + static System.UInt32? xamarin_nsnumber_to_nullable_uint (IntPtr value) { return Runtime.GetNSObject (value)?.UInt32Value ?? null; } + static System.Int64? xamarin_nsnumber_to_nullable_long (IntPtr value) { return Runtime.GetNSObject (value)?.Int64Value ?? null; } + static System.UInt64? xamarin_nsnumber_to_nullable_ulong (IntPtr value) { return Runtime.GetNSObject (value)?.UInt64Value ?? null; } + static nint? xamarin_nsnumber_to_nullable_nint (IntPtr value) { return Runtime.GetNSObject (value)?.NIntValue ?? null; } + static nuint? xamarin_nsnumber_to_nullable_nuint (IntPtr value) { return Runtime.GetNSObject (value)?.NUIntValue ?? null; } + static System.Single? xamarin_nsnumber_to_nullable_float (IntPtr value) { return Runtime.GetNSObject (value)?.FloatValue ?? null; } + static System.Double? xamarin_nsnumber_to_nullable_double (IntPtr value) { return Runtime.GetNSObject (value)?.DoubleValue ?? null; } + static System.Boolean? xamarin_nsnumber_to_nullable_bool (IntPtr value) { return Runtime.GetNSObject (value)?.BoolValue ?? null; } + static nfloat? xamarin_nsnumber_to_nullable_nfloat (IntPtr value) + { + if (value == IntPtr.Zero) + return null; + var number = Runtime.GetNSObject (value); + if (number is null) + return null; + if (IntPtr.Size == 4) + return (nfloat) number.FloatValue; + return (nfloat) number.DoubleValue; + } + + static IntPtr xamarin_sbyte_to_nsnumber (System.SByte value) { return NSNumber.FromSByte (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_byte_to_nsnumber (System.Byte value) { return NSNumber.FromByte (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_short_to_nsnumber (System.Int16 value) { return NSNumber.FromInt16 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_ushort_to_nsnumber (System.UInt16 value) { return NSNumber.FromUInt16 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_int_to_nsnumber (System.Int32 value) { return NSNumber.FromInt32 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_uint_to_nsnumber (System.UInt32 value) { return NSNumber.FromUInt32 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_long_to_nsnumber (System.Int64 value) { return NSNumber.FromInt64 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_ulong_to_nsnumber (System.UInt64 value) { return NSNumber.FromUInt64 (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nint_to_nsnumber (nint value) { return NSNumber.FromNInt (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nuint_to_nsnumber (nuint value) { return NSNumber.FromNUInt (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_float_to_nsnumber (System.Single value) { return NSNumber.FromFloat (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_double_to_nsnumber (System.Double value) { return NSNumber.FromDouble (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_bool_to_nsnumber (System.Boolean value) { return NSNumber.FromBoolean (value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nfloat_to_nsnumber (nfloat value) + { + if (IntPtr.Size == 4) + return NSNumber.FromFloat ((float) value).DangerousRetain ().DangerousAutorelease ().Handle; + return NSNumber.FromDouble ((double) value).DangerousRetain ().DangerousAutorelease ().Handle; + } + + static IntPtr xamarin_nullable_sbyte_to_nsnumber (System.SByte? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromSByte (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_byte_to_nsnumber (System.Byte? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromByte (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_short_to_nsnumber (System.Int16? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromInt16 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_ushort_to_nsnumber (System.UInt16? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromUInt16 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_int_to_nsnumber (System.Int32? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromInt32 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_uint_to_nsnumber (System.UInt32? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromUInt32 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_long_to_nsnumber (System.Int64? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromInt64 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_ulong_to_nsnumber (System.UInt64? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromUInt64 (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_nint_to_nsnumber (nint? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromNInt (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_nuint_to_nsnumber (nuint? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromNUInt (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_float_to_nsnumber (System.Single? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromFloat (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_double_to_nsnumber (System.Double? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromDouble (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_bool_to_nsnumber (System.Boolean? value) { if (!value.HasValue) return IntPtr.Zero; return NSNumber.FromBoolean (value.Value).DangerousRetain ().DangerousAutorelease ().Handle; } + static IntPtr xamarin_nullable_nfloat_to_nsnumber (nfloat? value) + { + if (!value.HasValue) + return IntPtr.Zero; + if (IntPtr.Size == 4) + return NSNumber.FromFloat ((float) value.Value).DangerousRetain ().DangerousAutorelease ().Handle; + return NSNumber.FromDouble ((double) value.Value).DangerousRetain ().DangerousAutorelease ().Handle; + } + } +} + +#endif // NET diff --git a/src/ObjCRuntime/Blocks.cs b/src/ObjCRuntime/Blocks.cs index 8777a888e5..970af4f83c 100644 --- a/src/ObjCRuntime/Blocks.cs +++ b/src/ObjCRuntime/Blocks.cs @@ -456,6 +456,33 @@ namespace ObjCRuntime { } #endif // NET + [EditorBrowsable (EditorBrowsableState.Never)] + [BindingImpl (BindingImplOptions.Optimizable)] + static IntPtr CreateBlockForDelegate (Delegate @delegate, Delegate delegateProxyFieldValue, string /*?*/ signature) + { + if (@delegate is null) + ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (@delegate)); + + if (delegateProxyFieldValue is null) + ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (delegateProxyFieldValue)); + + // Note that we must create a heap-allocated block, so we + // start off by creating a stack-allocated block, and then + // call _Block_copy, which will create a heap-allocated block + // with the proper reference count. + using var block = new BlockLiteral (); + if (signature is null) { + if (Runtime.DynamicRegistrationSupported) { + block.SetupBlock (delegateProxyFieldValue, @delegate); + } else { + throw ErrorHelper.CreateError (8026, $"BlockLiteral.GetBlockForDelegate with a null signature is not supported when the dynamic registrar has been linked away (delegate type: {@delegate.GetType ().FullName})."); + } + } else { + block.SetupBlockImpl (delegateProxyFieldValue, @delegate, true, signature); + } + return _Block_copy (&block); + } + [BindingImpl (BindingImplOptions.Optimizable)] internal static IntPtr GetBlockForDelegate (MethodInfo minfo, object @delegate, uint token_ref, string signature) { diff --git a/src/ObjCRuntime/Class.cs b/src/ObjCRuntime/Class.cs index a0b50b7df1..c85ff0ba8c 100644 --- a/src/ObjCRuntime/Class.cs +++ b/src/ObjCRuntime/Class.cs @@ -254,8 +254,28 @@ namespace ObjCRuntime { // Look for the type in the type map. var asm_name = type.Assembly.GetName ().Name!; - var mod_token = type.Module.MetadataToken; - var type_token = type.MetadataToken & ~0x02000000; + int mod_token; + int type_token; + + if (Runtime.IsManagedStaticRegistrar) { +#if NET + mod_token = unchecked((int) Runtime.INVALID_TOKEN_REF); + type_token = unchecked((int) RegistrarHelper.LookupRegisteredTypeId (type)); + +#if LOG_TYPELOAD + Runtime.NSLog ($"FindClass ({type.FullName}, {is_custom_type}): type token: 0x{type_token.ToString ("x")}"); +#endif + + if (type_token == -1) + return IntPtr.Zero; +#else + throw ErrorHelper.CreateError (99, Xamarin.Bundler.Errors.MX0099 /* Internal error */, "The managed static registrar is only available for .NET"); +#endif // NET + } else { + mod_token = type.Module.MetadataToken; + type_token = type.MetadataToken & ~0x02000000 /* TokenType.TypeDef */; + } + for (int i = 0; i < map->map_count; i++) { var class_map = map->map [i]; var token_reference = class_map.type_reference; @@ -308,7 +328,7 @@ namespace ObjCRuntime { // then the module token var module_token = entry.module_token; - if (mod_token != module_token) + if (unchecked((uint) mod_token) != module_token) return false; // leave the assembly name for the end, since it's the most expensive comparison (string comparison) @@ -410,7 +430,7 @@ namespace ObjCRuntime { var assembly = ResolveAssembly (assembly_name); var module = ResolveModule (assembly, module_token); - return ResolveToken (module, token); + return ResolveToken (assembly, module, token); } internal static Type? ResolveTypeTokenReference (uint token_reference) @@ -453,21 +473,41 @@ namespace ObjCRuntime { var assembly = ResolveAssembly (assembly_name); var module = ResolveModule (assembly, 0x1); - return ResolveToken (module, token | implicit_token_type); + return ResolveToken (assembly, module, token | implicit_token_type); } - static MemberInfo? ResolveToken (Module module, uint token) + static MemberInfo? ResolveToken (Assembly assembly, Module? module, uint token) { // Finally resolve the token. var token_type = token & 0xFF000000; switch (token & 0xFF000000) { case 0x02000000: // TypeDef - var type = module.ResolveType ((int) token); + Type type; +#if NET + if (Runtime.IsManagedStaticRegistrar) { + type = RegistrarHelper.LookupRegisteredType (assembly, token & 0x00FFFFFF); +#if LOG_TYPELOAD + Runtime.NSLog ($"ResolveToken (0x{token:X}) => Type: {type.FullName}"); +#endif + return type; + } +#endif // NET + if (module is null) { + throw ErrorHelper.CreateError (8053, Errors.MX8053 /* Could not resolve the module in the assembly {0}. */, assembly.FullName); + } else { + type = module.ResolveType ((int) token); + } #if LOG_TYPELOAD Runtime.NSLog ($"ResolveToken (0x{token:X}) => Type: {type.FullName}"); #endif return type; case 0x06000000: // Method + if (Runtime.IsManagedStaticRegistrar) + throw ErrorHelper.CreateError (8054, Errors.MX8054 /* Can't resolve metadata tokens for methods when using the managed static registrar (token: 0x{0}). */, token.ToString ("x")); + + if (module is null) + throw ErrorHelper.CreateError (8053, Errors.MX8053 /* Could not resolve the module in the assembly {0}. */, assembly.FullName); + var method = module.ResolveMethod ((int) token); #if LOG_TYPELOAD Runtime.NSLog ($"ResolveToken (0x{token:X}) => Method: {method?.DeclaringType?.FullName}.{method?.Name}"); @@ -478,8 +518,11 @@ namespace ObjCRuntime { } } - static Module ResolveModule (Assembly assembly, uint token) + static Module? ResolveModule (Assembly assembly, uint token) { + if (token == Runtime.INVALID_TOKEN_REF) + return null; + foreach (var mod in assembly.GetModules ()) { if (mod.MetadataToken != token) continue; @@ -577,7 +620,20 @@ namespace ObjCRuntime { var asm_name = type.Module.Assembly.GetName ().Name!; // First check if there's a full token reference to this type - var token = GetFullTokenReference (asm_name, type.Module.MetadataToken, type.MetadataToken); + uint token; + if (Runtime.IsManagedStaticRegistrar) { +#if NET + var id = RegistrarHelper.LookupRegisteredTypeId (type); + token = GetFullTokenReference (asm_name, unchecked((int) Runtime.INVALID_TOKEN_REF), 0x2000000 /* TokenType.TypeDef */ | unchecked((int) id)); +#if LOG_TYPELOAD + Runtime.NSLog ($"GetTokenReference ({type}, {throw_exception}) id: {id} token: 0x{token.ToString ("x")}"); +#endif +#else + throw ErrorHelper.CreateError (99, Xamarin.Bundler.Errors.MX0099 /* Internal error */, "The managed static registrar is only available for .NET"); +#endif // NET + } else { + token = GetFullTokenReference (asm_name, type.Module.MetadataToken, type.MetadataToken); + } if (token != uint.MaxValue) return token; @@ -626,7 +682,7 @@ namespace ObjCRuntime { if (token != metadata_token) continue; var mod_token = ftr.module_token; - if (mod_token != module_token) + if (unchecked((int) mod_token) != module_token) continue; var assembly_index = ftr.assembly_index; var assembly = map->assemblies [assembly_index]; diff --git a/src/ObjCRuntime/DynamicRegistrar.cs b/src/ObjCRuntime/DynamicRegistrar.cs index 02c4641050..2a4a8f428b 100644 --- a/src/ObjCRuntime/DynamicRegistrar.cs +++ b/src/ObjCRuntime/DynamicRegistrar.cs @@ -219,12 +219,12 @@ namespace Registrar { return assembly.GetTypes (); } - protected override BindAsAttribute GetBindAsAttribute (PropertyInfo property) + public override BindAsAttribute GetBindAsAttribute (PropertyInfo property) { return property?.GetCustomAttribute (false); } - protected override BindAsAttribute GetBindAsAttribute (MethodBase method, int parameter_index) + public override BindAsAttribute GetBindAsAttribute (MethodBase method, int parameter_index) { ICustomAttributeProvider provider; @@ -343,7 +343,7 @@ namespace Registrar { return type.MakeByRefType (); } - protected override CategoryAttribute GetCategoryAttribute (Type type) + public override CategoryAttribute GetCategoryAttribute (Type type) { return SharedDynamic.GetOneAttribute (type); } @@ -374,7 +374,7 @@ namespace Registrar { return ((MethodInfo) method).GetBaseDefinition (); } - protected override Type GetElementType (Type type) + public override Type GetElementType (Type type) { return type.GetElementType (); } @@ -460,7 +460,7 @@ namespace Registrar { return type.FullName; } - protected override bool VerifyIsConstrainedToNSObject (Type type, out Type constrained_type) + public override bool VerifyIsConstrainedToNSObject (Type type, out Type constrained_type) { constrained_type = null; @@ -523,7 +523,7 @@ namespace Registrar { return type.AssemblyQualifiedName; } - protected override bool HasReleaseAttribute (MethodBase method) + public override bool HasReleaseAttribute (MethodBase method) { var mi = method as MethodInfo; if (mi is null) @@ -539,7 +539,7 @@ namespace Registrar { return mi.IsDefined (typeof (System.Runtime.CompilerServices.ExtensionAttribute), false); } - protected override bool HasThisAttribute (MethodBase method) + public override bool HasThisAttribute (MethodBase method) { return HasThisAttributeImpl (method); } @@ -554,7 +554,7 @@ namespace Registrar { return type.IsDefined (typeof (ModelAttribute), false); } - protected override bool IsArray (Type type, out int rank) + public override bool IsArray (Type type, out int rank) { if (!type.IsArray) { rank = 0; @@ -594,7 +594,7 @@ namespace Registrar { return type.IsSubclassOf (typeof (System.Delegate)); } - protected override bool IsNullable (Type type) + public override bool IsNullable (Type type) { if (!type.IsGenericType) return false; diff --git a/src/ObjCRuntime/IManagedRegistrar.cs b/src/ObjCRuntime/IManagedRegistrar.cs new file mode 100644 index 0000000000..1c2c370d78 --- /dev/null +++ b/src/ObjCRuntime/IManagedRegistrar.cs @@ -0,0 +1,40 @@ +// +// IManagedRegistrar.cs +// +// Authors: +// Rolf Bjarne Kvinge +// +// Copyright 2023 Microsoft Corp + + +#if NET + +#nullable enable + +using System; +using System.Collections.Generic; + +namespace ObjCRuntime { + // The managed static registrar will generate/inject a type that implements this method + // in every assembly it processes. At runtime we'll instantiate a singleton instance + // of this type. + // The managed static registrar will make this interface public when needed. + interface IManagedRegistrar { + // Find a function pointer for a given [UnmanagedCallersOnly] method. + // The entryPoint parameter is the EntryPoint value in the attribute, but it's not used for lookup (only tracing/logging/error messages). + // The 'id' is instead used - the values and the lookup tables are generated and injected by the managed static registrar at build time. + IntPtr LookupUnmanagedFunction (string? entryPoint, int id); + // Find a type given an id generated by the managed static registrar. + // This method is the mirror method of LookupTypeId. + RuntimeTypeHandle LookupType (uint id); + // Find an id generated by the managed static registrar given an id. + // This method is the mirror method of LookupType. + uint LookupTypeId (RuntimeTypeHandle handle); + // Called by the runtime when looking up a wrapper type given an interface type. + // 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); + } +} + +#endif // NET diff --git a/src/ObjCRuntime/Registrar.cs b/src/ObjCRuntime/Registrar.cs index d6d9999c00..15bda29658 100644 --- a/src/ObjCRuntime/Registrar.cs +++ b/src/ObjCRuntime/Registrar.cs @@ -980,10 +980,7 @@ namespace Registrar { public bool IsPropertyAccessor { get { - if (Method is null) - return false; - - return Method.IsSpecialName && (Method.Name.StartsWith ("get_", StringComparison.Ordinal) || Method.Name.StartsWith ("set_", StringComparison.Ordinal)); + return Registrar.IsPropertyAccessor (Method); } } } @@ -1044,6 +1041,7 @@ namespace Registrar { public byte Alignment; #else public bool IsPrivate; + public TProperty Property; #endif public string FieldType; public bool IsProperty; @@ -1094,9 +1092,9 @@ namespace Registrar { protected abstract bool IsStatic (TField field); protected abstract bool IsStatic (TMethod method); protected abstract TType MakeByRef (TType type); - protected abstract bool HasThisAttribute (TMethod method); + public abstract bool HasThisAttribute (TMethod method); protected abstract bool IsConstructor (TMethod method); - protected abstract TType GetElementType (TType type); + public abstract TType GetElementType (TType type); protected abstract TType GetReturnType (TMethod method); protected abstract void GetNamespaceAndName (TType type, out string @namespace, out string name); protected abstract bool TryGetAttribute (TType type, string attributeNamespace, string attributeType, out object attribute); @@ -1104,23 +1102,23 @@ namespace Registrar { protected abstract ExportAttribute GetExportAttribute (TMethod method); // Return null if no attribute is found. Must check the base method (i.e. if method is overriding a method in a base class, must check the overridden method for the attribute). protected abstract Dictionary> PrepareMethodMapping (TType type); public abstract RegisterAttribute GetRegisterAttribute (TType type); // Return null if no attribute is found. Do not consider base types. - protected abstract CategoryAttribute GetCategoryAttribute (TType type); // Return null if no attribute is found. Do not consider base types. + public abstract CategoryAttribute GetCategoryAttribute (TType type); // Return null if no attribute is found. Do not consider base types. protected abstract ConnectAttribute GetConnectAttribute (TProperty property); // Return null if no attribute is found. Do not consider inherited properties. public abstract ProtocolAttribute GetProtocolAttribute (TType type); // Return null if no attribute is found. Do not consider base types. protected abstract IEnumerable GetProtocolMemberAttributes (TType type); // Return null if no attributes found. Do not consider base types. protected virtual Version GetSdkIntroducedVersion (TType obj, out string message) { message = null; return null; } // returns the sdk version when the type was introduced for the current platform (null if all supported versions) protected abstract Version GetSDKVersion (); protected abstract TType GetProtocolAttributeWrapperType (TType type); // Return null if no attribute is found. Do not consider base types. - protected abstract BindAsAttribute GetBindAsAttribute (TMethod method, int parameter_index); // If parameter_index = -1 then get the attribute for the return type. Return null if no attribute is found. Must consider base method. - protected abstract BindAsAttribute GetBindAsAttribute (TProperty property); + public abstract BindAsAttribute GetBindAsAttribute (TMethod method, int parameter_index); // If parameter_index = -1 then get the attribute for the return type. Return null if no attribute is found. Must consider base method. + public abstract BindAsAttribute GetBindAsAttribute (TProperty property); protected abstract IList GetAdoptsAttributes (TType type); public abstract TType GetNullableType (TType type); // For T? returns T. For T returns null. - protected abstract bool HasReleaseAttribute (TMethod method); // Returns true of the method's return type/value has a [Release] attribute. + public abstract bool HasReleaseAttribute (TMethod method); // Returns true of the method's return type/value has a [Release] attribute. protected abstract bool IsINativeObject (TType type); protected abstract bool IsValueType (TType type); - protected abstract bool IsArray (TType type, out int rank); + public abstract bool IsArray (TType type, out int rank); protected abstract bool IsEnum (TType type, out bool isNativeEnum); - protected abstract bool IsNullable (TType type); + public abstract bool IsNullable (TType type); protected abstract bool IsDelegate (TType type); protected abstract bool IsGenericType (TType type); protected abstract bool IsGenericMethod (TMethod method); @@ -1128,7 +1126,7 @@ namespace Registrar { protected abstract bool IsAbstract (TType type); protected abstract bool IsPointer (TType type); protected abstract TType GetGenericTypeDefinition (TType type); - protected abstract bool VerifyIsConstrainedToNSObject (TType type, out TType constrained_type); + public abstract bool VerifyIsConstrainedToNSObject (TType type, out TType constrained_type); protected abstract TType GetEnumUnderlyingType (TType type); protected abstract IEnumerable GetFields (TType type); // Must return all instance fields. May return static fields (they are filtered out automatically). protected abstract TType GetFieldType (TField field); @@ -1160,7 +1158,33 @@ namespace Registrar { { } - protected bool IsArray (TType type) + public static bool IsPropertyAccessor (TMethod method) + { + if (method is null) + return false; + + if (!method.IsSpecialName) + return false; + + var name = method.Name; + if (!name.StartsWith ("get_", StringComparison.Ordinal) && !name.StartsWith ("set_", StringComparison.Ordinal)) + return false; + + return true; + } + + public bool IsPropertyAccessor (TMethod method, out TProperty property) + { + property = null; + + if (!IsPropertyAccessor (method)) + return false; + + property = FindProperty (method.DeclaringType, method.Name.Substring (4)); + return property is not null; + } + + public bool IsArray (TType type) { int rank; return IsArray (type, out rank); @@ -2243,6 +2267,9 @@ namespace Registrar { FieldType = "@", IsProperty = true, IsStatic = IsStatic (property), +#if MTOUCH || MMP || BUNDLER + Property = property, +#endif }, ref exceptions); } } diff --git a/src/ObjCRuntime/RegistrarHelper.cs b/src/ObjCRuntime/RegistrarHelper.cs new file mode 100644 index 0000000000..6b764a7dfe --- /dev/null +++ b/src/ObjCRuntime/RegistrarHelper.cs @@ -0,0 +1,401 @@ +// +// RegistrarHelper.cs: Helper code for the managed static registra. +// +// Authors: +// Rolf Bjarne Kvinge +// +// Copyright 2023 Microsoft Corp + + +// #define TRACE + +#if NET + +#nullable enable + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +using CoreFoundation; +using CoreGraphics; +using Foundation; + +using Registrar; + +using Xamarin.Bundler; + +namespace ObjCRuntime { + // This class contains helper methods for the managed static registrar. + // The managed static registrar will make it public when needed. + static class RegistrarHelper { + class MapInfo { + public IManagedRegistrar Registrar; + public bool RegisteredWrapperTypes; + + public MapInfo (IManagedRegistrar registrar) + { + Registrar = registrar; + } + } + + // Ignore CS8618 for these two variables: + // Non-nullable variable must contain a non-null value when exiting constructor. + // Because we won't use a static constructor to initialize them, instead we're using a module initializer, + // it's safe to ignore this warning. +#pragma warning disable 8618 + static Dictionary assembly_map; + static Dictionary wrapper_types; + static StringEqualityComparer StringEqualityComparer; + static RuntimeTypeHandleEqualityComparer RuntimeTypeHandleEqualityComparer; +#pragma warning restore 8618 + + [ModuleInitializer] + internal static void Initialize () + { + StringEqualityComparer = new StringEqualityComparer (); + RuntimeTypeHandleEqualityComparer = new RuntimeTypeHandleEqualityComparer (); + assembly_map = new Dictionary (StringEqualityComparer); + wrapper_types = new Dictionary (RuntimeTypeHandleEqualityComparer); + } + + static NativeHandle CreateCFArray (params string[]? values) + { + if (values is null) + return NativeHandle.Zero; + return CFArray.Create (values); + } + + unsafe static IntPtr GetBlockPointer (BlockLiteral block) + { + var rv = BlockLiteral._Block_copy (&block); + block.Dispose (); + return rv; + } + + static IntPtr GetBlockForDelegate (object @delegate, RuntimeMethodHandle method_handle) + { + var method = (MethodInfo) MethodBase.GetMethodFromHandle (method_handle)!; + return BlockLiteral.GetBlockForDelegate (method, @delegate, Runtime.INVALID_TOKEN_REF, null); + } + + + static MapInfo GetMapEntry (Assembly assembly) + { + return GetMapEntry (assembly.GetName ().Name!); + } + + static MapInfo GetMapEntry (IntPtr assembly) + { + return GetMapEntry (Marshal.PtrToStringAuto (assembly)!); + } + + static MapInfo GetMapEntry (string assemblyName) + { + if (TryGetMapEntry (assemblyName, out var rv)) + return rv; + throw ErrorHelper.CreateError (8055, Errors.MX8055 /* Could not find the type 'ObjCRuntime.__Registrar__' in the assembly '{0}' */, assemblyName); + } + + static bool TryGetMapEntry (string assemblyName, [NotNullWhen (true)] out MapInfo? entry) + { + entry = null; + + lock (assembly_map) { + return assembly_map.TryGetValue (assemblyName, out entry); + } + } + + static void Register (IManagedRegistrar registrar) + { + var assembly = registrar.GetType ().Assembly; + var assemblyName = assembly.GetName ().Name!; + +#if TRACE + Runtime.NSLog ($"RegistrarHelper.Register ('{assemblyName}', '{registrar.GetType ().AssemblyQualifiedName}')"); +#endif + + lock (assembly_map) { + assembly_map.Add (assemblyName, new MapInfo (registrar)); + } + } + + internal static Type? FindProtocolWrapperType (Type type) + { + var typeHandle = type.TypeHandle; + + lock (assembly_map) { + // First check if the type is already in our dictionary. + if (wrapper_types.TryGetValue (typeHandle, out var wrapperType)) + return Type.GetTypeFromHandle (wrapperType); + + // Not in our dictionary, get the map entry to see if we've already + // called RegisterWrapperTypes for this assembly, + var entry = GetMapEntry (type.Assembly); + if (!entry.RegisteredWrapperTypes) { + entry.Registrar.RegisterWrapperTypes (wrapper_types); + entry.RegisteredWrapperTypes = true; + } + + // Return whatever's in the dictionary now. + if (wrapper_types.TryGetValue (typeHandle, out wrapperType)) + return Type.GetTypeFromHandle (wrapperType); + } + + return null; + } + +#if TRACE + [ThreadStatic] + static Stopwatch? lookupWatch; +#endif + + internal static IntPtr LookupUnmanagedFunction (IntPtr assembly, string? symbol, int id) + { + IntPtr rv; + +#if TRACE + if (lookupWatch is null) + lookupWatch = new Stopwatch (); + + lookupWatch.Start (); + Console.WriteLine ("LookupUnmanagedFunction (0x{0} = {1}, {2}, {3})", assembly.ToString ("x"), Marshal.PtrToStringAuto (assembly), symbol, id); +#endif + + if (id == -1) { + rv = IntPtr.Zero; + } else { + rv = LookupUnmanagedFunctionInAssembly (assembly, symbol, id); + } + +#if TRACE + lookupWatch.Stop (); + + Console.WriteLine ("LookupUnmanagedFunction (0x{0} = {1}, {2}, {3}) => 0x{4} ElapsedMilliseconds: {5}", assembly.ToString ("x"), Marshal.PtrToStringAuto (assembly), symbol, id, rv.ToString ("x"), lookupWatch.ElapsedMilliseconds); +#endif + + if (rv != IntPtr.Zero) + return rv; + + throw ErrorHelper.CreateError (8001, "Unable to find the managed function with id {0} ({1})", id, symbol); + } + + static IntPtr LookupUnmanagedFunctionInAssembly (IntPtr assembly_name, string? symbol, int id) + { + var entry = GetMapEntry (assembly_name); + return entry.Registrar.LookupUnmanagedFunction (symbol, id); + } + + internal static Type LookupRegisteredType (Assembly assembly, uint id) + { + var entry = GetMapEntry (assembly); + var handle = entry.Registrar.LookupType (id); + return Type.GetTypeFromHandle (handle)!; + } + + internal static uint LookupRegisteredTypeId (Type type) + { + if (!TryGetMapEntry (type.Assembly.GetName ().Name!, out var entry)) + return Runtime.INVALID_TOKEN_REF; + return entry.Registrar.LookupTypeId (type.TypeHandle); + } + + // helper functions for converting between native and managed objects + static NativeHandle ManagedArrayToNSArray (object array, bool retain) + { + if (array is null) + return NativeHandle.Zero; + + NSObject rv; + if (array is NSObject[] nsobjs) { + rv = NSArray.FromNSObjects (nsobjs); + } else if (array is INativeObject[] inativeobjs) { + rv = NSArray.FromNSObjects (inativeobjs); + } else { + throw new InvalidOperationException ($"Can't convert {array.GetType ()} to an NSArray."); // FIXME: better error + } + + if (retain) + return Runtime.RetainNSObject (rv); + return Runtime.RetainAndAutoreleaseNSObject (rv); + } + + unsafe static void NSArray_string_native_to_managed (IntPtr* ptr, ref string[]? value, ref string[]? copy) + { + if (ptr is not null) { + value = NSArray.StringArrayFromHandle (*ptr); + } else { + value = null; + } + copy = value; + } + + unsafe static void NSArray_string_managed_to_native (IntPtr* ptr, string[] value, string[] copy, bool isOut) + { + if (ptr is null) + return; + + // Note that we won't notice if individual array elements change, only if the array itself changes + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"NSArray_string_managed_to_native (0x{(*ptr).ToString ("x")}, equal)"); +#endif + return; + } + if (value is null) { +#if TRACE + Runtime.NSLog ($"NSArray_string_managed_to_native (0x{(*ptr).ToString ("x")}, null, !null)"); +#endif + *ptr = IntPtr.Zero; + return; + } + IntPtr rv = Runtime.RetainAndAutoreleaseNSObject (NSArray.FromStrings (value)); +#if TRACE + Runtime.NSLog ($"NSArray_string_managed_to_native (0x{(*ptr).ToString ("x")}, value != copy: {value?.Length} != {copy?.Length}): 0x{rv.ToString ("x")} => {value?.GetType ()}"); +#endif + *ptr = rv; + } + + unsafe static void NSArray_native_to_managed (IntPtr* ptr, ref T[]? value, ref T[]? copy) where T: class, INativeObject + { + if (ptr is not null) { + value = NSArray.ArrayFromHandle (*ptr); + } else { + value = null; + } + copy = value; + } + + unsafe static void NSArray_managed_to_native (IntPtr* ptr, T[] value, T[] copy, bool isOut) where T: class, INativeObject + { + if (ptr is null) { +#if TRACE + Runtime.NSLog ($"NSArray_managed_to_native (NULL, ?, ?)"); +#endif + return; + } + // Note that we won't notice if individual array elements change, only if the array itself changes + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"NSArray_managed_to_native (0x{(*ptr).ToString ("x")}, equal)"); +#endif + return; + } + if (value is null) { +#if TRACE + Runtime.NSLog ($"NSArray_managed_to_native (0x{(*ptr).ToString ("x")}, null, !null)"); +#endif + *ptr = IntPtr.Zero; + return; + } + IntPtr rv = Runtime.RetainAndAutoreleaseNSObject (NSArray.FromNSObjects (value)); +#if TRACE + Runtime.NSLog ($"NSArray_managed_to_native (0x{(*ptr).ToString ("x")}, value != copy: {value?.Length} != {copy?.Length}): 0x{rv.ToString ("x")} => {value?.GetType ()}"); +#endif + *ptr = rv; + } + + unsafe static void NSObject_native_to_managed (IntPtr* ptr, ref T? value, ref T? copy) where T: NSObject + { + if (ptr is not null) { + value = Runtime.GetNSObject (*ptr, owns: false); + } else { + value = null; + } + copy = value; + } + + unsafe static void NSObject_managed_to_native (IntPtr* ptr, NSObject value, NSObject copy, bool isOut) + { + if (ptr is null) { +#if TRACE + Runtime.NSLog ($"NSObject_managed_to_native (NULL, ?, ?)"); +#endif + return; + } + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"NSObject_managed_to_native (0x{(*ptr).ToString ("x")}, equal)"); +#endif + return; + } + IntPtr rv = Runtime.RetainAndAutoreleaseNSObject (value); +#if TRACE + Runtime.NSLog ($"NSObject_managed_to_native (0x{(*ptr).ToString ("x")}, ? != ?): 0x{rv.ToString ("x")} => {value?.GetType ()}"); +#endif + *ptr = rv; + } + + unsafe static void string_native_to_managed (NativeHandle *ptr, ref string? value, ref string? copy) + { + if (ptr is not null) { + value = CFString.FromHandle (*ptr); + } else { + value = null; + } + copy = value; + } + + unsafe static void string_managed_to_native (NativeHandle *ptr, string value, string copy, bool isOut) + { + if (ptr is null) { +#if TRACE + Runtime.NSLog ($"string_managed_to_native (NULL, ?, ?)"); +#endif + return; + } + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"string_managed_to_native (0x{(*ptr).ToString ()}, equal)"); +#endif + return; + } + var rv = CFString.CreateNative (value); +#if TRACE + Runtime.NSLog ($"string_managed_to_native (0x{(*ptr).ToString ()}, ? != ?): 0x{rv.ToString ()} => {value}"); +#endif + *ptr = rv; + } + + unsafe static void INativeObject_native_to_managed (IntPtr* ptr, ref T? value, ref T? copy, RuntimeTypeHandle implementationType) where T: class, INativeObject + { + if (ptr is not null) { + value = Runtime.GetINativeObject (*ptr, implementation: Type.GetTypeFromHandle (implementationType), forced_type: false, owns: false); + } else { + value = null; + } + copy = value; + } + + unsafe static void INativeObject_managed_to_native (IntPtr *ptr, INativeObject value, INativeObject copy, bool isOut) + { + if (ptr is null) { +#if TRACE + Runtime.NSLog ($"INativeObject_managed_to_native (NULL, ?, ?)"); +#endif + return; + } + if (!isOut && (object) value == (object) copy) { +#if TRACE + Runtime.NSLog ($"INativeObject_managed_to_native (0x{(*ptr).ToString ("x")}, equal)"); +#endif + return; + } + IntPtr rv = value.GetHandle (); +#if TRACE + Runtime.NSLog ($"INativeObject_managed_to_native (0x{(*ptr).ToString ("x")}, ? != ?): 0x{rv.ToString ("x")} => {value?.GetType ()}"); +#endif + *ptr = rv; + } + } +} + +#endif // NET diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs index de5871b27d..f411a851b9 100644 --- a/src/ObjCRuntime/Runtime.cs +++ b/src/ObjCRuntime/Runtime.cs @@ -27,6 +27,10 @@ using Registrar; using AppKit; #endif +#if !NET +using NativeHandle = System.IntPtr; +#endif + namespace ObjCRuntime { public partial class Runtime { @@ -149,7 +153,7 @@ namespace ObjCRuntime { [Flags] internal enum InitializationFlags : int { IsPartialStaticRegistrar = 0x01, - /* unused = 0x02,*/ + IsManagedStaticRegistrar = 0x02, /* unused = 0x04,*/ /* unused = 0x08,*/ IsSimulator = 0x10, @@ -220,6 +224,14 @@ namespace ObjCRuntime { } #endif + [BindingImpl (BindingImplOptions.Optimizable)] + internal unsafe static bool IsManagedStaticRegistrar { + get { + // The linker may turn calls to this property into a constant + return (options->Flags.HasFlag (InitializationFlags.IsManagedStaticRegistrar)); + } + } + [BindingImpl (BindingImplOptions.Optimizable)] public static bool DynamicRegistrationSupported { get { @@ -486,6 +498,11 @@ namespace ObjCRuntime { return AllocGCHandle (ex); } + static Exception CreateRuntimeException (int code, string message) + { + return ErrorHelper.CreateError (code, message); + } + static IntPtr CreateRuntimeException (int code, IntPtr message) { var ex = ErrorHelper.CreateError (code, Marshal.PtrToStringAuto (message)!); @@ -735,7 +752,14 @@ namespace ObjCRuntime { Registrar.GetMethodDescription (Class.Lookup (cls), sel, is_static != 0, desc); } - static sbyte HasNSObject (IntPtr ptr) +#if NET + internal static bool HasNSObject (NativeHandle ptr) + { + return TryGetNSObject (ptr, evenInFinalizerQueue: false) is not null; + } +#endif + + internal static sbyte HasNSObject (IntPtr ptr) { var rv = TryGetNSObject (ptr, evenInFinalizerQueue: false) is not null; return (sbyte) (rv ? 1 : 0); @@ -767,10 +791,15 @@ namespace ObjCRuntime { return IntPtr.Zero; var nsobj = GetGCHandleTarget (obj) as NSObject; - if (nsobj is null) - throw ErrorHelper.CreateError (8023, $"An instance object is required to construct a closed generic method for the open generic method: {method.DeclaringType!.FullName}.{method.Name} (token reference: 0x{token_ref:X}). {Constants.PleaseFileBugReport}"); + return AllocGCHandle (FindClosedMethodForObject (nsobj, method)); + } - return AllocGCHandle (FindClosedMethod (nsobj.GetType (), method)); + static MethodInfo FindClosedMethodForObject (NSObject? nsobj, MethodBase method) + { + if (nsobj is null) + throw ErrorHelper.CreateError (8023, $"An instance object is required to construct a closed generic method for the open generic method: {method.DeclaringType!.FullName}.{method.Name}. {Constants.PleaseFileBugReport}"); + + return FindClosedMethod (nsobj.GetType (), method); } static IntPtr TryGetOrConstructNSObjectWrapped (IntPtr ptr) @@ -1190,7 +1219,7 @@ namespace ObjCRuntime { Ignore, } - static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolution resolution) + static void MissingCtor (IntPtr ptr, IntPtr klass, Type type, MissingCtorResolution resolution, IntPtr sel, RuntimeMethodHandle method_handle) { if (resolution == MissingCtorResolution.Ignore) return; @@ -1225,6 +1254,37 @@ namespace ObjCRuntime { } 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")}"); + } + 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 ()); } @@ -1247,6 +1307,13 @@ namespace ObjCRuntime { // 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 + { + 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. + static T? ConstructNSObject (IntPtr ptr, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle) where T : class, INativeObject { if (type is null) throw new ArgumentNullException (nameof (type)); @@ -1254,7 +1321,7 @@ namespace ObjCRuntime { var ctor = GetIntPtrConstructor (type); if (ctor is null) { - MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution); + MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle); return null; } @@ -1273,7 +1340,7 @@ namespace ObjCRuntime { } // The generic argument T is only used to cast the return value. - static T? ConstructINativeObject (IntPtr ptr, bool owns, Type type, MissingCtorResolution missingCtorResolution) where T : class, INativeObject + static T? ConstructINativeObject (IntPtr ptr, bool owns, Type type, MissingCtorResolution missingCtorResolution, IntPtr sel, RuntimeMethodHandle method_handle) where T : class, INativeObject { if (type is null) throw new ArgumentNullException (nameof (type)); @@ -1284,7 +1351,7 @@ namespace ObjCRuntime { var ctor = GetIntPtr_BoolConstructor (type); if (ctor is null) { - MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution); + MissingCtor (ptr, IntPtr.Zero, type, missingCtorResolution, sel, method_handle); return null; } @@ -1439,6 +1506,13 @@ namespace ObjCRuntime { return null; } +#if NET + public static NSObject? GetNSObject (NativeHandle ptr) + { + return GetNSObject ((IntPtr) ptr, MissingCtorResolution.ThrowConstructor1NotFound); + } +#endif + public static NSObject? GetNSObject (IntPtr ptr) { return GetNSObject (ptr, MissingCtorResolution.ThrowConstructor1NotFound); @@ -1458,11 +1532,21 @@ namespace ObjCRuntime { } static public T? GetNSObject (IntPtr ptr) where T : NSObject + { + return GetNSObject (ptr, IntPtr.Zero, default (RuntimeMethodHandle)); + } + + static T? GetNSObject (IntPtr ptr, IntPtr sel, RuntimeMethodHandle method_handle) where T : NSObject + { + return GetNSObject (ptr, sel, method_handle, false); + } + + static T? GetNSObject (IntPtr ptr, IntPtr sel, RuntimeMethodHandle method_handle, bool evenInFinalizerQueue) where T : NSObject { if (ptr == IntPtr.Zero) return null; - var obj = TryGetNSObject (ptr, evenInFinalizerQueue: false); + var obj = TryGetNSObject (ptr, evenInFinalizerQueue: evenInFinalizerQueue); // First check if we got an object of the expected type if (obj is T o) @@ -1492,7 +1576,7 @@ namespace ObjCRuntime { target_type = typeof (NSObject); } - return ConstructNSObject (ptr, target_type, MissingCtorResolution.ThrowConstructor1NotFound); + return ConstructNSObject (ptr, target_type, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle); } static public T? GetNSObject (IntPtr ptr, bool owns) where T : NSObject @@ -1623,6 +1707,12 @@ namespace ObjCRuntime { // this method is identical in behavior to the generic one. static INativeObject? GetINativeObject (IntPtr ptr, bool owns, Type target_type, Type? implementation) + { + return GetINativeObject (ptr, owns, target_type, implementation, IntPtr.Zero, default (RuntimeMethodHandle)); + } + + // this method is identical in behavior to the generic one. + static INativeObject? GetINativeObject (IntPtr ptr, bool owns, Type target_type, Type? implementation, IntPtr sel, RuntimeMethodHandle method_handle) { if (ptr == IntPtr.Zero) return null; @@ -1657,10 +1747,10 @@ namespace ObjCRuntime { // 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); + return ConstructNSObject (ptr, implementation, MissingCtorResolution.ThrowConstructor1NotFound, sel, method_handle); } - return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound); + return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle); } // this method is identical in behavior to the non-generic one. @@ -1670,6 +1760,16 @@ namespace ObjCRuntime { } public static T? GetINativeObject (IntPtr ptr, bool forced_type, bool owns) where T : class, INativeObject + { + return GetINativeObject (ptr, forced_type, null, owns); + } + + internal static T? GetINativeObject (IntPtr ptr, bool forced_type, Type? implementation, bool owns) where T : class, INativeObject + { + return GetINativeObject (ptr, forced_type, implementation, owns, IntPtr.Zero, default (RuntimeMethodHandle)); + } + + static T? GetINativeObject (IntPtr ptr, bool forced_type, Type? implementation, bool owns, IntPtr sel, RuntimeMethodHandle method_handle) where T : class, INativeObject { if (ptr == IntPtr.Zero) return null; @@ -1695,7 +1795,7 @@ namespace ObjCRuntime { } // Lookup the ObjC type of the ptr and see if we can use it. - var implementation = LookupINativeObjectImplementation (ptr, typeof (T), forced_type: forced_type); + implementation = LookupINativeObjectImplementation (ptr, typeof (T), implementation, forced_type: forced_type); if (implementation.IsSubclassOf (typeof (NSObject))) { if (o is not null && !forced_type) { @@ -1710,7 +1810,7 @@ namespace ObjCRuntime { return rv; } - return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound); + return ConstructINativeObject (ptr, owns, implementation, MissingCtorResolution.ThrowConstructor2NotFound, sel, method_handle); } static void TryReleaseINativeObject (INativeObject? obj) @@ -1744,14 +1844,24 @@ namespace ObjCRuntime { return null; // Check if the static registrar knows about this protocol - unsafe { - var map = options->RegistrationMap; - if (map is not null) { - var token = Class.GetTokenReference (type, throw_exception: false); - if (token != INVALID_TOKEN_REF) { - var wrapper_token = xamarin_find_protocol_wrapper_type (token); - if (wrapper_token != INVALID_TOKEN_REF) - return Class.ResolveTypeTokenReference (wrapper_token); + if (IsManagedStaticRegistrar) { +#if NET + var rv = RegistrarHelper.FindProtocolWrapperType (type); + if (rv is not null) + return rv; +#else + throw ErrorHelper.CreateError (99, Xamarin.Bundler.Errors.MX0099 /* Internal error */, "The managed static registrar is only available for .NET"); +#endif + } else { + unsafe { + var map = options->RegistrationMap; + if (map is not null) { + var token = Class.GetTokenReference (type, throw_exception: false); + if (token != INVALID_TOKEN_REF) { + var wrapper_token = xamarin_find_protocol_wrapper_type (token); + if (wrapper_token != INVALID_TOKEN_REF) + return Class.ResolveTypeTokenReference (wrapper_token); + } } } } @@ -1919,10 +2029,54 @@ namespace ObjCRuntime { static void RetainNativeObject (IntPtr gchandle) { var obj = GetGCHandleTarget (gchandle); - if (obj is NativeObject nobj) + RetainNativeObject ((INativeObject?) obj); + } + + // Retain the input if it's either an NSObject or a NativeObject. + static NativeHandle RetainNativeObject (INativeObject? obj) + { + if (obj is null) + return NativeHandle.Zero; + if (obj is NSObject nsobj) + RetainNSObject (nsobj); + else if (obj is NativeObject nobj) nobj.Retain (); - else if (obj is NSObject nsobj) + return obj.GetHandle (); + } + + internal static NativeHandle RetainNSObject (NSObject? obj) + { + if (obj is null) + return NativeHandle.Zero; + obj.DangerousRetain (); + return obj.GetHandle (); + } + + internal static NativeHandle RetainAndAutoreleaseNSObject (NSObject? obj) + { + if (obj is null) + return NativeHandle.Zero; + obj.DangerousRetain (); + obj.DangerousAutorelease (); + return obj.GetHandle (); + } + + internal static NativeHandle RetainAndAutoreleaseNativeObject (INativeObject? obj) + { + if (obj is null) + return NativeHandle.Zero; + if (obj is NSObject nsobj) { nsobj.DangerousRetain (); + nsobj.DangerousAutorelease (); + } + return obj.GetHandle (); + } + + static IntPtr CopyAndAutorelease (IntPtr ptr) + { + ptr = Messaging.IntPtr_objc_msgSend (ptr, Selector.GetHandle ("copy")); + NSObject.DangerousAutorelease (ptr); + return ptr; } // Check if the input is an NSObject, and in that case retain it (and return true) @@ -2025,6 +2179,47 @@ namespace ObjCRuntime { throw ErrorHelper.CreateError (8003, $"Failed to find the closed generic method '{open_method.Name}' on the type '{closed_type.FullName}'."); } + internal static MethodInfo FindClosedMethod (object instance, RuntimeTypeHandle open_type_handle, RuntimeMethodHandle open_method_handle) + { + var closed_type = instance.GetType ()!; + var open_type = Type.GetTypeFromHandle (open_type_handle)!; + closed_type = FindClosedTypeInHierarchy (open_type, closed_type)!; + var closedMethod = MethodBase.GetMethodFromHandle (open_method_handle, closed_type.TypeHandle)!; + return (MethodInfo) closedMethod; + } + + static Type? FindClosedTypeInHierarchy (Type open_type, Type? closed_type) + { + if (closed_type is null) + return null; + + var closed_type_definition = closed_type; + if (closed_type_definition.IsGenericType) + closed_type_definition = closed_type_definition.GetGenericTypeDefinition (); + if (closed_type_definition == open_type) + return closed_type; + return FindClosedTypeInHierarchy (open_type, closed_type.BaseType); + } + + internal static Type FindClosedParameterType (object instance, RuntimeTypeHandle open_type_handle, RuntimeMethodHandle open_method_handle, int parameter) + { + var closed_type = instance.GetType ()!; + var open_type = Type.GetTypeFromHandle (open_type_handle)!; + closed_type = FindClosedTypeInHierarchy (open_type, closed_type)!; + var closedMethod = MethodBase.GetMethodFromHandle (open_method_handle, closed_type.TypeHandle)!; + var parameters = closedMethod.GetParameters (); + return parameters [parameter].ParameterType.GetElementType ()!; // FIX NAMING + } + +#if NET + // This method might be called by the generated code from the managed static registrar. + static void TraceCaller (string message) + { + var caller = new System.Diagnostics.StackFrame (1); + NSLog ($"{caller?.GetMethod ()?.ToString ()}: {message}"); + } +#endif + static void GCCollect () { GC.Collect (); @@ -2125,7 +2320,13 @@ namespace ObjCRuntime { } // Allocate a GCHandle and return the IntPtr to it. - internal static IntPtr AllocGCHandle (object? value, GCHandleType type = GCHandleType.Normal) + internal static IntPtr AllocGCHandle (object? value) + { + return AllocGCHandle (value, GCHandleType.Normal); + } + + // Allocate a GCHandle and return the IntPtr to it. + internal static IntPtr AllocGCHandle (object? value, GCHandleType type) { return GCHandle.ToIntPtr (GCHandle.Alloc (value, type)); } @@ -2218,6 +2419,14 @@ namespace ObjCRuntime { return (sbyte) (rv ? 1 : 0); } + static IntPtr LookupUnmanagedFunction (IntPtr assembly, IntPtr symbol, int id) + { +#if NET + return RegistrarHelper.LookupUnmanagedFunction (assembly, Marshal.PtrToStringAuto (symbol), id); +#else + return IntPtr.Zero; +#endif + } } internal class IntPtrEqualityComparer : IEqualityComparer { @@ -2244,6 +2453,28 @@ namespace ObjCRuntime { } } + internal class StringEqualityComparer : IEqualityComparer { + public bool Equals (string? x, string? y) + { + return string.Equals (x, y, StringComparison.Ordinal); + } + public int GetHashCode (string? obj) + { + return obj?.GetHashCode () ?? 0; + } + } + + internal class RuntimeTypeHandleEqualityComparer : IEqualityComparer { + public bool Equals (RuntimeTypeHandle x, RuntimeTypeHandle y) + { + return x.Equals (y); + } + public int GetHashCode (RuntimeTypeHandle obj) + { + return obj.GetHashCode (); + } + } + internal struct IntPtrTypeValueTuple : IEquatable { static readonly IEqualityComparer item1Comparer = Runtime.IntPtrEqualityComparer; static readonly IEqualityComparer item2Comparer = Runtime.TypeEqualityComparer; diff --git a/src/frameworks.sources b/src/frameworks.sources index 4e6775a347..ce89266761 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -1947,6 +1947,7 @@ SHARED_SOURCES = \ ObjCRuntime/AdoptsAttribute.cs \ ObjCRuntime/BackingField.cs \ ObjCRuntime/BaseWrapper.cs \ + ObjCRuntime/BindAs.cs \ ObjCRuntime/BlockProxyAttribute.cs \ ObjCRuntime/BindingImplAttribute.cs \ ObjCRuntime/CategoryAttribute.cs \ @@ -1958,8 +1959,10 @@ SHARED_SOURCES = \ ObjCRuntime/ErrorHelper.runtime.cs \ ObjCRuntime/Exceptions.cs \ ObjCRuntime/ExceptionMode.cs \ + ObjCRuntime/IManagedRegistrar.cs \ ObjCRuntime/Method.cs \ ObjCRuntime/Registrar.cs \ + ObjCRuntime/RegistrarHelper.cs \ ObjCRuntime/ReleaseAttribute.cs \ ObjCRuntime/RequiredFrameworkAttribute.cs \ ObjCRuntime/Runtime.cs \ diff --git a/tests/api-shared/ObjCRuntime/Registrar.cs b/tests/api-shared/ObjCRuntime/Registrar.cs index 01bb5af691..e93c0598f4 100644 --- a/tests/api-shared/ObjCRuntime/Registrar.cs +++ b/tests/api-shared/ObjCRuntime/Registrar.cs @@ -16,10 +16,12 @@ using ObjCRuntime; namespace XamarinTests.ObjCRuntime { + [Flags] public enum Registrars { Static = 1, + ManagedStatic = Static | 2, Dynamic = 4, - AllStatic = Static, + AllStatic = Static | ManagedStatic, AllDynamic = Dynamic, } @@ -27,8 +29,23 @@ namespace XamarinTests.ObjCRuntime { [Register ("__registration_test_CLASS")] class RegistrationTestClass : NSObject { } + public static bool IsStaticRegistrar { + get { + return CurrentRegistrar.HasFlag (Registrars.Static); + } + } + + public static bool IsDynamicRegistrar { + get { + return CurrentRegistrar.HasFlag (Registrars.Dynamic); + } + } + public static Registrars CurrentRegistrar { get { + var __registrar__ = typeof (Class).Assembly.GetType ("ObjCRuntime.__Registrar__"); + if (__registrar__ is not null) + return Registrars.ManagedStatic; #if NET var types = new Type [] { typeof (NativeHandle), typeof (bool).MakeByRefType () }; #else diff --git a/tests/bindings-test/ProtocolTest.cs b/tests/bindings-test/ProtocolTest.cs index b336a7c9ff..ba1d57be1f 100644 --- a/tests/bindings-test/ProtocolTest.cs +++ b/tests/bindings-test/ProtocolTest.cs @@ -215,7 +215,7 @@ namespace Xamarin.BindingTests { // The ObjC runtime won't add optional properties dynamically (the code is commented out, // see file objc4-647/runtime/objc-runtime-old.mm in Apple's open source code), // so we need to verify differently for the dynamic registrar. - if (XamarinTests.ObjCRuntime.Registrar.CurrentRegistrar == XamarinTests.ObjCRuntime.Registrars.Static) { + if (XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) { Assert.AreEqual (9, properties.Length, "Properties: Count"); } else { Assert.AreEqual (2, properties.Length, "Properties: Count"); @@ -232,7 +232,7 @@ namespace Xamarin.BindingTests { new objc_property_attribute ("N", "") })), "Properties: requiredReadonlyProperty"); - if (XamarinTests.ObjCRuntime.Registrar.CurrentRegistrar == XamarinTests.ObjCRuntime.Registrars.Static) { + if (XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) { Assert.That (properties, Contains.Item (new objc_property ("optionalInstanceProperty", "T@\"NSString\",N", new objc_property_attribute [] { new objc_property_attribute ("T", "@\"NSString\""), new objc_property_attribute ("N", "") diff --git a/tests/bindings-test/RegistrarBindingTest.cs b/tests/bindings-test/RegistrarBindingTest.cs index b483997349..c2f2ba5eaa 100644 --- a/tests/bindings-test/RegistrarBindingTest.cs +++ b/tests/bindings-test/RegistrarBindingTest.cs @@ -332,14 +332,12 @@ namespace Xamarin.BindingTests { Messaging.void_objc_msgSend_IntPtr_bool_bool (Class.GetHandle (typeof (ObjCBlockTester)), Selector.GetHandle ("setProtocolWithBlockProperties:required:instance:"), pb.Handle, required, instance); Assert.Fail ("Expected an MT8028 error"); } catch (RuntimeException re) { -#if __MACOS__ - Assert.AreEqual (8009, re.Code, "Code"); - Console.WriteLine (re.Message); - Assert.That (re.Message, Does.StartWith ("Unable to locate the block to delegate conversion method for the method Xamarin.BindingTests.RegistrarBindingTest+FakePropertyBlock.set_"), re.Message, "Message"); -#else - Assert.AreEqual (8028, re.Code, "Code"); - Assert.AreEqual ("The runtime function get_block_wrapper_creator has been linked away.", re.Message, "Message"); -#endif + Assert.That (re.Code, Is.EqualTo (8009).Or.EqualTo (8028), "Code"); + if (re.Code == 8009) { + Assert.That (re.Message, Does.StartWith ("Unable to locate the block to delegate conversion method for the method Xamarin.BindingTests.RegistrarBindingTest+FakePropertyBlock.set_"), re.Message, "Message"); + } else { + Assert.AreEqual ("The runtime function get_block_wrapper_creator has been linked away.", re.Message, "Message"); + } } } } diff --git a/tests/cecil-tests/BlittablePInvokes.cs b/tests/cecil-tests/BlittablePInvokes.cs index 4183b083f6..0e7ad252ea 100644 --- a/tests/cecil-tests/BlittablePInvokes.cs +++ b/tests/cecil-tests/BlittablePInvokes.cs @@ -385,6 +385,7 @@ namespace Cecil.Tests { } static HashSet knownFailuresBlockLiterals = new HashSet { + "The call to SetupBlock in ObjCRuntime.BlockLiteral.CreateBlockForDelegate(System.Delegate, System.Delegate, System.String) must be converted to new Block syntax.", "The call to SetupBlock in ObjCRuntime.BlockLiteral.GetBlockForDelegate(System.Reflection.MethodInfo, System.Object, System.UInt32, System.String) must be converted to new Block syntax.", "The call to SetupBlock in ObjCRuntime.BlockLiteral.SetupBlock(System.Delegate, System.Delegate) must be converted to new Block syntax.", "The call to SetupBlock in ObjCRuntime.BlockLiteral.SetupBlockUnsafe(System.Delegate, System.Delegate) must be converted to new Block syntax.", diff --git a/tests/monotouch-test/JavascriptCore/JSExportTest.cs b/tests/monotouch-test/JavascriptCore/JSExportTest.cs index dbafc42fb4..ec490d733c 100644 --- a/tests/monotouch-test/JavascriptCore/JSExportTest.cs +++ b/tests/monotouch-test/JavascriptCore/JSExportTest.cs @@ -24,7 +24,7 @@ namespace MonoTouchFixtures.JavascriptCore { { TestRuntime.AssertXcodeVersion (5, 0, 1); - if (RegistrarTest.CurrentRegistrar != Registrars.Static) + if (!global::XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) Assert.Ignore ("Exporting protocols to JavaScriptCore requires the static registrar."); var context = new JSContext (); diff --git a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs index ebfbf1c78e..24f1324a1e 100644 --- a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs +++ b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs @@ -213,7 +213,7 @@ namespace MonoTouchFixtures.ObjCRuntime { NativeHandle ptr; CGPath path; - if ((CurrentRegistrar & Registrars.AllStatic) == 0) + if (!global::XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) Assert.Ignore ("This test only passes with the static registrars."); Assert.False (Messaging.bool_objc_msgSend_IntPtr (receiver, new Selector ("INativeObject1:").Handle, NativeHandle.Zero), "#a1"); @@ -1296,12 +1296,14 @@ namespace MonoTouchFixtures.ObjCRuntime { void ThrowsICEIfDebug (TestDelegate code, string message, bool execute_release_mode = true) { #if NET - if (TestRuntime.IsCoreCLR) { + if (TestRuntime.IsCoreCLR || global::XamarinTests.ObjCRuntime.Registrar.CurrentRegistrar == Registrars.ManagedStatic) { if (execute_release_mode) { // In CoreCLR will either throw an ArgumentException: // ToObjCType (param.ParameterType)); builder.Append (string.Join (", ", argumentTypes)); @@ -527,6 +536,11 @@ namespace Registrar { } TypeDefinition ResolveType (TypeReference tr) + { + return ResolveType (LinkContext, tr); + } + + public static TypeDefinition ResolveType (Xamarin.Tuner.DerivedLinkContext context, TypeReference tr) { // The static registrar might sometimes deal with types that have been linked away // It's not always possible to call .Resolve () on types that have been linked away, @@ -537,29 +551,34 @@ namespace Registrar { if (tr is ArrayType arrayType) { return arrayType.ElementType.Resolve (); } else if (tr is GenericInstanceType git) { - return ResolveType (git.ElementType); + return ResolveType (context, git.ElementType); } else { var td = tr.Resolve (); if (td is null) - td = LinkContext?.GetLinkedAwayType (tr, out _); + td = context?.GetLinkedAwayType (tr, out _); return td; } } public bool IsNativeObject (TypeReference tr) + { + return IsNativeObject (LinkContext, tr); + } + + public static bool IsNativeObject (Xamarin.Tuner.DerivedLinkContext context, TypeReference tr) { var gp = tr as GenericParameter; if (gp is not null) { if (gp.HasConstraints) { foreach (var constraint in gp.Constraints) { - if (IsNativeObject (constraint.ConstraintType)) + if (IsNativeObject (context, constraint.ConstraintType)) return true; } } return false; } - var type = ResolveType (tr); + var type = ResolveType (context, tr); while (type is not null) { if (type.HasInterfaces) { @@ -756,13 +775,13 @@ namespace Registrar { return GetValueTypeSize (type.Resolve (), Is64Bits); } - protected override bool HasReleaseAttribute (MethodDefinition method) + public override bool HasReleaseAttribute (MethodDefinition method) { method = GetBaseMethodInTypeHierarchy (method); return HasAttribute (method.MethodReturnType, ObjCRuntime, StringConstants.ReleaseAttribute); } - protected override bool HasThisAttribute (MethodDefinition method) + public override bool HasThisAttribute (MethodDefinition method) { return HasAttribute (method, "System.Runtime.CompilerServices", "ExtensionAttribute"); } @@ -955,7 +974,7 @@ namespace Registrar { return false; } - protected override TypeReference GetElementType (TypeReference type) + public override TypeReference GetElementType (TypeReference type) { var ts = type as TypeSpecification; if (ts is not null) { @@ -1054,7 +1073,7 @@ namespace Registrar { return HasAttribute (td, ObjCRuntime, StringConstants.NativeAttribute); } - protected override bool IsNullable (TypeReference type) + public override bool IsNullable (TypeReference type) { return GetNullableType (type) is not null; } @@ -1070,7 +1089,7 @@ namespace Registrar { return type.IsEnum; } - protected override bool IsArray (TypeReference type, out int rank) + public override bool IsArray (TypeReference type, out int rank) { var arrayType = type as ArrayType; if (arrayType is null) { @@ -1155,7 +1174,7 @@ namespace Registrar { return TypeMatch (a, b); } - protected override bool VerifyIsConstrainedToNSObject (TypeReference type, out TypeReference constrained_type) + public override bool VerifyIsConstrainedToNSObject (TypeReference type, out TypeReference constrained_type) { constrained_type = null; @@ -1338,7 +1357,7 @@ namespace Registrar { return rv; } - protected override CategoryAttribute GetCategoryAttribute (TypeReference type) + public override CategoryAttribute GetCategoryAttribute (TypeReference type) { string name = null; @@ -1831,19 +1850,30 @@ namespace Registrar { return PrepareInterfaceMethodMapping (type); } + public TypeReference GetProtocolAttributeWrapperType (TypeDefinition type) + { + return GetProtocolAttributeWrapperType ((TypeReference) type); + } + + public static TypeReference GetProtocolAttributeWrapperType (ICustomAttribute attrib) + { + if (!attrib.HasProperties) + return null; + + foreach (var prop in attrib.Properties) { + if (prop.Name == "WrapperType") + return (TypeReference) prop.Argument.Value; + } + + return null; + } + protected override TypeReference GetProtocolAttributeWrapperType (TypeReference type) { if (!TryGetAttribute (type.Resolve (), Foundation, StringConstants.ProtocolAttribute, out var attrib)) return null; - if (attrib.HasProperties) { - foreach (var prop in attrib.Properties) { - if (prop.Name == "WrapperType") - return (TypeReference) prop.Argument.Value; - } - } - - return null; + return GetProtocolAttributeWrapperType (attrib); } protected override IList GetAdoptsAttributes (TypeReference type) @@ -1890,7 +1920,7 @@ namespace Registrar { } } - protected override BindAsAttribute GetBindAsAttribute (PropertyDefinition property) + public override BindAsAttribute GetBindAsAttribute (PropertyDefinition property) { if (property is null) return null; @@ -1903,7 +1933,7 @@ namespace Registrar { return CreateBindAsAttribute (attrib, property); } - protected override BindAsAttribute GetBindAsAttribute (MethodDefinition method, int parameter_index) + public override BindAsAttribute GetBindAsAttribute (MethodDefinition method, int parameter_index) { if (method is null) return null; @@ -1968,7 +1998,7 @@ namespace Registrar { } } - ExportAttribute CreateExportAttribute (IMemberDefinition candidate) + public static ExportAttribute CreateExportAttribute (IMemberDefinition candidate) { bool is_variadic = false; var attribute = GetExportAttribute (candidate); @@ -2001,7 +2031,7 @@ namespace Registrar { } // [Export] is not sealed anymore - so we cannot simply compare strings - ICustomAttribute GetExportAttribute (ICustomAttributeProvider candidate) + public static ICustomAttribute GetExportAttribute (ICustomAttributeProvider candidate) { if (!candidate.HasCustomAttributes) return null; @@ -2068,7 +2098,7 @@ namespace Registrar { return true; } - MethodDefinition GetBaseMethodInTypeHierarchy (MethodDefinition method) + public MethodDefinition GetBaseMethodInTypeHierarchy (MethodDefinition method) { if (!IsOverride (method)) return method; @@ -2477,7 +2507,7 @@ namespace Registrar { return ToObjCParameterType (type, descriptiveMethodName, exceptions, inMethod); } - string ToObjCParameterType (TypeReference type, string descriptiveMethodName, List exceptions, MemberReference inMethod, bool delegateToBlockType = false) + string ToObjCParameterType (TypeReference type, string descriptiveMethodName, List exceptions, MemberReference inMethod, bool delegateToBlockType = false, bool cSyntaxForBlocks = false) { GenericParameter gp = type as GenericParameter; if (gp is not null) @@ -2599,7 +2629,7 @@ namespace Registrar { } return CheckStructure (td, descriptiveMethodName, inMethod); } else { - return ToObjCType (td, delegateToBlockType: delegateToBlockType); + return ToObjCType (td, delegateToBlockType: delegateToBlockType, cSyntaxForBlocks: cSyntaxForBlocks); } } } @@ -2737,7 +2767,7 @@ namespace Registrar { return sb.ToString (); } - static string EncodeNonAsciiCharacters (string value) + public static string EncodeNonAsciiCharacters (string value) { StringBuilder sb = null; for (int i = 0; i < value.Length; i++) { @@ -2787,7 +2817,7 @@ namespace Registrar { public ObjCType Protocol; } - class SkippedType { + public class SkippedType { public TypeReference Skipped; public ObjCType Actual; public uint SkippedTokenReference; @@ -2800,6 +2830,10 @@ namespace Registrar { skipped_types.Add (new SkippedType { Skipped = type, Actual = registered_type }); } + public List SkippedTypes { + get => skipped_types; + } + public string GetInitializationMethodName (string single_assembly) { if (!string.IsNullOrEmpty (single_assembly)) { @@ -3298,7 +3332,7 @@ namespace Registrar { return false; } - void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List exceptions) + bool SpecializeTrampoline (AutoIndentStringBuilder sb, ObjCMethod method, List exceptions) { var isGeneric = method.DeclaringType.IsGeneric; @@ -3309,21 +3343,21 @@ namespace Registrar { sb.WriteLine ("return xamarin_retain_trampoline (self, _cmd);"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.Release: sb.WriteLine ("-(void) release"); sb.WriteLine ("{"); sb.WriteLine ("xamarin_release_trampoline (self, _cmd);"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.GetGCHandle: sb.WriteLine ("-(GCHandle) xamarinGetGCHandle"); sb.WriteLine ("{"); sb.WriteLine ("return __monoObjectGCHandle.gc_handle;"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.SetGCHandle: sb.WriteLine ("-(bool) xamarinSetGCHandle: (GCHandle) gc_handle flags: (enum XamarinGCHandleFlags) flags"); sb.WriteLine ("{"); @@ -3337,21 +3371,21 @@ namespace Registrar { sb.WriteLine ("return true;"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.GetFlags: sb.WriteLine ("-(enum XamarinGCHandleFlags) xamarinGetFlags"); sb.WriteLine ("{"); sb.WriteLine ("return __monoObjectGCHandle.flags;"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.SetFlags: sb.WriteLine ("-(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags"); sb.WriteLine ("{"); sb.WriteLine ("__monoObjectGCHandle.flags = flags;"); sb.WriteLine ("}"); sb.WriteLine (); - return; + return true; case Trampoline.Constructor: if (isGeneric) { sb.WriteLine (GetObjCSignature (method, exceptions)); @@ -3359,7 +3393,7 @@ namespace Registrar { sb.WriteLine ("xamarin_throw_product_exception (4126, \"Cannot construct an instance of the type '{0}' from Objective-C because the type is generic.\");\n", method.DeclaringType.Type.FullName.Replace ("/", "+")); sb.WriteLine ("return self;"); sb.WriteLine ("}"); - return; + return true; } break; case Trampoline.CopyWithZone1: @@ -3380,13 +3414,13 @@ namespace Registrar { sb.AppendLine (); sb.AppendLine ("return rv;"); sb.AppendLine ("}"); - return; + return true; case Trampoline.CopyWithZone2: sb.AppendLine ("-(id) copyWithZone: (NSZone *) zone"); sb.AppendLine ("{"); sb.AppendLine ("return xamarin_copyWithZone_trampoline2 (self, _cmd, zone);"); sb.AppendLine ("}"); - return; + return true; } var customConformsToProtocol = method.Selector == "conformsToProtocol:" && method.Method.DeclaringType.Is ("Foundation", "NSObject") && method.Method.Name == "InvokeConformsToProtocol" && method.Parameters.Length == 1; @@ -3404,20 +3438,17 @@ namespace Registrar { sb.AppendLine ("xamarin_process_managed_exception_gchandle (exception_gchandle);"); sb.AppendLine ("return rv;"); sb.AppendLine ("}"); - return; + return true; } } - var rettype = string.Empty; - var returntype = method.ReturnType; - var isStatic = method.IsStatic; - var isInstanceCategory = method.IsCategoryInstance; - var isCtor = false; - var num_arg = method.Method.HasParameters ? method.Method.Parameters.Count : 0; - var descriptiveMethodName = method.DescriptiveMethodName; - var name = GetUniqueTrampolineName ("native_to_managed_trampoline_" + descriptiveMethodName); - var isVoid = returntype.FullName == "System.Void"; - var merge_bodies = true; + return false; + } + + bool TryGetReturnType (ObjCMethod method, string descriptiveMethodName, List exceptions, out string rettype, out bool isCtor) + { + rettype = string.Empty; + isCtor = false; switch (method.CurrentTrampoline) { case Trampoline.None: @@ -3436,145 +3467,32 @@ namespace Registrar { switch (method.NativeReturnType.FullName) { case "System.Int64": rettype = "long long"; - break; + return true; case "System.UInt64": rettype = "unsigned long long"; - break; + return true; case "System.Single": rettype = "float"; - break; + return true; case "System.Double": rettype = "double"; - break; + return true; default: rettype = ToSimpleObjCParameterType (method.NativeReturnType, descriptiveMethodName, exceptions, method.Method); - break; + return true; } - break; case Trampoline.Constructor: rettype = "id"; isCtor = true; - break; + return true; default: - return; + return false; } + } - comment.Clear (); - nslog_start.Clear (); - nslog_end.Clear (); - copyback.Clear (); - invoke.Clear (); - setup_call_stack.Clear (); - body.Clear (); - body_setup.Clear (); - setup_return.Clear (); - cleanup.Clear (); - - counter++; - - body.WriteLine ("{"); - - var indent = merge_bodies ? sb.Indentation : sb.Indentation + 1; - body.Indentation = indent; - body_setup.Indentation = indent; - copyback.Indentation = indent; - invoke.Indentation = indent; - setup_call_stack.Indentation = indent; - setup_return.Indentation = indent; - cleanup.Indentation = indent; - - if (!TryCreateTokenReference (method.Method, TokenType.Method, out var token_ref, exceptions)) - return; - - // A comment describing the managed signature - if (trace) { - nslog_start.Indentation = sb.Indentation; - comment.Indentation = sb.Indentation; - nslog_end.Indentation = sb.Indentation; - - comment.AppendFormat ("// {2} {0}.{1} (", method.Method.DeclaringType.FullName, method.Method.Name, method.Method.ReturnType.FullName); - for (int i = 0; i < num_arg; i++) { - var param = method.Method.Parameters [i]; - if (i > 0) - comment.Append (", "); - comment.AppendFormat ("{0} {1}", param.ParameterType.FullName, param.Name); - } - comment.AppendLine (")"); - comment.AppendLine ("// ArgumentSemantic: {0} IsStatic: {1} Selector: '{2}' Signature: '{3}'", method.ArgumentSemantic, method.IsStatic, method.Selector, method.Signature); - } - - // a couple of debug printfs - if (trace) { - StringBuilder args = new StringBuilder (); - nslog_start.AppendFormat ("NSLog (@\"{0} (this: %@, sel: %@", name); - for (int i = 0; i < num_arg; i++) { - var type = method.Method.Parameters [i].ParameterType; - bool isRef = type.IsByReference; - if (isRef) - type = type.GetElementType (); - var td = type.Resolve (); - - nslog_start.AppendFormat (", {0}: ", method.Method.Parameters [i].Name); - args.Append (", "); - switch (type.FullName) { - case "System.Drawing.RectangleF": - var rectFunc = App.Platform == ApplePlatform.MacOSX ? "NSStringFromRect" : "NSStringFromCGRect"; - if (isRef) { - nslog_start.Append ("%p : %@"); - args.AppendFormat ("p{0}, p{0} ? {1} (*p{0}) : @\"NULL\"", i, rectFunc); - } else { - nslog_start.Append ("%@"); - args.AppendFormat ("{1} (p{0})", i, rectFunc); - } - break; - case "System.Drawing.PointF": - var pointFunc = App.Platform == ApplePlatform.MacOSX ? "NSStringFromPoint" : "NSStringFromCGPoint"; - if (isRef) { - nslog_start.Append ("%p: %@"); - args.AppendFormat ("p{0}, p{0} ? {1} (*p{0}) : @\"NULL\"", i, pointFunc); - } else { - nslog_start.Append ("%@"); - args.AppendFormat ("{1} (p{0})", i, pointFunc); - } - break; - default: - bool unknown; - var spec = GetPrintfFormatSpecifier (td, out unknown); - if (unknown) { - nslog_start.AppendFormat ("%{0}", spec); - args.AppendFormat ("&p{0}", i); - } else if (isRef) { - nslog_start.AppendFormat ("%p *= %{0}", spec); - args.AppendFormat ("p{0}, *p{0}", i); - } else { - nslog_start.AppendFormat ("%{0}", spec); - args.AppendFormat ("p{0}", i); - } - break; - } - } - - string ret_arg = string.Empty; - nslog_end.Append (nslog_start.ToString ()); - if (!isVoid) { - bool unknown; - var spec = GetPrintfFormatSpecifier (method.Method.ReturnType.Resolve (), out unknown); - if (!unknown) { - nslog_end.Append (" ret: %"); - nslog_end.Append (spec); - ret_arg = ", res"; - } - } - nslog_end.Append (") END\", self, NSStringFromSelector (_cmd)"); - nslog_end.Append (args.ToString ()); - nslog_end.Append (ret_arg); - nslog_end.AppendLine (");"); - - nslog_start.Append (") START\", self, NSStringFromSelector (_cmd)"); - nslog_start.Append (args.ToString ()); - nslog_start.AppendLine (");"); - } + void SpecializePrepareParameters (AutoIndentStringBuilder sb, ObjCMethod method, int num_arg, string descriptiveMethodName, List exceptions) + { // prepare the parameters var baseMethod = GetBaseMethodInTypeHierarchy (method.Method); for (int i = 0; i < num_arg; i++) { @@ -3985,6 +3903,151 @@ namespace Registrar { break; } } + } + + void Specialize (AutoIndentStringBuilder sb, ObjCMethod method, List exceptions) + { + if (SpecializeTrampoline (sb, method, exceptions)) + return; + + var isGeneric = method.DeclaringType.IsGeneric; + var returntype = method.ReturnType; + var isStatic = method.IsStatic; + var isInstanceCategory = method.IsCategoryInstance; + var num_arg = method.Method.HasParameters ? method.Method.Parameters.Count : 0; + var descriptiveMethodName = method.DescriptiveMethodName; + var name = GetUniqueTrampolineName ("native_to_managed_trampoline_" + descriptiveMethodName); + var isVoid = returntype.FullName == "System.Void"; + var merge_bodies = true; + + if (!TryGetReturnType (method, descriptiveMethodName, exceptions, out var rettype, out var isCtor)) + return; + + comment.Clear (); + nslog_start.Clear (); + nslog_end.Clear (); + copyback.Clear (); + invoke.Clear (); + setup_call_stack.Clear (); + body.Clear (); + body_setup.Clear (); + setup_return.Clear (); + cleanup.Clear (); + + counter++; + + body.WriteLine ("{"); + + var indent = merge_bodies ? sb.Indentation : sb.Indentation + 1; + body.Indentation = indent; + body_setup.Indentation = indent; + copyback.Indentation = indent; + invoke.Indentation = indent; + setup_call_stack.Indentation = indent; + setup_return.Indentation = indent; + cleanup.Indentation = indent; + + // A comment describing the managed signature + if (trace) { + nslog_start.Indentation = sb.Indentation; + comment.Indentation = sb.Indentation; + nslog_end.Indentation = sb.Indentation; + + comment.AppendFormat ("// {2} {0}.{1} (", method.Method.DeclaringType.FullName, method.Method.Name, method.Method.ReturnType.FullName); + for (int i = 0; i < num_arg; i++) { + var param = method.Method.Parameters [i]; + if (i > 0) + comment.Append (", "); + comment.AppendFormat ("{0} {1}", param.ParameterType.FullName, param.Name); + } + comment.AppendLine (")"); + comment.AppendLine ("// ArgumentSemantic: {0} IsStatic: {1} Selector: '{2}' Signature: '{3}'", method.ArgumentSemantic, method.IsStatic, method.Selector, method.Signature); + } + + // a couple of debug printfs + if (trace) { + StringBuilder args = new StringBuilder (); + nslog_start.AppendFormat ("NSLog (@\"{0} (this: %@, sel: %@", name); + for (int i = 0; i < num_arg; i++) { + var type = method.Method.Parameters [i].ParameterType; + bool isRef = type.IsByReference; + if (isRef) + type = type.GetElementType (); + var td = type.Resolve (); + + nslog_start.AppendFormat (", {0}: ", method.Method.Parameters [i].Name); + args.Append (", "); + switch (type.FullName) { + case "System.Drawing.RectangleF": + var rectFunc = App.Platform == ApplePlatform.MacOSX ? "NSStringFromRect" : "NSStringFromCGRect"; + if (isRef) { + nslog_start.Append ("%p : %@"); + args.AppendFormat ("p{0}, p{0} ? {1} (*p{0}) : @\"NULL\"", i, rectFunc); + } else { + nslog_start.Append ("%@"); + args.AppendFormat ("{1} (p{0})", i, rectFunc); + } + break; + case "System.Drawing.PointF": + var pointFunc = App.Platform == ApplePlatform.MacOSX ? "NSStringFromPoint" : "NSStringFromCGPoint"; + if (isRef) { + nslog_start.Append ("%p: %@"); + args.AppendFormat ("p{0}, p{0} ? {1} (*p{0}) : @\"NULL\"", i, pointFunc); + } else { + nslog_start.Append ("%@"); + args.AppendFormat ("{1} (p{0})", i, pointFunc); + } + break; + default: + bool unknown; + var spec = GetPrintfFormatSpecifier (td, out unknown); + if (unknown) { + nslog_start.AppendFormat ("%{0}", spec); + args.AppendFormat ("&p{0}", i); + } else if (isRef) { + nslog_start.AppendFormat ("%p *= %{0}", spec); + args.AppendFormat ("p{0}, *p{0}", i); + } else { + nslog_start.AppendFormat ("%{0}", spec); + args.AppendFormat ("p{0}", i); + } + break; + } + } + + string ret_arg = string.Empty; + nslog_end.Append (nslog_start.ToString ()); + if (!isVoid) { + bool unknown; + var spec = GetPrintfFormatSpecifier (method.Method.ReturnType.Resolve (), out unknown); + if (!unknown) { + nslog_end.Append (" ret: %"); + nslog_end.Append (spec); + ret_arg = ", res"; + } + } + nslog_end.Append (") END\", self, NSStringFromSelector (_cmd)"); + nslog_end.Append (args.ToString ()); + nslog_end.Append (ret_arg); + nslog_end.AppendLine (");"); + + nslog_start.Append (") START\", self, NSStringFromSelector (_cmd)"); + nslog_start.Append (args.ToString ()); + nslog_start.AppendLine (");"); + } + +#if NET + // Generate the native trampoline to call the generated UnmanagedCallersOnly method if we're using the managed static registrar. + if (LinkContext.App.Registrar == RegistrarMode.ManagedStatic) { + GenerateCallToUnmanagedCallersOnlyMethod (sb, method, isCtor, isVoid, num_arg, descriptiveMethodName, exceptions); + return; + } +#endif + + if (!TryCreateTokenReference (method.Method, TokenType.Method, out var token_ref, exceptions)) + return; + + SpecializePrepareParameters (sb, method, num_arg, descriptiveMethodName, exceptions); // the actual invoke if (isCtor) { @@ -4021,125 +4084,8 @@ namespace Registrar { invoke.AppendLine (post_invoke_check); body_setup.AppendLine ("GCHandle exception_gchandle = INVALID_GCHANDLE;"); - // prepare the return value - if (!isVoid) { - switch (rettype) { - case "CGRect": - body_setup.AppendLine ("{0} res = {{{{0}}}};", rettype); - break; - default: - body_setup.AppendLine ("{0} res = {{0}};", rettype); - break; - } - var isArray = returntype is ArrayType; - var type = returntype.Resolve () ?? returntype; - var retain = method.RetainReturnValue; - if (returntype != method.NativeReturnType) { - body_setup.AppendLine ("MonoClass *retparamclass = NULL;"); - cleanup.AppendLine ("xamarin_mono_object_release (&retparamclass);"); - body_setup.AppendLine ("MonoType *retparamtype = NULL;"); - cleanup.AppendLine ("xamarin_mono_object_release (&retparamtype);"); - setup_call_stack.AppendLine ("retparamtype = xamarin_get_parameter_type (managed_method, -1);"); - setup_call_stack.AppendLine ("retparamclass = mono_class_from_mono_type (retparamtype);"); - GenerateConversionToNative (returntype, method.NativeReturnType, setup_return, descriptiveMethodName, ref exceptions, method, "retval", "res", "retparamclass"); - } else if (returntype.IsValueType) { - setup_return.AppendLine ("res = *({0} *) mono_object_unbox ((MonoObject *) retval);", rettype); - } else if (isArray) { - var elementType = ((ArrayType) returntype).ElementType; - var conversion_func = string.Empty; - if (elementType.FullName == "System.String") { - conversion_func = "xamarin_managed_string_array_to_nsarray"; - } else if (IsNSObject (elementType)) { - conversion_func = "xamarin_managed_nsobject_array_to_nsarray"; - } else if (IsINativeObject (elementType)) { - conversion_func = "xamarin_managed_inativeobject_array_to_nsarray"; - } else { - throw ErrorHelper.CreateError (App, 4111, method.Method, Errors.MT4111, method.NativeReturnType.FullName, descriptiveMethodName); - } - setup_return.AppendLine ("res = {0} ((MonoArray *) retval, &exception_gchandle);", conversion_func); - if (retain) - setup_return.AppendLine ("[res retain];"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); - setup_return.AppendLine ("mt_dummy_use (retval);"); - } else { - setup_return.AppendLine ("if (!retval) {"); - setup_return.AppendLine ("res = NULL;"); - setup_return.AppendLine ("} else {"); - - if (IsNSObject (type)) { - setup_return.AppendLine ("id retobj;"); - setup_return.AppendLine ("retobj = xamarin_get_nsobject_handle (retval);"); - setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); - setup_return.AppendLine ("[retobj retain];"); - if (!retain) - setup_return.AppendLine ("[retobj autorelease];"); - setup_return.AppendLine ("mt_dummy_use (retval);"); - setup_return.AppendLine ("res = retobj;"); - } else if (IsPlatformType (type, "ObjCRuntime", "Selector")) { - setup_return.AppendLine ("res = (SEL) xamarin_get_handle_for_inativeobject (retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - } else if (IsPlatformType (type, "ObjCRuntime", "Class")) { - setup_return.AppendLine ("res = (Class) xamarin_get_handle_for_inativeobject (retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - } else if (IsNativeObject (type)) { - setup_return.AppendLine ("{0} retobj;", rettype); - setup_return.AppendLine ("retobj = xamarin_get_handle_for_inativeobject ((MonoObject *) retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); - setup_return.AppendLine ("if (retobj != NULL) {"); - if (retain) { - setup_return.AppendLine ("xamarin_retain_nativeobject (retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - } else { - // If xamarin_attempt_retain_nsobject returns true, the input is an NSObject, so it's safe to call the 'autorelease' selector on it. - // We don't retain retval if it's not an NSObject, because we'd have to immediately release it, - // and that serves no purpose. - setup_return.AppendLine ("bool retained = xamarin_attempt_retain_nsobject (retval, &exception_gchandle);"); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - setup_return.AppendLine ("if (retained) {"); - setup_return.AppendLine ("[retobj autorelease];"); - setup_return.AppendLine ("}"); - } - setup_return.AppendLine ("mt_dummy_use (retval);"); - setup_return.AppendLine ("res = retobj;"); - setup_return.AppendLine ("} else {"); - setup_return.AppendLine ("res = NULL;"); - setup_return.AppendLine ("}"); - } else if (type.FullName == "System.String") { - // This should always be an NSString and never char* - setup_return.AppendLine ("res = xamarin_string_to_nsstring ((MonoString *) retval, {0});", retain ? "true" : "false"); - } else if (IsDelegate (type.Resolve ())) { - var signature = "NULL"; - var token = "INVALID_TOKEN_REF"; - if (App.Optimizations.OptimizeBlockLiteralSetupBlock == true) { - if (type.Is ("System", "Delegate") || type.Is ("System", "MulticastDelegate")) { - ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173, type.FullName, descriptiveMethodName)); - } else { - var delegateMethod = type.Resolve ().GetMethods ().FirstOrDefault ((v) => v.Name == "Invoke"); - if (delegateMethod is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173_A, type.FullName, descriptiveMethodName)); - } else { - signature = "\"" + ComputeSignature (method.DeclaringType.Type, null, method, isBlockSignature: true) + "\""; - } - } - var delegateProxyType = GetDelegateProxyType (method); - if (delegateProxyType is null) { - exceptions.Add (ErrorHelper.CreateWarning (App, 4176, method.Method, "Unable to locate the delegate to block conversion type for the return value of the method {0}.", method.DescriptiveMethodName)); - } else if (TryCreateTokenReference (delegateProxyType, TokenType.TypeDef, out var delegate_proxy_type_token_ref, out _)) { - token = $"0x{delegate_proxy_type_token_ref:X} /* {delegateProxyType.FullName} */ "; - } - } - setup_return.AppendLine ("res = xamarin_get_block_for_delegate (managed_method, retval, {0}, {1}, &exception_gchandle);", signature, token); - setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); - } else { - throw ErrorHelper.CreateError (4104, Errors.MT4104, returntype.FullName, descriptiveMethodName); - } - - setup_return.AppendLine ("}"); - } - } + SpecializePrepareReturnValue (sb, method, descriptiveMethodName, rettype, exceptions); if (App.Embeddinator) body.WriteLine ("xamarin_embeddinator_initialize ();"); @@ -4319,27 +4265,7 @@ namespace Registrar { sb.Write (", 0x{0:X}", token_ref); sb.WriteLine (");"); if (isCtor) { - sb.WriteLine ("if (call_super && rv) {"); - sb.Write ("struct objc_super super = { rv, [").Write (method.DeclaringType.SuperType.ExportedName).WriteLine (" class] };"); - sb.Write ("rv = ((id (*)(objc_super*, SEL"); - - if (method.Parameters is not null) { - for (int i = 0; i < method.Parameters.Length; i++) - sb.Append (", ").Append (ToObjCParameterType (method.Parameters [i], method.DescriptiveMethodName, exceptions, method.Method)); - } - if (method.IsVariadic) - sb.Append (", ..."); - - sb.Write (")) objc_msgSendSuper) (&super, @selector ("); - sb.Write (method.Selector); - sb.Write (")"); - var split = method.Selector.Split (':'); - for (int i = 0; i < split.Length - 1; i++) { - sb.Append (", "); - sb.AppendFormat ("p{0}", i); - } - sb.WriteLine (");"); - sb.WriteLine ("}"); + GenerateCallToSuperForConstructor (sb, method, exceptions); sb.WriteLine ("return rv;"); } sb.WriteLine ("}"); @@ -4348,6 +4274,256 @@ namespace Registrar { } } +#if NET + void GenerateCallToUnmanagedCallersOnlyMethod (AutoIndentStringBuilder sb, ObjCMethod method, bool isCtor, bool isVoid, int num_arg, string descriptiveMethodName, List exceptions) + { + // Generate the native trampoline to call the generated UnmanagedCallersOnly method. + // We try to do as little as possible in here, and instead do the work in managed code. + + // If we're AOT-compiled, we don't need to look for the UnmanagedCallersOnly method, + // we can just call the corresponding entry point directly. Otherwise we'll have to + // call into managed code to find the function pointer for the UnmanagedCallersOnly + // method (we store the result in a static variable, so that we only do this once + // per method, the first time it's called). + var staticCall = App.IsAOTCompiled (method.DeclaringType.Type.Module.Assembly.Name.Name); + if (!App.Configuration.AssemblyTrampolineInfos.TryFindInfo (method.Method, out var pinvokeMethodInfo)) { + exceptions.Add (ErrorHelper.CreateError (99, "Could not find the managed callback for {0}", descriptiveMethodName)); + return; + } + var ucoEntryPoint = pinvokeMethodInfo.UnmanagedCallersOnlyEntryPoint; + sb.AppendLine (); + if (!staticCall) + sb.Append ("typedef "); + + var callbackReturnType = string.Empty; + var hasReturnType = true; + if (isCtor) { + callbackReturnType = "id"; + } else if (isVoid) { + callbackReturnType = "void"; + hasReturnType = false; + } else { + callbackReturnType = ToObjCParameterType (method.NativeReturnType, descriptiveMethodName, exceptions, method.Method); + } + + sb.Append (callbackReturnType); + + sb.Append (" "); + if (staticCall) { + sb.Append (ucoEntryPoint); + } else { + sb.Append ("(*"); + sb.Append (ucoEntryPoint); + sb.Append ("_function)"); + } + sb.Append (" (id self, SEL sel"); + var indexOffset = method.IsCategoryInstance ? 1 : 0; + for (var i = indexOffset; i < num_arg; i++) { + sb.Append (", "); + var parameterType = ToObjCParameterType (method.NativeParameters [i], method.DescriptiveMethodName, exceptions, method.Method, delegateToBlockType: true, cSyntaxForBlocks: true); + var containsBlock = parameterType.Contains ("%PARAMETERNAME%"); + parameterType = parameterType.Replace ("%PARAMETERNAME%", $"p{i - indexOffset}"); + sb.Append (parameterType); + if (!containsBlock) + sb.AppendFormat (" p{0}", i - indexOffset); + } + if (isCtor) + sb.Append (", bool* call_super"); + sb.Append (", GCHandle* exception_gchandle"); + + if (method.IsVariadic) + sb.Append (", ..."); + sb.Append (");"); + + sb.WriteLine (); + sb.WriteLine (GetObjCSignature (method, exceptions)); + sb.WriteLine ("{"); + sb.WriteLine ("GCHandle exception_gchandle = INVALID_GCHANDLE;"); + if (isCtor) + sb.WriteLine ($"bool call_super = false;"); + if (hasReturnType) + sb.WriteLine ($"{callbackReturnType} rv = {{ 0 }};"); + + if (!staticCall) { + sb.WriteLine ($"static {ucoEntryPoint}_function {ucoEntryPoint};"); + sb.WriteLine ($"xamarin_registrar_dlsym ((void **) &{ucoEntryPoint}, \"{method.Method.Module.Assembly.Name.Name}\", \"{ucoEntryPoint}\", {pinvokeMethodInfo.Id});"); + } + if (hasReturnType) + sb.Write ("rv = "); + sb.Write (ucoEntryPoint); + sb.Write (" (self, _cmd"); + for (var i = indexOffset; i < num_arg; i++) { + sb.AppendFormat (", p{0}", i - indexOffset); + } + if (isCtor) + sb.Write (", &call_super"); + sb.Write (", &exception_gchandle"); + sb.WriteLine (");"); + + sb.WriteLine ("xamarin_process_managed_exception_gchandle (exception_gchandle);"); + + if (isCtor) { + GenerateCallToSuperForConstructor (sb, method, exceptions); + } + + if (hasReturnType) + sb.WriteLine ("return rv;"); + + sb.WriteLine ("}"); + } +#endif + + void SpecializePrepareReturnValue (AutoIndentStringBuilder sb, ObjCMethod method, string descriptiveMethodName, string rettype, List exceptions) + { + var returntype = method.ReturnType; + var isVoid = returntype.FullName == "System.Void"; + + if (isVoid) + return; + + switch (rettype) { + case "CGRect": + body_setup.AppendLine ("{0} res = {{{{0}}}};", rettype); + break; + default: + body_setup.AppendLine ("{0} res = {{0}};", rettype); + break; + } + var isArray = returntype is ArrayType; + var type = returntype.Resolve () ?? returntype; + var retain = method.RetainReturnValue; + + if (returntype != method.NativeReturnType) { + body_setup.AppendLine ("MonoClass *retparamclass = NULL;"); + cleanup.AppendLine ("xamarin_mono_object_release (&retparamclass);"); + body_setup.AppendLine ("MonoType *retparamtype = NULL;"); + cleanup.AppendLine ("xamarin_mono_object_release (&retparamtype);"); + setup_call_stack.AppendLine ("retparamtype = xamarin_get_parameter_type (managed_method, -1);"); + setup_call_stack.AppendLine ("retparamclass = mono_class_from_mono_type (retparamtype);"); + GenerateConversionToNative (returntype, method.NativeReturnType, setup_return, descriptiveMethodName, ref exceptions, method, "retval", "res", "retparamclass"); + } else if (returntype.IsValueType) { + setup_return.AppendLine ("res = *({0} *) mono_object_unbox ((MonoObject *) retval);", rettype); + } else if (isArray) { + var elementType = ((ArrayType) returntype).ElementType; + var conversion_func = string.Empty; + if (elementType.FullName == "System.String") { + conversion_func = "xamarin_managed_string_array_to_nsarray"; + } else if (IsNSObject (elementType)) { + conversion_func = "xamarin_managed_nsobject_array_to_nsarray"; + } else if (IsINativeObject (elementType)) { + conversion_func = "xamarin_managed_inativeobject_array_to_nsarray"; + } else { + throw ErrorHelper.CreateError (App, 4111, method.Method, Errors.MT4111, method.NativeReturnType.FullName, descriptiveMethodName); + } + setup_return.AppendLine ("res = {0} ((MonoArray *) retval, &exception_gchandle);", conversion_func); + if (retain) + setup_return.AppendLine ("[res retain];"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); + setup_return.AppendLine ("mt_dummy_use (retval);"); + } else { + setup_return.AppendLine ("if (!retval) {"); + setup_return.AppendLine ("res = NULL;"); + setup_return.AppendLine ("} else {"); + + if (IsNSObject (type)) { + setup_return.AppendLine ("id retobj;"); + setup_return.AppendLine ("retobj = xamarin_get_nsobject_handle (retval);"); + setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); + setup_return.AppendLine ("[retobj retain];"); + if (!retain) + setup_return.AppendLine ("[retobj autorelease];"); + setup_return.AppendLine ("mt_dummy_use (retval);"); + setup_return.AppendLine ("res = retobj;"); + } else if (IsPlatformType (type, "ObjCRuntime", "Selector")) { + setup_return.AppendLine ("res = (SEL) xamarin_get_handle_for_inativeobject (retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + } else if (IsPlatformType (type, "ObjCRuntime", "Class")) { + setup_return.AppendLine ("res = (Class) xamarin_get_handle_for_inativeobject (retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + } else if (IsNativeObject (type)) { + setup_return.AppendLine ("{0} retobj;", rettype); + setup_return.AppendLine ("retobj = xamarin_get_handle_for_inativeobject ((MonoObject *) retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + setup_return.AppendLine ("xamarin_framework_peer_waypoint ();"); + setup_return.AppendLine ("if (retobj != NULL) {"); + if (retain) { + setup_return.AppendLine ("xamarin_retain_nativeobject (retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + } else { + // If xamarin_attempt_retain_nsobject returns true, the input is an NSObject, so it's safe to call the 'autorelease' selector on it. + // We don't retain retval if it's not an NSObject, because we'd have to immediately release it, + // and that serves no purpose. + setup_return.AppendLine ("bool retained = xamarin_attempt_retain_nsobject (retval, &exception_gchandle);"); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + setup_return.AppendLine ("if (retained) {"); + setup_return.AppendLine ("[retobj autorelease];"); + setup_return.AppendLine ("}"); + } + setup_return.AppendLine ("mt_dummy_use (retval);"); + setup_return.AppendLine ("res = retobj;"); + setup_return.AppendLine ("} else {"); + setup_return.AppendLine ("res = NULL;"); + setup_return.AppendLine ("}"); + } else if (type.FullName == "System.String") { + // This should always be an NSString and never char* + setup_return.AppendLine ("res = xamarin_string_to_nsstring ((MonoString *) retval, {0});", retain ? "true" : "false"); + } else if (IsDelegate (type.Resolve ())) { + var signature = "NULL"; + var token = "INVALID_TOKEN_REF"; + if (App.Optimizations.OptimizeBlockLiteralSetupBlock == true) { + if (type.Is ("System", "Delegate") || type.Is ("System", "MulticastDelegate")) { + ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173, type.FullName, descriptiveMethodName)); + } else { + var delegateMethod = type.Resolve ().GetMethods ().FirstOrDefault ((v) => v.Name == "Invoke"); + if (delegateMethod is null) { + ErrorHelper.Show (ErrorHelper.CreateWarning (App, 4173, method.Method, Errors.MT4173_A, type.FullName, descriptiveMethodName)); + } else { + signature = "\"" + ComputeSignature (method.DeclaringType.Type, null, method, isBlockSignature: true) + "\""; + } + } + var delegateProxyType = GetDelegateProxyType (method); + if (delegateProxyType is null) { + exceptions.Add (ErrorHelper.CreateWarning (App, 4176, method.Method, "Unable to locate the delegate to block conversion type for the return value of the method {0}.", method.DescriptiveMethodName)); + } else if (TryCreateTokenReference (delegateProxyType, TokenType.TypeDef, out var delegate_proxy_type_token_ref, out _)) { + token = $"0x{delegate_proxy_type_token_ref:X} /* {delegateProxyType.FullName} */ "; + } + } + setup_return.AppendLine ("res = xamarin_get_block_for_delegate (managed_method, retval, {0}, {1}, &exception_gchandle);", signature, token); + setup_return.AppendLine ("if (exception_gchandle != INVALID_GCHANDLE) goto exception_handling;"); + } else { + throw ErrorHelper.CreateError (4104, Errors.MT4104, returntype.FullName, descriptiveMethodName); + } + + setup_return.AppendLine ("}"); + } + } + + void GenerateCallToSuperForConstructor (AutoIndentStringBuilder sb, ObjCMethod method, List exceptions) + { + sb.WriteLine ("if (call_super && rv) {"); + sb.Write ("struct objc_super super = { rv, [").Write (method.DeclaringType.SuperType.ExportedName).WriteLine (" class] };"); + sb.Write ("rv = ((id (*)(objc_super*, SEL"); + + if (method.Parameters is not null) { + for (int i = 0; i < method.Parameters.Length; i++) + sb.Append (", ").Append (ToObjCParameterType (method.Parameters [i], method.DescriptiveMethodName, exceptions, method.Method)); + } + if (method.IsVariadic) + sb.Append (", ..."); + + sb.Write (")) objc_msgSendSuper) (&super, @selector ("); + sb.Write (method.Selector); + sb.Write (")"); + var split = method.Selector.Split (':'); + for (int i = 0; i < split.Length - 1; i++) { + sb.Append (", "); + sb.AppendFormat ("p{0}", i); + } + sb.WriteLine (");"); + sb.WriteLine ("}"); + } + public TypeDefinition GetInstantiableType (TypeDefinition td, List exceptions, string descriptiveMethodName) { TypeDefinition nativeObjType = td; @@ -4367,7 +4543,31 @@ namespace Registrar { return nativeObjType; } - TypeDefinition GetDelegateProxyType (ObjCMethod obj_method) + // This method finds the CreateBlock method generated by the generator. + public MethodDefinition GetCreateBlockMethod (TypeDefinition delegateProxyType) + { + if (!delegateProxyType.HasMethods) + return null; + + foreach (var method in delegateProxyType.Methods) { + if (method.Name != "CreateBlock") + continue; + if (!method.ReturnType.Is ("ObjCRuntime", "BlockLiteral")) + continue; + if (!method.HasParameters) + continue; + if (method.Parameters.Count != 1) + continue; + if (!IsDelegate (method.Parameters [0].ParameterType)) + continue; + + return method; + } + + return null; + } + + public TypeDefinition GetDelegateProxyType (ObjCMethod obj_method) { // A mirror of this method is also implemented in BlockLiteral:GetDelegateProxyType // If this method is changed, that method will probably have to be updated too (tests!!!) @@ -4414,7 +4614,12 @@ namespace Registrar { return null; } - MethodDefinition GetBlockWrapperCreator (ObjCMethod obj_method, int parameter) + // + // Returns a MethodInfo that represents the method that can be used to turn + // a the block in the given method at the given parameter into a strongly typed + // delegate + // + public MethodDefinition GetBlockWrapperCreator (ObjCMethod obj_method, int parameter) { // A mirror of this method is also implemented in Runtime:GetBlockWrapperCreator // If this method is changed, that method will probably have to be updated too (tests!!!) @@ -4488,6 +4693,27 @@ namespace Registrar { return null; } + public bool TryFindType (TypeDefinition type, [NotNullWhen (true)] out ObjCType? objcType) + { + return Types.TryGetValue (type, out objcType); + } + + public bool TryFindMethod (MethodDefinition method, [NotNullWhen (true)] out ObjCMethod? objcMethod) + { + if (TryFindType (method.DeclaringType, out var type)) { + if (type.Methods is not null) { + foreach (var m in type.Methods) { + if ((object) m.Method == (object) method) { + objcMethod = m; + return true; + } + } + } + } + objcMethod = null; + return false; + } + MethodDefinition GetBlockProxyAttributeMethod (MethodDefinition method, int parameter) { var param = method.Parameters [parameter]; @@ -4595,7 +4821,7 @@ namespace Registrar { return false; } - string GetManagedToNSNumberFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName) + public string GetManagedToNSNumberFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName) { var typeName = managedType.FullName; switch (typeName) { @@ -4623,7 +4849,7 @@ namespace Registrar { } } - string GetNSNumberToManagedFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out string nativeType) + public string GetNSNumberToManagedFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out string nativeType) { var typeName = managedType.FullName; switch (typeName) { @@ -4653,7 +4879,7 @@ namespace Registrar { } } - string GetNSValueToManagedFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out string nativeType) + public string GetNSValueToManagedFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out string nativeType) { var underlyingTypeName = managedType.FullName; @@ -4682,7 +4908,7 @@ namespace Registrar { } } - string GetManagedToNSValueFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName) + public string GetManagedToNSValueFunc (TypeReference managedType, TypeReference inputType, TypeReference outputType, string descriptiveMethodName) { var underlyingTypeName = managedType.FullName; @@ -4725,6 +4951,7 @@ namespace Registrar { void GenerateConversionToManaged (TypeReference inputType, TypeReference outputType, AutoIndentStringBuilder sb, string descriptiveMethodName, ref List exceptions, ObjCMethod method, string inputName, string outputName, string managedClassExpression, int parameter) { // This is a mirror of the native method xamarin_generate_conversion_to_managed (for the dynamic registrar). + // It's also a mirror of the method ManagedRegistrarStep.GenerateConversionToManaged. // These methods must be kept in sync. var managedType = outputType; var nativeType = inputType; @@ -4820,6 +5047,7 @@ namespace Registrar { void GenerateConversionToNative (TypeReference inputType, TypeReference outputType, AutoIndentStringBuilder sb, string descriptiveMethodName, ref List exceptions, ObjCMethod method, string inputName, string outputName, string managedClassExpression) { // This is a mirror of the native method xamarin_generate_conversion_to_native (for the dynamic registrar). + // It's also a mirror of the method ManagedRegistrarStep.GenerateConversionToNative. // These methods must be kept in sync. var managedType = inputType; var nativeType = outputType; @@ -4914,25 +5142,31 @@ namespace Registrar { bool TryCreateFullTokenReference (MemberReference member, out uint token_ref, out Exception exception) { - token_ref = (full_token_reference_count++ << 1) + 1; switch (member.MetadataToken.TokenType) { case TokenType.TypeDef: case TokenType.Method: break; // OK default: exception = ErrorHelper.CreateError (99, Errors.MX0099, $"unsupported tokentype ({member.MetadataToken.TokenType}) for {member.FullName}"); + token_ref = INVALID_TOKEN_REF; return false; } - var assemblyIndex = registered_assemblies.FindIndex (v => v.Assembly == member.Module.Assembly); - if (assemblyIndex == -1) { - exception = ErrorHelper.CreateError (99, Errors.MX0099, $"Could not find {member.Module.Assembly.Name.Name} in the list of registered assemblies when processing {member.FullName}:\n\t{string.Join ("\n\t", registered_assemblies.Select (v => v.Assembly.Name.Name))}"); - return false; - } - var assemblyName = registered_assemblies [assemblyIndex].Name; var moduleToken = member.Module.MetadataToken.ToUInt32 (); var moduleName = member.Module.Name; var memberToken = member.MetadataToken.ToUInt32 (); var memberName = member.FullName; + return WriteFullTokenReference (member.Module.Assembly, moduleToken, moduleName, memberToken, memberName, out token_ref, out exception); + } + + bool WriteFullTokenReference (AssemblyDefinition assembly, uint moduleToken, string moduleName, uint memberToken, string memberName, out uint token_ref, out Exception exception) + { + token_ref = (full_token_reference_count++ << 1) + 1; + var assemblyIndex = registered_assemblies.FindIndex (v => v.Assembly == assembly); + if (assemblyIndex == -1) { + exception = ErrorHelper.CreateError (99, Errors.MX0099, $"Could not find {assembly.Name.Name} in the list of registered assemblies when processing {memberName}:\n\t{string.Join ("\n\t", registered_assemblies.Select (v => v.Assembly.Name.Name))}"); + return false; + } + var assemblyName = registered_assemblies [assemblyIndex].Name; exception = null; full_token_references.Append ($"\t\t{{ /* #{full_token_reference_count} = 0x{token_ref:X} */ {assemblyIndex} /* {assemblyName} */, 0x{moduleToken:X} /* {moduleName} */, 0x{memberToken:X} /* {memberName} */ }},\n"); return true; @@ -4963,6 +5197,22 @@ namespace Registrar { { var token = member.MetadataToken; +#if NET + if (App.Registrar == RegistrarMode.ManagedStatic) { + if (implied_type == TokenType.TypeDef && member is TypeDefinition td) { + if (App.Configuration.AssemblyTrampolineInfos.TryGetValue (td.Module.Assembly, out var infos) && infos.TryGetRegisteredTypeIndex (td, out var id)) { + id = id | (uint) TokenType.TypeDef; + return WriteFullTokenReference (member.Module.Assembly, INVALID_TOKEN_REF, member.Module.Name, id, member.FullName, out token_ref, out exception); + } + throw ErrorHelper.CreateError (99, $"Can't create a token reference to an unregistered type when using the managed static registrar: {member.FullName}"); + } + if (implied_type == TokenType.Method) { + throw ErrorHelper.CreateError (99, $"Can't create a token reference to a method when using the managed static registrar: {member.FullName}"); + } + throw ErrorHelper.CreateError (99, "Can't create a token reference to a token type {0} when using the managed static registrar.", implied_type.ToString ()); + } +#endif + /* We can't create small token references if we're in partial mode, because we may have multiple arrays of registered assemblies, and no way of saying which one we refer to with the assembly index */ if (IsSingleAssembly) return TryCreateFullTokenReference (member, out token_ref, out exception); @@ -5145,6 +5395,115 @@ namespace Registrar { pinfo.EntryPoint = wrapperName; } + public void Register (IEnumerable assemblies) + { + Register (null, assemblies); + } + + public void Register (PlatformResolver resolver, IEnumerable assemblies) + { + this.resolver = resolver; + + if (Target?.CachedLink == true) + throw ErrorHelper.CreateError (99, Errors.MX0099, "the static registrar should not execute unless the linker also executed (or was disabled). A potential workaround is to pass '-f' as an additional " + Driver.NAME + " argument to force a full build"); + + this.input_assemblies = assemblies; + + foreach (var assembly in assemblies) { + Driver.Log (3, "Generating static registrar for {0}", assembly.Name); + RegisterAssembly (assembly); + } + } + + static bool IsPropertyTrimmed (PropertyDefinition pd, AnnotationStore annotations) + { + if (pd is null) + return false; + + if (!IsTrimmed (pd, annotations)) + return false; + if (pd.GetMethod is not null && !IsTrimmed (pd.GetMethod, annotations)) + return false; + if (pd.SetMethod is not null && !IsTrimmed (pd.SetMethod, annotations)) + return false; + return true; + } + + public static bool IsTrimmed (MemberReference tr, AnnotationStore annotations) + { + if (tr is null) + return false; + + var assembly = tr.Module?.Assembly; + if (assembly is null) { + // Trimmed away + return true; + } + + var action = annotations.GetAction (assembly); + switch (action) { + case AssemblyAction.Skip: + case AssemblyAction.Copy: + case AssemblyAction.CopyUsed: + case AssemblyAction.Save: + return false; + case AssemblyAction.Link: + break; + case AssemblyAction.Delete: + return true; + case AssemblyAction.AddBypassNGen: + case AssemblyAction.AddBypassNGenUsed: + default: + throw ErrorHelper.CreateError (99, $"Unknown linker action: {action}"); + } + + if (annotations.IsMarked (tr)) + return false; + + if (annotations.IsMarked (tr.Resolve ())) + return false; + + return true; + } + + public void FilterTrimmedApi (AnnotationStore annotations) + { + var trimmedAway = Types.Where (kvp => IsTrimmed (kvp.Value.Type, annotations)).ToArray (); + foreach (var trimmed in trimmedAway) + Types.Remove (trimmed.Key); + + var skippedTrimmedAway = skipped_types.Where (v => IsTrimmed (v.Skipped, annotations)).ToArray (); + foreach (var trimmed in skippedTrimmedAway) + skipped_types.Remove (trimmed); + + foreach (var kvp in Types) { + var methods = kvp.Value.Methods; + if (methods is not null) { + for (var i = methods.Count - 1; i >= 0; i--) { + var method = methods [i].Method; + if (IsTrimmed (method, annotations)) + methods.RemoveAt (i); + } + } + var properties = kvp.Value.Properties; + if (properties is not null) { + for (var i = properties.Count - 1; i >= 0; i--) { + var property = properties [i].Property; + if (IsPropertyTrimmed (property, annotations)) + properties.RemoveAt (i); + } + } + var fields = kvp.Value.Fields; + if (fields is not null) { + foreach (var fieldName in fields.Keys.ToArray ()) { + var property = fields [fieldName].Property; + if (IsPropertyTrimmed (property, annotations)) + fields.Remove (fieldName); + } + } + } + } + public void GenerateSingleAssembly (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, string assembly, out string initialization_method, string type_map_path) { single_assembly = assembly; @@ -5158,22 +5517,11 @@ namespace Registrar { public void Generate (PlatformResolver resolver, IEnumerable assemblies, string header_path, string source_path, out string initialization_method, string type_map_path) { - this.resolver = resolver; - - if (Target?.CachedLink == true) - throw ErrorHelper.CreateError (99, Errors.MX0099, "the static registrar should not execute unless the linker also executed (or was disabled). A potential workaround is to pass '-f' as an additional " + Driver.NAME + " argument to force a full build"); - - this.input_assemblies = assemblies; - - foreach (var assembly in assemblies) { - Driver.Log (3, "Generating static registrar for {0}", assembly.Name); - RegisterAssembly (assembly); - } - + Register (resolver, assemblies); Generate (header_path, source_path, out initialization_method, type_map_path); } - void Generate (string header_path, string source_path, out string initialization_method, string type_map_path) + public void Generate (string header_path, string source_path, out string initialization_method, string type_map_path) { var sb = new AutoIndentStringBuilder (); header = new AutoIndentStringBuilder (); @@ -5251,6 +5599,86 @@ namespace Registrar { return base.SkipRegisterAssembly (assembly); } + + // Find the value of the [UserDelegateType] attribute on the specified delegate + TypeReference GetUserDelegateType (TypeReference delegateType) + { + var delegateTypeDefinition = delegateType.Resolve (); + foreach (var attrib in delegateTypeDefinition.CustomAttributes) { + var attribType = attrib.AttributeType; + if (!attribType.Is (Namespaces.ObjCRuntime, "UserDelegateTypeAttribute")) + continue; + return attrib.ConstructorArguments [0].Value as TypeReference; + } + return null; + } + + MethodDefinition GetDelegateInvoke (TypeReference delegateType) + { + var td = delegateType.Resolve (); + foreach (var method in td.Methods) { + if (method.Name == "Invoke") + return method; + } + return null; + } + + MethodReference InflateMethod (TypeReference inflatedDeclaringType, MethodDefinition openMethod) + { + if (inflatedDeclaringType is not GenericInstanceType git) + return openMethod; + + var inflatedReturnType = TypeReferenceExtensions.InflateGenericType (git, openMethod.ReturnType); + var mr = new MethodReference (openMethod.Name, inflatedReturnType, git); + if (openMethod.HasParameters) { + for (int i = 0; i < openMethod.Parameters.Count; i++) { + var inflatedParameterType = TypeReferenceExtensions.InflateGenericType (git, openMethod.Parameters [i].ParameterType); + var p = new ParameterDefinition (openMethod.Parameters [i].Name, openMethod.Parameters [i].Attributes, inflatedParameterType); + mr.Parameters.Add (p); + } + } + return mr; + } + + public bool TryComputeBlockSignature (ICustomAttributeProvider codeLocation, TypeReference trampolineDelegateType, out Exception exception, out string signature) + { + signature = null; + exception = null; + try { + // Calculate the block signature. + var blockSignature = false; + MethodReference userMethod = null; + + // First look for any [UserDelegateType] attributes on the trampoline delegate type. + var userDelegateType = GetUserDelegateType (trampolineDelegateType); + if (userDelegateType is not null) { + var userMethodDefinition = GetDelegateInvoke (userDelegateType); + userMethod = InflateMethod (userDelegateType, userMethodDefinition); + blockSignature = true; + } else { + // Couldn't find a [UserDelegateType] attribute, use the type of the actual trampoline instead. + var userMethodDefinition = GetDelegateInvoke (trampolineDelegateType); + userMethod = InflateMethod (trampolineDelegateType, userMethodDefinition); + blockSignature = false; + } + + // No luck finding the signature, so give up. + if (userMethod is null) { + exception = ErrorHelper.CreateError (App, 4187 /* Could not find a [UserDelegateType] attribute on the type '{0}'. */, codeLocation, Errors.MX4187, trampolineDelegateType.FullName); + return false; + } + + var parameters = new TypeReference [userMethod.Parameters.Count]; + for (int p = 0; p < parameters.Length; p++) + parameters [p] = userMethod.Parameters [p].ParameterType; + signature = LinkContext.Target.StaticRegistrar.ComputeSignature (userMethod.DeclaringType, false, userMethod.ReturnType, parameters, userMethod.Resolve (), isBlockSignature: blockSignature); + return true; + } catch (Exception e) { + exception = ErrorHelper.CreateError (App, 4188 /* Unable to compute the block signature for the type '{0}': {1} */, e, codeLocation, Errors.MX4188, trampolineDelegateType.FullName, e.Message); + return false; + } + } + } // Replicate a few attribute types here, with TypeDefinition instead of Type diff --git a/tools/common/Target.cs b/tools/common/Target.cs index 89085f4752..c952d93fed 100644 --- a/tools/common/Target.cs +++ b/tools/common/Target.cs @@ -856,6 +856,8 @@ namespace Xamarin.Bundler { #if NET sw.WriteLine ("\txamarin_runtime_configuration_name = {0};", string.IsNullOrEmpty (app.RuntimeConfigurationFile) ? "NULL" : $"\"{app.RuntimeConfigurationFile}\""); #endif + if (app.Registrar == RegistrarMode.ManagedStatic) + sw.WriteLine ("\txamarin_set_is_managed_static_registrar (true);"); sw.WriteLine ("}"); sw.WriteLine (); sw.Write ("int "); diff --git a/tools/create-dotnet-linker-launch-json/Program.cs b/tools/create-dotnet-linker-launch-json/Program.cs new file mode 100644 index 0000000000..9a27932c4b --- /dev/null +++ b/tools/create-dotnet-linker-launch-json/Program.cs @@ -0,0 +1,136 @@ +using System.IO; +using System.Text; + +using Microsoft.Build.Framework; +using Microsoft.Build.Logging; +using Microsoft.Build.Logging.StructuredLogger; + +using Mono.Options; + +class Program { + static int Main (string [] args) + { + var printHelp = false; + var binlog = string.Empty; + var rootDirectory = string.Empty; + var skippedLinkerCommands = 0; + var options = new OptionSet { + { "h|?|help", "Print this help message", (v) => printHelp = true }, + { "r|root=", "The root directory", (v) => rootDirectory = v }, + { "bl|binlog=", "The binlog", (v) => binlog = v }, + { "s|skip", "Task invocations to skip", (v) => skippedLinkerCommands++ }, + }; + + if (printHelp) { + options.WriteOptionDescriptions (Console.Out); + return 0; + } + + var others = options.Parse (args); + if (others.Any ()) { + Console.WriteLine ("Unexpected arguments:"); + foreach (var arg in others) + Console.WriteLine ("\t{0}", arg); + Console.WriteLine ("Expected arguments are:"); + options.WriteOptionDescriptions (Console.Out); + return 1; + } + + if (string.IsNullOrEmpty (binlog)) { + Console.Error.WriteLine ("A binlog is required"); + Console.WriteLine ("Expected arguments are:"); + options.WriteOptionDescriptions (Console.Out); + return 1; + } + + var path = Path.GetFullPath (binlog); + + if (string.IsNullOrEmpty (rootDirectory)) + rootDirectory = Path.GetDirectoryName (path)!; + + Console.WriteLine ($"Processing {path} with root directory {rootDirectory}..."); + + + var reader = new BinLogReader (); + var records = reader.ReadRecords (path).ToArray (); + foreach (var record in records) { + if (record is null) + continue; + + if (record.Args is null) + continue; + + if (record.Args is TaskStartedEventArgs tsea && tsea.TaskName == "ILLink") { + if (skippedLinkerCommands > 0) { + Console.WriteLine ($"Skipped an ILLink task invocation, {skippedLinkerCommands} left to skip..."); + skippedLinkerCommands--; + continue; + } + + + var relevantRecords = records.Where (v => v?.Args?.BuildEventContext?.TaskId == tsea.BuildEventContext.TaskId).Select (v => v.Args).ToArray (); + var cla = relevantRecords.Where (v => v is BuildMessageEventArgs).Cast ().Where (v => v?.ToString ()?.Contains ("CommandLineArguments") == true).ToArray (); + foreach (var rr in relevantRecords) { + if (rr is TaskCommandLineEventArgs tclea) { + if (!Xamarin.Utils.StringUtils.TryParseArguments (tclea.CommandLine.Replace ('\n', ' '), out var arguments, out var ex)) { + Console.WriteLine ($"Failed to parse command line arguments: {ex.Message}"); + return 1; + } + + WriteLaunchJson (CreateLaunchJson (rootDirectory, arguments)); + return 0; + } + } + } + } + + Console.Error.WriteLine ($"Unable to find command line arguments for ILLink in {path}"); + return 1; + } + + static void WriteLaunchJson (string contents) + { + var dir = Environment.CurrentDirectory!; + while (!Directory.Exists (Path.Combine (dir, "tools", "dotnet-linker"))) + dir = Path.GetDirectoryName (dir)!; + var path = Path.Combine (dir, "tools", "dotnet-linker", ".vscode", "launch.json"); + File.WriteAllText (path, contents); + Console.WriteLine ($"Created {path}"); + } + + static string CreateLaunchJson (string workingDirectory, string [] arguments) + { + var dotnet = arguments [0]; + var sb = new StringBuilder (); + sb.AppendLine ("{"); + sb.AppendLine (" // Use IntelliSense to learn about possible attributes."); + sb.AppendLine (" // Hover to view descriptions of existing attributes."); + sb.AppendLine (" // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387"); + sb.AppendLine (" \"version\": \"0.2.0\","); + sb.AppendLine (" \"configurations\": ["); + sb.AppendLine (" {"); + sb.AppendLine (" \"justMyCode\": false,"); + sb.AppendLine (" \"preLaunchTask\": \"make\","); + sb.AppendLine (" \"name\": \".NET Core Launch (console)\","); + sb.AppendLine (" \"type\": \"coreclr\","); + sb.AppendLine (" \"request\": \"launch\","); + sb.AppendLine ($" \"program\": \"{dotnet}\","); + sb.AppendLine (" \"args\": ["); + for (var i = 1; i < arguments.Length; i++) { + sb.AppendLine ($" \"{arguments [i]}\"{(i < arguments.Length - 1 ? "," : "")}"); + } + sb.AppendLine (" ],"); + sb.AppendLine ($" \"cwd\": \"{Path.GetFullPath (workingDirectory)}\","); + sb.AppendLine (" \"console\": \"internalConsole\","); + sb.AppendLine (" \"stopAtEntry\": false"); + sb.AppendLine (" },"); + sb.AppendLine (" {"); + sb.AppendLine (" \"name\": \".NET Core Attach\","); + sb.AppendLine (" \"type\": \"coreclr\","); + sb.AppendLine (" \"request\": \"attach\""); + sb.AppendLine (" }"); + sb.AppendLine (" ]"); + sb.AppendLine ("}"); + return sb.ToString (); + } +} diff --git a/tools/create-dotnet-linker-launch-json/README.md b/tools/create-dotnet-linker-launch-json/README.md new file mode 100644 index 0000000000..5050b74606 --- /dev/null +++ b/tools/create-dotnet-linker-launch-json/README.md @@ -0,0 +1,3 @@ +This tool takes a binlog that executes dotnet-linker, and creates a +launch.json file for the dotnet-linker directory so that the steps can be +debugged in VSCode. diff --git a/tools/create-dotnet-linker-launch-json/create-dotnet-linker-launch-json.csproj b/tools/create-dotnet-linker-launch-json/create-dotnet-linker-launch-json.csproj new file mode 100644 index 0000000000..7a25f66db1 --- /dev/null +++ b/tools/create-dotnet-linker-launch-json/create-dotnet-linker-launch-json.csproj @@ -0,0 +1,21 @@ + + + + Exe + net7.0 + create_dotnet_linker_launch_json + enable + enable + + + + + + + + + + StringUtils.cs + + + diff --git a/tools/create-dotnet-linker-launch-json/create-dotnet-linker-launch-json.sln b/tools/create-dotnet-linker-launch-json/create-dotnet-linker-launch-json.sln new file mode 100644 index 0000000000..39f717572e --- /dev/null +++ b/tools/create-dotnet-linker-launch-json/create-dotnet-linker-launch-json.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 25.0.1706.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "create-dotnet-linker-launch-json", "create-dotnet-linker-launch-json.csproj", "{9D626D84-80BA-43E8-ADDF-EAE8944F73D8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9D626D84-80BA-43E8-ADDF-EAE8944F73D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D626D84-80BA-43E8-ADDF-EAE8944F73D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D626D84-80BA-43E8-ADDF-EAE8944F73D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D626D84-80BA-43E8-ADDF-EAE8944F73D8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B197B398-E8C7-444F-B2E2-1F479AE20CA2} + EndGlobalSection +EndGlobal diff --git a/tools/devops/automation/build-pull-request.yml b/tools/devops/automation/build-pull-request.yml index 92effdedce..5dc3f332c3 100644 --- a/tools/devops/automation/build-pull-request.yml +++ b/tools/devops/automation/build-pull-request.yml @@ -166,6 +166,8 @@ variables: value: '' - name: MaciosUploadPrefix value: '' +- name: Packaging.EnableSBOMSigning + value: false trigger: none diff --git a/tools/devops/automation/scripts/VSTS.psm1 b/tools/devops/automation/scripts/VSTS.psm1 index 37cef4d381..5e79b66d41 100644 --- a/tools/devops/automation/scripts/VSTS.psm1 +++ b/tools/devops/automation/scripts/VSTS.psm1 @@ -586,6 +586,51 @@ function Set-BuildTags { } } +function Get-YamlPreview { + param ( + [String] + $Org, + + [String] + $Project, + + [String] + $AccessToken, + + [String] + $PipelineId, + + [String] + $Branch, + + [String] + $OutputFile + ) + + $headers = Get-AuthHeader -AccessToken $AccessToken + + # create the payload, this payload will point to the correct branch of the repo we want to work with, the repository is always 'self' + $payload=@{ + "previewRun"=$true + "resources"=@{ + "repositories"=@{ + "self"=@{ + "refName"="refs/heads/$Branch" + } + } + } + } + $body = ConvertTo-Json $payload -Depth 100 + + $url="https://dev.azure.com/$Org/$Project/_apis/pipelines/$PipelineId/preview?api-version=7.1-preview.1" + try { + $response=Invoke-RestMethod -Uri $url -Headers $headers -Method "POST" -ContentType 'application/json' -Body $body + } catch { + Write-Host $_ + } + Set-Content -Path $OutputFile -Value $response.finalYaml +} + function New-BuildConfiguration { param ( @@ -617,3 +662,4 @@ Export-ModuleMember -Function Set-BuildTags Export-ModuleMember -Function New-VSTS Export-ModuleMember -Function New-BuildConfiguration Export-ModuleMember -Function Import-BuildConfiguration +Export-ModuleMember -Function Get-YamlPreview diff --git a/tools/devops/automation/scripts/preview_yaml.ps1 b/tools/devops/automation/scripts/preview_yaml.ps1 new file mode 100644 index 0000000000..6f8217e4ec --- /dev/null +++ b/tools/devops/automation/scripts/preview_yaml.ps1 @@ -0,0 +1,67 @@ + <# + .SYNOPSIS + Peform a dry run of the xamarin-macios pipelines that will + download the expanded yaml. + .DESCRIPTION + This script helps tp debug issues that happen in the azure devops + pipelines by performing a dry run of the yaml and returning its expanded + version. That way a developer can see the full yaml and find issues without running + the pipelines. + .EXAMPLE + + Query the expanded template from the ci pipeline in main: + + ./preview_yaml.ps1 -AccessToken $pat -OutputFile ./full.yaml + + Query the expansion in the pr pipeline: + + ./preview_yaml.ps1 -AccessToken $pat -OutputFile ./full.yaml -Pipeline "pr" + + Query the expansion in the ci pipeline for a diff branch than main: + + ./preview_yaml.ps1 -AccessToken $pat -OutputFile ./full.yaml -Branch "trigger-issues" + + .PARAMETER AccessToken + Personal access token for VSTS that has at least access to the pipelines API. + + .PARAMETER Pipeline + Name of the pipeline to use for the dry run, it accepts two values: + - ci + - pr + The defualt value is ci. + .PARAMETER Branch + The branch to be used for the dry run. Takes the name of the branch, it should + not have the prefix refs/head. + + .PARAMETER OutputFile + Path to the file where the expanded yaml will be written. The output file is + ALLWAYS overwritten. + +#> +param ( + [Parameter(Mandatory)] + [String] + $AccessToken, + + [String] + $Pipeline="ci", + + [String] + $Branch="main", + + [Parameter(Mandatory)] + [String] + $OutputFile +) + +Import-Module ./VSTS.psm1 -Force + +if ($Pipeline -eq "pr") { + Write-Host "Querying the PR pipeline." + $PipelineId="16533" +} else { + Write-Host "Querying the CI pipeline." + $PipelineId="14411" +} + +Get-YamlPreview -Org "devdiv" -Project "DevDiv" -AccessToken $AccessToken -PipelineId $PipelineId -Branch $Branch -OutputFile $OutputFile diff --git a/tools/dotnet-linker/.vscode/tasks.json b/tools/dotnet-linker/.vscode/tasks.json new file mode 100644 index 0000000000..c7afbabdf3 --- /dev/null +++ b/tools/dotnet-linker/.vscode/tasks.json @@ -0,0 +1,12 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "make", + "type": "shell", + "command": "make" + } + ] +} diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs new file mode 100644 index 0000000000..edd7dc2ace --- /dev/null +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -0,0 +1,1114 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Tuner; + +using Xamarin.Bundler; +using Xamarin.Linker; + +#nullable enable + +namespace Xamarin.Linker { + + // This is a helper class when rewriting and adding code to assemblies with Cecil. + // It knows how to find types and methods in other assemblies, and will create a + // (type/method) reference to them in the assembly currently being processed when + // these types/methods are fetched. + class AppBundleRewriter { + LinkerConfiguration configuration; + + AssemblyDefinition? current_assembly; + AssemblyDefinition? corlib_assembly; + AssemblyDefinition? platform_assembly; + + public AssemblyDefinition CurrentAssembly { + get { + if (current_assembly is null) + throw ErrorHelper.CreateError (99, "No current assembly!"); + return current_assembly; + } + } + + public AssemblyDefinition CorlibAssembly { + get { + if (corlib_assembly is null) + throw ErrorHelper.CreateError (99, "No corlib assembly!"); + return corlib_assembly; + } + } + + public AssemblyDefinition PlatformAssembly { + get { + if (platform_assembly is null) + throw ErrorHelper.CreateError (99, "No platform assembly!"); + return platform_assembly; + } + } + + Dictionary> type_map = new (); + Dictionary method_map = new (); + + public AppBundleRewriter (LinkerConfiguration configuration) + { + this.configuration = configuration; + + // Find corlib and the platform assemblies + foreach (var asm in configuration.Assemblies) { + if (asm.Name.Name == Driver.CorlibName) { + corlib_assembly = asm; + } else if (asm.Name.Name == configuration.PlatformAssembly) { + platform_assembly = asm; + } + } + } + + TypeReference GetTypeReference (AssemblyDefinition assembly, string fullname, out TypeDefinition type) + { + if (!type_map.TryGetValue (assembly, out var map)) + type_map.Add (assembly, map = new Dictionary ()); + + if (!map.TryGetValue (fullname, out var tuple)) { + var td = assembly.MainModule.Types.SingleOrDefault (v => v.FullName == fullname); + if (td is null) + throw ErrorHelper.CreateError (99, $"Unable to find the type '{fullname}' in {assembly.Name.Name}"); + if (!td.IsPublic) { + td.IsPublic = true; + SaveAssembly (td.Module.Assembly); + } + var tr = CurrentAssembly.MainModule.ImportReference (td); + map [fullname] = tuple = new (td, tr); + } + + type = tuple.Item1; + return tuple.Item2; + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, string fullname, string name, Func? predicate) + { + GetTypeReference (assembly, fullname, out var td); + return GetMethodReference (assembly, td, name, fullname + "::" + name, predicate); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name) + { + return GetMethodReference (assembly, tr, name, tr.FullName + "::" + name, null); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, Func? predicate) + { + return GetMethodReference (assembly, tr, name, tr.FullName + "::" + name, predicate); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, string key, Func? predicate) + { + return GetMethodReference (assembly, tr, name, key, predicate, out var _); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, string key, Func? predicate, out MethodDefinition method) + { + if (!method_map.TryGetValue (key, out var tuple)) { + var td = tr.Resolve (); + var md = td.Methods.SingleOrDefault (v => v.Name == name && (predicate is null || predicate (v))); + if (md is null) + throw new InvalidOperationException ($"Unable to find the method '{tr.FullName}::{name}' (for key '{key}') in {assembly.Name.Name}. Methods in type:\n\t{string.Join ("\n\t", td.Methods.Select (GetMethodSignature).OrderBy (v => v))}"); + + tuple.Item1 = md; + tuple.Item2 = CurrentAssembly.MainModule.ImportReference (md); + method_map.Add (key, tuple); + + // Make the method public so that we can call it. + if (!md.IsPublic) { + md.IsPublic = true; + SaveAssembly (md.Module.Assembly); + } + } + + method = tuple.Item1; + return tuple.Item2; + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, bool isStatic, params TypeReference [] parameterTypes) + { + return GetMethodReference (assembly, tr, name, name, isStatic, parameterTypes); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, string key, bool isStatic, params TypeReference [] parameterTypes) + { + return GetMethodReference (assembly, tr, name, key, isStatic, 0, parameterTypes); + } + + public MethodReference GetMethodReference (AssemblyDefinition assembly, TypeReference tr, string name, string key, bool isStatic, int genericParameterCount, params TypeReference [] parameterTypes) + { + return GetMethodReference (assembly, tr, name, key, (v) => { + if (v.IsStatic != isStatic) + return false; + if (v.HasParameters != (parameterTypes.Length != 0)) + return false; + if (v.Parameters.Count != parameterTypes.Length) + return false; + + if (v.HasGenericParameters != (genericParameterCount != 0)) + return false; + if (v.GenericParameters.Count != genericParameterCount) + return false; + + for (var p = 0; p < parameterTypes.Length; p++) { + var p1 = v.Parameters [p].ParameterType; + var p2 = parameterTypes [p]; + if ((object) p1 == (object) p2) + continue; + if (p1.Name != p2.Name) + return false; + if (p1.Namespace != p2.Namespace) + return false; + } + + return true; + }); + } + + 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 ())})"; + } + + /* Types */ + + public TypeReference System_Boolean { + get { + return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.Boolean); + } + } + + public TypeReference System_Byte { + get { + return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.Byte); + } + } + + public TypeReference System_Delegate { + get { + return GetTypeReference (CorlibAssembly, "System.Delegate", out var _); + } + } + + public TypeReference System_Exception { + get { + return GetTypeReference (CorlibAssembly, "System.Exception", out var _); + } + } + public TypeReference System_Int32 { + get { + return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.Int32); + } + } + + public TypeReference System_IntPtr { + get { + return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.IntPtr); + } + } + + public TypeReference System_Nullable_1 { + get { + return GetTypeReference (CorlibAssembly, "System.Nullable`1", out var _); + } + } + + public TypeReference System_Object { + get { + return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.Object); + } + } + + public TypeReference System_RuntimeMethodHandle { + get { + return GetTypeReference (CorlibAssembly, "System.RuntimeMethodHandle", out var _); + } + } + + public TypeReference System_RuntimeTypeHandle { + get { + return GetTypeReference (CorlibAssembly, "System.RuntimeTypeHandle", out var _); + } + } + + public TypeReference System_String { + get { + return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.String); + } + } + + public TypeReference System_Type { + get { + return GetTypeReference (CorlibAssembly, "System.Type", out var _); + } + } + + public TypeReference System_UInt16 { + get { + return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.UInt16); + } + } + + public TypeReference System_UInt32 { + get { + return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.UInt32); + } + } + + public TypeReference System_Void { + get { + return CurrentAssembly.MainModule.ImportReference (CorlibAssembly.MainModule.TypeSystem.Void); + } + } + + public TypeReference System_Collections_Generic_Dictionary2 { + get { + return GetTypeReference (CorlibAssembly, "System.Collections.Generic.Dictionary`2", out var _); + } + } + + public TypeReference System_Diagnostics_CodeAnalysis_DynamicDependencyAttribute { + get { + return GetTypeReference (CorlibAssembly, "System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute", out var _); + } + } + + public TypeReference System_Reflection_MethodBase { + get { + return GetTypeReference (CorlibAssembly, "System.Reflection.MethodBase", out var _); + } + } + + public TypeReference System_Reflection_MethodInfo { + get { + return GetTypeReference (CorlibAssembly, "System.Reflection.MethodInfo", out var _); + } + } + + public TypeReference CoreFoundation_CFArray { + get { + return GetTypeReference (PlatformAssembly, "CoreFoundation.CFArray", out var _); + } + } + + public TypeReference CoreFoundation_CFString { + get { + return GetTypeReference (PlatformAssembly, "CoreFoundation.CFString", out var _); + } + } + + public TypeReference Foundation_NSArray { + get { + return GetTypeReference (PlatformAssembly, "Foundation.NSArray", out var _); + } + } + + public TypeReference Foundation_NSString { + get { + return GetTypeReference (PlatformAssembly, "Foundation.NSString", out var _); + } + } + + public TypeReference Foundation_NSObject { + get { + return GetTypeReference (PlatformAssembly, "Foundation.NSObject", out var _); + } + } + + public TypeReference ObjCRuntime_BindAs { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.BindAs", out var _); + } + } + + public TypeReference ObjCRuntime_BlockLiteral { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.BlockLiteral", out var _); + } + } + + public TypeReference ObjCRuntime_IManagedRegistrar { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.IManagedRegistrar", out var _); + } + } + + public TypeReference ObjCRuntime_INativeObject { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.INativeObject", out var _); + } + } + + public TypeReference ObjCRuntime_NativeHandle { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.NativeHandle", out var _); + } + } + + public TypeReference ObjCRuntime_NativeObjectExtensions { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.NativeObjectExtensions", out var _); + } + } + + public TypeReference ObjCRuntime_RegistrarHelper { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.RegistrarHelper", out var _); + } + } + + public TypeReference ObjCRuntime_Runtime { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.Runtime", out var _); + } + } + + public TypeReference ObjCRuntime_RuntimeException { + get { + return GetTypeReference (PlatformAssembly, "ObjCRuntime.RuntimeException", out var _); + } + } + + /* Methods */ + + public MethodReference System_Object__ctor { + get { + return GetMethodReference (CorlibAssembly, System_Object, ".ctor", (v) => v.IsDefaultConstructor ()); + } + } + + public MethodReference Nullable_HasValue { + get { + return GetMethodReference (CorlibAssembly, System_Nullable_1, "get_HasValue", isStatic: false); + } + } + + public MethodReference Nullable_Value { + get { + return GetMethodReference (CorlibAssembly, System_Nullable_1, "get_Value", isStatic: false); + } + } + + public MethodReference Type_GetTypeFromHandle { + get { + return GetMethodReference (CorlibAssembly, System_Type, "GetTypeFromHandle", isStatic: true, System_RuntimeTypeHandle); + } + } + + public MethodReference Dictionary2_Add { + get { + return GetMethodReference (CorlibAssembly, System_Collections_Generic_Dictionary2, "Add", (v) => + !v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && !v.HasGenericParameters); + } + } + + public MethodReference DynamicDependencyAttribute_ctor__String_Type { + get { + return GetMethodReference (CorlibAssembly, + System_Diagnostics_CodeAnalysis_DynamicDependencyAttribute, + ".ctor", + isStatic: false, + System_String, + System_Type); + } + } + + public MethodReference RuntimeTypeHandle_Equals { + get { + return GetMethodReference (CorlibAssembly, System_RuntimeTypeHandle, "Equals", isStatic: false, System_RuntimeTypeHandle); + } + } + public MethodReference MethodBase_Invoke { + get { + return GetMethodReference (CorlibAssembly, System_Reflection_MethodBase, "Invoke", (v) => + !v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "Object") + && v.Parameters [1].ParameterType is ArrayType at && at.ElementType.Is ("System", "Object") + && !v.HasGenericParameters); + } + } + + public MethodReference MethodBase_GetMethodFromHandle__RuntimeMethodHandle { + get { + return GetMethodReference (CorlibAssembly, + System_Reflection_MethodBase, "GetMethodFromHandle", + nameof (MethodBase_GetMethodFromHandle__RuntimeMethodHandle), + isStatic: true, + System_RuntimeMethodHandle); + } + } + + public MethodReference NSObject_AllocateNSObject { + get { + return GetMethodReference (PlatformAssembly, + Foundation_NSObject, "AllocateNSObject", + nameof (NSObject_AllocateNSObject), + isStatic: true, + genericParameterCount: 1, + System_IntPtr); + } + } + + public MethodReference BindAs_ConvertNSArrayToManagedArray { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "ConvertNSArrayToManagedArray", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is FunctionPointerType fpt + && v.HasGenericParameters + && v.GenericParameters.Count == 1); + } + } + + public MethodReference BindAs_ConvertNSArrayToManagedArray2 { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "ConvertNSArrayToManagedArray2", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is FunctionPointerType fpt + && v.HasGenericParameters + && v.GenericParameters.Count == 2); + } + } + + public MethodReference BindAs_ConvertManagedArrayToNSArray { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "ConvertManagedArrayToNSArray", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType is ArrayType at + && v.Parameters [1].ParameterType is FunctionPointerType fpt + && v.HasGenericParameters + && v.GenericParameters.Count == 1); + } + } + + public MethodReference BindAs_ConvertManagedArrayToNSArray2 { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "ConvertManagedArrayToNSArray2", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is ArrayType at + && v.Parameters [1].ParameterType is FunctionPointerType fpt + && v.HasGenericParameters + && v.GenericParameters.Count == 2); + } + } + + public MethodReference BindAs_CreateNullable { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "CreateNullable", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 2 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is FunctionPointerType fpt + && v.HasGenericParameters + && v.GenericParameters.Count == 1); + } + } + + public MethodReference BindAs_CreateNullable2 { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_BindAs, "CreateNullable2", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is FunctionPointerType fpt1 + && v.Parameters [2].ParameterType is FunctionPointerType fpt2 + && v.HasGenericParameters + && v.GenericParameters.Count == 2); + } + } + + public MethodReference RegistrarHelper_NSArray_string_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSArray_string_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType is ArrayType at1 && at1.ElementType.Is ("System", "String") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType is ArrayType at2 && at2.ElementType.Is ("System", "String") + && !v.HasGenericParameters); + } + } + + public MethodReference RegistrarHelper_NSArray_string_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSArray_string_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ArrayType at1 && at1.ElementType.Is ("System", "String") + && v.Parameters [2].ParameterType is ArrayType at2 && at2.ElementType.Is ("System", "String") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && !v.HasGenericParameters); + } + } + + public MethodReference RegistrarHelper_NSArray_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSArray_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType is ArrayType at1 && at1.ElementType.Is ("", "T") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType is ArrayType at2 && at2.ElementType.Is ("", "T") + && v.HasGenericParameters + && v.GenericParameters.Count == 1); + } + } + + public MethodReference RegistrarHelper_NSArray_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSArray_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ArrayType at1 && at1.ElementType.Is ("", "T") + && v.Parameters [2].ParameterType is ArrayType at2 && at2.ElementType.Is ("", "T") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && v.HasGenericParameters + && v.GenericParameters.Count == 1); + } + } + + public MethodReference RegistrarHelper_NSObject_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSObject_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType.Is ("", "T") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType.Is ("", "T") + && v.HasGenericParameters + && v.GenericParameters.Count == 1); + } + } + + public MethodReference RegistrarHelper_NSObject_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "NSObject_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType.Is ("Foundation", "NSObject") + && v.Parameters [2].ParameterType.Is ("Foundation", "NSObject") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && !v.HasGenericParameters); + } + } + + public MethodReference RegistrarHelper_string_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "string_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 3 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("ObjCRuntime", "NativeHandle") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType.Is ("System", "String") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType.Is ("System", "String") + && !v.HasGenericParameters); + } + } + + public MethodReference RegistrarHelper_string_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "string_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("ObjCRuntime", "NativeHandle") + && v.Parameters [1].ParameterType.Is ("System", "String") + && v.Parameters [2].ParameterType.Is ("System", "String") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && !v.HasGenericParameters); + } + } + + public MethodReference RegistrarHelper_INativeObject_native_to_managed { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "INativeObject_native_to_managed", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType is ByReferenceType brt1 && brt1.ElementType.Is ("", "T") + && v.Parameters [2].ParameterType is ByReferenceType brt2 && brt2.ElementType.Is ("", "T") + && v.Parameters [3].ParameterType.Is ("System", "RuntimeTypeHandle") + && v.HasGenericParameters + && v.GenericParameters.Count == 1); + } + } + + public MethodReference RegistrarHelper_INativeObject_managed_to_native { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "INativeObject_managed_to_native", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 4 + && v.Parameters [0].ParameterType is PointerType pt && pt.ElementType.Is ("System", "IntPtr") + && v.Parameters [1].ParameterType.Is ("ObjCRuntime", "INativeObject") + && v.Parameters [2].ParameterType.Is ("ObjCRuntime", "INativeObject") + && v.Parameters [3].ParameterType.Is ("System", "Boolean") + && !v.HasGenericParameters); + } + } + + public MethodReference RegistrarHelper_Register { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_RegistrarHelper, "Register", + isStatic: true, + ObjCRuntime_IManagedRegistrar); + } + } + + public MethodReference IManagedRegistrar_LookupUnmanagedFunction { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_IManagedRegistrar, "LookupUnmanagedFunction", + isStatic: false, + System_String, + System_Int32); + } + } + + public MethodReference IManagedRegistrar_LookupType { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_IManagedRegistrar, "LookupType", + isStatic: false, + System_UInt32); + } + } + + public MethodReference IManagedRegistrar_LookupTypeId { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_IManagedRegistrar, "LookupTypeId", + isStatic: false, + System_RuntimeTypeHandle); + } + } + + public MethodReference IManagedRegistrar_RegisterWrapperTypes { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_IManagedRegistrar, "RegisterWrapperTypes", (v) => + v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType is GenericInstanceType git && git.ElementType.Is ("System.Collections.Generic", "Dictionary`2") + && !v.HasGenericParameters); + } + } + + public MethodReference Runtime_AllocGCHandle { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "AllocGCHandle", + nameof (Runtime_AllocGCHandle), + isStatic: true, + System_Object); + } + } + + public MethodReference Runtime_HasNSObject { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "HasNSObject", + nameof (Runtime_HasNSObject), + isStatic: true, + System_IntPtr); + } + } + + public MethodReference Runtime_GetNSObject__System_IntPtr { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "GetNSObject", + nameof (Runtime_GetNSObject__System_IntPtr), + isStatic: true, + System_IntPtr); + } + } + + public MethodReference Runtime_GetNSObject_T___System_IntPtr_System_IntPtr_System_RuntimeMethodHandle_bool { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "GetNSObject", + nameof (Runtime_GetNSObject_T___System_IntPtr_System_IntPtr_System_RuntimeMethodHandle_bool), + isStatic: true, + genericParameterCount: 1, + System_IntPtr, + System_IntPtr, + System_RuntimeMethodHandle, + System_Boolean); + } + } + + public MethodReference Runtime_GetNSObject_T___System_IntPtr { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "GetNSObject", + nameof (Runtime_GetNSObject_T___System_IntPtr), + isStatic: true, + genericParameterCount: 1, + System_IntPtr); + } + } + + public MethodReference Runtime_GetINativeObject__IntPtr_Boolean_Type_Type { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "GetINativeObject", + nameof (Runtime_GetINativeObject__IntPtr_Boolean_Type_Type), + isStatic: true, + System_IntPtr, + System_Boolean, + System_Type, + System_Type); + } + } + + public MethodReference Runtime_CreateRuntimeException { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "CreateRuntimeException", + nameof (Runtime_CreateRuntimeException), + isStatic: true, + System_Int32, + System_String); + } + } + + public MethodReference BlockLiteral_CreateBlockForDelegate { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_BlockLiteral, + "CreateBlockForDelegate", + isStatic: true, + System_Delegate, + System_Delegate, + System_String); + } + } + + public MethodReference RegistrarHelper_GetBlockForDelegate { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_RegistrarHelper, "GetBlockForDelegate", + isStatic: true, + System_Object, + System_RuntimeMethodHandle); + } + } + + public MethodReference RegistrarHelper_GetBlockPointer { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_RegistrarHelper, "GetBlockPointer", + isStatic: true, + ObjCRuntime_BlockLiteral); + } + } + + public MethodReference BlockLiteral_Copy { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_BlockLiteral, "Copy", + isStatic: true, + System_IntPtr); + } + } + + public MethodReference Runtime_ReleaseBlockWhenDelegateIsCollected { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "ReleaseBlockWhenDelegateIsCollected", + nameof (Runtime_ReleaseBlockWhenDelegateIsCollected), + isStatic: true, + System_IntPtr, + System_Delegate); + } + } + + public MethodReference Runtime_GetBlockWrapperCreator { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "GetBlockWrapperCreator", + nameof (Runtime_GetBlockWrapperCreator), + isStatic: true, + System_Reflection_MethodInfo, + System_Int32); + } + } + + public MethodReference Runtime_CreateBlockProxy { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "CreateBlockProxy", + nameof (Runtime_CreateBlockProxy), + isStatic: true, + System_Reflection_MethodInfo, + System_IntPtr); + } + } + + public MethodReference Runtime_TraceCaller { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "TraceCaller", + isStatic: true, + System_String); + } + } + + public MethodReference Runtime_FindClosedMethod { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "FindClosedMethod", + isStatic: true, + System_Object, + System_RuntimeTypeHandle, + System_RuntimeMethodHandle); + } + } + + public MethodReference Runtime_FindClosedParameterType { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "FindClosedParameterType", + isStatic: true, + System_Object, + System_RuntimeTypeHandle, + System_RuntimeMethodHandle, + System_Int32); + } + } + public MethodReference CFString_FromHandle { + get { + return GetMethodReference (PlatformAssembly, + CoreFoundation_CFString, "FromHandle", + nameof (CFString_FromHandle), + isStatic: true, + ObjCRuntime_NativeHandle); + } + } + + public MethodReference CFString_CreateNative { + get { + return GetMethodReference (PlatformAssembly, + CoreFoundation_CFString, "CreateNative", + nameof (CFString_CreateNative), + isStatic: true, + System_String); + } + } + + public MethodReference CFArray_StringArrayFromHandle { + get { + return GetMethodReference (PlatformAssembly, + CoreFoundation_CFArray, "StringArrayFromHandle", + nameof (CFArray_StringArrayFromHandle), + isStatic: true, + ObjCRuntime_NativeHandle); + } + } + + public MethodReference RegistrarHelper_CreateCFArray { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_RegistrarHelper, "CreateCFArray", nameof (RegistrarHelper_CreateCFArray), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType is ArrayType at && at.GetElementType ().Is ("System", "String") + && !v.HasGenericParameters); + } + } + + public MethodReference NSArray_ArrayFromHandle { + get { + return GetMethodReference (PlatformAssembly, + Foundation_NSArray, "ArrayFromHandle", + nameof (NSArray_ArrayFromHandle), + isStatic: true, + ObjCRuntime_NativeHandle, + System_Type); + } + } + + public MethodReference NSArray_ArrayFromHandle_1 { + get { + return GetMethodReference (PlatformAssembly, + Foundation_NSArray, "ArrayFromHandle", + nameof (NSArray_ArrayFromHandle_1), + isStatic: true, + genericParameterCount: 1, + ObjCRuntime_NativeHandle); + } + } + + public MethodReference RegistrarHelper_ManagedArrayToNSArray { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_RegistrarHelper, "ManagedArrayToNSArray", + nameof (RegistrarHelper_ManagedArrayToNSArray), + isStatic: true, + System_Object, + System_Boolean); + } + } + + public MethodReference NativeObjectExtensions_GetHandle { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_NativeObjectExtensions, "GetHandle"); + } + } + + public MethodReference NativeObject_op_Implicit_IntPtr { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_NativeHandle, "op_Implicit", nameof (NativeObject_op_Implicit_IntPtr), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("ObjCRuntime", "NativeHandle") + && v.ReturnType.Is ("System", "IntPtr") + && !v.HasGenericParameters); + } + } + + public MethodReference NativeObject_op_Implicit_NativeHandle { + get { + return GetMethodReference (PlatformAssembly, ObjCRuntime_NativeHandle, "op_Implicit", nameof (NativeObject_op_Implicit_NativeHandle), (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.Is ("System", "IntPtr") + && v.ReturnType.Is ("ObjCRuntime", "NativeHandle") + && !v.HasGenericParameters); + } + } + + public MethodReference Runtime_CopyAndAutorelease { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "CopyAndAutorelease", + isStatic: true, + System_IntPtr); + } + } + + public MethodReference Runtime_RetainNSObject { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "RetainNSObject", + isStatic: true, + Foundation_NSObject); + } + } + + public MethodReference Runtime_RetainNativeObject { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "RetainNativeObject", + isStatic: true, + ObjCRuntime_INativeObject); + } + } + + public MethodReference Runtime_RetainAndAutoreleaseNSObject { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "RetainAndAutoreleaseNSObject", + isStatic: true, + Foundation_NSObject); + } + } + + public MethodReference Runtime_RetainAndAutoreleaseNativeObject { + get { + return GetMethodReference (PlatformAssembly, + ObjCRuntime_Runtime, "RetainAndAutoreleaseNativeObject", + isStatic: true, + ObjCRuntime_INativeObject); + } + } + + public MethodReference UnmanagedCallersOnlyAttribute_Constructor { + get { + return GetMethodReference (CorlibAssembly, "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute", ".ctor", (v) => v.IsDefaultConstructor ()); + } + } + + public MethodReference Unsafe_AsRef { + get { + return GetMethodReference (CorlibAssembly, "System.Runtime.CompilerServices.Unsafe", "AsRef", (v) => + v.IsStatic + && v.HasParameters + && v.Parameters.Count == 1 + && v.Parameters [0].ParameterType.IsPointer && v.Parameters [0].ParameterType.GetElementType ().Is ("System", "Void") + && v.HasGenericParameters); + } + } + + public void SetCurrentAssembly (AssemblyDefinition value) + { + current_assembly = value; + } + + public void SaveCurrentAssembly () + { + SaveAssembly (CurrentAssembly); + } + + void SaveAssembly (AssemblyDefinition assembly) + { + if (assembly != CurrentAssembly && assembly != PlatformAssembly) + throw new InvalidOperationException ($"Can't save assembly {assembly.Name} because it's not the current assembly ({CurrentAssembly.Name}) or the platform assembly ({PlatformAssembly.Name})."); + var annotations = configuration.Context.Annotations; + var action = annotations.GetAction (assembly); + if (action == AssemblyAction.Copy) { + // Preserve TypeForwardedTo which would the linker sweep otherwise + // Note that the linker will sweep type forwarders even if the assembly isn't trimmed: + // https://github.com/dotnet/runtime/blob/9dd59af3aee2f403e63887afef50d98022a2e575/src/tools/illink/src/linker/Linker.Steps/SweepStep.cs#L191-L200 + if (assembly.MainModule.HasExportedTypes) { + foreach (var type in assembly.MainModule.ExportedTypes) { + annotations.Mark (type); + } + } + annotations.SetAction (assembly, AssemblyAction.Save); + } + } + + public void ClearCurrentAssembly () + { + current_assembly = null; + type_map.Clear (); + method_map.Clear (); + } + } +} diff --git a/tools/dotnet-linker/CecilExtensions.cs b/tools/dotnet-linker/CecilExtensions.cs new file mode 100644 index 0000000000..e4b0dcd34e --- /dev/null +++ b/tools/dotnet-linker/CecilExtensions.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; + +using Mono.Cecil; +using Mono.Cecil.Cil; + +#nullable enable + +namespace Xamarin.Linker { + + static class Cecil_Extensions { + public static VariableDefinition AddVariable (this MethodBody self, TypeReference variableType) + { + var rv = new VariableDefinition (variableType); + self.Variables.Add (rv); + return rv; + } + + public static ParameterDefinition AddParameter (this MethodDefinition self, string name, TypeReference parameterType) + { + var rv = new ParameterDefinition (name, ParameterAttributes.None, parameterType); + self.Parameters.Add (rv); + return rv; + } + + public static MethodDefinition AddMethod (this TypeDefinition self, string name, MethodAttributes attributes, TypeReference returnType) + { + var rv = new MethodDefinition (name, attributes, returnType); + rv.DeclaringType = self; + self.Methods.Add (rv); + return rv; + } + + public static MethodBody CreateBody (this MethodDefinition self, out ILProcessor il) + { + var body = new MethodBody (self); + body.InitLocals = true; + self.Body = body; + il = body.GetILProcessor (); + return body; + } + + public static void AddRange (this Mono.Collections.Generic.Collection self, IEnumerable? items) + { + if (items is null) + return; + + foreach (var item in items) { + self.Add (item); + } + } + + public static void EmitLoadArgument (this ILProcessor il, int argument) + { + il.Append (il.CreateLoadArgument (argument)); + } + + public static Instruction CreateLoadArgument (this ILProcessor il, int argument) + { + switch (argument) { + case 0: + return il.Create (OpCodes.Ldarg_0); + case 1: + return il.Create (OpCodes.Ldarg_1); + case 2: + return il.Create (OpCodes.Ldarg_2); + case 3: + return il.Create (OpCodes.Ldarg_3); + default: + return il.Create (OpCodes.Ldarg, argument); + } + } + + public static Instruction CreateLdc (this ILProcessor il, bool value) + { + if (value) + return il.Create (OpCodes.Ldc_I4_1); + return il.Create (OpCodes.Ldc_I4_0); + } + + public static void EmitLdc (this ILProcessor il, bool value) + { + il.Append (il.CreateLdc (value)); + } + + public static GenericInstanceMethod CreateGenericInstanceMethod (this MethodReference mr, params TypeReference [] genericTypeArguments) + { + var gim = new GenericInstanceMethod (mr); + gim.GenericArguments.AddRange (genericTypeArguments); + return gim; + } + + public static MethodReference CreateMethodReferenceOnGenericType (this TypeReference type, MethodReference mr, params TypeReference [] genericTypeArguments) + { + var git = new GenericInstanceType (type); + git.GenericArguments.AddRange (genericTypeArguments); + + var rv = new MethodReference (mr.Name, mr.ReturnType, git); + rv.HasThis = mr.HasThis; + rv.ExplicitThis = mr.ExplicitThis; + rv.CallingConvention = mr.CallingConvention; + rv.Parameters.AddRange (mr.Parameters); + return rv; + } + + public static GenericInstanceType CreateGenericInstanceType (this TypeReference type, params TypeReference [] genericTypeArguments) + { + var git = new GenericInstanceType (type); + git.GenericArguments.AddRange (genericTypeArguments); + return git; + } + + public static MethodDefinition AddDefaultConstructor (this TypeDefinition type, AppBundleRewriter abr) + { + // Add default ctor that just calls the base ctor + var defaultCtor = type.AddMethod (".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, abr.System_Void); + defaultCtor.CreateBody (out var il); + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Call, abr.System_Object__ctor); + il.Emit (OpCodes.Ret); + return defaultCtor; + } + } +} diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 7536bcc57e..21de0954d7 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -8,6 +8,7 @@ using System.Xml.Linq; using Mono.Cecil; using Mono.Linker; +using Mono.Linker.Steps; using Xamarin.Bundler; using Xamarin.Utils; @@ -60,6 +61,18 @@ namespace Xamarin.Linker { Dictionary> msbuild_items = new Dictionary> (); + AppBundleRewriter? abr; + internal AppBundleRewriter AppBundleRewriter { + get { + if (abr is null) + abr = new AppBundleRewriter (this); + return abr; + } + } + + // This dictionary contains information about the trampolines created for each assembly. + public AssemblyTrampolineInfos AssemblyTrampolineInfos = new (); + internal PInvokeWrapperGenerator? PInvokeWrapperGenerationState; public static bool TryGetInstance (LinkContext context, [NotNullWhen (true)] out LinkerConfiguration? configuration) @@ -503,6 +516,15 @@ namespace Xamarin.Linker { // ErrorHelper.Show will print our errors and warnings to stderr. ErrorHelper.Show (list); } + + public IEnumerable GetNonDeletedAssemblies (BaseStep step) + { + foreach (var assembly in Assemblies) { + if (step.Annotations.GetAction (assembly) == Mono.Linker.AssemblyAction.Delete) + continue; + yield return assembly; + } + } } } diff --git a/tools/dotnet-linker/Makefile b/tools/dotnet-linker/Makefile index d898d5c638..2a7c5f052b 100644 --- a/tools/dotnet-linker/Makefile +++ b/tools/dotnet-linker/Makefile @@ -34,3 +34,7 @@ $(DOTNET_DIRECTORIES): all-local:: $(DOTNET_TARGETS) install-local:: $(DOTNET_TARGETS) + +VSCODE?="/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" +vscode: + PATH=$(DOTNET_DIR):$(PATH) $(VSCODE) . diff --git a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs index 76e0e3c1e9..1fb21c07ec 100644 --- a/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs +++ b/tools/dotnet-linker/Steps/ConfigurationAwareStep.cs @@ -5,6 +5,7 @@ using System.Linq; using Mono.Cecil; using Mono.Linker.Steps; +using Xamarin.Tuner; using Xamarin.Bundler; @@ -16,16 +17,25 @@ namespace Xamarin.Linker { get { return LinkerConfiguration.GetInstance (Context); } } - protected void Report (Exception exception) - { - LinkerConfiguration.Report (Context, exception); + public DerivedLinkContext DerivedLinkContext { + get { return Configuration.DerivedLinkContext; } } - protected void Report (List exceptions) + public Application App { + get { return DerivedLinkContext.App; } + } + + protected void Report (params Exception [] exceptions) { - // Maybe there's a better way to show errors that integrates with the linker? - // We can't just throw an exception or exit here, since there might be only warnings in the list of exceptions. - ErrorHelper.Show (exceptions); + Report ((IList) exceptions); + } + + protected void Report (IList? exceptions) + { + if (exceptions is null) + return; + + LinkerConfiguration.Report (Context, exceptions); } protected sealed override void Process () @@ -49,7 +59,8 @@ namespace Xamarin.Linker { protected sealed override void EndProcess () { try { - TryEndProcess (); + TryEndProcess (out var exceptions); + Report (exceptions); } catch (Exception e) { Report (FailEnd (e)); } @@ -68,6 +79,12 @@ namespace Xamarin.Linker { { } + protected virtual void TryEndProcess (out List? exceptions) + { + exceptions = null; + TryEndProcess (); + } + // failure overrides, with defaults bool CollectProductExceptions (Exception e, [NotNullWhen (true)] out List? productExceptions) @@ -87,55 +104,39 @@ namespace Xamarin.Linker { return false; } - protected virtual Exception Fail (AssemblyDefinition assembly, Exception e) + protected virtual Exception [] Fail (AssemblyDefinition assembly, Exception e) { - // Detect if we're reporting one or more ProductExceptions (and no other exceptions), and in that case - // report the product exceptions as top-level exceptions + the step-specific exception at the end, - // instead of the step-specific exception with all the other exceptions as an inner exception. - // This makes the errors show up nicer in the output. - if (CollectProductExceptions (e, out var productExceptions)) { - // don't add inner exception - var ex = ErrorHelper.CreateError (ErrorCode, Errors.MX_ConfigurationAwareStepWithAssembly, Name, assembly?.FullName, e.Message); - // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. - productExceptions.Add (ex); - return new AggregateException (productExceptions); - } - - return ErrorHelper.CreateError (ErrorCode, e, Errors.MX_ConfigurationAwareStepWithAssembly, Name, assembly?.FullName, e.Message); + return CollectExceptions (e, () => ErrorHelper.CreateError (ErrorCode, Errors.MX_ConfigurationAwareStepWithAssembly, Name, assembly?.FullName, e.Message)); } - protected virtual Exception Fail (Exception e) + protected virtual Exception [] Fail (Exception e) { - // Detect if we're reporting one or more ProductExceptions (and no other exceptions), and in that case - // report the product exceptions as top-level exceptions + the step-specific exception at the end, - // instead of the step-specific exception with all the other exceptions as an inner exception. - // This makes the errors show up nicer in the output. - if (CollectProductExceptions (e, out var productExceptions)) { - // don't add inner exception - var ex = ErrorHelper.CreateError (ErrorCode | 1, Errors.MX_ConfigurationAwareStep, Name, e.Message); - // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. - productExceptions.Add (ex); - return new AggregateException (productExceptions); - } - - return ErrorHelper.CreateError (ErrorCode | 1, e, Errors.MX_ConfigurationAwareStep, Name, e.Message); + return CollectExceptions (e, () => ErrorHelper.CreateError (ErrorCode | 1, Errors.MX_ConfigurationAwareStep, Name, e.Message)); } - protected virtual Exception FailEnd (Exception e) + protected virtual Exception [] FailEnd (Exception e) + { + return CollectExceptions (e, () => ErrorHelper.CreateError (ErrorCode | 2, Errors.MX_ConfigurationAwareStep, Name, e.Message)); + } + + Exception [] CollectExceptions (Exception e, Func createException) { // Detect if we're reporting one or more ProductExceptions (and no other exceptions), and in that case // report the product exceptions as top-level exceptions + the step-specific exception at the end, // instead of the step-specific exception with all the other exceptions as an inner exception. // This makes the errors show up nicer in the output. + // If we're only reporting warnings, then don't add the step-specific exception at all. if (CollectProductExceptions (e, out var productExceptions)) { // don't add inner exception - var ex = ErrorHelper.CreateError (ErrorCode | 2, Errors.MX_ConfigurationAwareStep, Name, e.Message); - // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. - productExceptions.Add (ex); - return new AggregateException (productExceptions); + if (productExceptions.Any (v => v.Error)) { + var ex = createException (); + // instead return an aggregate exception with the original exception and all the ProductExceptions we're reporting. + productExceptions.Add (ex); + } + return productExceptions.ToArray (); } - return ErrorHelper.CreateError (ErrorCode | 2, e, Errors.MX_ConfigurationAwareStep, Name, e.Message); + return new Exception [] { createException () }; } // abstracts diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs new file mode 100644 index 0000000000..28dc1c22f2 --- /dev/null +++ b/tools/dotnet-linker/Steps/ManagedRegistrarLookupTablesStep.cs @@ -0,0 +1,491 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +using Xamarin.Bundler; +using Xamarin.Utils; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Tuner; + +using Registrar; +using System.Globalization; + +#nullable enable + +namespace Xamarin.Linker { + // This type will generate the lookup code to: + // * Convert between types and their type IDs. + // * Map between a protocol interface and its wrapper type. + // * Find the UnmanagedCallersOnly method for a given method ID. + // This must be done after the linker has trimmed away any unused code, + // because otherwise the lookup code would reference (and thus keep) + // every exported type and method. + public class ManagedRegistrarLookupTablesStep : ConfigurationAwareStep { + class TypeData { + public TypeReference Reference; + public TypeDefinition Definition; + + public TypeData (TypeReference reference, TypeDefinition definition) + { + Reference = reference; + Definition = definition; + } + } + + protected override string Name { get; } = "ManagedRegistrarLookupTables"; + protected override int ErrorCode { get; } = 2440; + + AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } + + protected override void TryProcessAssembly (AssemblyDefinition assembly) + { + base.TryProcessAssembly (assembly); + + if (App.Registrar != RegistrarMode.ManagedStatic) + return; + + var annotation = DerivedLinkContext.Annotations.GetCustomAnnotation ("ManagedRegistrarStep", assembly); + var info = annotation as AssemblyTrampolineInfo; + if (info is null) + return; + + abr.SetCurrentAssembly (assembly); + + CreateRegistrarType (info); + + abr.ClearCurrentAssembly (); + } + + void CreateRegistrarType (AssemblyTrampolineInfo info) + { + var registrarType = new TypeDefinition ("ObjCRuntime", "__Registrar__", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit); + registrarType.BaseType = abr.System_Object; + registrarType.Interfaces.Add (new InterfaceImplementation (abr.ObjCRuntime_IManagedRegistrar)); + abr.CurrentAssembly.MainModule.Types.Add (registrarType); + + info.RegistrarType = registrarType; + + // Remove any methods that were linked away + for (var i = info.Count - 1; i >= 0; i--) { + var tinfo = info [i]; + if (IsTrimmed (tinfo.Target)) + info.RemoveAt (i); + } + + info.SetIds (); + var sorted = info.OrderBy (v => v.Id).ToList (); + + // + // The callback methods themselves are all public, and thus accessible from anywhere inside + // the assembly even if the containing type is not public, as long as the containing type is not nested. + // However, if the containing type is nested inside another type, it gets complicated. + // + // We have two options: + // + // 1. Just change the visibility on the nested type to make it visible inside the assembly. + // 2. Add a method in the containing type (which has access to any directly nested private types) that can look up any unmanaged trampolines. + // If the containing type is also a private nested type, when we'd have to add another method in its containing type, and so on. + // + // The second option is more complicated to implement than the first, so we're doing the first option. If someone + // runs into any problems (there might be with reflection: looking up a type using the wrong visibility will fail to find that type). + // That said, there may be all sorts of problems with reflection (we're adding methods to types, + // any logic that depends on a type having a certain number of methods will fail for instance). + // + foreach (var md in sorted) { + var declType = md.Trampoline.DeclaringType; + while (declType.IsNested) { + if (declType.IsNestedPrivate) { + declType.IsNestedAssembly = true; + } else if (declType.IsNestedFamilyAndAssembly || declType.IsNestedFamily) { + declType.IsNestedFamilyOrAssembly = true; + } + declType = declType.DeclaringType; + } + } + + // Add default ctor that just calls the base ctor + var defaultCtor = registrarType.AddDefaultConstructor (abr); + + // Create an instance of the registrar type in the module constructor, + // and call ObjCRuntime.RegistrarHelper.Register with the instance. + AddLoadTypeToModuleConstructor (registrarType); + + // Compute the list of types that we need to register + var types = GetTypesToRegister (registrarType, info); + + GenerateLookupUnmanagedFunction (registrarType, sorted); + GenerateLookupType (info, registrarType, types); + GenerateLookupTypeId (info, registrarType, types); + GenerateRegisterWrapperTypes (registrarType); + + // Make sure the linker doesn't sweep away anything we just generated. + Annotations.Mark (registrarType); + foreach (var method in registrarType.Methods) + Annotations.Mark (method); + foreach (var iface in registrarType.Interfaces) { + Annotations.Mark (iface); + Annotations.Mark (iface.InterfaceType); + Annotations.Mark (iface.InterfaceType.Resolve ()); + } + } + + void AddLoadTypeToModuleConstructor (TypeDefinition registrarType) + { + // Add a module constructor to initialize the callbacks + var moduleType = registrarType.Module.Types.SingleOrDefault (v => v.Name == ""); + if (moduleType is null) + throw ErrorHelper.CreateError (99, $"No type found in {registrarType.Module.Name}"); + var moduleConstructor = moduleType.GetTypeConstructor (); + MethodBody body; + ILProcessor il; + if (moduleConstructor is null) { + moduleConstructor = moduleType.AddMethod (".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.Static, abr.System_Void); + body = moduleConstructor.CreateBody (out il); + il.Emit (OpCodes.Ret); + } else { + body = moduleConstructor.Body; + il = body.GetILProcessor (); + } + var lastInstruction = body.Instructions.Last (); + + il.InsertBefore (lastInstruction, il.Create (OpCodes.Newobj, registrarType.GetDefaultInstanceConstructor ())); + il.InsertBefore (lastInstruction, il.Create (OpCodes.Call, abr.RegistrarHelper_Register)); + } + + List GetTypesToRegister (TypeDefinition registrarType, AssemblyTrampolineInfo info) + { + // Compute the list of types that we need to register + var types = new List (); + + // We want all the types that have been registered + types.AddRange (StaticRegistrar.Types.Select (v => { + var tr = v.Value.Type; + var td = tr.Resolve (); + return new TypeData (tr, td); + })); + + // We also want all the types the registrar skipped (otherwise we won't be able to generate the corresponding table of skipped types). + foreach (var st in StaticRegistrar.SkippedTypes) { + if (!types.Any (v => v.Reference == st.Skipped)) + types.Add (new (st.Skipped, st.Skipped.Resolve ())); + if (!types.Any (v => v.Reference == st.Actual.Type)) + types.Add (new (st.Actual.Type, st.Actual.Type.Resolve ())); + } + + // Skip any types that are not defined in the current assembly + types.RemoveAll (v => v.Definition.Module.Assembly != abr.CurrentAssembly); + + // Skip any types that have been linked away + types.RemoveAll (v => IsTrimmed (v.Definition)); + + // We also want all the protocol wrapper types + foreach (var type in registrarType.Module.Types) { + if (IsTrimmed (type)) + continue; + var wrapperType = StaticRegistrar.GetProtocolAttributeWrapperType (type); + if (wrapperType is null) + continue; + types.Add (new (wrapperType, wrapperType.Resolve ())); + } + + // Now create a mapping from type to index + for (var i = 0; i < types.Count; i++) + info.RegisterType (types [i].Definition, (uint) i); + + return types; + } + + bool IsTrimmed (MemberReference type) + { + return StaticRegistrar.IsTrimmed (type, Annotations); + } + + void GenerateLookupTypeId (AssemblyTrampolineInfo infos, TypeDefinition registrarType, List types) + { + var lookupTypeMethod = registrarType.AddMethod ("LookupTypeId", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_UInt32); + var handleParameter = lookupTypeMethod.AddParameter ("handle", abr.System_RuntimeTypeHandle); + lookupTypeMethod.Overrides.Add (abr.IManagedRegistrar_LookupTypeId); + var body = lookupTypeMethod.CreateBody (out var il); + + // The current implementation does something like this: + // + // if (RuntimeTypeHandle.Equals (handle, ) + // return 0; + // if (RuntimeTypeHandle.Equals (handle, ) + // return 1; + // + // This can potentially be improved to do a dictionary lookup. The downside would be higher memory usage + // (a simple implementation that's just a series of if conditions doesn't consume any dirty memory). + // One idea could be to use a dictionary lookup if we have more than X types, and then fall back to the + // linear search otherwise. + + for (var i = 0; i < types.Count; i++) { + il.Emit (OpCodes.Ldarga_S, handleParameter); + il.Emit (OpCodes.Ldtoken, types [i].Reference); + il.Emit (OpCodes.Call, abr.RuntimeTypeHandle_Equals); + var falseTarget = il.Create (OpCodes.Nop); + il.Emit (OpCodes.Brfalse_S, falseTarget); + il.Emit (OpCodes.Ldc_I4, i); + il.Emit (OpCodes.Ret); + il.Append (falseTarget); + } + + // No match, return -1 + il.Emit (OpCodes.Ldc_I4_M1); + il.Emit (OpCodes.Ret); + } + + void GenerateLookupType (AssemblyTrampolineInfo infos, TypeDefinition registrarType, List types) + { + var lookupTypeMethod = registrarType.AddMethod ("LookupType", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_RuntimeTypeHandle); + lookupTypeMethod.AddParameter ("id", abr.System_UInt32); + lookupTypeMethod.Overrides.Add (abr.IManagedRegistrar_LookupType); + var body = lookupTypeMethod.CreateBody (out var il); + + // We know all the IDs are contiguous, so we can just do a switch statement. + // + // The current implementation does something like this: + // switch (id) { + // case 0: return ; + // case 1: return ; + // default: return default (RuntimeTypeHandle); + // } + + var targets = new Instruction [types.Count]; + + for (var i = 0; i < targets.Length; i++) { + targets [i] = Instruction.Create (OpCodes.Ldtoken, types [i].Reference); + var td = types [i].Definition; + if (IsTrimmed (td)) + throw ErrorHelper.CreateError (99, $"Trying to add the type {td.FullName} to the registrar's lookup tables, but it's been trimmed away."); + } + + il.Emit (OpCodes.Ldarg_1); + il.Emit (OpCodes.Switch, targets); + for (var i = 0; i < targets.Length; i++) { + il.Append (targets [i]); + il.Emit (OpCodes.Ret); + } + + // return default (RuntimeTypeHandle) + var temporary = body.AddVariable (abr.System_RuntimeTypeHandle); + il.Emit (OpCodes.Ldloca, temporary); + il.Emit (OpCodes.Initobj, abr.System_RuntimeTypeHandle); + il.Emit (OpCodes.Ldloc, temporary); + il.Emit (OpCodes.Ret); + } + + void GenerateRegisterWrapperTypes (TypeDefinition type) + { + var method = type.AddMethod ("RegisterWrapperTypes", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_Void); + method.AddParameter ("type", abr.System_Collections_Generic_Dictionary2.CreateGenericInstanceType (abr.System_RuntimeTypeHandle, abr.System_RuntimeTypeHandle)); + method.Overrides.Add (abr.IManagedRegistrar_RegisterWrapperTypes); + method.CreateBody (out var il); + + // Find all the protocol interfaces that are defined in the current assembly, and their corresponding wrapper type, + // and add the pair to the dictionary. + var addMethodReference = abr.System_Collections_Generic_Dictionary2.CreateMethodReferenceOnGenericType (abr.Dictionary2_Add, abr.System_RuntimeTypeHandle, abr.System_RuntimeTypeHandle); + var currentTypes = StaticRegistrar.Types.Where (v => v.Value.Type.Resolve ().Module.Assembly == abr.CurrentAssembly); + foreach (var ct in currentTypes) { + if (!ct.Value.IsProtocol) + continue; + if (ct.Value.ProtocolWrapperType is null) + continue; + + // If both the protocol interface type and the wrapper type are trimmed away, skip them. + var keyMarked = !IsTrimmed (ct.Key.Resolve ()); + var wrapperTypeMarked = !IsTrimmed (ct.Value.ProtocolWrapperType.Resolve ()); + if (!keyMarked && !wrapperTypeMarked) + continue; + // If only one of them is trimmed, throw an error + if (keyMarked ^ wrapperTypeMarked) + throw ErrorHelper.CreateError (99, $"Mismatched trimming results between the protocol interface {ct.Key.FullName} (trimmed: {!keyMarked}) and its wrapper type {ct.Value.ProtocolWrapperType.FullName} (trimmed: {!wrapperTypeMarked})"); + + il.Emit (OpCodes.Ldarg_1); + il.Emit (OpCodes.Ldtoken, type.Module.ImportReference (ct.Key)); + il.Emit (OpCodes.Ldtoken, type.Module.ImportReference (ct.Value.ProtocolWrapperType)); + il.Emit (OpCodes.Call, addMethodReference); + } + + il.Emit (OpCodes.Ret); + } + + void GenerateLookupUnmanagedFunction (TypeDefinition registrar_type, IList trampolineInfos) + { + MethodDefinition? lookupMethods = null; + if (App.IsAOTCompiled (abr.CurrentAssembly.Name.Name)) { + // Don't generate lookup code, because native code will call the EntryPoint for the UnmanagedCallerOnly methods directly. + Driver.Log (9, $"Not generating method lookup code for {abr.CurrentAssembly.Name.Name}, because it's AOT compiled"); + } else if (trampolineInfos.Count > 0) { + // All the methods in a given assembly will have consecutive IDs (but might not start at 0). + if (trampolineInfos.First ().Id + trampolineInfos.Count - 1 != trampolineInfos.Last ().Id) + throw ErrorHelper.CreateError (99, $"Invalid ID range: {trampolineInfos.First ().Id} + {trampolineInfos.Count - 1} != {trampolineInfos.Last ().Id}"); + + const int methodsPerLevel = 10; + var levels = (int) Math.Ceiling (Math.Log (trampolineInfos.Count, methodsPerLevel)); + levels = levels == 0 ? 1 : levels; + GenerateLookupMethods (registrar_type, trampolineInfos, methodsPerLevel, 1, levels, 0, trampolineInfos.Count - 1, out lookupMethods); + } + + var method = registrar_type.AddMethod ("LookupUnmanagedFunction", MethodAttributes.Private | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig, abr.System_IntPtr); + method.AddParameter ("symbol", abr.System_String); + method.AddParameter ("id", abr.System_Int32); + method.Overrides.Add (abr.IManagedRegistrar_LookupUnmanagedFunction); + method.CreateBody (out var il); + if (lookupMethods is null) { + il.Emit (OpCodes.Ldc_I4_M1); + il.Emit (OpCodes.Conv_I); + } else { + il.Emit (OpCodes.Ldarg_1); + il.Emit (OpCodes.Ldarg_2); + il.Emit (OpCodes.Call, lookupMethods); + } + il.Emit (OpCodes.Ret); + } + + // If WrappedLook is true we'll wrap the ldftn instruction in a separate method, which can be useful for debugging, + // because if there's a problem with the IL in the lookup method, it can be hard to figure out which ldftn + // instruction is causing the problem (because the JIT will just fail when compiling the method, not necessarily + // saying which instruction is broken). + static bool? wrappedLookup; + static bool WrappedLookup { + get { + if (!wrappedLookup.HasValue) + wrappedLookup = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("XAMARIN_MSR_WRAPPED_LOOKUP")); + return wrappedLookup.Value; + } + } + MethodDefinition GenerateLookupMethods (TypeDefinition type, IList trampolineInfos, int methodsPerLevel, int level, int levels, int startIndex, int endIndex, out MethodDefinition method) + { + if (startIndex > endIndex) + throw ErrorHelper.CreateError (99, $"startIndex ({startIndex}) can't be higher than endIndex ({endIndex})"); + + var startId = trampolineInfos [startIndex].Id; + var name = level == 1 ? "LookupUnmanagedFunctionImpl" : $"LookupUnmanagedFunction_{level}_{levels}__{startIndex}_{endIndex}__"; + method = type.AddMethod (name, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, abr.System_IntPtr); + method.AddParameter ("symbol", abr.System_String); + method.AddParameter ("id", abr.System_Int32); + method.CreateBody (out var il); + + if (level == levels) { + // This is the leaf method where we do the actual lookup. + // + // The code is something like this: + // switch (id - ) { + // case 0: return ; + // case 1: return ; + // case : return ; + // default: return -1; + // } + var targetCount = endIndex - startIndex + 1; + var targets = new Instruction [targetCount]; + for (var i = 0; i < targets.Length; i++) { + var ti = trampolineInfos [startIndex + i]; + var md = ti.Trampoline; + var mr = abr.CurrentAssembly.MainModule.ImportReference (md); + if (WrappedLookup) { + var wrappedLookup = type.AddMethod (name + ti.Id, MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, abr.System_IntPtr); + wrappedLookup.CreateBody (out var wrappedIl); + wrappedIl.Emit (OpCodes.Ldftn, mr); + wrappedIl.Emit (OpCodes.Ret); + + targets [i] = Instruction.Create (OpCodes.Call, wrappedLookup); + } else { + targets [i] = Instruction.Create (OpCodes.Ldftn, mr); + } + } + + il.Emit (OpCodes.Ldarg_1); + if (startId != 0) { + il.Emit (OpCodes.Ldc_I4, startId); + il.Emit (OpCodes.Sub_Ovf_Un); + } + il.Emit (OpCodes.Switch, targets); + for (var k = 0; k < targetCount; k++) { + il.Append (targets [k]); + il.Emit (OpCodes.Ret); + } + } else { + // This is an intermediate method to not have too many ldftn instructions in a single method (it takes a long time to JIT). + var chunkSize = (int) Math.Pow (methodsPerLevel, levels - level); + + // Some validation + if (level == 1) { + if (chunkSize * methodsPerLevel < trampolineInfos.Count) + throw ErrorHelper.CreateError (99, $"chunkSize ({chunkSize}) * methodsPerLevel ({methodsPerLevel}) < trampolineInfos.Count {trampolineInfos.Count}"); + } + + // The code is something like this: + // switch ((id - ) / ) { + // case 0: return Lookup_1_2__0_10__ (symbol, id); + // case 1: return Lookup_1_2__11_20__ (symbol, id); + // case ... + // default: return -1; + // } + + var count = endIndex - startIndex + 1; + var chunks = (int) Math.Ceiling (count / (double) chunkSize); + var targets = new Instruction [chunks]; + + var lookupMethods = new MethodDefinition [targets.Length]; + for (var i = 0; i < targets.Length; i++) { + var subStartIndex = startIndex + (chunkSize) * i; + var subEndIndex = subStartIndex + (chunkSize) - 1; + if (subEndIndex > endIndex) + subEndIndex = endIndex; + var md = GenerateLookupMethods (type, trampolineInfos, methodsPerLevel, level + 1, levels, subStartIndex, subEndIndex, out _); + lookupMethods [i] = md; + targets [i] = Instruction.Create (OpCodes.Ldarg_0); + } + + il.Emit (OpCodes.Ldarg_1); + if (startId != 0) { + il.Emit (OpCodes.Ldc_I4, startId); + il.Emit (OpCodes.Sub_Ovf_Un); + } + il.Emit (OpCodes.Ldc_I4, chunkSize); + il.Emit (OpCodes.Div); + il.Emit (OpCodes.Switch, targets); + for (var k = 0; k < targets.Length; k++) { + il.Append (targets [k]); // OpCodes.Ldarg_0 + il.Emit (OpCodes.Ldarg_1); + il.Emit (OpCodes.Call, lookupMethods [k]); + il.Emit (OpCodes.Ret); + } + } + + // no hit? this shouldn't happen + il.Emit (OpCodes.Ldc_I4_M1); + il.Emit (OpCodes.Conv_I); + il.Emit (OpCodes.Ret); + + return method; + } + + 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) + { + if (type.IsNested) { + type.IsNestedPublic = true; + EnsureVisible (caller, type.DeclaringType); + } else { + type.IsPublic = true; + } + } + + StaticRegistrar StaticRegistrar { + get { return DerivedLinkContext.StaticRegistrar; } + } + } +} + diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs new file mode 100644 index 0000000000..33ff8b81e2 --- /dev/null +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -0,0 +1,1240 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +using Xamarin.Bundler; +using Xamarin.Utils; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker; +using Mono.Tuner; + +using ObjCRuntime; +using Registrar; +using System.Globalization; + +#nullable enable + +namespace Xamarin.Linker { + // Class to contain (trampoline) info for every assembly in the app bundle + public class AssemblyTrampolineInfos : Dictionary { + Dictionary? map; + public bool TryFindInfo (MethodDefinition method, [NotNullWhen (true)] out TrampolineInfo? info) + { + if (map is null) { + map = new Dictionary (); + foreach (var kvp in this) { + foreach (var ai in kvp.Value) { + map.Add (ai.Target, ai); + } + } + } + return map.TryGetValue (method, out info); + } + } + + // Class to contain all the trampoline infos for an assembly + // Also between a type and its ID. + public class AssemblyTrampolineInfo : List { + Dictionary registered_type_map = new (); + + public TypeDefinition? RegistrarType; + + public void RegisterType (TypeDefinition td, uint id) + { + registered_type_map.Add (td, id); + } + + public bool TryGetRegisteredTypeIndex (TypeDefinition td, out uint id) + { + return registered_type_map.TryGetValue (td, out id); + } + + public void SetIds () + { + for (var i = 0; i < Count; i++) + this [i].Id = i; + } + } + + // Class to contain info for each exported method, with its UCO trampoline. + public class TrampolineInfo { + public MethodDefinition Trampoline; + public MethodDefinition Target; + public string UnmanagedCallersOnlyEntryPoint; + public int Id; + + public TrampolineInfo (MethodDefinition trampoline, MethodDefinition target, string entryPoint) + { + this.Trampoline = trampoline; + this.Target = target; + this.UnmanagedCallersOnlyEntryPoint = entryPoint; + this.Id = -1; + } + } + + public class ManagedRegistrarStep : ConfigurationAwareStep { + protected override string Name { get; } = "ManagedRegistrar"; + protected override int ErrorCode { get; } = 2430; + + AppBundleRewriter abr { get { return Configuration.AppBundleRewriter; } } + List exceptions = new List (); + + void AddException (Exception exception) + { + if (exceptions is null) + exceptions = new List (); + exceptions.Add (exception); + } + + protected override void TryProcess () + { + base.TryProcess (); + + App.SelectRegistrar (); + if (App.Registrar != RegistrarMode.ManagedStatic) + return; + + Configuration.Target.StaticRegistrar.Register (Configuration.GetNonDeletedAssemblies (this)); + } + + protected override void TryEndProcess (out List? exceptions) + { + base.TryEndProcess (); + + if (App.Registrar != RegistrarMode.ManagedStatic) { + exceptions = null; + return; + } + + // Report back any exceptions that occurred during the processing. + exceptions = this.exceptions; + + // Mark some stuff we use later on. + abr.SetCurrentAssembly (abr.PlatformAssembly); + Annotations.Mark (abr.RegistrarHelper_Register.Resolve ()); + abr.ClearCurrentAssembly (); + } + + protected override void TryProcessAssembly (AssemblyDefinition assembly) + { + base.TryProcessAssembly (assembly); + + if (App.Registrar != RegistrarMode.ManagedStatic) + return; + + if (Annotations.GetAction (assembly) == AssemblyAction.Delete) + return; + + // No SDK assemblies will have anything we need to register + if (Configuration.Profile.IsSdkAssembly (assembly)) + return; + + if (!assembly.MainModule.HasAssemblyReferences) + return; + + // In fact, unless an assembly references our platform assembly, then it won't have anything we need to register + if (!Configuration.Profile.IsProductAssembly (assembly) && !assembly.MainModule.AssemblyReferences.Any (v => Configuration.Profile.IsProductAssembly (v.Name))) + return; + + if (!assembly.MainModule.HasTypes) + return; + + abr.SetCurrentAssembly (assembly); + + var current_trampoline_lists = new AssemblyTrampolineInfo (); + Configuration.AssemblyTrampolineInfos [assembly] = current_trampoline_lists; + + var modified = false; + foreach (var type in assembly.MainModule.Types) + modified |= ProcessType (type, current_trampoline_lists); + + // Make sure the linker saves any changes in the assembly. + if (modified) { + DerivedLinkContext.Annotations.SetCustomAnnotation ("ManagedRegistrarStep", assembly, current_trampoline_lists); + abr.SaveCurrentAssembly (); + } + + abr.ClearCurrentAssembly (); + } + + bool ProcessType (TypeDefinition type, AssemblyTrampolineInfo infos) + { + var modified = false; + if (type.HasNestedTypes) { + foreach (var nested in type.NestedTypes) + modified |= ProcessType (nested, infos); + } + + // Figure out if there are any types we need to process + var process = false; + + process |= IsNSObject (type); + process |= StaticRegistrar.GetCategoryAttribute (type) is not null; + + var registerAttribute = StaticRegistrar.GetRegisterAttribute (type); + if (registerAttribute is not null && registerAttribute.IsWrapper) + return modified; + + if (!process) + return modified; + + // Figure out if there are any methods we need to process + var methods_to_wrap = new HashSet (); + if (type.HasMethods) { + foreach (var method in type.Methods) + ProcessMethod (method, methods_to_wrap); + } + + if (type.HasProperties) { + foreach (var prop in type.Properties) { + ProcessProperty (prop, methods_to_wrap); + } + } + + // Create an UnmanagedCallersOnly method for each method we need to wrap + foreach (var method in methods_to_wrap) { + try { + CreateUnmanagedCallersMethod (method, infos); + } catch (Exception e) { + AddException (ErrorHelper.CreateError (99, e, "Failed to create an UnmanagedCallersOnly trampoline for {0}: {1}", method.FullName, e.Message)); + } + } + + return true; + } + + void ProcessMethod (MethodDefinition method, HashSet methods_to_wrap) + { + if (!(method.IsConstructor && !method.IsStatic)) { + var ea = StaticRegistrar.GetExportAttribute (method); + if (ea is null && !method.IsVirtual) + return; + } + + if (!StaticRegistrar.TryFindMethod (method, out _)) { + // If the registrar doesn't know about a method, then we don't need to generate an UnmanagedCallersOnly trampoline for it + return; + } + + methods_to_wrap.Add (method); + } + + void ProcessProperty (PropertyDefinition property, HashSet methods_to_wrap) + { + var ea = StaticRegistrar.GetExportAttribute (property); + if (ea is null) + return; + + if (property.GetMethod is not null) + methods_to_wrap.Add (property.GetMethod); + + if (property.SetMethod is not null) + methods_to_wrap.Add (property.SetMethod); + } + + static string Sanitize (string str) + { + str = str.Replace ('.', '_'); + str = str.Replace ('/', '_'); + str = str.Replace ('`', '_'); + str = str.Replace ('<', '_'); + str = str.Replace ('>', '_'); + str = str.Replace ('$', '_'); + str = str.Replace ('@', '_'); + str = StaticRegistrar.EncodeNonAsciiCharacters (str); + str = str.Replace ('\\', '_'); + return str; + } + + // Set the XAMARIN_MSR_TRACE environment variable at build time to inject tracing statements. + // Note that the tracing is quite basic, because we don't want to add a unique string to + // each method we emit, because there's a fairly low limit in the IL file format for constant + // strings - around 4mb IIRC - so we're emitting a call to a method that will do most of the + // heavy work. + // Note that Cecil doesn't complain if a file has too many string constants, it will happily + // emit garbage and really weird things start happening at runtime. + bool? trace; + void Trace (ILProcessor il, string message) + { + if (!trace.HasValue) + trace = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("XAMARIN_MSR_TRACE")); + if (trace.Value) { + il.Emit (OpCodes.Ldstr, message); + il.Emit (OpCodes.Call, abr.Runtime_TraceCaller); + } + } + + int counter; + void CreateUnmanagedCallersMethod (MethodDefinition method, AssemblyTrampolineInfo infos) + { + var baseMethod = StaticRegistrar.GetBaseMethodInTypeHierarchy (method); + var placeholderType = abr.System_IntPtr; + ParameterDefinition? callSuperParameter = null; + VariableDefinition? returnVariable = null; + var leaveTryInstructions = new List (); + var isVoid = method.ReturnType.Is ("System", "Void"); + + var name = $"callback_{counter++}_{Sanitize (method.DeclaringType.FullName)}_{Sanitize (method.Name)}"; + + var callbackType = method.DeclaringType.NestedTypes.SingleOrDefault (v => v.Name == "__Registrar_Callbacks__"); + if (callbackType is null) { + callbackType = new TypeDefinition (string.Empty, "__Registrar_Callbacks__", TypeAttributes.NestedPrivate | TypeAttributes.Sealed | TypeAttributes.Class); + callbackType.BaseType = abr.System_Object; + method.DeclaringType.NestedTypes.Add (callbackType); + } + + var callback = callbackType.AddMethod (name, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, placeholderType); + callback.CustomAttributes.Add (CreateUnmanagedCallersAttribute (name)); + infos.Add (new TrampolineInfo (callback, method, name)); + + // If the target method is marked, then we must mark the trampoline as well. + method.CustomAttributes.Add (CreateDynamicDependencyAttribute (callbackType, callback.Name)); + + var body = callback.CreateBody (out var il); + var placeholderInstruction = il.Create (OpCodes.Nop); + var placeholderNextInstruction = il.Create (OpCodes.Nop); + var postProcessing = new List (); + var categoryAttribute = StaticRegistrar.GetCategoryAttribute (method.DeclaringType); + var isCategory = categoryAttribute is not null; + var isInstanceCategory = isCategory && StaticRegistrar.HasThisAttribute (method); + var isGeneric = method.DeclaringType.HasGenericParameters; + var isDynamicInvoke = isGeneric; + VariableDefinition? selfVariable = null; + + Trace (il, $"ENTER"); + + callback.AddParameter ("pobj", abr.System_IntPtr); + + if (!isVoid || method.IsConstructor) + returnVariable = body.AddVariable (placeholderType); + + if (isGeneric) { + if (method.IsStatic) + throw ErrorHelper.CreateError (4130 /* The registrar cannot export static methods in generic classes ('{0}'). */, method.FullName); + + if (!method.IsConstructor) { + il.Emit (OpCodes.Ldtoken, method); + + il.Emit (OpCodes.Ldarg_0); + EmitConversion (method, il, method.DeclaringType, true, -1, out var nativeType, postProcessing, selfVariable, isDynamicInvoke: isDynamicInvoke); + + selfVariable = body.AddVariable (abr.System_Object); + il.Emit (OpCodes.Stloc, selfVariable); + il.Emit (OpCodes.Ldloc, selfVariable); + il.Emit (OpCodes.Ldtoken, method.DeclaringType); + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Call, abr.Runtime_FindClosedMethod); + } + } + + // Our code emission is intermingled with creating the method signature (the code to convert between native and managed values is also the best location + // to determine exactly which are the corresponding native and managed types in the method signatures). Unfortunately we might need to skip code emission + // after a certain point (if we detected a failure scenario where we're just throwing an exception, there's no need to keep generating code for that + // particular scenario), but we still need to create the proper method signatures. Thus this hack: we keep emitting code, but the last important instruction + // and later on remove everything after this instruction. Maybe at a later point I'll figure out a way to make the code emission conditional without + // littering the logic with conditional statements. + Instruction? skipEverythingAfter = null; + if (isInstanceCategory) { + il.Emit (OpCodes.Ldarg_0); + EmitConversion (method, il, method.Parameters [0].ParameterType, true, 0, out var nativeType, postProcessing, selfVariable, isDynamicInvoke: isDynamicInvoke); + } else if (method.IsStatic) { + // nothing to do + } else if (method.IsConstructor) { + callSuperParameter = new ParameterDefinition ("call_super", ParameterAttributes.None, new PointerType (abr.System_Byte)); + var postLeaveInstructionPlaceholder = il.Create (OpCodes.Nop); + // if (Runtime.HasNSObject (p0)) { + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Call, abr.Runtime_HasNSObject); + il.Emit (OpCodes.Brfalse, postLeaveInstructionPlaceholder); + var postLeaveBranch = il.Body.Instructions.Last (); + // *call_super = 1; + il.Emit (OpCodes.Ldarg, callSuperParameter); + il.Emit (OpCodes.Ldc_I4_1); + il.Emit (OpCodes.Stind_I1); + // return rv; + il.Emit (OpCodes.Ldarg_0); + il.Emit (OpCodes.Call, abr.NativeObject_op_Implicit_NativeHandle); + il.Emit (OpCodes.Stloc, returnVariable); + il.Emit (OpCodes.Leave, placeholderInstruction); + // } + leaveTryInstructions.Add (il.Body.Instructions.Last ()); + + if (isGeneric) { + il.Emit (OpCodes.Ldc_I4, 4133); + postLeaveBranch.Operand = il.Body.Instructions.Last (); + il.Emit (OpCodes.Ldstr, $"Cannot construct an instance of the type '{method.DeclaringType.FullName}' from Objective-C because the type is generic."); + il.Emit (OpCodes.Call, abr.Runtime_CreateRuntimeException); + il.Emit (OpCodes.Throw); + // We're throwing an exception, so there's no need for any more code. + skipEverythingAfter = il.Body.Instructions.Last (); + } else { + + il.Emit (OpCodes.Ldarg_0); + postLeaveBranch.Operand = il.Body.Instructions.Last (); + var git = new GenericInstanceMethod (abr.NSObject_AllocateNSObject); + git.GenericArguments.Add (method.DeclaringType); + il.Emit (OpCodes.Call, git); + il.Emit (OpCodes.Dup); // this is for the call to ObjCRuntime.NativeObjectExtensions::GetHandle after the call to the constructor + } + } else { + // instance method + il.Emit (OpCodes.Ldarg_0); + EmitConversion (method, il, method.DeclaringType, true, -1, out var nativeType, postProcessing, selfVariable, isDynamicInvoke: isDynamicInvoke); + } + + callback.AddParameter ("sel", abr.System_IntPtr); + + var managedParameterCount = 0; + var nativeParameterOffset = isInstanceCategory ? 1 : 2; + var parameterStart = isInstanceCategory ? 1 : 0; + if (method.HasParameters) + managedParameterCount = method.Parameters.Count; + + if (isGeneric) { + il.Emit (OpCodes.Ldc_I4, managedParameterCount); + il.Emit (OpCodes.Newarr, abr.System_Object); + } + + if (method.HasParameters) { + for (var p = parameterStart; p < managedParameterCount; p++) { + var nativeParameter = callback.AddParameter ($"p{p}", placeholderType); + var nativeParameterIndex = p + nativeParameterOffset; + var managedParameterType = method.Parameters [p].ParameterType; + var baseParameter = baseMethod.Parameters [p]; + var isOutParameter = IsOutParameter (method, p, baseParameter); + if (isDynamicInvoke && !isOutParameter) { + if (parameterStart != 0) { + AddException (ErrorHelper.CreateError (99, $"Unexpected parameterStart {parameterStart} in method {GetMethodSignature (method)} for parameter {p}")); + continue; + } + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Ldc_I4, p); + } + if (!isOutParameter) { + il.EmitLoadArgument (nativeParameterIndex); + } + if (EmitConversion (method, il, managedParameterType, true, p, out var nativeType, postProcessing, selfVariable, isOutParameter, nativeParameterIndex, isDynamicInvoke)) { + nativeParameter.ParameterType = nativeType; + } else { + nativeParameter.ParameterType = placeholderType; + AddException (ErrorHelper.CreateError (99, "Unable to emit conversion for parameter {2} of type {0}. Method: {1}", method.Parameters [p].ParameterType, GetMethodSignatureWithSourceCode (method), p)); + } + if (isDynamicInvoke && !isOutParameter) { + if (managedParameterType.IsValueType) + il.Emit (OpCodes.Box, managedParameterType); + il.Emit (OpCodes.Stelem_Ref); + } + } + } + + if (callSuperParameter is not null) + callback.Parameters.Add (callSuperParameter); + + callback.AddParameter ("exception_gchandle", new PointerType (abr.System_IntPtr)); + + var isDynamicInvokeReturnType = false; + if (isGeneric) { + il.Emit (OpCodes.Call, abr.MethodBase_Invoke); + if (isVoid) { + il.Emit (OpCodes.Pop); + } else if (method.ReturnType.IsValueType) { + il.Emit (OpCodes.Unbox_Any, method.ReturnType); + } else { + isDynamicInvokeReturnType = true; + } + } else if (method.IsStatic) { + il.Emit (OpCodes.Call, method); + } else { + il.Emit (OpCodes.Callvirt, method); + } + + if (returnVariable is not null) { + if (EmitConversion (method, il, method.ReturnType, false, -1, out var nativeReturnType, postProcessing, selfVariable, isDynamicInvoke: isDynamicInvoke, isDynamicInvokeReturnType: isDynamicInvokeReturnType)) { + returnVariable.VariableType = nativeReturnType; + callback.ReturnType = nativeReturnType; + } else { + AddException (ErrorHelper.CreateError (99, "Unable to emit conversion for return value of type {0}. Method: {1}", method.ReturnType, GetMethodSignatureWithSourceCode (method))); + } + il.Emit (OpCodes.Stloc, returnVariable); + } else { + callback.ReturnType = abr.System_Void; + } + + body.Instructions.AddRange (postProcessing); + + Trace (il, $"EXIT"); + + il.Emit (OpCodes.Leave, placeholderInstruction); + leaveTryInstructions.Add (il.Body.Instructions.Last ()); + + if (skipEverythingAfter is not null) { + var skipIndex = body.Instructions.IndexOf (skipEverythingAfter); + for (var i = body.Instructions.Count - 1; i > skipIndex; i--) + body.Instructions.RemoveAt (i); + } + + AddExceptionHandler (il, returnVariable, placeholderNextInstruction, out var eh, out var leaveEHInstruction); + + // Generate code to return null/default value/void + if (returnVariable is not null) { + var returnType = returnVariable.VariableType!; + if (returnType.IsValueType) { + // return default() + il.Emit (OpCodes.Ldloca, returnVariable); + il.Emit (OpCodes.Initobj, returnType); + il.Emit (OpCodes.Ldloc, returnVariable); + } else { + il.Emit (OpCodes.Ldnull); + } + } + il.Emit (OpCodes.Ret); + + // Generate code to return the return value + Instruction leaveTryInstructionOperand; + if (returnVariable is not null) { + il.Emit (OpCodes.Ldloc, returnVariable); + leaveTryInstructionOperand = il.Body.Instructions.Last (); + il.Emit (OpCodes.Ret); + } else { + // Here we can re-use the ret instruction from the previous block. + leaveTryInstructionOperand = il.Body.Instructions.Last (); + } + + // Replace any 'placeholderNextInstruction' operands with the actual next instruction. + foreach (var instr in body.Instructions) { + if (object.ReferenceEquals (instr.Operand, placeholderNextInstruction)) + instr.Operand = instr.Next; + } + + foreach (var instr in leaveTryInstructions) + instr.Operand = leaveTryInstructionOperand; + eh.HandlerEnd = (Instruction) leaveEHInstruction.Operand; + } + + void AddExceptionHandler (ILProcessor il, VariableDefinition? returnVariable, Instruction placeholderNextInstruction, out ExceptionHandler eh, out Instruction leaveEHInstruction) + { + var body = il.Body; + var method = body.Method; + + // Exception handler + eh = new ExceptionHandler (ExceptionHandlerType.Catch); + eh.CatchType = abr.System_Exception; + eh.TryStart = il.Body.Instructions [0]; + il.Body.ExceptionHandlers.Add (eh); + + var exceptionVariable = body.AddVariable (abr.System_Exception); + il.Emit (OpCodes.Stloc, exceptionVariable); + eh.HandlerStart = il.Body.Instructions.Last (); + eh.TryEnd = eh.HandlerStart; + il.Emit (OpCodes.Ldarg, method.Parameters.Count - 1); + il.Emit (OpCodes.Ldloc, exceptionVariable); + il.Emit (OpCodes.Call, abr.Runtime_AllocGCHandle); + il.Emit (OpCodes.Stind_I); + Trace (il, $"EXCEPTION"); + il.Emit (OpCodes.Leave, placeholderNextInstruction); + leaveEHInstruction = body.Instructions.Last (); + } + + 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 ())})"; + } + + static string GetMethodSignatureWithSourceCode (MethodDefinition method) + { + var rv = GetMethodSignature (method); + if (method.HasBody && method.DebugInformation.HasSequencePoints) { + var seq = method.DebugInformation.SequencePoints [0]; + rv += " " + seq.Document.Url + ":" + seq.StartLine.ToString () + " "; + } + return rv; + } + + bool IsNSObject (TypeReference type) + { + if (type is ArrayType) + return false; + + if (type is ByReferenceType) + return false; + + if (type is PointerType) + return false; + + if (type is GenericParameter) + return false; + + return type.IsNSObject (DerivedLinkContext); + } + + BindAsAttribute? GetBindAsAttribute (MethodDefinition method, int parameter) + { + if (StaticRegistrar.IsPropertyAccessor (method, out var property)) { + return StaticRegistrar.GetBindAsAttribute (property); + } else { + return StaticRegistrar.GetBindAsAttribute (method, parameter); + } + } + + // This emits a conversion between the native and the managed representation of a parameter or return value, + // and returns the corresponding native type. The returned nativeType will (must) be a blittable type. + bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type, bool toManaged, int parameter, [NotNullWhen (true)] out TypeReference? nativeType, List postProcessing, VariableDefinition? selfVariable, bool isOutParameter = false, int nativeParameterIndex = -1, bool isDynamicInvoke = false, bool isDynamicInvokeReturnType = false) + { + nativeType = null; + + if (!(parameter == -1 && !method.IsStatic && method.DeclaringType == type)) { + var bindAsAttribute = GetBindAsAttribute (method, parameter); + if (bindAsAttribute is not null) { + if (toManaged) { + GenerateConversionToManaged (method, il, bindAsAttribute.OriginalType, type, "descriptiveMethodName", parameter, out nativeType); + return true; + } else { + if (isDynamicInvokeReturnType) + il.Emit (OpCodes.Castclass, type); + GenerateConversionToNative (method, il, type, bindAsAttribute.OriginalType, "descriptiveMethodName", out nativeType); + return true; + } + } + } + + if (type.Is ("System", "Void")) { + if (parameter == -1 && method.IsConstructor) { + if (toManaged) { + AddException (ErrorHelper.CreateError (99, "Don't know how (9) to convert ctor. Method: {0}", GetMethodSignatureWithSourceCode (method))); + } else { + il.Emit (OpCodes.Call, abr.NativeObjectExtensions_GetHandle); + nativeType = abr.ObjCRuntime_NativeHandle; + return true; + } + } + AddException (ErrorHelper.CreateError (99, "Can't convert System.Void. Method: {0}", GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (type.IsValueType) { + if (type.Is ("System", "Boolean")) { + // no conversion necessary either way + nativeType = abr.System_Byte; + return true; + } + + if (type.Is ("System", "Char")) { + // no conversion necessary either way + nativeType = abr.System_UInt16; + return true; + } + + if (isDynamicInvokeReturnType) + AddException (ErrorHelper.CreateError (99, "Unexpected value type {0}: can't result from dynamic invoke. Method: {1}", type, GetMethodSignatureWithSourceCode (method))); + + // no conversion necessary if we're any other value type + nativeType = type; + return true; + } + + if (type is PointerType pt) { + var elementType = pt.ElementType; + if (!elementType.IsValueType) + AddException (ErrorHelper.CreateError (99, "Unexpected pointer type {0}: must be a value type. Method: {1}", type, GetMethodSignatureWithSourceCode (method))); + if (isDynamicInvokeReturnType) + AddException (ErrorHelper.CreateError (99, "Unexpected pointer type {0}: can't result from dynamic invoke. Method: {1}", type, GetMethodSignatureWithSourceCode (method))); + // no conversion necessary either way + nativeType = type; + return true; + } + + if (type is ByReferenceType brt) { + if (toManaged) { + var elementType = brt.ElementType; + if (elementType is GenericParameter gp) { + if (!StaticRegistrar.VerifyIsConstrainedToNSObject (gp, out var constrained)) { + AddException (ErrorHelper.CreateError (99, "Incorrectly constrained generic parameter. Method: {0}", GetMethodSignatureWithSourceCode (method))); + return false; + } + elementType = constrained; + } + + if (elementType.IsValueType) { + // call !!0& [System.Runtime]System.Runtime.CompilerServices.Unsafe::AsRef(void*) + var mr = new GenericInstanceMethod (abr.CurrentAssembly.MainModule.ImportReference (abr.Unsafe_AsRef)); + if (isOutParameter) + il.EmitLoadArgument (nativeParameterIndex); + mr.GenericArguments.Add (elementType); + il.Emit (OpCodes.Call, mr); + // reference types aren't blittable, so the managed signature must have be a pointer type + nativeType = new PointerType (elementType); + return true; + } + + MethodReference? native_to_managed = null; + MethodReference? managed_to_native = null; + Instruction? addBeforeNativeToManagedCall = null; + + if (elementType is ArrayType elementArrayType) { + if (elementArrayType.ElementType.Is ("System", "String")) { + native_to_managed = abr.RegistrarHelper_NSArray_string_native_to_managed; + managed_to_native = abr.RegistrarHelper_NSArray_string_managed_to_native; + } else { + native_to_managed = abr.RegistrarHelper_NSArray_native_to_managed.CreateGenericInstanceMethod (elementArrayType.ElementType); + managed_to_native = abr.RegistrarHelper_NSArray_managed_to_native.CreateGenericInstanceMethod (elementArrayType.ElementType); + } + nativeType = new PointerType (abr.ObjCRuntime_NativeHandle); + } else if (elementType.Is ("System", "String")) { + native_to_managed = abr.RegistrarHelper_string_native_to_managed; + managed_to_native = abr.RegistrarHelper_string_managed_to_native; + nativeType = new PointerType (abr.ObjCRuntime_NativeHandle); + } else if (elementType.IsNSObject (DerivedLinkContext)) { + native_to_managed = abr.RegistrarHelper_NSObject_native_to_managed.CreateGenericInstanceMethod (elementType); + managed_to_native = abr.RegistrarHelper_NSObject_managed_to_native; + nativeType = new PointerType (abr.System_IntPtr); + } else if (StaticRegistrar.IsNativeObject (DerivedLinkContext, elementType)) { + var nativeObjType = StaticRegistrar.GetInstantiableType (type.Resolve (), exceptions, GetMethodSignature (method)); + addBeforeNativeToManagedCall = il.Create (OpCodes.Ldtoken, method.Module.ImportReference (nativeObjType)); // implementation type + native_to_managed = abr.RegistrarHelper_INativeObject_native_to_managed.CreateGenericInstanceMethod (elementType); + managed_to_native = abr.RegistrarHelper_INativeObject_managed_to_native; + nativeType = new PointerType (abr.System_IntPtr); + } else { + AddException (ErrorHelper.CreateError (99, "Don't know how (4) to convert {0} between managed and native code. Method: {1}", type.FullName, GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (managed_to_native is not null && native_to_managed is not null) { + EnsureVisible (method, managed_to_native); + EnsureVisible (method, native_to_managed); + + var indirectVariable = il.Body.AddVariable (elementType); + // We store a copy of the value in a separate variable, to detect if it changes. + var copyIndirectVariable = il.Body.AddVariable (elementType); + + // We don't read the input for 'out' parameters, it might be garbage. + if (!isOutParameter) { + il.Emit (OpCodes.Ldloca, indirectVariable); + il.Emit (OpCodes.Ldloca, copyIndirectVariable); + if (addBeforeNativeToManagedCall is not null) + il.Append (addBeforeNativeToManagedCall); + il.Emit (OpCodes.Call, native_to_managed); + if (isDynamicInvoke) { + il.Emit (OpCodes.Ldloc, indirectVariable); + } else { + il.Emit (OpCodes.Ldloca, indirectVariable); + } + } else { + if (!isDynamicInvoke) + il.Emit (OpCodes.Ldloca, indirectVariable); + } + postProcessing.Add (il.CreateLoadArgument (nativeParameterIndex)); + postProcessing.Add (il.Create (OpCodes.Ldloc, indirectVariable)); + postProcessing.Add (il.Create (OpCodes.Ldloc, copyIndirectVariable)); + postProcessing.Add (il.CreateLdc (isOutParameter)); + postProcessing.Add (il.Create (OpCodes.Call, managed_to_native)); + return true; + } + } + + AddException (ErrorHelper.CreateError (99, "Don't know how (2) to convert {0} between managed and native code. Method: {1}", type.FullName, GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (isOutParameter) { + AddException (ErrorHelper.CreateError (99, "Parameter must be ByReferenceType to be an out parameter. Method: {0}", GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (type is ArrayType at) { + var elementType = at.GetElementType (); + if (elementType.Is ("System", "String")) { + if (!toManaged && isDynamicInvokeReturnType) + il.Emit (OpCodes.Castclass, new ArrayType (abr.System_String)); + il.Emit (OpCodes.Call, toManaged ? abr.CFArray_StringArrayFromHandle : abr.RegistrarHelper_CreateCFArray); + nativeType = abr.ObjCRuntime_NativeHandle; + return true; + } + + var isGenericParameter = false; + if (elementType is GenericParameter gp) { + if (!StaticRegistrar.VerifyIsConstrainedToNSObject (gp, out var constrained)) { + AddException (ErrorHelper.CreateError (99, "Incorrectly constrained generic parameter. Method: {0}", GetMethodSignatureWithSourceCode (method))); + return false; + } + elementType = constrained; + isGenericParameter = true; + } + + var isNSObject = elementType.IsNSObject (DerivedLinkContext); + var isNativeObject = StaticRegistrar.IsNativeObject (elementType); + if (isNSObject || isNativeObject) { + if (toManaged) { + if (isGenericParameter) { + il.Emit (OpCodes.Ldloc, selfVariable); + il.Emit (OpCodes.Ldtoken, method.DeclaringType); + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Ldc_I4, parameter); + il.Emit (OpCodes.Call, abr.Runtime_FindClosedParameterType); + il.Emit (OpCodes.Call, abr.NSArray_ArrayFromHandle); + } else { + var gim = new GenericInstanceMethod (abr.NSArray_ArrayFromHandle_1); + gim.GenericArguments.Add (elementType); + il.Emit (OpCodes.Call, gim); + } + } else { + var retain = StaticRegistrar.HasReleaseAttribute (method); + il.Emit (retain ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + il.Emit (OpCodes.Call, abr.RegistrarHelper_ManagedArrayToNSArray); + } + nativeType = abr.ObjCRuntime_NativeHandle; + return true; + } + + AddException (ErrorHelper.CreateError (99, "Don't know how (3) to convert array element type {1} for array type {0} between managed and native code. Method: {2}", type.FullName, elementType.FullName, GetMethodSignatureWithSourceCode (method))); + return false; + } + + if (IsNSObject (type)) { + if (toManaged) { + var ea = StaticRegistrar.CreateExportAttribute (method); + if (ea is not null && ea.ArgumentSemantic == ArgumentSemantic.Copy) + il.Emit (OpCodes.Call, abr.Runtime_CopyAndAutorelease); + if (IsOpenType (type)) { + il.Emit (OpCodes.Call, abr.Runtime_GetNSObject__System_IntPtr); + if (!isDynamicInvoke) + AddException (ErrorHelper.CreateError (99, "Unable to call a statically resolved method 1 with object in {0} - {1} - {2}", GetMethodSignature (method), il.Body.Method.Name, type.FullName)); + + // We're calling the target method dynamically (using MethodBase.Invoke), so there's no + // need to check the type of the returned object, because MethodBase.Invoke will do type checks. + } else { + il.Emit (OpCodes.Ldarg_1); // SEL + il.Emit (OpCodes.Ldtoken, method); + il.EmitLdc (parameter == -1); // evenInFinalizerQueue + il.Emit (OpCodes.Call, abr.Runtime_GetNSObject_T___System_IntPtr_System_IntPtr_System_RuntimeMethodHandle_bool.CreateGenericInstanceMethod (type)); + var tmpVariable = il.Body.AddVariable (type); + il.Emit (OpCodes.Stloc, tmpVariable); + il.Emit (OpCodes.Ldloc, tmpVariable); + } + nativeType = abr.System_IntPtr; + } else { + if (isDynamicInvokeReturnType) + il.Emit (OpCodes.Castclass, abr.Foundation_NSObject); + + if (parameter == -1) { + var retain = StaticRegistrar.HasReleaseAttribute (method); + il.Emit (OpCodes.Dup); + if (retain) { + il.Emit (OpCodes.Call, abr.Runtime_RetainNSObject); + } else { + il.Emit (OpCodes.Call, abr.Runtime_RetainAndAutoreleaseNSObject); + } + } else { + il.Emit (OpCodes.Call, abr.NativeObjectExtensions_GetHandle); + } + nativeType = abr.ObjCRuntime_NativeHandle; + } + return true; + } + + if (StaticRegistrar.IsNativeObject (DerivedLinkContext, type)) { + if (toManaged) { + if (IsOpenType (type)) { + il.Emit (OpCodes.Call, abr.Runtime_GetNSObject__System_IntPtr); + if (!isDynamicInvoke) + AddException (ErrorHelper.CreateError (99, "Unable to call a statically resolved method 2 with object in {0} - {1} - {2}", GetMethodSignature (method), il.Body.Method.Name, type.FullName)); + + // We're calling the target method dynamically (using MethodBase.Invoke), so there's no + // need to check the type of the returned object, because MethodBase.Invoke will do type checks. + } else { + var nativeObjType = StaticRegistrar.GetInstantiableType (type.Resolve (), exceptions, GetMethodSignature (method)); + il.Emit (OpCodes.Ldc_I4_0); // false + il.Emit (OpCodes.Ldtoken, method.Module.ImportReference (type)); // target type + il.Emit (OpCodes.Call, abr.Type_GetTypeFromHandle); + il.Emit (OpCodes.Ldtoken, method.Module.ImportReference (nativeObjType)); // implementation type + il.Emit (OpCodes.Call, abr.Type_GetTypeFromHandle); + il.Emit (OpCodes.Call, abr.Runtime_GetINativeObject__IntPtr_Boolean_Type_Type); + il.Emit (OpCodes.Castclass, type); + } + nativeType = abr.System_IntPtr; + } else { + if (parameter == -1) { + var retain = StaticRegistrar.HasReleaseAttribute (method); + var isNSObject = IsNSObject (type); + + if (isDynamicInvokeReturnType) + il.Emit (OpCodes.Castclass, isNSObject ? abr.Foundation_NSObject : abr.ObjCRuntime_INativeObject); + + if (retain) { + il.Emit (OpCodes.Call, isNSObject ? abr.Runtime_RetainNSObject : abr.Runtime_RetainNativeObject); + } else { + il.Emit (OpCodes.Call, isNSObject ? abr.Runtime_RetainAndAutoreleaseNSObject : abr.Runtime_RetainAndAutoreleaseNativeObject); + } + } else { + if (isDynamicInvokeReturnType) + il.Emit (OpCodes.Castclass, abr.ObjCRuntime_INativeObject); + + il.Emit (OpCodes.Call, abr.NativeObjectExtensions_GetHandle); + } + nativeType = abr.ObjCRuntime_NativeHandle; + } + return true; + } + + if (type.Is ("System", "String")) { + if (!toManaged && isDynamicInvokeReturnType) + il.Emit (OpCodes.Castclass, abr.System_String); + + il.Emit (OpCodes.Call, toManaged ? abr.CFString_FromHandle : abr.CFString_CreateNative); + nativeType = abr.ObjCRuntime_NativeHandle; + return true; + } + + if (StaticRegistrar.IsDelegate (type.Resolve ())) { + if (!StaticRegistrar.TryFindMethod (method, out var objcMethod)) { + AddException (ErrorHelper.CreateError (99, "Unable to find method {0}", GetMethodSignature (method))); + return false; + } + if (toManaged) { + var createMethod = StaticRegistrar.GetBlockWrapperCreator (objcMethod, parameter); + if (createMethod is null) { + AddException (ErrorHelper.CreateWarning (App, 4174 /* Unable to locate the block to delegate conversion method for the method {0}'s parameter #{1}. */, method, Errors.MT4174, method.FullName, parameter + 1)); + // var blockCopy = BlockLiteral.Copy (block); + var tmpVariable = il.Body.AddVariable (abr.System_IntPtr); + il.Emit (OpCodes.Call, abr.BlockLiteral_Copy); + il.Emit (OpCodes.Stloc, tmpVariable); + // var blockWrapperCreator = Runtime.GetBlockWrapperCreator ((MethodInfo) MethodBase.GetMethodFromHandle (ldtoken ), parameter); + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Call, abr.MethodBase_GetMethodFromHandle__RuntimeMethodHandle); + il.Emit (OpCodes.Castclass, abr.System_Reflection_MethodInfo); + il.Emit (OpCodes.Ldc_I4, parameter); + il.Emit (OpCodes.Call, abr.Runtime_GetBlockWrapperCreator); + // Runtime.CreateBlockProxy (blockWrapperCreator, blockCopy) + il.Emit (OpCodes.Ldloc, tmpVariable); + il.Emit (OpCodes.Call, abr.Runtime_CreateBlockProxy); + } else { + EnsureVisible (method, createMethod); + // var blockCopy = BlockLiteral.Copy (block) + // Runtime.ReleaseBlockWhenDelegateIsCollected (blockCopy, (blockCopy)) + il.Emit (OpCodes.Call, abr.BlockLiteral_Copy); + il.Emit (OpCodes.Dup); + il.Emit (OpCodes.Call, method.Module.ImportReference (createMethod)); + il.Emit (OpCodes.Call, abr.Runtime_ReleaseBlockWhenDelegateIsCollected); + } + il.Emit (OpCodes.Castclass, method.Module.ImportReference (type)); + } else { + FieldDefinition? delegateProxyField = null; + MethodDefinition? createBlockMethod = null; + + if (!DerivedLinkContext.StaticRegistrar.TryComputeBlockSignature (method, trampolineDelegateType: type, out var exception, out var signature)) { + AddException (ErrorHelper.CreateWarning (99, exception, "Error while converting block/delegates: {0}", exception.ToString ())); + } else { + var delegateProxyType = StaticRegistrar.GetDelegateProxyType (objcMethod); + if (delegateProxyType is null) { + exceptions.Add (ErrorHelper.CreateWarning (App, 4176, method, Errors.MT4176 /* "Unable to locate the delegate to block conversion type for the return value of the method {0}." */, method.FullName)); + } else { + createBlockMethod = StaticRegistrar.GetCreateBlockMethod (delegateProxyType); + if (createBlockMethod is null) { + delegateProxyField = delegateProxyType.Fields.SingleOrDefault (v => v.Name == "Handler"); + if (delegateProxyField is null) { + AddException (ErrorHelper.CreateWarning (99, "No delegate proxy field on {0}", delegateProxyType.FullName)); + } + } + } + } + + if (isDynamicInvokeReturnType) + il.Emit (OpCodes.Castclass, abr.System_Delegate); + + // the delegate is already on the stack + if (createBlockMethod is not null) { + EnsureVisible (method, createBlockMethod); + il.Emit (OpCodes.Call, method.Module.ImportReference (createBlockMethod)); + il.Emit (OpCodes.Call, abr.RegistrarHelper_GetBlockPointer); + } else if (delegateProxyField is not null) { + EnsureVisible (method, delegateProxyField); + il.Emit (OpCodes.Ldsfld, method.Module.ImportReference (delegateProxyField)); + il.Emit (OpCodes.Ldstr, signature); + il.Emit (OpCodes.Call, abr.BlockLiteral_CreateBlockForDelegate); + } else { + il.Emit (OpCodes.Ldtoken, method); + il.Emit (OpCodes.Call, abr.RegistrarHelper_GetBlockForDelegate); + } + } + nativeType = abr.System_IntPtr; + return true; + } + + AddException (ErrorHelper.CreateError (99, "Don't know how (1) to convert {0} between managed and native code: {1}. Method: {2}", type.FullName, type.GetType ().FullName, GetMethodSignatureWithSourceCode (method))); + return false; + } + + bool IsOpenType (TypeReference tr) + { + if (tr is GenericParameter) + return true; + + if (tr is GenericInstanceType git) { + foreach (var ga in git.GenericArguments) { + if (IsOpenType (ga)) + return true; + } + return false; + } + + if (tr is TypeSpecification ts) + return IsOpenType (ts.ElementType); + + if (tr is TypeDefinition td) + return td.HasGenericParameters; + + return IsOpenType (tr.Resolve ()); + } + + void EnsureVisible (MethodDefinition caller, FieldDefinition field) + { + field.IsPublic = true; + EnsureVisible (caller, field.DeclaringType); + } + + void EnsureVisible (MethodDefinition caller, TypeDefinition type) + { + if (type.IsNested) { + type.IsNestedPublic = true; + EnsureVisible (caller, type.DeclaringType); + } else { + type.IsPublic = true; + } + } + + void EnsureVisible (MethodDefinition caller, MethodReference method) + { + var md = method.Resolve (); + md.IsPublic = true; + EnsureVisible (caller, md.DeclaringType); + } + + bool IsOutParameter (MethodDefinition method, int parameter, ParameterDefinition baseParameter) + { + return method.Parameters [parameter].IsOut || baseParameter.IsOut; + } + + StaticRegistrar StaticRegistrar { + get { return DerivedLinkContext.StaticRegistrar; } + } + + CustomAttribute CreateUnmanagedCallersAttribute (string entryPoint) + { + var unmanagedCallersAttribute = new CustomAttribute (abr.UnmanagedCallersOnlyAttribute_Constructor); + // Mono didn't prefix the entry point with an underscore until .NET 8: https://github.com/dotnet/runtime/issues/79491 + var entryPointPrefix = Driver.TargetFramework.Version.Major < 8 ? "_" : string.Empty; + unmanagedCallersAttribute.Fields.Add (new CustomAttributeNamedArgument ("EntryPoint", new CustomAttributeArgument (abr.System_String, entryPointPrefix + entryPoint))); + return unmanagedCallersAttribute; + } + + CustomAttribute CreateDynamicDependencyAttribute (TypeDefinition type, string member) + { + var attribute = new CustomAttribute (abr.DynamicDependencyAttribute_ctor__String_Type); + attribute.ConstructorArguments.Add (new CustomAttributeArgument (abr.System_String, member)); + attribute.ConstructorArguments.Add (new CustomAttributeArgument (abr.System_Type, type)); + return attribute; + } + + void GenerateConversionToManaged (MethodDefinition method, ILProcessor il, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, int parameter, out TypeReference nativeCallerType) + { + // This is a mirror of the native method xamarin_generate_conversion_to_managed (for the dynamic registrar). + // It's also a mirror of the method StaticRegistrar.GenerateConversionToManaged. + // These methods must be kept in sync. + var managedType = outputType; + var nativeType = inputType; + + var isManagedNullable = StaticRegistrar.IsNullable (managedType); + + var underlyingManagedType = managedType; + var underlyingNativeType = nativeType; + + var isManagedArray = StaticRegistrar.IsArray (managedType); + var isNativeArray = StaticRegistrar.IsArray (nativeType); + + nativeCallerType = abr.System_IntPtr; + + if (isManagedArray != isNativeArray) + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + + if (isManagedArray) { + if (isManagedNullable) + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + underlyingNativeType = StaticRegistrar.GetElementType (nativeType); + underlyingManagedType = StaticRegistrar.GetElementType (managedType); + } else if (isManagedNullable) { + underlyingManagedType = StaticRegistrar.GetNullableType (managedType); + } + + string? func = null; + MethodReference? conversionFunction = null; + MethodReference? conversionFunction2 = null; + if (underlyingNativeType.Is ("Foundation", "NSNumber")) { + func = StaticRegistrar.GetNSNumberToManagedFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName, out var _); + } else if (underlyingNativeType.Is ("Foundation", "NSValue")) { + func = StaticRegistrar.GetNSValueToManagedFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName, out var _); + } else if (underlyingNativeType.Is ("Foundation", "NSString")) { + if (!StaticRegistrar.IsSmartEnum (underlyingManagedType, out var getConstantMethod, out var getValueMethod)) { + // method linked away!? this should already be verified + AddException (ErrorHelper.CreateError (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); + return; + } + + var gim = new GenericInstanceMethod (abr.Runtime_GetNSObject_T___System_IntPtr); + gim.GenericArguments.Add (abr.Foundation_NSString); + conversionFunction = gim; + + conversionFunction2 = abr.CurrentAssembly.MainModule.ImportReference (getValueMethod); + } else { + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + } + + if (func is not null) { + conversionFunction = abr.GetMethodReference (abr.PlatformAssembly, abr.ObjCRuntime_BindAs, func, func, (v) => + v.IsStatic, out MethodDefinition conversionFunctionDefinition); + EnsureVisible (method, conversionFunctionDefinition.DeclaringType); + } + + if (isManagedArray) { + il.Emit (OpCodes.Ldftn, conversionFunction); + if (conversionFunction2 is not null) { + il.Emit (OpCodes.Ldftn, conversionFunction2); + var gim = new GenericInstanceMethod (abr.BindAs_ConvertNSArrayToManagedArray2); + gim.GenericArguments.Add (underlyingManagedType); + gim.GenericArguments.Add (abr.Foundation_NSString); + il.Emit (OpCodes.Call, gim); + } else { + var gim = new GenericInstanceMethod (abr.BindAs_ConvertNSArrayToManagedArray); + gim.GenericArguments.Add (underlyingManagedType); + il.Emit (OpCodes.Call, gim); + } + nativeCallerType = abr.System_IntPtr; + } else { + if (isManagedNullable) { + il.Emit (OpCodes.Ldftn, conversionFunction); + if (conversionFunction2 is not null) { + il.Emit (OpCodes.Ldftn, conversionFunction2); + var gim = new GenericInstanceMethod (abr.BindAs_CreateNullable2); + gim.GenericArguments.Add (underlyingManagedType); + gim.GenericArguments.Add (abr.Foundation_NSString); + il.Emit (OpCodes.Call, gim); + } else { + var gim = new GenericInstanceMethod (abr.BindAs_CreateNullable); + gim.GenericArguments.Add (underlyingManagedType); + il.Emit (OpCodes.Call, gim); + } + nativeCallerType = abr.System_IntPtr; + } else { + il.Emit (OpCodes.Call, conversionFunction); + if (conversionFunction2 is not null) + il.Emit (OpCodes.Call, conversionFunction2); + nativeCallerType = abr.System_IntPtr; + } + } + } + + void GenerateConversionToNative (MethodDefinition method, ILProcessor il, TypeReference inputType, TypeReference outputType, string descriptiveMethodName, out TypeReference nativeCallerType) + { + // This is a mirror of the native method xamarin_generate_conversion_to_native (for the dynamic registrar). + // It's also a mirror of the method StaticRegistrar.GenerateConversionToNative. + // These methods must be kept in sync. + var managedType = inputType; + var nativeType = outputType; + + var isManagedNullable = StaticRegistrar.IsNullable (managedType); + + var underlyingManagedType = managedType; + var underlyingNativeType = nativeType; + + var isManagedArray = StaticRegistrar.IsArray (managedType); + var isNativeArray = StaticRegistrar.IsArray (nativeType); + + nativeCallerType = abr.System_IntPtr; + + if (isManagedArray != isNativeArray) + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + + if (isManagedArray) { + if (isManagedNullable) + throw ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}"); + underlyingNativeType = StaticRegistrar.GetElementType (nativeType); + underlyingManagedType = StaticRegistrar.GetElementType (managedType); + } else if (isManagedNullable) { + underlyingManagedType = StaticRegistrar.GetNullableType (managedType); + } + + string? func = null; + MethodReference? conversionFunction = null; + MethodReference? conversionFunction2 = null; + MethodReference? conversionFunction3 = null; + if (underlyingNativeType.Is ("Foundation", "NSNumber")) { + func = StaticRegistrar.GetManagedToNSNumberFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName); + } else if (underlyingNativeType.Is ("Foundation", "NSValue")) { + func = StaticRegistrar.GetManagedToNSValueFunc (underlyingManagedType, inputType, outputType, descriptiveMethodName); + } else if (underlyingNativeType.Is ("Foundation", "NSString")) { + if (!StaticRegistrar.IsSmartEnum (underlyingManagedType, out var getConstantMethod, out var getValueMethod)) { + // method linked away!? this should already be verified + ErrorHelper.Show (ErrorHelper.CreateError (99, Errors.MX0099, $"the smart enum {underlyingManagedType.FullName} doesn't seem to be a smart enum after all")); + return; + } + + conversionFunction = abr.CurrentAssembly.MainModule.ImportReference (getConstantMethod); + conversionFunction2 = abr.NativeObjectExtensions_GetHandle; + conversionFunction3 = abr.NativeObject_op_Implicit_IntPtr; + } else { + AddException (ErrorHelper.CreateError (99, Errors.MX0099, $"can't convert from '{inputType.FullName}' to '{outputType.FullName}' in {descriptiveMethodName}")); + return; + } + + if (func is not null) { + conversionFunction = abr.GetMethodReference (abr.PlatformAssembly, abr.ObjCRuntime_BindAs, func, func, (v) => + v.IsStatic, out MethodDefinition conversionFunctionDefinition); + EnsureVisible (method, conversionFunctionDefinition.DeclaringType); + } + + if (isManagedArray) { + il.Emit (OpCodes.Ldftn, conversionFunction); + if (conversionFunction2 is not null) { + il.Emit (OpCodes.Ldftn, conversionFunction2); + var gim = new GenericInstanceMethod (abr.BindAs_ConvertManagedArrayToNSArray2); + gim.GenericArguments.Add (underlyingManagedType); + gim.GenericArguments.Add (abr.Foundation_NSString); + il.Emit (OpCodes.Call, gim); + } else { + var gim = new GenericInstanceMethod (abr.BindAs_ConvertManagedArrayToNSArray); + gim.GenericArguments.Add (underlyingManagedType); + il.Emit (OpCodes.Call, gim); + } + } else { + var tmpVariable = il.Body.AddVariable (managedType); + + var trueTarget = il.Create (OpCodes.Nop); + var endTarget = il.Create (OpCodes.Nop); + if (isManagedNullable) { + il.Emit (OpCodes.Stloc, tmpVariable); + il.Emit (OpCodes.Ldloca, tmpVariable); + var mr = abr.System_Nullable_1.CreateMethodReferenceOnGenericType (abr.Nullable_HasValue, underlyingManagedType); + il.Emit (OpCodes.Call, mr); + il.Emit (OpCodes.Brtrue, trueTarget); + il.Emit (OpCodes.Ldc_I4_0); + il.Emit (OpCodes.Conv_I); + il.Emit (OpCodes.Br, endTarget); + il.Append (trueTarget); + il.Emit (OpCodes.Ldloca, tmpVariable); + il.Emit (OpCodes.Call, abr.System_Nullable_1.CreateMethodReferenceOnGenericType (abr.Nullable_Value, underlyingManagedType)); + } + il.Emit (OpCodes.Call, conversionFunction); + if (conversionFunction2 is not null) { + il.Emit (OpCodes.Call, conversionFunction2); + if (conversionFunction3 is not null) + il.Emit (OpCodes.Call, conversionFunction3); + } + if (isManagedNullable) + il.Append (endTarget); + } + } + } +} diff --git a/tools/dotnet-linker/Steps/RegistrarStep.cs b/tools/dotnet-linker/Steps/RegistrarStep.cs index b11e090641..dd6f8795d9 100644 --- a/tools/dotnet-linker/Steps/RegistrarStep.cs +++ b/tools/dotnet-linker/Steps/RegistrarStep.cs @@ -30,15 +30,19 @@ namespace Xamarin.Linker { Configuration.CompilerFlags.AddLinkWith (Configuration.PartialStaticRegistrarLibrary); break; case RegistrarMode.Static: + Configuration.Target.StaticRegistrar.Register (Configuration.GetNonDeletedAssemblies (this)); + goto case RegistrarMode.ManagedStatic; + case RegistrarMode.ManagedStatic: var dir = Configuration.CacheDirectory; var header = Path.Combine (dir, "registrar.h"); var code = Path.Combine (dir, "registrar.mm"); - var bundled_assemblies = new List (); - foreach (var assembly in Configuration.Assemblies) { - if (Annotations.GetAction (assembly) != Mono.Linker.AssemblyAction.Delete) - bundled_assemblies.Add (assembly); + if (app.Registrar == RegistrarMode.ManagedStatic) { + // Every api has been registered if we're using the managed registrar + // (since we registered types before the trimmer did anything), + // so we need to remove those that were later trimmed away by the trimmer. + Configuration.Target.StaticRegistrar.FilterTrimmedApi (Annotations); } - Configuration.Target.StaticRegistrar.Generate (bundled_assemblies, header, code, out var initialization_method, app.ClassMapPath); + Configuration.Target.StaticRegistrar.Generate (header, code, out var initialization_method, app.ClassMapPath); var items = new List (); foreach (var abi in Configuration.Abis) { diff --git a/tools/dotnet-linker/Steps/StoreAttributesStep.cs b/tools/dotnet-linker/Steps/StoreAttributesStep.cs index 4a93a6bbbd..2af32740f9 100644 --- a/tools/dotnet-linker/Steps/StoreAttributesStep.cs +++ b/tools/dotnet-linker/Steps/StoreAttributesStep.cs @@ -31,6 +31,13 @@ namespace Xamarin.Linker.Steps { break; } break; + case "Foundation": + switch (attr_type.Name) { + case "ProtocolAttribute": + store = LinkContext.App.Optimizations.RegisterProtocols == true; + break; + } + break; } if (store) diff --git a/tools/linker/CoreOptimizeGeneratedCode.cs b/tools/linker/CoreOptimizeGeneratedCode.cs index 98f4b7d917..e3d8fb1209 100644 --- a/tools/linker/CoreOptimizeGeneratedCode.cs +++ b/tools/linker/CoreOptimizeGeneratedCode.cs @@ -938,8 +938,14 @@ namespace Xamarin.Linker { if (!mr.DeclaringType.Is (Namespaces.ObjCRuntime, "BlockLiteral")) return 0; - if (caller.Name == "GetBlockForDelegate" && caller.DeclaringType.Is ("ObjCRuntime", "BlockLiteral")) - return 0; // BlockLiteral.GetBlockForDelegate contains a non-optimizable call to SetupBlock, and this way we don't show any warnings to users about things they can't do anything about. + if (caller.DeclaringType.Is ("ObjCRuntime", "BlockLiteral")) { + switch (caller.Name) { + case "GetBlockForDelegate": + case "CreateBlockForDelegate": + // These methods contain a non-optimizable call to SetupBlock, and this way we don't show any warnings to users about things they can't do anything about. + return 0; + } + } string signature = null; try { @@ -986,33 +992,11 @@ namespace Xamarin.Linker { return 0; } - // Calculate the block signature. - var blockSignature = false; - MethodReference userMethod = null; - - // First look for any [UserDelegateType] attributes on the trampoline delegate type. - var userDelegateType = GetUserDelegateType (trampolineDelegateType); - if (userDelegateType is not null) { - var userMethodDefinition = GetDelegateInvoke (userDelegateType); - userMethod = InflateMethod (userDelegateType, userMethodDefinition); - blockSignature = true; - } else { - // Couldn't find a [UserDelegateType] attribute, use the type of the actual trampoline instead. - var userMethodDefinition = GetDelegateInvoke (trampolineDelegateType); - userMethod = InflateMethod (trampolineDelegateType, userMethodDefinition); - blockSignature = false; - } - - // No luck finding the signature, so give up. - if (userMethod is null) { - ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2106, caller, ins, Errors.MM2106_C, caller, ins.Offset, trampolineDelegateType.FullName)); + if (!LinkContext.Target.StaticRegistrar.TryComputeBlockSignature (caller, trampolineDelegateType, out var exception, out signature)) { + ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2106, exception, caller, ins, Errors.MM2106_D, caller, ins.Offset, exception.Message)); return 0; - } - var parameters = new TypeReference [userMethod.Parameters.Count]; - for (int p = 0; p < parameters.Length; p++) - parameters [p] = userMethod.Parameters [p].ParameterType; - signature = LinkContext.Target.StaticRegistrar.ComputeSignature (userMethod.DeclaringType, false, userMethod.ReturnType, parameters, userMethod.Resolve (), isBlockSignature: blockSignature); + } } catch (Exception e) { ErrorHelper.Show (ErrorHelper.CreateWarning (LinkContext.App, 2106, e, caller, ins, Errors.MM2106_D, caller, ins.Offset, e.Message)); return 0; @@ -1309,29 +1293,6 @@ namespace Xamarin.Linker { } } - // Find the value of the [UserDelegateType] attribute on the specified delegate - TypeReference GetUserDelegateType (TypeReference delegateType) - { - var delegateTypeDefinition = delegateType.Resolve (); - foreach (var attrib in delegateTypeDefinition.CustomAttributes) { - var attribType = attrib.AttributeType; - if (!attribType.Is (Namespaces.ObjCRuntime, "UserDelegateTypeAttribute")) - continue; - return attrib.ConstructorArguments [0].Value as TypeReference; - } - return null; - } - - MethodDefinition GetDelegateInvoke (TypeReference delegateType) - { - var td = delegateType.Resolve (); - foreach (var method in td.Methods) { - if (method.Name == "Invoke") - return method; - } - return null; - } - MethodDefinition setupblock_def; MethodReference GetBlockSetupImpl (MethodDefinition caller, Instruction ins) { @@ -1376,23 +1337,5 @@ namespace Xamarin.Linker { } return caller.Module.ImportReference (block_ctor_def); } - - MethodReference InflateMethod (TypeReference inflatedDeclaringType, MethodDefinition openMethod) - { - var git = inflatedDeclaringType as GenericInstanceType; - if (git is null) - return openMethod; - - var inflatedReturnType = TypeReferenceExtensions.InflateGenericType (git, openMethod.ReturnType); - var mr = new MethodReference (openMethod.Name, inflatedReturnType, git); - if (openMethod.HasParameters) { - for (int i = 0; i < openMethod.Parameters.Count; i++) { - var inflatedParameterType = TypeReferenceExtensions.InflateGenericType (git, openMethod.Parameters [i].ParameterType); - var p = new ParameterDefinition (openMethod.Parameters [i].Name, openMethod.Parameters [i].Attributes, inflatedParameterType); - mr.Parameters.Add (p); - } - } - return mr; - } } } diff --git a/tools/linker/MobileExtensions.cs b/tools/linker/MobileExtensions.cs index f555e6cbdf..5fa9a84cb0 100644 --- a/tools/linker/MobileExtensions.cs +++ b/tools/linker/MobileExtensions.cs @@ -23,6 +23,17 @@ namespace Xamarin.Linker { return provider.ToString (); } + public static bool HasCustomAttribute (this ICustomAttributeProvider provider, DerivedLinkContext context, string @namespace, string name, out ICustomAttribute attrib) + { + attrib = null; + if (provider?.HasCustomAttribute (@namespace, name, out attrib) == true) + return true; + + var attribs = context?.GetCustomAttributes (provider, @namespace, name); + attrib = attribs?.FirstOrDefault (); + return attrib is not null; + + } // This method will look in any stored attributes in the link context as well as the provider itself. public static bool HasCustomAttribute (this ICustomAttributeProvider provider, DerivedLinkContext context, string @namespace, string name) { @@ -34,13 +45,22 @@ namespace Xamarin.Linker { public static bool HasCustomAttribute (this ICustomAttributeProvider provider, string @namespace, string name) { + return HasCustomAttribute (provider, @namespace, name, out _); + } + + public static bool HasCustomAttribute (this ICustomAttributeProvider provider, string @namespace, string name, out ICustomAttribute attrib) + { + attrib = null; + if (provider is null || !provider.HasCustomAttributes) return false; foreach (CustomAttribute attribute in provider.CustomAttributes) { TypeReference tr = attribute.Constructor.DeclaringType; - if (tr.Is (@namespace, name)) + if (tr.Is (@namespace, name)) { + attrib = attribute; return true; + } } return false; } diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index a23b34a1af..d770fa6bdd 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -337,16 +337,6 @@ namespace Xamarin.Bundler { } } - /// - /// Looks up a localized string similar to Could not optimize the call to BlockLiteral.SetupBlock in {0} at offset {1} because no [UserDelegateType] attribute could be found on {2}. - /// . - /// - public static string MM2106_C { - get { - return ResourceManager.GetString("MM2106_C", resourceCulture); - } - } - /// /// Looks up a localized string similar to Could not optimize the call to BlockLiteral.SetupBlock in {0} at offset {1}: {2}. /// . @@ -4006,6 +3996,24 @@ namespace Xamarin.Bundler { } } + /// + /// Looks up a localized string similar to Could not find a [UserDelegateType] attribute on the type '{0}'.. + /// + public static string MX4187 { + get { + return ResourceManager.GetString("MX4187", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to compute the block signature for the type '{0}': {1}. + /// + public static string MX4188 { + get { + return ResourceManager.GetString("MX4188", resourceCulture); + } + } + /// /// Looks up a localized string similar to The native linker failed to execute: {0}. Please file a bug report at https://github.com/xamarin/xamarin-macios/issues/new /// . @@ -4257,5 +4265,32 @@ namespace Xamarin.Bundler { return ResourceManager.GetString("MX8052", resourceCulture); } } + + /// + /// Looks up a localized string similar to Could not resolve the module in the assembly {0}.. + /// + public static string MX8053 { + get { + return ResourceManager.GetString("MX8053", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Can't resolve metadata tokens for methods when using the managed static registrar (token: 0x{0}).. + /// + public static string MX8054 { + get { + return ResourceManager.GetString("MX8054", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not find the type 'ObjCRuntime.__Registrar__' in the assembly '{0}'.. + /// + public static string MX8055 { + get { + return ResourceManager.GetString("MX8055", resourceCulture); + } + } } } diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index 45687cea40..8ebe9f75b5 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -1289,11 +1289,6 @@ - - Could not optimize the call to BlockLiteral.SetupBlock in {0} at offset {1} because no [UserDelegateType] attribute could be found on {2}. - - - Could not optimize the call to BlockLiteral.SetupBlock in {0} at offset {1}: {2}. @@ -1914,6 +1909,14 @@ The registrar found a non-optimal type `{0}`: the type does not have a constructor that takes two (ObjCRuntime.NativeHandle, bool) arguments. However, a constructor that takes two (System.IntPtr, bool) arguments was found (and will be used instead). It's highly recommended to change the signature of the (System.IntPtr, bool) constructor to be (ObjCRuntime.NativeHandle, bool). + + Could not find a [UserDelegateType] attribute on the type '{0}'. + + + + Unable to compute the block signature for the type '{0}': {1} + + Missing '{0}' compiler. Please install Xcode 'Command-Line Tools' component @@ -2240,4 +2243,15 @@ The signature must be a non-empty string. + + Could not resolve the module in the assembly {0}. + + + + Can't resolve metadata tokens for methods when using the managed static registrar (token: 0x{0}). + + + + Could not find the type 'ObjCRuntime.__Registrar__' in the assembly '{0}'. +