Skip to content

Commit 56e0baa

Browse files
committed
Don't try to call abstract methods.
When deriving from managed classes in Python don't create the final base method wrapper for abstract methods. If an abstract method that hasn't been implemented in Python raise an Exception. Previously the base method was called, resulting in a crash. This behaviour was noticed when trying to implement events declared on an interface when the implicit add_X and remove_X methods weren't implemented, and tests have been added to cover that case.
1 parent b147b16 commit 56e0baa

3 files changed

Lines changed: 75 additions & 28 deletions

File tree

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: 19 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,10 @@ public static IInterfaceTest pass_through(IInterfaceTest s)
7589
{
7690
return s;
7791
}
92+
93+
public static void test_event(IInterfaceTest x, int value)
94+
{
95+
x.OnTestEvent(value);
96+
}
7897
}
7998
}

src/tests/test_subclass.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import sys, os, string, unittest, types
1414
from Python.Test import TestFunctions, SubClassTest, IInterfaceTest
1515
from System.Collections.Generic import List
16+
from System import NotImplementedException
1617

1718
# class that implements the test interface
1819
class InterfaceTestClass(IInterfaceTest):
@@ -109,6 +110,23 @@ def testCreateInstance(self):
109110
y = TestFunctions.pass_through(object2)
110111
self.assertEqual(id(y), id(object2))
111112

113+
def testEvents(self):
114+
115+
class EventHandler:
116+
def handler(self, x, args):
117+
self.value = args.value
118+
119+
event_handler = EventHandler()
120+
121+
x = SubClassTest()
122+
x.TestEvent += event_handler.handler
123+
TestFunctions.test_event(x, 1)
124+
self.assertEqual(event_handler.value, 1)
125+
126+
i = InterfaceTestClass()
127+
self.assertRaises(NotImplementedException, TestFunctions.test_event, i, 2)
128+
129+
112130
def test_suite():
113131
return unittest.makeSuite(SubClassTests)
114132

0 commit comments

Comments
 (0)