From 56e0baa02305825432603d3b4ef1a8198712dfa2 Mon Sep 17 00:00:00 2001 From: Tony Roberts Date: Mon, 4 Apr 2016 10:34:51 +0100 Subject: [PATCH 1/2] 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. --- src/runtime/classderived.cs | 66 +++++++++++++++++++++---------------- src/testing/subclasstest.cs | 19 +++++++++++ src/tests/test_subclass.py | 18 ++++++++++ 3 files changed, 75 insertions(+), 28 deletions(-) diff --git a/src/runtime/classderived.cs b/src/runtime/classderived.cs index 685becef9..baffbd4e3 100644 --- a/src/runtime/classderived.cs +++ b/src/runtime/classderived.cs @@ -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); @@ -624,7 +634,7 @@ public static T InvokeMethod(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, @@ -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, diff --git a/src/testing/subclasstest.cs b/src/testing/subclasstest.cs index 64cea87c6..cea004933 100644 --- a/src/testing/subclasstest.cs +++ b/src/testing/subclasstest.cs @@ -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() { } @@ -48,6 +55,13 @@ public static IList 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 @@ -75,5 +89,10 @@ public static IInterfaceTest pass_through(IInterfaceTest s) { return s; } + + public static void test_event(IInterfaceTest x, int value) + { + x.OnTestEvent(value); + } } } diff --git a/src/tests/test_subclass.py b/src/tests/test_subclass.py index 113397dba..2c27f4962 100644 --- a/src/tests/test_subclass.py +++ b/src/tests/test_subclass.py @@ -13,6 +13,7 @@ import sys, os, string, unittest, types from Python.Test import TestFunctions, SubClassTest, IInterfaceTest from System.Collections.Generic import List +from System import NotImplementedException # class that implements the test interface class InterfaceTestClass(IInterfaceTest): @@ -109,6 +110,23 @@ 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 + TestFunctions.test_event(x, 1) + self.assertEqual(event_handler.value, 1) + + i = InterfaceTestClass() + self.assertRaises(NotImplementedException, TestFunctions.test_event, i, 2) + + def test_suite(): return unittest.makeSuite(SubClassTests) From b04658e5acf96cd2b60433897bd1ce744c9ac312 Mon Sep 17 00:00:00 2001 From: Tony Roberts Date: Mon, 4 Apr 2016 11:51:48 +0100 Subject: [PATCH 2/2] Update subclass tests with events example. Test events can be implemented in Python and that raising them invokes any handlers added in managed code. --- src/testing/subclasstest.cs | 10 +++++++++- src/tests/test_subclass.py | 30 ++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/testing/subclasstest.cs b/src/testing/subclasstest.cs index cea004933..7a68c6dca 100644 --- a/src/testing/subclasstest.cs +++ b/src/testing/subclasstest.cs @@ -90,9 +90,17 @@ public static IInterfaceTest pass_through(IInterfaceTest s) return s; } - public static void test_event(IInterfaceTest x, int value) + 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; } } } diff --git a/src/tests/test_subclass.py b/src/tests/test_subclass.py index 2c27f4962..f116eb4de 100644 --- a/src/tests/test_subclass.py +++ b/src/tests/test_subclass.py @@ -11,7 +11,7 @@ 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 @@ -48,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""" @@ -120,12 +140,18 @@ def handler(self, x, args): x = SubClassTest() x.TestEvent += event_handler.handler - TestFunctions.test_event(x, 1) + 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)