From 385592b12837238d5fb4302330013d1935d02007 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 10 Sep 2018 10:56:02 +0200 Subject: [PATCH] [runtime] Throw a managed exception instead of trying to call a null function pointer if the runtime tries to call a function that has been linked away. (#4770) * [runtime] Throw a managed exception instead of trying to call a null function pointer if the runtime tries to call a function that has been linked away. * [tests] Add new file for Xamarin.Mac tests. * Direct people to file issues in github. --- docs/website/mmp-errors.md | 5 +++ docs/website/mtouch-errors.md | 5 +++ runtime/delegates.inc.t4 | 14 +++++++ runtime/runtime.m | 14 ++++--- runtime/xamarin/runtime.h | 1 + tests/bindings-test/Messaging.cs | 16 +++++++ tests/bindings-test/RegistrarBindingTest.cs | 44 ++++++++++++++++++++ tests/bindings-test/bindings-test-mac.csproj | 1 + tests/bindings-test/bindings-test.csproj | 1 + tests/common/TestRuntime.cs | 10 +++++ 10 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 tests/bindings-test/Messaging.cs diff --git a/docs/website/mmp-errors.md b/docs/website/mmp-errors.md index 862f06b110..dc518378eb 100644 --- a/docs/website/mmp-errors.md +++ b/docs/website/mmp-errors.md @@ -522,3 +522,8 @@ There are a few reasons this may happen: * It could be a bug in Xamarin.Mac. If this is the case, please file a bug at [https://bugzilla.xamarin.com](https://bugzilla.xamarin.com/enter_bug.cgi?product=Xamarin.Mac). + +### MM8028: The runtime function {function} has been linked away. + +This usually indicates a bug in Xamarin.Mac, because runtime functions should +not be linked away if they're needed. Please [submit an issue](https://github.com/xamarin/xamarin-macios/wiki/Submitting-Bugs-&-Suggestions). diff --git a/docs/website/mtouch-errors.md b/docs/website/mtouch-errors.md index 8ac2322efd..7be20ea709 100644 --- a/docs/website/mtouch-errors.md +++ b/docs/website/mtouch-errors.md @@ -2541,3 +2541,8 @@ There are a few reasons this may happen: * It could be a bug in Xamarin.iOS. If this is the case, please file a bug at [https://bugzilla.xamarin.com](https://bugzilla.xamarin.com/enter_bug.cgi?product=iOS). + +### MT8028: The runtime function {function} has been linked away. + +This usually indicates a bug in Xamarin.iOS, because runtime functions should +not be linked away if they're needed. Please [submit an issue](https://github.com/xamarin/xamarin-macios/wiki/Submitting-Bugs-&-Suggestions). diff --git a/runtime/delegates.inc.t4 b/runtime/delegates.inc.t4 index e763fde8d6..dd8a04034a 100644 --- a/runtime/delegates.inc.t4 +++ b/runtime/delegates.inc.t4 @@ -27,10 +27,24 @@ struct Delegates { static struct Delegates delegates = { 0 }; +static guint32 +create_linked_away_exception (const char *func) +{ + char *msg = xamarin_strdup_printf ("The runtime function %s has been linked away.", func); + guint32 gchandle = xamarin_create_product_exception (8028, msg); + xamarin_free (msg); + return gchandle; +} + <# foreach (var d in delegates) { #> <#= d.CReturnType #> <#= d.EntryPoint #> (<#= d.CArgumentSignature #>) { + <#if (d.ExceptionHandling && d.OnlyDynamicUsage) {#>if (delegates.<#= d.EntryPoint.Substring ("xamarin_".Length) #> == NULL) { + *exception_gchandle = create_linked_away_exception ("<#= d.EntryPoint.Substring ("xamarin_".Length) #>"); + return<# if (d.CReturnType != "void") { #> (<#= d.CReturnType #>) 0<# } #>; + } +<#}#> <# if (d.CReturnType != "void") { #>return <# } #>delegates.<#= d.EntryPoint.Substring ("xamarin_".Length) #> (<#= d.CArgumentNames #>); } diff --git a/runtime/runtime.m b/runtime/runtime.m index 91cbc21de8..8c4fb45d69 100644 --- a/runtime/runtime.m +++ b/runtime/runtime.m @@ -2424,14 +2424,18 @@ xamarin_process_managed_exception (MonoObject *exception) void xamarin_throw_product_exception (int code, const char *message) +{ + xamarin_process_managed_exception_gchandle (xamarin_create_product_exception (code, message)); +} + +guint32 +xamarin_create_product_exception (int code, const char *message) { guint32 exception_gchandle = 0; guint32 handle = xamarin_create_product_exception_for_error (code, message, &exception_gchandle); - if (exception_gchandle != 0) { - xamarin_process_managed_exception_gchandle (exception_gchandle); - } else { - xamarin_process_managed_exception_gchandle (handle); - } + if (exception_gchandle != 0) + return exception_gchandle; + return handle; } void diff --git a/runtime/xamarin/runtime.h b/runtime/xamarin/runtime.h index 25f93ee837..765f05d8d6 100644 --- a/runtime/xamarin/runtime.h +++ b/runtime/xamarin/runtime.h @@ -233,6 +233,7 @@ void xamarin_process_nsexception_using_mode (NSException *ns_exception, bool t void xamarin_process_managed_exception (MonoObject *exc); void xamarin_process_managed_exception_gchandle (guint32 gchandle); void xamarin_throw_product_exception (int code, const char *message); +guint32 xamarin_create_product_exception (int code, const char *message); NSString * xamarin_print_all_exceptions (MonoObject *exc); id xamarin_invoke_objc_method_implementation (id self, SEL sel, IMP xamarin_impl); diff --git a/tests/bindings-test/Messaging.cs b/tests/bindings-test/Messaging.cs new file mode 100644 index 0000000000..83866b7808 --- /dev/null +++ b/tests/bindings-test/Messaging.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; + +namespace ObjCRuntime { + public static class Messaging { + internal const string LIBOBJC_DYLIB = "/usr/lib/libobjc.dylib"; + + public struct objc_super { + public IntPtr Handle; + public IntPtr SuperHandle; + } + + [DllImport (LIBOBJC_DYLIB, EntryPoint = "objc_msgSend")] + public extern static void void_objc_msgSend_IntPtr_bool_bool (IntPtr receiver, IntPtr selector, IntPtr a, bool b, bool c); + } +} \ No newline at end of file diff --git a/tests/bindings-test/RegistrarBindingTest.cs b/tests/bindings-test/RegistrarBindingTest.cs index 8da468fcbd..b4daa9aa25 100644 --- a/tests/bindings-test/RegistrarBindingTest.cs +++ b/tests/bindings-test/RegistrarBindingTest.cs @@ -245,5 +245,49 @@ namespace Xamarin.BindingTests ObjCBlockTester.CallProtocolWithBlockReturnValue (pb, required, instance); } } + + [Test] + [TestCase (true, true)] + [TestCase (true, false)] + [TestCase (false, true)] + [TestCase (false, false)] + public void LinkedAway (bool required, bool instance) + { + if (!(TestRuntime.IsLinkAll && TestRuntime.IsOptimizeAll)) + Assert.Ignore ("This test is only applicable if optimized & linking all assemblies."); + + using (var pb = new FakePropertyBlock ()) { + try { + 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) { + Assert.AreEqual (8028, re.Code, "Code"); + Assert.AreEqual ("The runtime function get_block_wrapper_creator has been linked away.", re.Message, "Message"); + } + } + } + + // Each method here will show a warning like this: + // MT4174: Unable to locate the block to delegate conversion method for the method ... + // This is expected. + // This is 'fake' because it doesn't implement the ProtocolWithBlockProperties protocol, + // which means that XI won't be able to find the delegate<->block conversion methods + // (either at runtime or build time). + // The point of this test is to ensure that we don't crash when the runtime tries + // to find those conversion methods, and instead finds that many required functions + // have been linked away (which happen when _forcing_ the dynamic registrar to be linked away). + public class FakePropertyBlock : NSObject { + [Export ("myOptionalProperty")] + public SimpleCallback MyOptionalProperty { get; set; } + + [Export ("myRequiredProperty")] + public SimpleCallback MyRequiredProperty { get; set; } + + [Export ("myOptionalStaticProperty")] + public static SimpleCallback MyOptionalStaticProperty { get; set; } + + [Export ("myRequiredStaticProperty")] + public static SimpleCallback MyRequiredStaticProperty { get; set; } + } } } diff --git a/tests/bindings-test/bindings-test-mac.csproj b/tests/bindings-test/bindings-test-mac.csproj index 6458d05945..01ed64edae 100644 --- a/tests/bindings-test/bindings-test-mac.csproj +++ b/tests/bindings-test/bindings-test-mac.csproj @@ -68,6 +68,7 @@ + diff --git a/tests/bindings-test/bindings-test.csproj b/tests/bindings-test/bindings-test.csproj index 067057d277..ad7a79ad52 100644 --- a/tests/bindings-test/bindings-test.csproj +++ b/tests/bindings-test/bindings-test.csproj @@ -80,6 +80,7 @@ + diff --git a/tests/common/TestRuntime.cs b/tests/common/TestRuntime.cs index 8ce56c2ae1..a79dee40f3 100644 --- a/tests/common/TestRuntime.cs +++ b/tests/common/TestRuntime.cs @@ -655,4 +655,14 @@ partial class TestRuntime } } class LinkerSentinel { } + + public static bool IsOptimizeAll { + get { +#if OPTIMIZEALL + return true; +#else + return false; +#endif + } + } }