Why are default constructors called on exceptions?
Consider this C# library namespace MyLib
{ public class CXDoc { // Construction public CXDoc(string sPath) { XmlDocument oDoc = new XmlDocument(); oDoc.Load(sPath); } public CXDoc() { throw new Exception("Deprecated constructor."); } } }
Now this script
import clr clr.AddReference("System") <System.Reflection.Assembly object at 0x0435AE50> from System import * clr.AddReference("System.Data") <System.Reflection.Assembly object at 0x0435AE90> from System.Data import * clr.AddReference("System.Xml") <System.Reflection.Assembly object at 0x044F9B10> from System.Xml import * clr.AddReference("MyLib") <System.Reflection.Assembly object at 0x02A95390>
import MyLib from MyLib import *
Ready to go!
testI1 = MyLib.CXDoc("path that does exist")
Yeah all worked fine!
testI2 = MyLib.CXDoc("path that dosn't exist") Traceback (most recent call last): File "<stdin>", line 1, in <module> System.Exception: Deprecated constructor. at MyLib.CXDoc..ctor()
What happened! Well inside the constructor it hit an file not found exception of some such. Can someone explain why it hides that from me and calls the default constructor and shows me that exception. This seems like an unintuitive thing to do, and has made debugging a huge pain. -Chris
On Tue, Dec 15, 2009 at 6:02 AM, Chris Gagnon <cgagnon@zindagigames.com> wrote:
namespace MyLib { public class CXDoc { // Construction public CXDoc(string sPath) { XmlDocument oDoc = new XmlDocument(); oDoc.Load(sPath); } public CXDoc() { throw new Exception("Deprecated constructor."); } } }
Now this script
testI2 = MyLib.CXDoc("path that dosn't exist") Traceback (most recent call last): File "<stdin>", line 1, in <module> System.Exception: Deprecated constructor. at MyLib.CXDoc..ctor()
I think what happens here is a mistake in classobject.cs tp_new. If some exception happens during invoking of constructor, it just assumes that binding failed and tries to call constructor without argument in hopes that subclass's __init__ will initialize it. Here's some preliminary patch, though I didn't test it at all. You may try it and tell me if it helps. This patch ensures that we call "default" constructor only if we failed to bind. It won't happen if bound constructor throws some exception. diff --git a/src/runtime/classobject.cs b/src/runtime/classobject.cs index 4d70918..4a90c44 100644 --- a/src/runtime/classobject.cs +++ b/src/runtime/classobject.cs @@ -106,19 +106,7 @@ namespace Python.Runtime { Object obj = self.binder.InvokeRaw(IntPtr.Zero, args, kw); if (obj == null) { - // It is possible for __new__ to be invoked on construction - // of a Python subclass of a managed class, so args may - // reflect more args than are required to instantiate the - // class. So if we cant find a ctor that matches, we'll see - // if there is a default constructor and, if so, assume that - // any extra args are intended for the subclass' __init__. - - IntPtr eargs = Runtime.PyTuple_New(0); - obj = self.binder.InvokeRaw(IntPtr.Zero, eargs, kw); - Runtime.Decref(eargs); - if (obj == null) { - return IntPtr.Zero; - } + return IntPtr.Zero; } return CLRObject.GetInstHandle(obj, tp); diff --git a/src/runtime/constructorbinder.cs b/src/runtime/constructorbinder.cs index f7244c3..69d8c17 100644 --- a/src/runtime/constructorbinder.cs +++ b/src/runtime/constructorbinder.cs @@ -43,10 +43,23 @@ namespace Python.Runtime { Object result; if (binding == null) { - Exceptions.SetError(Exceptions.TypeError, - "no constructor matches given arguments" - ); - return null; + // It is possible for __new__ to be invoked on construction + // of a Python subclass of a managed class, so args may + // reflect more args than are required to instantiate the + // class. So if we cant find a ctor that matches, we'll see + // if there is a default constructor and, if so, assume that + // any extra args are intended for the subclass' __init__. + + IntPtr eargs = Runtime.PyTuple_New(0); + binding = this.Bind(inst, eargs, kw); + Runtime.Decref(eargs); + + if (binding == null) { + Exceptions.SetError(Exceptions.TypeError, + "no constructor matches given arguments" + ); + return null; + } } // Object construction is presumed to be non-blocking and fast
On Thu, Dec 17, 2009 at 12:39 AM, Alexey Borzenkov <snaury@gmail.com> wrote:
You may try it and tell me if it helps.
Ok, I just checked it myself and it appears to work in your case. The patch is now in my tree: http://git.kitsu.ru/patched/pythonnet.git?a=commitdiff;h=995109e3ed27d6061dd... I hope maintainers would take a look at this and maybe apply it in their tree.
Thanks Alexey, I haven't had a chance to try it yet, but I'm sure I'll be integrating your patch into our python.NET here. Cheers, Chris On Thu, Dec 17, 2009 at 1:01 AM, Alexey Borzenkov <snaury@gmail.com> wrote:
On Thu, Dec 17, 2009 at 12:39 AM, Alexey Borzenkov <snaury@gmail.com> wrote:
You may try it and tell me if it helps.
Ok, I just checked it myself and it appears to work in your case. The patch is now in my tree:
http://git.kitsu.ru/patched/pythonnet.git?a=commitdiff;h=995109e3ed27d6061dd...
I hope maintainers would take a look at this and maybe apply it in their tree.
participants (2)
-
Alexey Borzenkov
-
Chris Gagnon