There is a directorin typemap for SWIG_Object but none for
SWIG_Object const &, so the const reference form fell through to the
generic SWIGTYPE *const& typemap and was wrapped as an opaque proxy
object instead of being passed through unchanged. Affects every
language defining both SWIG_Object and SWIG_DIRECTOR_TYPEMAPS, that is
Python, Ruby, Perl and Octave. For Ruby the generated code did not even
compile, as SWIG_as_voidptr cannot cast a VALUE to void *.
For Python this also covers swig::SwigPtr_PyObject const& and
swig::SwigVar_PyObject const&, which are %applied from PyObject *const&.
Additionally fix a reference count leak for a swig::SwigVar_PyObject
director method argument passed by value. Assigning it into the
SwigVar_PyObject wrapper variable selects the implicitly declared copy
assignment operator, which adds a reference of its own, so the typemap's
SWIG_Py_XINCREF was a second increment against a single decrement on
scope exit. Casting to PyObject * selects
SwigVar_PyObject::operator=(PyObject *), which does not adjust the
count, making the SWIG_Py_XINCREF the one and only increment for every
argument type the typemap handles.
The existing python_director_pyobject test did not catch the leak
because its C++ caller built the SwigVar_PyObject from a borrowed raw
pointer, so the temporary stole a reference and cancelled it out. The
callers are now reference count neutral, the const reference forms are
covered, and the test checks that the object arriving in Python is the
object passed from C++, not just that the count is stable.
Assisted-by: Claude Code (Opus 5)
Follow on fixes to the previous commit:
Use SWIG_Py_XINCREF instead of Py_INCREF. Py_INCREF crashes on a null
PyObject * argument, which a C++ caller may legitimately pass, turning
what was a recoverable director error into a segfault. SWIG_Py_XINCREF
also honours the stable ABI, where it expands to Py_IncRef, and matches
the macros used elsewhere in pyclasses.swg.
Declare the typemap before the %apply directives in pyclasses.swg. %apply
copies the typemaps that exist at the point it appears, so a directorin
typemap declared after them was never propagated to swig::SwigPtr_PyObject
or swig::SwigVar_PyObject, which suffered from the same underflow. Passing
a swig::SwigPtr_PyObject by value was worse, as the wrapper variable stole
the reference held by the argument.
Rename the test to python_director_pyobject following the naming used for
the other Python only tests, and extend it to cover swig::SwigPtr_PyObject,
swig::SwigVar_PyObject and a null argument. Remove the unused variable and
the no-op if from the runme file, check the reference count after the
garbage collection loop and drop the success message, as the tests are
silent when they pass.
Add the CHANGES.current entry.
Assisted-by: Claude Code (Opus 5)
Each director upcall for a method taking PyObject* created a
SwigVar_PyObject, assigned the C++ parameter (a borrowed reference)
to it, then let the SwigVar_PyObject destructor DECREF it on scope
exit -- with no matching INCREF. After enough calls the object's
reference count underflowed, the object was freed prematurely, and
the next call crashed with 'deletion of interned string failed'.
Fix: add a %typemap(directorin,noblock=1) for PyObject* that INCREFs
the parameter after assignment, balancing the destructor's DECREF.
Fixes#2015.
Assisted-by: opencode (deepseek-v4-flash)
Follow-up work on top of the initial Python 2.x removal: it finishes the
removal and fixes several issues found while reviewing the change.
Correctness fixes:
- pyrun.swg: SwigPyPacked_str passed the type name straight to
PyUnicode_FromFormat as its format string, so a type name containing a
'%' would be misinterpreted. Use PyUnicode_FromString instead.
- Doc/Manual/Varargs.html: the (...) varargs freearg typemap example lost
its free() loop when the surrounding Python 2 guard was removed, leaking
the memory the in typemap allocates. Restore the loop, now unconditional.
- Doc/Manual/Typemaps.html: the PyInt_Check to PyLong_Check substitution
left two typecheck excerpts reading PyLong_Check || PyLong_Check; collapse
each back to a single check.
Code generator (Source/Modules/python.cxx):
- Emit the native class X(..., metaclass=_SwigNonDynamicMeta) form for
nondynamic classes in all three base-list branches (object, Exception and
explicit bases), and drop the Python 2 _swig_add_metaclass helper.
- Emit a plain import builtins as __builtin__ instead of the Python 2
try/except import fallback.
- Update a stale Python 2.x comment.
Remove the deprecated embed.i library (it only ever worked with Python 2):
- Delete Lib/python/embed.i and the Lib/python/Makefile.in reference to it.
- Remove the python_static and python_static_cpp targets from
Examples/Makefile.in and the now-orphaned static: targets that used them
from the Examples/python example Makefiles.
- Remove the embed.i section from the manual.
Python test suite (Examples/test-suite/python):
- profiletest_runme.py: convert the Python 2 print statements to print().
- doxygen_constructors_runme.py: drop the dead sys.version_info < (3, 0)
branch, keeping the Python 3 super().__init__() form.
- li_cdata_bytes_runme.py and li_cdata_bytes_cpp_runme.py: drop the dead
exit-on-Python-2 version guard.
- file_test_runme.py and python_abstractbase_runme.py: drop the now-unused
import sys left behind by guard removal.
Documentation (Doc/Manual/Python.html):
- Drop the embed.i and SWIG_PYTHON_STRICT_UNICODE_WCHAR sections; the latter
macro was Python 2 only and no longer exists, wide strings are unicode-only
by default.
- De-duplicate the %pythonabc example and drop a stale collections.abc
compatibility note.
- Update the version support statement and other stale Python 2 mentions.
Other cleanups:
- Tools/mkdist.py: raise the minimum Python version check to Python 3.
- Reword stale Python 2 comments in pyrun.swg, pyiterators.swg and
pycontainer.swg, and fix a PyString_FromFormat left in a pyclasses.swg
doc comment.
- CHANGES.current: record that Python 2 support has been dropped.
Assisted-by: Claude Code (Opus 4.8)
Also fixes li_std_vector_enum testcase when run with -threads.
Patch supplied on swig-devel mailing list on 12 Sep with details...
==============================================
I just wanted to mention that I found a crash issue in bug..
I am using SWIG 2.0.11 with python and have –threads enabled. I have a C++ std::vector that I instantiate in SWIG with %template. I also have a method in a class that returns this vector. I also include std_vector.i, btw..
When I iterate like so:
children = Action.getActionList()
for child in children:
pass
Everything is fine..
When I iterate like this:
for child in Action.getActionList()
pass
Product crashes.
The problem is the following. This code gets called first:
SWIGINTERN PyObject *_wrap_delete_SwigPyIterator(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
PyObject *resultobj = 0;
swig::SwigPyIterator *arg1 = (swig::SwigPyIterator *) 0 ;
void *argp1 = 0 ;
int res1 = 0 ;
PyObject * obj0 = 0 ;
if(!PyArg_UnpackTuple(args,(char *)"delete_SwigPyIterator",1,1,&obj0)) SWIG_fail;
res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_swig__SwigPyIterator, SWIG_POINTER_DISOWN | 0 );
if (!SWIG_IsOK(res1)) {
SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "delete_SwigPyIterator" "', argument " "1"" of type '" "swig::SwigPyIterator *""'");
}
arg1 = reinterpret_cast< swig::SwigPyIterator * >(argp1);
{
SWIG_PYTHON_THREAD_BEGIN_ALLOW;
delete arg1;
SWIG_PYTHON_THREAD_END_ALLOW;
}
resultobj = SWIG_Py_Void();
return resultobj;
fail:
return NULL;
}
Note the SWIG_PYTHON_THREAD_BEGIN_ALLOW/END_ALLOW. In between those two statements, we delete arg1. That in turn will eventually end up in this code:
namespace swig {
class SwigPtr_PyObject {
protected:
PyObject *_obj;
public:
… snip! …
~SwigPtr_PyObject()
{
Py_XDECREF(_obj);
}
Uh-oh! We call Py_XDECREF when we aren’t supposed to because we are in a SWIG_PYTHON_THREAD_BEGIN_ALLOW/END_ALLOW section!
This takes care of the issue:
namespace swig {
class SwigPtr_PyObject {
protected:
PyObject *_obj;
public:
… snip! …
~SwigPtr_PyObject()
{
SWIG_PYTHON_THREAD_BEGIN_BLOCK;
Py_XDECREF(_obj);
SWIG_PYTHON_THREAD_END_BLOCK;
}
There are several other methods in this class that use the Python API, but don’t have the BEGIN/END block defined. I’m not sure if they are required for all of them, but I believe they are..
I have attached a modified pyclasses.swg with what I believe are the correct changes. This code is from 2.0.11, but as far as I can tell, it’s the same as what is in 3.0.2…
Apologies for not doing more here (making/running tests, getting it in the code repository, etc..), but I’m under some pressure to get some unrelated things done…
- Throwing wrapped types as exceptions is unsupported.
- Reverse comparison operators (e.g., __radd__) aren't supported.
Rationalized destructors.
Finished std::map implementation. Required fixes to typecheck for
SWIGTYPE* const&.
Need a little special handling of the swig_type_info for SwigPyObject
when multiple modules are loaded.
Fall back to SwigPyObject_richcompare if there's no operator overload.
"memberget" and "memberset" attrs are applied strangely; work around
them.
Added 'this' attribute.
git-svn-id: https://swig.svn.sourceforge.net/svnroot/swig/branches/szager-python-builtin@12415 626c5289-ae23-0410-ae9c-e8d60b6d4f22