Skip to content

Fix crash when abstract methods aren't implemented #196

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 38 additions & 28 deletions src/runtime/classderived.cs
Original file line number Diff line number Diff line change
Expand Up @@ -321,37 +321,47 @@ private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuild
ParameterInfo[] parameters = method.GetParameters();
Type[] parameterTypes = (from param in parameters select param.ParameterType).ToArray();

// create a method for calling the original method
string baseMethodName = "_" + baseType.Name + "__" + method.Name;
MethodBuilder methodBuilder = typeBuilder.DefineMethod(baseMethodName,
MethodAttributes.Public |
MethodAttributes.Final |
MethodAttributes.HideBySig,
method.ReturnType,
parameterTypes);

// emit the assembly for calling the original method using call instead of callvirt
ILGenerator il = methodBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
for (int i = 0; i < parameters.Length; ++i)
il.Emit(OpCodes.Ldarg, i + 1);
il.Emit(OpCodes.Call, method);
il.Emit(OpCodes.Ret);
// If the method isn't abstract create a method for calling the original method
string baseMethodName = null;
if (!method.IsAbstract)
{
baseMethodName = "_" + baseType.Name + "__" + method.Name;
MethodBuilder baseMethodBuilder = typeBuilder.DefineMethod(baseMethodName,
MethodAttributes.Public |
MethodAttributes.Final |
MethodAttributes.HideBySig,
method.ReturnType,
parameterTypes);

// emit the assembly for calling the original method using call instead of callvirt
ILGenerator baseIl = baseMethodBuilder.GetILGenerator();
baseIl.Emit(OpCodes.Ldarg_0);
for (int i = 0; i < parameters.Length; ++i)
baseIl.Emit(OpCodes.Ldarg, i + 1);
baseIl.Emit(OpCodes.Call, method);
baseIl.Emit(OpCodes.Ret);
}

// override the original method with a new one that dispatches to python
methodBuilder = typeBuilder.DefineMethod(method.Name,
MethodAttributes.Public |
MethodAttributes.ReuseSlot |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
method.CallingConvention,
method.ReturnType,
parameterTypes);
il = methodBuilder.GetILGenerator();
MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name,
MethodAttributes.Public |
MethodAttributes.ReuseSlot |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
method.CallingConvention,
method.ReturnType,
parameterTypes);
ILGenerator il = methodBuilder.GetILGenerator();
il.DeclareLocal(typeof(Object[]));
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, method.Name);
il.Emit(OpCodes.Ldstr, baseMethodName);

// don't fall back to the base type's method if it's abstract
if (null != baseMethodName)
il.Emit(OpCodes.Ldstr, baseMethodName);
else
il.Emit(OpCodes.Ldnull);

il.Emit(OpCodes.Ldc_I4, parameters.Length);
il.Emit(OpCodes.Newarr, typeof(System.Object));
il.Emit(OpCodes.Stloc_0);
Expand Down Expand Up @@ -624,7 +634,7 @@ public static T InvokeMethod<T>(IPythonDerivedType obj, string methodName, strin
}

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

return (T)obj.GetType().InvokeMember(origMethodName,
BindingFlags.InvokeMethod,
Expand Down Expand Up @@ -683,7 +693,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s
}

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

obj.GetType().InvokeMember(origMethodName,
BindingFlags.InvokeMethod,
Expand Down
27 changes: 27 additions & 0 deletions src/testing/subclasstest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@ public interface IInterfaceTest

// test passing objects and boxing primitives
string bar(string s, int i);

// test events on interfaces
event TestEventHandler TestEvent;

void OnTestEvent(int value);
}

public class SubClassTest : IInterfaceTest
{
public event TestEventHandler TestEvent;

public SubClassTest()
{
}
Expand Down Expand Up @@ -48,6 +55,13 @@ public static IList<string> test_list(SubClassTest x)
// calls into python if return_list is overriden
return x.return_list();
}

// raise the test event
public virtual void OnTestEvent(int value)
{
if (null != TestEvent)
TestEvent(this, new TestEventArgs(value));
}
}

public class TestFunctions
Expand Down Expand Up @@ -75,5 +89,18 @@ public static IInterfaceTest pass_through(IInterfaceTest s)
{
return s;
}

public static int test_event(IInterfaceTest x, int value)
{
// reuse the event handler from eventtest.cs
EventTest et = new EventTest();
x.TestEvent += et.GenericHandler;

// raise the event (should trigger both python and managed handlers)
x.OnTestEvent(value);

x.TestEvent -= et.GenericHandler;
return et.value;
}
}
}
46 changes: 45 additions & 1 deletion src/tests/test_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
clr.AddReference('System')

import sys, os, string, unittest, types
from Python.Test import TestFunctions, SubClassTest, IInterfaceTest
from Python.Test import TestFunctions, SubClassTest, IInterfaceTest, TestEventArgs
from System.Collections.Generic import List
from System import NotImplementedException

# class that implements the test interface
class InterfaceTestClass(IInterfaceTest):
Expand Down Expand Up @@ -47,6 +48,26 @@ def return_list(self):
l.Add("C")
return l

# class that implements IInterfaceTest.TestEvent
class DerivedEventTest(IInterfaceTest):
__namespace__ = "Python.Test"

def __init__(self):
self.event_handlers = []

# event handling
def add_TestEvent(self, handler):
self.event_handlers.append(handler)

def remove_TestEvent(self, handler):
self.event_handlers.remove(handler)

def OnTestEvent(self, value):
args = TestEventArgs(value)
for handler in self.event_handlers:
handler(self, args)


class SubClassTests(unittest.TestCase):
"""Test subclassing managed types"""

Expand Down Expand Up @@ -109,6 +130,29 @@ def testCreateInstance(self):
y = TestFunctions.pass_through(object2)
self.assertEqual(id(y), id(object2))

def testEvents(self):

class EventHandler:
def handler(self, x, args):
self.value = args.value

event_handler = EventHandler()

x = SubClassTest()
x.TestEvent += event_handler.handler
self.assertEqual(TestFunctions.test_event(x, 1), 1)
self.assertEqual(event_handler.value, 1)

i = InterfaceTestClass()
self.assertRaises(NotImplementedException, TestFunctions.test_event, i, 2)

d = DerivedEventTest()
d.add_TestEvent(event_handler.handler)
self.assertEqual(TestFunctions.test_event(d, 3), 3)
self.assertEqual(event_handler.value, 3)
self.assertEqual(len(d.event_handlers), 1)


def test_suite():
return unittest.makeSuite(SubClassTests)

Expand Down