Why are default constructors called on exceptions?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Why are default constructors called on exceptions?

Chris Gagnon
 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

_________________________________________________
Python.NET mailing list - [hidden email]
http://mail.python.org/mailman/listinfo/pythondotnet
Reply | Threaded
Open this post in threaded view
|

Re: Why are default constructors called on exceptions?

Alexey Borzenkov
On Tue, Dec 15, 2009 at 6:02 AM, Chris Gagnon <[hidden email]> 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
_________________________________________________
Python.NET mailing list - [hidden email]
http://mail.python.org/mailman/listinfo/pythondotnet
Reply | Threaded
Open this post in threaded view
|

Re: Why are default constructors called on exceptions?

Alexey Borzenkov
On Thu, Dec 17, 2009 at 12:39 AM, Alexey Borzenkov <[hidden email]> 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=995109e3ed27d6061dde5376f2968c9fbad894b7

I hope maintainers would take a look at this and maybe apply it in their tree.
_________________________________________________
Python.NET mailing list - [hidden email]
http://mail.python.org/mailman/listinfo/pythondotnet
Reply | Threaded
Open this post in threaded view
|

Re: Why are default constructors called on exceptions?

Chris Gagnon
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 <[hidden email]> wrote:
On Thu, Dec 17, 2009 at 12:39 AM, Alexey Borzenkov <[hidden email]> 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=995109e3ed27d6061dde5376f2968c9fbad894b7

I hope maintainers would take a look at this and maybe apply it in their tree.


_________________________________________________
Python.NET mailing list - [hidden email]
http://mail.python.org/mailman/listinfo/pythondotnet