Skip to content

Commit de142ae

Browse files
committed
Merge pull request #196 from tonyroberts/develop
Fix crash when abstract methods aren't implemented
2 parents b147b16 + b04658e commit de142ae

File tree

3 files changed

+110
-29
lines changed

3 files changed

+110
-29
lines changed

src/runtime/classderived.cs

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -321,37 +321,47 @@ private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuild
321321
ParameterInfo[] parameters = method.GetParameters();
322322
Type[] parameterTypes = (from param in parameters select param.ParameterType).ToArray();
323323

324-
// create a method for calling the original method
325-
string baseMethodName = "_" + baseType.Name + "__" + method.Name;
326-
MethodBuilder methodBuilder = typeBuilder.DefineMethod(baseMethodName,
327-
MethodAttributes.Public |
328-
MethodAttributes.Final |
329-
MethodAttributes.HideBySig,
330-
method.ReturnType,
331-
parameterTypes);
332-
333-
// emit the assembly for calling the original method using call instead of callvirt
334-
ILGenerator il = methodBuilder.GetILGenerator();
335-
il.Emit(OpCodes.Ldarg_0);
336-
for (int i = 0; i < parameters.Length; ++i)
337-
il.Emit(OpCodes.Ldarg, i + 1);
338-
il.Emit(OpCodes.Call, method);
339-
il.Emit(OpCodes.Ret);
324+
// If the method isn't abstract create a method for calling the original method
325+
string baseMethodName = null;
326+
if (!method.IsAbstract)
327+
{
328+
baseMethodName = "_" + baseType.Name + "__" + method.Name;
329+
MethodBuilder baseMethodBuilder = typeBuilder.DefineMethod(baseMethodName,
330+
MethodAttributes.Public |
331+
MethodAttributes.Final |
332+
MethodAttributes.HideBySig,
333+
method.ReturnType,
334+
parameterTypes);
335+
336+
// emit the assembly for calling the original method using call instead of callvirt
337+
ILGenerator baseIl = baseMethodBuilder.GetILGenerator();
338+
baseIl.Emit(OpCodes.Ldarg_0);
339+
for (int i = 0; i < parameters.Length; ++i)
340+
baseIl.Emit(OpCodes.Ldarg, i + 1);
341+
baseIl.Emit(OpCodes.Call, method);
342+
baseIl.Emit(OpCodes.Ret);
343+
}
340344

341345
// override the original method with a new one that dispatches to python
342-
methodBuilder = typeBuilder.DefineMethod(method.Name,
343-
MethodAttributes.Public |
344-
MethodAttributes.ReuseSlot |
345-
MethodAttributes.Virtual |
346-
MethodAttributes.HideBySig,
347-
method.CallingConvention,
348-
method.ReturnType,
349-
parameterTypes);
350-
il = methodBuilder.GetILGenerator();
346+
MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name,
347+
MethodAttributes.Public |
348+
MethodAttributes.ReuseSlot |
349+
MethodAttributes.Virtual |
350+
MethodAttributes.HideBySig,
351+
method.CallingConvention,
352+
method.ReturnType,
353+
parameterTypes);
354+
ILGenerator il = methodBuilder.GetILGenerator();
351355
il.DeclareLocal(typeof(Object[]));
352356
il.Emit(OpCodes.Ldarg_0);
353357
il.Emit(OpCodes.Ldstr, method.Name);
354-
il.Emit(OpCodes.Ldstr, baseMethodName);
358+
359+
// don't fall back to the base type's method if it's abstract
360+
if (null != baseMethodName)
361+
il.Emit(OpCodes.Ldstr, baseMethodName);
362+
else
363+
il.Emit(OpCodes.Ldnull);
364+
355365
il.Emit(OpCodes.Ldc_I4, parameters.Length);
356366
il.Emit(OpCodes.Newarr, typeof(System.Object));
357367
il.Emit(OpCodes.Stloc_0);
@@ -624,7 +634,7 @@ public static T InvokeMethod<T>(IPythonDerivedType obj, string methodName, strin
624634
}
625635

626636
if (origMethodName == null)
627-
throw new NullReferenceException("Python object does not have a '" + methodName + "' method");
637+
throw new NotImplementedException("Python object does not have a '" + methodName + "' method");
628638

629639
return (T)obj.GetType().InvokeMember(origMethodName,
630640
BindingFlags.InvokeMethod,
@@ -683,7 +693,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
683693
}
684694

685695
if (origMethodName == null)
686-
throw new NullReferenceException("Python object does not have a '" + methodName + "' method");
696+
throw new NotImplementedException("Python object does not have a '" + methodName + "' method");
687697

688698
obj.GetType().InvokeMember(origMethodName,
689699
BindingFlags.InvokeMethod,

src/testing/subclasstest.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@ public interface IInterfaceTest
1212

1313
// test passing objects and boxing primitives
1414
string bar(string s, int i);
15+
16+
// test events on interfaces
17+
event TestEventHandler TestEvent;
18+
19+
void OnTestEvent(int value);
1520
}
1621

1722
public class SubClassTest : IInterfaceTest
1823
{
24+
public event TestEventHandler TestEvent;
25+
1926
public SubClassTest()
2027
{
2128
}
@@ -48,6 +55,13 @@ public static IList<string> test_list(SubClassTest x)
4855
// calls into python if return_list is overriden
4956
return x.return_list();
5057
}
58+
59+
// raise the test event
60+
public virtual void OnTestEvent(int value)
61+
{
62+
if (null != TestEvent)
63+
TestEvent(this, new TestEventArgs(value));
64+
}
5165
}
5266

5367
public class TestFunctions
@@ -75,5 +89,18 @@ public static IInterfaceTest pass_through(IInterfaceTest s)
7589
{
7690
return s;
7791
}
92+
93+
public static int test_event(IInterfaceTest x, int value)
94+
{
95+
// reuse the event handler from eventtest.cs
96+
EventTest et = new EventTest();
97+
x.TestEvent += et.GenericHandler;
98+
99+
// raise the event (should trigger both python and managed handlers)
100+
x.OnTestEvent(value);
101+
102+
x.TestEvent -= et.GenericHandler;
103+
return et.value;
104+
}
78105
}
79106
}

src/tests/test_subclass.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
clr.AddReference('System')
1212

1313
import sys, os, string, unittest, types
14-
from Python.Test import TestFunctions, SubClassTest, IInterfaceTest
14+
from Python.Test import TestFunctions, SubClassTest, IInterfaceTest, TestEventArgs
1515
from System.Collections.Generic import List
16+
from System import NotImplementedException
1617

1718
# class that implements the test interface
1819
class InterfaceTestClass(IInterfaceTest):
@@ -47,6 +48,26 @@ def return_list(self):
4748
l.Add("C")
4849
return l
4950

51+
# class that implements IInterfaceTest.TestEvent
52+
class DerivedEventTest(IInterfaceTest):
53+
__namespace__ = "Python.Test"
54+
55+
def __init__(self):
56+
self.event_handlers = []
57+
58+
# event handling
59+
def add_TestEvent(self, handler):
60+
self.event_handlers.append(handler)
61+
62+
def remove_TestEvent(self, handler):
63+
self.event_handlers.remove(handler)
64+
65+
def OnTestEvent(self, value):
66+
args = TestEventArgs(value)
67+
for handler in self.event_handlers:
68+
handler(self, args)
69+
70+
5071
class SubClassTests(unittest.TestCase):
5172
"""Test subclassing managed types"""
5273

@@ -109,6 +130,29 @@ def testCreateInstance(self):
109130
y = TestFunctions.pass_through(object2)
110131
self.assertEqual(id(y), id(object2))
111132

133+
def testEvents(self):
134+
135+
class EventHandler:
136+
def handler(self, x, args):
137+
self.value = args.value
138+
139+
event_handler = EventHandler()
140+
141+
x = SubClassTest()
142+
x.TestEvent += event_handler.handler
143+
self.assertEqual(TestFunctions.test_event(x, 1), 1)
144+
self.assertEqual(event_handler.value, 1)
145+
146+
i = InterfaceTestClass()
147+
self.assertRaises(NotImplementedException, TestFunctions.test_event, i, 2)
148+
149+
d = DerivedEventTest()
150+
d.add_TestEvent(event_handler.handler)
151+
self.assertEqual(TestFunctions.test_event(d, 3), 3)
152+
self.assertEqual(event_handler.value, 3)
153+
self.assertEqual(len(d.event_handlers), 1)
154+
155+
112156
def test_suite():
113157
return unittest.makeSuite(SubClassTests)
114158

0 commit comments

Comments
 (0)