From 92b88db7abcc688735fd0bbd315207864911767f Mon Sep 17 00:00:00 2001 From: Yoann Vandoorselaere Date: Mon, 2 Feb 2015 11:15:31 +0100 Subject: [PATCH 1/4] Make __dict__ accessible for Python builtin classes Attribute set within instance of a SWIG Python wrapped class are stored in SwigPyObject->dict, which tp_dictoffset slot is pointing to. However, SWIG wrapped classes did not have a __dict__ attribute. Inheriting subclasses did not get the attribute either because the SWIG wrapped classes initialize the tp_dictoffset slot: From http://bugs.python.org/issue16272: "If a type defines a nonzero tp_dictoffset, that type is responsible for defining a `__dict__` slot as part of the tp_getset structures. Failure to do so will result in the dict being inaccessible from Python via `obj.__dict__` from instances of the type or subtypes." Provide a SwigPyObject_get___dict__() function to retrieve the dict attribute or create it when it does not exist yet (it is normally created when setting attribute set), and a PyGetSetDef entry pointing to this function. --- Lib/python/pyrun.swg | 17 +++++++++++++++++ Source/Modules/python.cxx | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/Lib/python/pyrun.swg b/Lib/python/pyrun.swg index a713486d1..daa0b7eef 100644 --- a/Lib/python/pyrun.swg +++ b/Lib/python/pyrun.swg @@ -381,6 +381,23 @@ typedef struct { #endif } SwigPyObject; + +#ifdef SWIGPYTHON_BUILTIN + +SWIGRUNTIME PyObject * +SwigPyObject_get___dict__(PyObject *v, PyObject *SWIGUNUSEDPARM(args)) +{ + SwigPyObject *sobj = (SwigPyObject *)v; + + if (!sobj->dict) + sobj->dict = PyDict_New(); + + Py_INCREF(sobj->dict); + return sobj->dict; +} + +#endif + SWIGRUNTIME PyObject * SwigPyObject_long(SwigPyObject *v) { diff --git a/Source/Modules/python.cxx b/Source/Modules/python.cxx index c71a0f364..9b1cb71e5 100644 --- a/Source/Modules/python.cxx +++ b/Source/Modules/python.cxx @@ -3057,6 +3057,17 @@ public: } /* If this is a builtin type, create a PyGetSetDef entry for this member variable. */ + if (builtin) { + const char *memname = "__dict__"; + Hash *h = Getattr(builtin_getset, memname); + if (!h) { + h = NewHash(); + Setattr(builtin_getset, memname, h); + Delete(h); + } + Setattr(h, "getter", "SwigPyObject_get___dict__"); + } + if (builtin_getter) { String *memname = Getattr(n, "membervariableHandler:sym:name"); if (!memname) From 3983d7b2308c27bf2eb40602fe22ae161d5e5e2d Mon Sep 17 00:00:00 2001 From: Yoann Vandoorselaere Date: Fri, 6 Feb 2015 14:30:47 +0100 Subject: [PATCH 2/4] Fix SwigPyObject->dict memory leak The following patch attempt to fix a memory leak happening when a random class attribute is set. The internal instance dictionary is created but never freed. This fixes the problem for me, although I am not sure the patch is correct. p = MySWIGClass() p.random_attribute = 0 Valgrind report: ==18267== 280 bytes in 1 blocks are definitely lost in loss record 1,372 of 1,780 ==18267== at 0x4A0645D: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) ==18267== by 0x3A90A885DC: PyObject_Malloc (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90B101A8: _PyObject_GC_Malloc (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90B102AC: _PyObject_GC_New (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90A80943: PyDict_New (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90A6E8FC: PyFrame_New (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AE1A65: PyEval_EvalCodeEx (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AE088E: PyEval_EvalFrameEx (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AE21DC: PyEval_EvalCodeEx (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AE22E1: PyEval_EvalCode (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AFB71E: ??? (in /usr/lib64/libpython2.7.so.1.0) ==18267== by 0x3A90AFC8DD: PyRun_FileExFlags (in /usr/lib64/libpython2.7.so.1.0) ==18267== --- Lib/python/builtin.swg | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/python/builtin.swg b/Lib/python/builtin.swg index 28c557a21..1d892375c 100644 --- a/Lib/python/builtin.swg +++ b/Lib/python/builtin.swg @@ -9,6 +9,7 @@ SWIGINTERN void \ wrapper##_closure(PyObject *a) { \ SwigPyObject *sobj; \ sobj = (SwigPyObject *)a; \ + Py_XDECREF(sobj->dict); \ if (sobj->own) { \ PyObject *o = wrapper(a, NULL); \ Py_XDECREF(o); \ From 327d7c968d5eab272ef5ebd1cba3129b95418145 Mon Sep 17 00:00:00 2001 From: Yoann Vandoorselaere Date: Fri, 6 Feb 2015 16:24:43 +0100 Subject: [PATCH 3/4] Attribute of SWIG wrapped classes instances were overwritten on __init__() When a SWIG classes instances is initialized, its internal dictionary was reset to NULL, which result in the loss of any attribute that might have been set for the instance. Only initialize the internal dictionary on actual PyObject creation. class Test(MySwigWrappedClass): def __init__(self): self.val = "Random Value" MySwigWrappedClass.__init__(self) p = Test() print hasattr(p, "val") # Should return True, but used to return False --- Lib/python/pyrun.swg | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/python/pyrun.swg b/Lib/python/pyrun.swg index daa0b7eef..7ef8d5e39 100644 --- a/Lib/python/pyrun.swg +++ b/Lib/python/pyrun.swg @@ -1437,18 +1437,21 @@ SWIG_Python_NewPointerObj(PyObject *self, void *ptr, swig_type_info *type, int f newobj = (SwigPyObject *) newobj->next; newobj->next = next_self; newobj = (SwigPyObject *)next_self; +#ifdef SWIGPYTHON_BUILTIN + newobj->dict = 0; +#endif } } else { newobj = PyObject_New(SwigPyObject, clientdata->pytype); +#ifdef SWIGPYTHON_BUILTIN + newobj->dict = 0; +#endif } if (newobj) { newobj->ptr = ptr; newobj->ty = type; newobj->own = own; newobj->next = 0; -#ifdef SWIGPYTHON_BUILTIN - newobj->dict = 0; -#endif return (PyObject*) newobj; } return SWIG_Py_Void(); From 1a64e74c46be2275ad3b62993de7b9eb125fcf57 Mon Sep 17 00:00:00 2001 From: William S Fulton Date: Sat, 11 Apr 2015 12:35:58 +0100 Subject: [PATCH 4/4] Add python runtime test for dynamically added attributes From #320 --- .../test-suite/python/struct_value_runme.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Examples/test-suite/python/struct_value_runme.py b/Examples/test-suite/python/struct_value_runme.py index 727996f53..a20e83439 100644 --- a/Examples/test-suite/python/struct_value_runme.py +++ b/Examples/test-suite/python/struct_value_runme.py @@ -7,3 +7,24 @@ if b.a.x != 3: raise RuntimeError b.b.x = 3 if b.b.x != 3: raise RuntimeError + + +# Test dynamically added attributes - Github pull request #320 +b.added = 123 + +if b.added != 123: + raise RuntimeError("Wrong attribute value") + +if not b.__dict__.has_key("added"): + raise RuntimeError("Missing added attribute in __dict__") + +class PyBar(struct_value.Bar): + def __init__(self): + self.extra = "hi" + struct_value.Bar.__init__(self) + +pybar = PyBar() +if not pybar.__dict__.has_key("extra"): + raise RuntimeError("Missing extra attribute in __dict__") +if pybar.extra != "hi": + raise RuntimeError("Incorrect attribute value for extra")