[C++-sig] Segfault with keyword arguments and overloads.
Alex Leach
beamesleach at gmail.com
Wed Aug 7 12:21:39 CEST 2013
Hi,
On Wed, 07 Aug 2013 01:35:54 +0100, Alex Mohr <amohr at pixar.com> wrote:
> I'm hitting a repeatable segfault trying to upgrade our boost version. I
> believe there may be a bug with overload resolution in the presence of
> keyword arguments. Here's a minimal example that crashes for me.
>
> // C++
> #include <boost/python.hpp>
>
> static void f1(int a0, int a1) { }
> static void f2(int a0, int a1, int a2) { }
>
> BOOST_PYTHON_MODULE(kwargCrash) {
> boost::python::def("f", f1, (arg("a1")=2));
> boost::python::def("f", f2, (arg("a2")=2));
> }
I think the signature looks incorrect here. From my understanding of
signatures, if you define one argument name, you should define the name
for all arguments. On class methods, this includes adding an arg for
'self'.
See
http://www.boost.org/doc/libs/1_54_0/libs/python/doc/v2/function_doc_signature.html
Also, it seems strange that you're assigning the final argument a default
value, when the actual C++ function has none. I did add the corresponding
default argument to the C++ function signatures, but that still seg
faults, so I guess you've got default arguments in your actual code (good
minimal example, though!). However, the function signature in the
documentation - ie. from `help(kwargCrash)` - looks correct.
Still, the following code makes the documentation more complete, and also
it actually works:-
#include <boost/python/module.hpp>
#include <boost/python/args.hpp>
#include <boost/python/def.hpp>
static void f1(int a0, int a1=2) { }
static void f2(int a0, int a1, int a2=2) { }
BOOST_PYTHON_MODULE(kwargCrash) {
boost::python::def("f", f1, (boost::python::arg("a0"),
boost::python::arg("a1")=2));
boost::python::def("f", f2, (boost::python::arg("a0"),
boost::python::arg("a1"),
boost::python::arg("a2")=2));
}
>
> # Python
> import kwargCrash
> kwargCrash.f(0, a1=2)
>
> Version info: boost 1.51.0, Python 2.7.5, gcc 4.8.1.
>
> In the example we pass one positional argument and one keyword argument.
> This should fail to match the second overload (f2) since two
> positional arguments are required, and should match and invoke the first
> (f1). Instead, it segfaults.
>
> Here's my hypothesis as to what's happening from inspecting the
> boost.python code:
>
> In libs/python/src/object/function.cpp, function::call() (line 123)
> - We consider overloads in reverse reg order, so we try 'f2' first.
> - We pass the if on line 138, since n_actual is 2,
> f->m_nkeyword_values is 1, and min/max_arity==3.
> - The if on line 144 passes, since kwargs were supplied.
> - The fn has kwargs, so we take the 'else' on line 152.
> - We reach the 'build a new arg tuple...' else clause on line 167.
> - We make a tuple of size 3 and set the first element with the
> positional arg '0'.
> - Now we loop arg_pos = 1 to max_arity-1 (line 180).
> - Line 183, we set: kv = PyTuple_GET_ITEM(f->m_arg_names, arg_pos).
> - Line 189, we do: PyTuple_GET_ITEM(kv, 0).
>
> In this case, I believe kv is actually None, not a tuple, which gives us
> a bogus result, and I crash in the PyDict_GetItem call. Here's why I
> believe kv is actually None:
>
> In function.cpp again, function ctor (line 67)
> - Consider when an instance is created for 'f2'.
> - Line 72: keyword_offset gets set to 2 (max_arity = 3, num_keywords
> = 1).
> - m_arg_names is created with size 3 (max_arity)
> - Line 80: loop and set the first 2 elements (keyword_offset) to None.
>
> So the leading elements of the tuple get set to None, until we actually
> have keywords -- those elements get set to pairs of kwarg-name,
> kwarg-value.
>
> If my inspection is valid, then the indexing logic in function::call is
> busted. It assumes that it always gets a tuple of name/value for kv if
> it gets to line 189. But in this case it gets None since the argument
> at index 1 has no keyword.
True, the first arguments have no signature. I guess it was assumed that
if you supply one argument's signature, you would supply them all. Looking
at the docs for PyTuple_GET_ITEM, it is the same as PyTuple_GetItem,
except that no checking is performed. So it probably returns NULL, which
in turn causes PyDict_GetItem to seg fault. Here's my take, in the form of
a working patch:
---
/home/albl500/builds/boost/src/boost_1_53_0/libs/python/src/object/function.cpp.orig
2013-08-07 11:02:28.569236835 +0100
+++
/home/albl500/builds/boost/src/boost_1_53_0/libs/python/src/object/function.cpp
2013-08-07 11:09:09.688030081 +0100
@@ -177,17 +177,19 @@
// Grab remaining arguments by name from the
keyword dictionary
std::size_t n_actual_processed = n_unnamed_actual;
+ PyObject *kv, *k, *value;
for (std::size_t arg_pos = n_unnamed_actual;
arg_pos < max_arity ; ++arg_pos)
{
// Get the keyword[, value pair] corresponding
- PyObject* kv =
PyTuple_GET_ITEM(f->m_arg_names.ptr(), arg_pos);
+ kv = PyTuple_GET_ITEM(f->m_arg_names.ptr(),
arg_pos);
// If there were any keyword arguments,
// look up the one we need for this
// argument position
- PyObject* value = n_keyword_actual
- ? PyDict_GetItem(keywords,
PyTuple_GET_ITEM(kv, 0))
- : 0;
+ k = PyTuple_GET_ITEM(kv, 0);
+ value = (k != NULL && n_keyword_actual)
+ ? PyDict_GetItem(keywords, k)
+ : 0;
if (!value)
{
>
> Does this sound plausible? I'm a bit surprised this was working for us
> previously, as the code looks like it hasn't really changed. Maybe we
> were getting lucky and Python changed in a way that breaks us? I.e.
> maybe PyTuple_GET_ITEM actually gave None if kv was None, and we never
> had None as a key in the passed keywords dict.
I don't know of any code changes that would have introduced this..
>
> Assuming my hypothesis is true, I'm not sure what the right fix here is
> off the top of my head. Naively it seems like checking for None in
> m_arg_names and rejecting the overload might work, but it feels like the
> right thing to do is to rework the logic used to decide whether or not a
> call has a satisfactory combination of positional and keyword args.
>
> Any thoughts appreciated.
>
> Alex
HTH. Cheers,
Alex
More information about the Cplusplus-sig
mailing list