Hi,
maybe this is me misunderstanding some deep memory allocation issues, but I noticed a quite strange behaviour with SWIG and temporary objects. If this is the case, I'd like to have the matching pointer to the documentation or ask you to consider this as a documentation bug :-)
See attached reduced test case. When calling make.sh, I expect to have the same result (1.0) twice, but I get:
./make.sh
A: 1.0
B: 0.0
I am using debian, python version 2.5, and swig 1.3.40, g++ 4.3.4, though this is not the only configuration showing this behaviour (Kubuntu 9.10 with default packages does the same).
Best regards,
Onno
Boiled-down test case for access problem/bug
Modifying your test.i file to:
///////////////////////////////////////////////////////////
%module test
%{
#include "test.hh"
%}
%feature("exceptvar") {
printf("%p In variable wrapper ... $symname\n", arg1);
$action;
}
%exception {
printf("%p In function... $symname\n", arg1);
$action;
}
%exception v::v {
$action;
printf("%p In constructor... $symname\n", result);
}
%exception comp::comp {
$action;
printf("%p In constructor... $symname\n", result);
}
%exception v::~v() {
printf("%p In destructor... $symname\n", arg1);
$action;
}
%exception comp::~comp() {
printf("%p In destructor... $symname\n", arg1);
$action;
}
%include "test.hh"
///////////////////////////////////////////////////////////
With just the one line of python code:
print "B:", comp(v(1,1,1), v(2,2,2)).a.x
The output is:
B:0x45ae698 In constructor... new_v
0x45af200 In constructor... new_v
0x45af470 In constructor... new_comp
0x45af200 In destructor... delete_v
0x45ae698 In destructor... delete_v
0x45af470 In variable wrapper ... comp_a_get
0x45af470 In destructor... delete_comp
0x45af470 In variable wrapper ... v_x_get
1.0
So v_x_get uses deleted memory (after delete_comp is called). Note the address of comp is the same as comp::x as x is at the first member variable. Valgrind confirms the problem:
==26786== Invalid read of size 8
==26786== at 0x4036746: _wrap_v_x_get (in /home/william/swig/trunk/Examples/william/temp_access/_test.so)
==26786== by 0x8065B83: PyObject_CallFunction (in /usr/bin/python2.6)
==26786== by 0x80940C6: PyObject_GenericGetAttr (in /usr/bin/python2.6)
==26786== by 0x80B280E: ??? (in /usr/bin/python2.6)
==26786== by 0x80DDD1F: PyEval_EvalFrameEx (in /usr/bin/python2.6)
==26786== by 0x80E2806: PyEval_EvalCodeEx (in /usr/bin/python2.6)
==26786== by 0x80E2906: PyEval_EvalCode (in /usr/bin/python2.6)
==26786== by 0x81005AC: PyRun_FileExFlags (in /usr/bin/python2.6)
==26786== by 0x8100811: PyRun_SimpleFileExFlags (in /usr/bin/python2.6)
==26786== by 0x805DE5B: Py_Main (in /usr/bin/python2.6)
==26786== by 0x805D03A: main (in /usr/bin/python2.6)
I don't have the solution nor the expertise about the python reference counting, so all I can do is confirm the behaviour will provide undefined results, even though I see the correct result of 1.0. However the equivalent code in Java is known to be problematic and the solution is to enforce a reference in the 'comp' instance to 'a' so that it is not garbage collected early.
Still present in git master.
All-in-one testcase
test.i
:And
runme
:The output shows two
delete_v
beforecomp_a_get
and a bogus value:Still reproducible with git master shortly after 4.2.0.
Looking at this afresh, the two
delete_v
calls are OK as those are for the temporaryv(1.1.1)
andv(2,2,2)
objects passed to thecomp
constructor.The essential problem here is that
comp_a_get
returns a Pythonv
object which wraps a pointer to the C++v a
inside the temporarycomp
object, but thendelete_comp
is called before we callv_x_get
on thatv
.For this to work it seems either a reference to
comp
needs to be kept by thev
returned bycomp_a_get
, or else the C++v
needs to be copied. Copying would be reasonable in this case, but in general it's not ideal -struct v
could be very large or not support copying.