Fix premature destruction of args/kwargs arguments

The `py::args` or `py::kwargs` arguments aren't properly referenced
when added to the function_call arguments list: their reference counts
drop to zero if the first (non-converting) function call fails, which
means they might be cleaned up before the second pass call runs.

This commit adds a couple of extra `object`s to the `function_call`
where we can stash a reference to them when needed to tie their
lifetime to the function_call object's lifetime.

(Credit to YannickJadoul for catching and proposing a fix in #1223).
This commit is contained in:
Jason Rhinelander 2017-12-23 09:42:32 -04:00
parent 5e7591c66b
commit 48e1f9aadc
2 changed files with 7 additions and 1 deletions

View File

@ -1801,6 +1801,10 @@ struct function_call {
/// The `convert` value the arguments should be loaded with /// The `convert` value the arguments should be loaded with
std::vector<bool> args_convert; std::vector<bool> args_convert;
/// Extra references for the optional `py::args` and/or `py::kwargs` arguments (which, if
/// present, are also in `args` but without a reference).
object args_ref, kwargs_ref;
/// The parent, if any /// The parent, if any
handle parent; handle parent;

View File

@ -568,8 +568,8 @@ protected:
continue; // Unconsumed kwargs, but no py::kwargs argument to accept them continue; // Unconsumed kwargs, but no py::kwargs argument to accept them
// 4a. If we have a py::args argument, create a new tuple with leftovers // 4a. If we have a py::args argument, create a new tuple with leftovers
tuple extra_args;
if (func.has_args) { if (func.has_args) {
tuple extra_args;
if (args_to_copy == 0) { if (args_to_copy == 0) {
// We didn't copy out any position arguments from the args_in tuple, so we // We didn't copy out any position arguments from the args_in tuple, so we
// can reuse it directly without copying: // can reuse it directly without copying:
@ -586,6 +586,7 @@ protected:
} }
call.args.push_back(extra_args); call.args.push_back(extra_args);
call.args_convert.push_back(false); call.args_convert.push_back(false);
call.args_ref = std::move(extra_args);
} }
// 4b. If we have a py::kwargs, pass on any remaining kwargs // 4b. If we have a py::kwargs, pass on any remaining kwargs
@ -594,6 +595,7 @@ protected:
kwargs = dict(); // If we didn't get one, send an empty one kwargs = dict(); // If we didn't get one, send an empty one
call.args.push_back(kwargs); call.args.push_back(kwargs);
call.args_convert.push_back(false); call.args_convert.push_back(false);
call.kwargs_ref = std::move(kwargs);
} }
// 5. Put everything in a vector. Not technically step 5, we've been building it // 5. Put everything in a vector. Not technically step 5, we've been building it