diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs index 39d3800be714a8..ad42e6970918b1 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DelegateTests.cs @@ -1276,6 +1276,24 @@ public static void CreateDelegate10_Nullable_ClosedDelegate() AssertExtensions.Throws( () => Delegate.CreateDelegate(typeof(NullableIntToString), num, mi)); } + + [Fact] + public static void CreateDelegate_ClosedOverNull_InstanceMethod() + { + MethodInfo mi = typeof(C).GetMethod(nameof(C.IsThisNull)); + Func del = (Func)Delegate.CreateDelegate(typeof(Func), null, mi); + Assert.Null(del.Target); + Assert.True(del()); + } + + [Fact] + public static void CreateDelegate_ClosedOverNull_InstanceMethodViaMethodInfoCreateDelegate() + { + MethodInfo mi = typeof(C).GetMethod(nameof(C.IsThisNull)); + Func del = mi.CreateDelegate>(null); + Assert.Null(del.Target); + Assert.True(del()); + } #endregion Tests #region Test Setup @@ -1370,6 +1388,11 @@ public static void S(C c) { } + public bool IsThisNull() + { + return this is null; + } + private void PrivateInstance() { } diff --git a/src/mono/mono/metadata/marshal.c b/src/mono/mono/metadata/marshal.c index 39d522bb570c6f..9e0e82d02dfebe 100644 --- a/src/mono/mono/metadata/marshal.c +++ b/src/mono/mono/metadata/marshal.c @@ -2345,10 +2345,15 @@ mono_marshal_get_delegate_invoke_subtype (MonoMethod *method, MonoDelegate *del) return WRAPPER_SUBTYPE_NONE; if (!del->target && mono_method_signature_internal (del->method)->hasthis) { - if (!(del->method->flags & METHOD_ATTRIBUTE_VIRTUAL) && !m_class_is_valuetype (del->method->klass) && sig->param_count == mono_method_signature_internal (del->method)->param_count + 1) - return WRAPPER_SUBTYPE_NONE; - else - return WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL; + if (!(del->method->flags & METHOD_ATTRIBUTE_VIRTUAL) && !m_class_is_valuetype (del->method->klass)) { + if (sig->param_count == mono_method_signature_internal (del->method)->param_count + 1) + /* Open instance delegate: first delegate arg becomes 'this' */ + return WRAPPER_SUBTYPE_NONE; + if (sig->param_count == mono_method_signature_internal (del->method)->param_count) + /* Closed over null: target (null) is passed as 'this' via the bound path */ + return WRAPPER_SUBTYPE_NONE; + } + return WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL; } if (mono_method_signature_internal (del->method)->param_count == sig->param_count + 1 && (del->method->flags & METHOD_ATTRIBUTE_STATIC)) @@ -2372,8 +2377,10 @@ mono_marshal_get_delegate_invoke (MonoMethod *method, MonoDelegate *del) sig = mono_signature_no_pinvoke (method); if (del && !del->target && del->method && mono_method_signature_internal (del->method)->hasthis) { - if (!(del->method->flags & METHOD_ATTRIBUTE_VIRTUAL) && !m_class_is_valuetype (del->method->klass) && sig->param_count == mono_method_signature_internal (del->method)->param_count + 1) { - /* The first argument of the delegate is passed as this, the normal invoke code can handle this */ + if (!(del->method->flags & METHOD_ATTRIBUTE_VIRTUAL) && !m_class_is_valuetype (del->method->klass) && + (sig->param_count == mono_method_signature_internal (del->method)->param_count + 1 || + sig->param_count == mono_method_signature_internal (del->method)->param_count)) { + /* Open instance delegate or closed over null: the normal invoke code can handle this */ } else { callvirt = TRUE; } diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index 0a12875dcce179..bb8020515fa5bb 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -4355,6 +4355,9 @@ mono_interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClause } else { LOCAL_VAR (call_args_offset, MonoObject*) = this_arg; } + } else if (!m_method_is_static (cmethod->method) && cmethod->param_count == param_count) { + // Closed over null: instance method bound with null this + LOCAL_VAR (call_args_offset, MonoObject*) = NULL; } else { // skip the delegate pointer for static calls // FIXME we could avoid memmove diff --git a/src/mono/mono/mini/llvmonly-runtime.c b/src/mono/mono/mini/llvmonly-runtime.c index 92b8c65ece72d3..1d1023fbd35d74 100644 --- a/src/mono/mono/mini/llvmonly-runtime.c +++ b/src/mono/mono/mini/llvmonly-runtime.c @@ -817,6 +817,12 @@ mini_llvmonly_init_delegate (MonoDelegate *del, MonoDelegateTrampInfo *info) if (subtype == WRAPPER_SUBTYPE_DELEGATE_INVOKE_BOUND) del->bound = TRUE; + /* Closed over null: instance method bound with null target. + * Set bound so the wrapper passes target (null) as 'this'. */ + if (!del->target && del->method && mono_method_signature_internal (del->method)->hasthis && + mono_method_signature_internal (info->invoke)->param_count == mono_method_signature_internal (del->method)->param_count) + del->bound = TRUE; + ftndesc = info->invoke_impl; if (G_UNLIKELY (!ftndesc) || subtype != WRAPPER_SUBTYPE_NONE) { MonoMethod *invoke_impl = mono_marshal_get_delegate_invoke (info->invoke, del); diff --git a/src/mono/mono/mini/mini-trampolines.c b/src/mono/mono/mini/mini-trampolines.c index 2abe69f98eef25..7ece3ffa9deac6 100644 --- a/src/mono/mono/mini/mini-trampolines.c +++ b/src/mono/mono/mini/mini-trampolines.c @@ -1076,6 +1076,15 @@ mono_delegate_trampoline (host_mgreg_t *regs, guint8 *code, gpointer *arg, guint if (m_method_is_static (method) && sig->param_count == tramp_info->invoke_sig->param_count + 1) closed_static = TRUE; + if (callvirt && closed_over_null) { + if (!m_method_is_virtual (method) && !m_class_is_valuetype (method->klass)) { + /* Closed over null with non-virtual instance method: use the bound path */ + callvirt = FALSE; + delegate->bound = TRUE; + enable_caching = FALSE; + } + } + if (callvirt && !closed_over_null) { /* * The delegate needs to make a virtual call to the target method using its @@ -1136,7 +1145,7 @@ mono_delegate_trampoline (host_mgreg_t *regs, guint8 *code, gpointer *arg, guint multicast = ((MonoMulticastDelegate*)delegate)->delegates != NULL; if (!multicast && !callvirt) { - if (closed_static) + if (closed_static || closed_over_null) invoke_impl = impl_this; else invoke_impl = delegate->target ? impl_this : impl_nothis;