Skip to content

Commit e6cd9a1

Browse files
authored
[2019-10] [mini] Fix delegate trampoline virtual call via delgate Invoke (#18115)
[2019-10] [mini] Fix delegate trampoline virtual call via delgate Invoke If we need to jit the Invoke method of a delegate, we get `tramp_info` with a NULL method. Background: normally when we create a delegate around a virtual method, `handle_delegate_ctor` will just create a virtual invoke trampoline with `mono_arch_get_delegate_virtual_invoke_impl` which doesn't get here. But if we're asked to compile the delegate's `Invoke` method, then `compile_special ()` will create a `tramp_info` with a null method, and return a delegate trampoline. That's the case here - we had ``` var del = SomeDelegate(obj.VirtualMethod); var invoke_method = del.GetType().GetMethod ("Invoke"); invoke_method.Invoke (del, args); ``` or ``` var del = SomeDelegate(obj.VirtualMethod); var another_del = OtherDelegate (del.Invoke); another_del (args); ``` in both cases, we end up in mono_delegate_trampoline with `tramp_info->method == NULL`. in the second case the IL is like this: ``` newobj instance void Derived::'.ctor' ldvirtftn instance void class Base::VirtualMethod() newobj instance void class SomeDelegate::'.ctor'(object, native int) ``` So `delegate->target` is a derived instance but `delegate->method` is some base class method. Addresses #17718 Backport of #18073. /cc @lambdageek
1 parent 7c95e4b commit e6cd9a1

File tree

4 files changed

+95
-1
lines changed

4 files changed

+95
-1
lines changed

mono/mini/mini-trampolines.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,15 @@ mono_delegate_trampoline (host_mgreg_t *regs, guint8 *code, gpointer *arg, guint
11421142
}
11431143
}
11441144

1145-
if (delegate->target &&
1145+
if (tramp_info->method == NULL && delegate->target != NULL && method->flags & METHOD_ATTRIBUTE_VIRTUAL) {
1146+
/* tramp_info->method == NULL happens when someone asks us to JIT some delegate's
1147+
* Invoke method (see compile_special). In that case if method is virtual, the target
1148+
* could be some derived class, so we need to find the correct override.
1149+
*/
1150+
/* FIXME: does it make sense that we get called with tramp_info for the Invoke? */
1151+
method = mono_object_get_virtual_method_internal (delegate->target, method);
1152+
enable_caching = FALSE;
1153+
} else if (delegate->target &&
11461154
method->flags & METHOD_ATTRIBUTE_VIRTUAL &&
11471155
method->flags & METHOD_ATTRIBUTE_ABSTRACT &&
11481156
mono_class_is_abstract (method->klass)) {

mono/tests/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,8 @@ TESTS_CS_SRC= \
421421
delegate14.cs \
422422
delegate15.cs \
423423
delegate16.cs \
424+
delegate17.cs \
425+
delegate18.cs \
424426
largeexp.cs \
425427
largeexp2.cs \
426428
marshalbyref1.cs \

mono/tests/delegate17.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Reflection;
3+
4+
internal class Program
5+
{
6+
public static int Main (string[] args)
7+
{
8+
// newobj Derived
9+
Derived d = new Derived ();
10+
// ldvirtftn Base::Foo
11+
// newobj Del1::.ctor
12+
Del1 b = new Del1 (d.Foo);
13+
// ldftn Del1::Invoke
14+
// newobj Del2::.ctor
15+
Del2 f = new Del2 (b.Invoke);
16+
// should call Derived.Foo not Base.Foo
17+
var r = f ("abcd");
18+
return r;
19+
}
20+
}
21+
22+
23+
public delegate int Del1 (string s);
24+
public delegate int Del2 (string s);
25+
26+
public class Base
27+
{
28+
public virtual int Foo (string s)
29+
{
30+
Console.WriteLine ("Base.Foo called. Bad");
31+
return 1;
32+
}
33+
}
34+
35+
public class Derived : Base
36+
{
37+
public override int Foo (string s)
38+
{
39+
Console.WriteLine ("Derived.Foo called. Good");
40+
return 0;
41+
}
42+
}

mono/tests/delegate18.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Reflection;
3+
4+
internal class Program
5+
{
6+
public static int Main (string[] args)
7+
{
8+
// newobj Derived
9+
Derived d = new Derived ();
10+
// ldvirtftn Base::Foo
11+
// newobj Del1::.ctor
12+
Del1 b = new Del1 (d.Foo);
13+
var mi = typeof (Del1).GetMethod ("Invoke");
14+
if (mi is null)
15+
return 2;
16+
// should call Derived.Foo not Base.Foo
17+
var r = (int) mi.Invoke (b, new object[] {"abcd"});
18+
return r;
19+
}
20+
}
21+
22+
23+
public delegate int Del1 (string s);
24+
public delegate int Del2 (string s);
25+
26+
public class Base
27+
{
28+
public virtual int Foo (string s)
29+
{
30+
Console.WriteLine ("Base.Foo called. Bad");
31+
return 1;
32+
}
33+
}
34+
35+
public class Derived : Base
36+
{
37+
public override int Foo (string s)
38+
{
39+
Console.WriteLine ("Derived.Foo called. Good");
40+
return 0;
41+
}
42+
}

0 commit comments

Comments
 (0)