[C++-sig] Segfault with keyword arguments and overloads.

Alex Mohr amohr at pixar.com
Wed Aug 7 02:35:54 CEST 2013


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));
}

# 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.

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.

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


More information about the Cplusplus-sig mailing list