-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Call PySys_SetArgv when initializing interpreter. #2341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
1801811
c3e96c2
5145e58
62243f2
32c1445
3ff967d
27ad85e
fb3de9f
3b8438e
0566160
74243c5
c55ab94
fe38a24
3aa548f
1d7f2b3
d5c1df9
aa3b8b8
0627b12
5c38bc5
8358be4
8d64831
3ad3c6d
abc7b38
65881fe
7763c78
6ddc929
ff2976d
912e1c9
52d88c3
1ede69e
318495e
96cb69f
e5c3305
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -87,29 +87,126 @@ struct embedded_module { | |||||
} | ||||||
}; | ||||||
|
||||||
/// Python 2.x/3.x-compatible version of `PySys_SetArgv` | ||||||
inline void set_interpreter_argv(int argc, char** argv, bool add_current_dir_to_path) { | ||||||
// Before it was special-cased in python 3.8, passing an empty or null argv | ||||||
// caused a segfault, so we have to reimplement the special case ourselves. | ||||||
char** safe_argv = argv; | ||||||
if (nullptr == argv || argc <= 0) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I prefer the variable first (such as in the second half of this line, in fact). |
||||||
safe_argv = new char*[1]; | ||||||
if (nullptr == safe_argv) return; | ||||||
safe_argv[0] = new char[1]; | ||||||
if (nullptr == safe_argv[0]) { | ||||||
delete[] safe_argv; | ||||||
return; | ||||||
} | ||||||
safe_argv[0][0] = '\0'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this function really modify the You can always be more strict; if you have a |
||||||
argc = 1; | ||||||
} | ||||||
#if PY_MAJOR_VERSION >= 3 | ||||||
// SetArgv* on python 3 takes wchar_t, so we have to convert. | ||||||
wchar_t** widened_argv = new wchar_t*[static_cast<unsigned>(argc)]; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. Although I couldn't reproduce it on any of my local compilers, github's clang gave an error there. argc is guaranteed to be positive by the if guard above, so the cast is safe. |
||||||
for (int ii = 0; ii < argc; ++ii) { | ||||||
# if PY_MINOR_VERSION >= 5 | ||||||
// From Python 3.5 onwards, we're supposed to use Py_DecodeLocale to | ||||||
// generate the wchar_t version of argv. | ||||||
widened_argv[ii] = Py_DecodeLocale(safe_argv[ii], nullptr); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're trying to avoid sign conversions, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, but that would make the loop condition a signed comparison error There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think it would be cleaner if I did size_t argc_unsigned = static_cast<unsigned>(argc) at the top of the function and then just used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤦 ...nope. If I do that I get narrowing conversion errors on the PySys_SetArgv calls. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one of the impossible things to get completely right without using some casts. The array indices are I'm not going to nag about this, but there is some inconsistency in this patch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improved slightly ( |
||||||
# define FREE_WIDENED_ARG(X) PyMem_RawFree(X) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
# else | ||||||
// Before Python 3.5, we're stuck with mbstowcs, which may or may not | ||||||
// actually work. Mercifully, pyconfig.h provides this define: | ||||||
# ifdef HAVE_BROKEN_MBSTOWCS | ||||||
size_t count = strlen(safe_argv[ii]); | ||||||
# else | ||||||
size_t count = mbstowcs(nullptr, safe_argv[ii], 0); | ||||||
# endif | ||||||
widened_argv[ii] = nullptr; | ||||||
if (count != static_cast<size_t>(-1)) { | ||||||
widened_argv[ii] = new wchar_t[count + 1]; | ||||||
mbstowcs(widened_argv[ii], safe_argv[ii], count + 1); | ||||||
} | ||||||
# define FREE_WIDENED_ARG(X) delete[] X | ||||||
# endif | ||||||
if (nullptr == widened_argv[ii]) { | ||||||
// Either we ran out of memory or had a unicode encoding issue. | ||||||
// Free what we've encoded so far and bail. | ||||||
for (--ii; ii >= 0; --ii) | ||||||
FREE_WIDENED_ARG(widened_argv[ii]); | ||||||
return; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we raise an exception here, instead of silently doing nothing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think silently doing nothing is actually OK here. Consider:
That said, I'm not married to the decision. It just seemed like a reasonable fallback to revert to the old behavior. |
||||||
} | ||||||
} | ||||||
|
||||||
# if PY_MINOR_VERSION < 1 || (PY_MINOR_VERSION == 1 && PY_MICRO_VERSION < 3) | ||||||
# define NEED_PYRUN_TO_SANITIZE_PATH 1 | ||||||
// don't have SetArgvEx yet | ||||||
PySys_SetArgv(argc, widened_argv); | ||||||
# else | ||||||
PySys_SetArgvEx(argc, widened_argv, add_current_dir_to_path ? 1 : 0); | ||||||
# endif | ||||||
|
||||||
// PySys_SetArgv makes new PyUnicode objects so we can clean up this memory | ||||||
if (nullptr != widened_argv) { | ||||||
for (int ii = 0; ii < argc; ++ii) | ||||||
if (nullptr != widened_argv[ii]) | ||||||
FREE_WIDENED_ARG(widened_argv[ii]); | ||||||
delete[] widened_argv; | ||||||
} | ||||||
# undef FREE_WIDENED_ARG | ||||||
#else | ||||||
// python 2.x | ||||||
# if PY_MINOR_VERSION < 6 || (PY_MINOR_VERSION == 6 && PY_MICRO_VERSION < 6) | ||||||
# define NEED_PYRUN_TO_SANITIZE_PATH 1 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need to be a macro? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure. My worry was that if I made it a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really, really don't like using macros for flow control. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed how this code is gated in the rewrite; it's still gated by the |
||||||
// don't have SetArgvEx yet | ||||||
PySys_SetArgv(argc, safe_argv); | ||||||
# else | ||||||
PySys_SetArgvEx(argc, safe_argv, add_current_dir_to_path ? 1 : 0); | ||||||
# endif | ||||||
#endif | ||||||
|
||||||
#ifdef NEED_PYRUN_TO_SANITIZE_PATH | ||||||
# undef NEED_PYRUN_TO_SANITIZE_PATH | ||||||
if (!add_current_dir_to_path) | ||||||
PyRun_SimpleString("import sys; sys.path.pop(0)\n"); | ||||||
#endif | ||||||
|
||||||
// if we allocated new memory to make safe_argv, we need to free it | ||||||
if (safe_argv != argv) { | ||||||
delete[] safe_argv[0]; | ||||||
delete[] safe_argv; | ||||||
} | ||||||
} | ||||||
|
||||||
PYBIND11_NAMESPACE_END(detail) | ||||||
|
||||||
/** \rst | ||||||
Initialize the Python interpreter. No other pybind11 or CPython API functions can be | ||||||
called before this is done; with the exception of `PYBIND11_EMBEDDED_MODULE`. The | ||||||
optional parameter can be used to skip the registration of signal handlers (see the | ||||||
`Python documentation`_ for details). Calling this function again after the interpreter | ||||||
has already been initialized is a fatal error. | ||||||
optional `init_signal_handlers` parameter can be used to skip the registration of | ||||||
signal handlers (see the `Python documentation`_ for details). Calling this function | ||||||
again after the interpreter has already been initialized is a fatal error. | ||||||
|
||||||
If initializing the Python interpreter fails, then the program is terminated. (This | ||||||
is controlled by the CPython runtime and is an exception to pybind11's normal behavior | ||||||
of throwing exceptions on errors.) | ||||||
|
||||||
The remaining optional parameters, `argc`, `argv`, and `add_current_dir_to_path` are | ||||||
used to populate ``sys.argv`` and ``sys.path``. | ||||||
See the |PySys_SetArgvEx documentation|_ for details. | ||||||
|
||||||
.. _Python documentation: https://docs.python.org/3/c-api/init.html#c.Py_InitializeEx | ||||||
.. |PySys_SetArgvEx documentation| replace:: ``PySys_SetArgvEx`` documentation | ||||||
.. _PySys_SetArgvEx documentation: https://docs.python.org/3/c-api/init.html#c.PySys_SetArgvEx | ||||||
\endrst */ | ||||||
inline void initialize_interpreter(bool init_signal_handlers = true) { | ||||||
inline void initialize_interpreter(bool init_signal_handlers = true, | ||||||
int argc = 0, | ||||||
char** argv = nullptr, | ||||||
bool add_current_dir_to_path = true) { | ||||||
if (Py_IsInitialized()) | ||||||
pybind11_fail("The interpreter is already running"); | ||||||
|
||||||
Py_InitializeEx(init_signal_handlers ? 1 : 0); | ||||||
|
||||||
// Make .py files in the working directory available by default | ||||||
module::import("sys").attr("path").cast<list>().append("."); | ||||||
detail::set_interpreter_argv(argc, argv, add_current_dir_to_path); | ||||||
} | ||||||
|
||||||
/** \rst | ||||||
|
@@ -171,6 +268,8 @@ inline void finalize_interpreter() { | |||||
Scope guard version of `initialize_interpreter` and `finalize_interpreter`. | ||||||
This a move-only guard and only a single instance can exist. | ||||||
|
||||||
See `initialize_interpreter` for a discussion of its constructor arguments. | ||||||
|
||||||
.. code-block:: cpp | ||||||
|
||||||
#include <pybind11/embed.h> | ||||||
|
@@ -182,8 +281,11 @@ inline void finalize_interpreter() { | |||||
\endrst */ | ||||||
class scoped_interpreter { | ||||||
public: | ||||||
scoped_interpreter(bool init_signal_handlers = true) { | ||||||
initialize_interpreter(init_signal_handlers); | ||||||
scoped_interpreter(bool init_signal_handlers = true, | ||||||
int argc = 0, | ||||||
char** argv = nullptr, | ||||||
bool add_current_dir_to_path = true) { | ||||||
initialize_interpreter(init_signal_handlers, argc, argv, add_current_dir_to_path); | ||||||
} | ||||||
|
||||||
scoped_interpreter(const scoped_interpreter &) = delete; | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: shouldn't we add an debug assert that argc >= 0? We are casting a signed int to an unsigned size_t
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's covered by the
argc <= 0
below, combined withargc = 1
.