return unittest.skipUnless(hasattr(os, name), 'requires os.%s' % name)
+# On platforms without a native spawnv(), os.py provides a Python fallback
+# built on fork()+exec*() that reports argument conversion failures from the
+# child as exit status 127 instead of raising, so tests of the C
+# implementation's error paths cannot run against it.
+requires_native_spawnv = unittest.skipUnless(
+ isinstance(getattr(os, 'spawnv', None), types.BuiltinFunctionType),
+ 'requires the native C os.spawnv')
+
+
# bpo-41625: On AIX, splice() only works with a socket, not with a pipe.
requires_splice_pipe = unittest.skipIf(sys.platform.startswith("aix"),
'on AIX, splice() only accepts sockets')
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, ('',), {})
self.assertRaises(ValueError, os.spawnve, os.P_NOWAIT, program, [''], {})
+ @requires_native_spawnv
+ def test_spawnv_arg_conversion_errors(self):
+ # A non-path argv item gets a TypeError naming the argument...
+ with self.assertRaisesRegex(TypeError, 'must contain only strings'):
+ os.spawnv(os.P_NOWAIT, sys.executable, [sys.executable, 123])
+ # ...but other conversion errors must not be masked as TypeError
+ # (gh-151416).
+ with self.assertRaises(ValueError):
+ os.spawnv(os.P_NOWAIT, sys.executable,
+ [sys.executable, 'embedded\0null'])
+
+ class RaisingPath:
+ def __fspath__(self):
+ raise RuntimeError('gotcha')
+
+ with self.assertRaisesRegex(RuntimeError, 'gotcha'):
+ os.spawnv(os.P_NOWAIT, sys.executable,
+ [sys.executable, RaisingPath()])
+
def _test_invalid_env(self, spawn):
program = sys.executable
args = self.quote_args([program, '-c', 'pass'])
int i;
Py_ssize_t argc;
intptr_t spawnval;
- PyObject *(*getitem)(PyObject *, Py_ssize_t);
/* spawnv has three arguments: (mode, path, argv), where
argv is a list or tuple of strings. */
if (PyList_Check(argv)) {
argc = PyList_Size(argv);
- getitem = PyList_GetItem;
}
else if (PyTuple_Check(argv)) {
argc = PyTuple_Size(argv);
- getitem = PyTuple_GetItem;
}
else {
PyErr_SetString(PyExc_TypeError,
return PyErr_NoMemory();
}
for (i = 0; i < argc; i++) {
- if (!fsconvert_strdup((*getitem)(argv, i),
- &argvlist[i])) {
+ // The item must be a strong reference because of possible
+ // side-effects of PyUnicode_FS{Converter,Decoder}() in
+ // fsconvert_strdup(): an item's __fspath__() can mutate a list
+ // *argv*, releasing the list's reference to the item (gh-151416).
+ PyObject *item = PySequence_ITEM(argv, i);
+ if (item == NULL) {
free_string_array(argvlist, i);
- PyErr_SetString(
- PyExc_TypeError,
- "spawnv() arg 2 must contain only strings");
return NULL;
}
+ if (!fsconvert_strdup(item, &argvlist[i])) {
+ Py_DECREF(item);
+ free_string_array(argvlist, i);
+ // Add argument context to the converter's terse TypeError, but
+ // let MemoryError, codec errors, embedded-null ValueError, etc.
+ // propagate unmasked.
+ if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+ PyErr_SetString(
+ PyExc_TypeError,
+ "spawnv() arg 2 must contain only strings");
+ }
+ return NULL;
+ }
+ Py_DECREF(item);
if (i == 0 && !argvlist[0][0]) {
free_string_array(argvlist, i + 1);
PyErr_SetString(
PyObject *res = NULL;
Py_ssize_t argc, i, envc;
intptr_t spawnval;
- PyObject *(*getitem)(PyObject *, Py_ssize_t);
Py_ssize_t lastarg = 0;
/* spawnve has four arguments: (mode, path, argv, env), where
if (PyList_Check(argv)) {
argc = PyList_Size(argv);
- getitem = PyList_GetItem;
}
else if (PyTuple_Check(argv)) {
argc = PyTuple_Size(argv);
- getitem = PyTuple_GetItem;
}
else {
PyErr_SetString(PyExc_TypeError,
goto fail_0;
}
for (i = 0; i < argc; i++) {
- if (!fsconvert_strdup((*getitem)(argv, i),
- &argvlist[i]))
- {
+ // The item must be a strong reference because of possible
+ // side-effects of PyUnicode_FS{Converter,Decoder}() in
+ // fsconvert_strdup(): an item's __fspath__() can mutate a list
+ // *argv*, releasing the list's reference to the item (gh-151416).
+ PyObject *item = PySequence_ITEM(argv, i);
+ if (item == NULL) {
lastarg = i;
goto fail_1;
}
+ if (!fsconvert_strdup(item, &argvlist[i])) {
+ Py_DECREF(item);
+ lastarg = i;
+ goto fail_1;
+ }
+ Py_DECREF(item);
if (i == 0 && !argvlist[0][0]) {
lastarg = i + 1;
PyErr_SetString(