Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit efde146

Browse files
committed
Issue #23571: _Py_CheckFunctionResult() now gives the name of the function
which returned an invalid result (result+error or no result without error) in the exception message. Add also unit test to check that the exception contains the name of the function. Special case: the final _PyEval_EvalFrameEx() check doesn't mention the function since it didn't execute a single function but a whole frame.
1 parent 6921c13 commit efde146

6 files changed

Lines changed: 93 additions & 12 deletions

File tree

Include/abstract.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,9 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
267267
PyObject *args, PyObject *kw);
268268

269269
#ifndef Py_LIMITED_API
270-
PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *obj,
271-
const char *func_name);
270+
PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *func,
271+
PyObject *result,
272+
const char *where);
272273
#endif
273274

274275
/*

Lib/test/test_capi.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import random
77
import subprocess
88
import sys
9+
import textwrap
910
import time
1011
import unittest
1112
from test import support
1213
from test.support import MISSING_C_DOCSTRINGS
14+
from test.script_helper import assert_python_failure
1315
try:
1416
import _posixsubprocess
1517
except ImportError:
@@ -21,6 +23,9 @@
2123
# Skip this test if the _testcapi module isn't available.
2224
_testcapi = support.import_module('_testcapi')
2325

26+
# Were we compiled --with-pydebug or with #define Py_DEBUG?
27+
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
28+
2429

2530
def testfunction(self):
2631
"""some doc"""
@@ -167,6 +172,45 @@ def test_c_type_with_matrix_multiplication(self):
167172
o @= m1
168173
self.assertEqual(o, ("matmul", 42, m1))
169174

175+
def test_return_null_without_error(self):
176+
# Issue #23571: A function must not return NULL without setting an
177+
# error
178+
if Py_DEBUG:
179+
code = textwrap.dedent("""
180+
import _testcapi
181+
from test import support
182+
183+
with support.SuppressCrashReport():
184+
_testcapi.return_null_without_error()
185+
""")
186+
rc, out, err = assert_python_failure('-c', code)
187+
self.assertIn(b'_Py_CheckFunctionResult: Assertion', err)
188+
else:
189+
with self.assertRaises(SystemError) as cm:
190+
_testcapi.return_null_without_error()
191+
self.assertRegex(str(cm.exception),
192+
'return_null_without_error.* '
193+
'returned NULL without setting an error')
194+
195+
def test_return_result_with_error(self):
196+
# Issue #23571: A function must not return a result with an error set
197+
if Py_DEBUG:
198+
code = textwrap.dedent("""
199+
import _testcapi
200+
from test import support
201+
202+
with support.SuppressCrashReport():
203+
_testcapi.return_result_with_error()
204+
""")
205+
rc, out, err = assert_python_failure('-c', code)
206+
self.assertIn(b'_Py_CheckFunctionResult: Assertion', err)
207+
else:
208+
with self.assertRaises(SystemError) as cm:
209+
_testcapi.return_result_with_error()
210+
self.assertRegex(str(cm.exception),
211+
'return_result_with_error.* '
212+
'returned a result with an error set')
213+
170214

171215
@unittest.skipUnless(threading, 'Threading required for this test.')
172216
class TestPendingCalls(unittest.TestCase):

Modules/_testcapimodule.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3360,6 +3360,24 @@ pymarshal_read_object_from_file(PyObject* self, PyObject *args)
33603360
return Py_BuildValue("Nl", obj, pos);
33613361
}
33623362

3363+
static PyObject*
3364+
return_null_without_error(PyObject *self, PyObject *args)
3365+
{
3366+
/* invalid call: return NULL without setting an error,
3367+
* _Py_CheckFunctionResult() must detect such bug at runtime. */
3368+
PyErr_Clear();
3369+
return NULL;
3370+
}
3371+
3372+
static PyObject*
3373+
return_result_with_error(PyObject *self, PyObject *args)
3374+
{
3375+
/* invalid call: return a result with an error set,
3376+
* _Py_CheckFunctionResult() must detect such bug at runtime. */
3377+
PyErr_SetNone(PyExc_ValueError);
3378+
Py_RETURN_NONE;
3379+
}
3380+
33633381

33643382
static PyMethodDef TestMethods[] = {
33653383
{"raise_exception", raise_exception, METH_VARARGS},
@@ -3519,6 +3537,10 @@ static PyMethodDef TestMethods[] = {
35193537
pymarshal_read_last_object_from_file, METH_VARARGS},
35203538
{"pymarshal_read_object_from_file",
35213539
pymarshal_read_object_from_file, METH_VARARGS},
3540+
{"return_null_without_error",
3541+
return_null_without_error, METH_NOARGS},
3542+
{"return_result_with_error",
3543+
return_result_with_error, METH_NOARGS},
35223544
{NULL, NULL} /* sentinel */
35233545
};
35243546

Objects/abstract.c

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2074,10 +2074,12 @@ PyObject_CallObject(PyObject *o, PyObject *a)
20742074
}
20752075

20762076
PyObject*
2077-
_Py_CheckFunctionResult(PyObject *result, const char *func_name)
2077+
_Py_CheckFunctionResult(PyObject *func, PyObject *result, const char *where)
20782078
{
20792079
int err_occurred = (PyErr_Occurred() != NULL);
20802080

2081+
assert((func != NULL) ^ (where != NULL));
2082+
20812083
#ifndef NDEBUG
20822084
/* In debug mode: abort() with an assertion error. Use two different
20832085
assertions, so if an assertion fails, it's possible to know
@@ -2090,8 +2092,14 @@ _Py_CheckFunctionResult(PyObject *result, const char *func_name)
20902092

20912093
if (result == NULL) {
20922094
if (!err_occurred) {
2093-
PyErr_Format(PyExc_SystemError,
2094-
"NULL result without error in %s", func_name);
2095+
if (func)
2096+
PyErr_Format(PyExc_SystemError,
2097+
"%R returned NULL without setting an error",
2098+
func);
2099+
else
2100+
PyErr_Format(PyExc_SystemError,
2101+
"%s returned NULL without setting an error",
2102+
where);
20952103
return NULL;
20962104
}
20972105
}
@@ -2102,8 +2110,14 @@ _Py_CheckFunctionResult(PyObject *result, const char *func_name)
21022110

21032111
Py_DECREF(result);
21042112

2105-
PyErr_Format(PyExc_SystemError,
2106-
"result with error in %s", func_name);
2113+
if (func)
2114+
PyErr_Format(PyExc_SystemError,
2115+
"%R returned a result with an error set",
2116+
func);
2117+
else
2118+
PyErr_Format(PyExc_SystemError,
2119+
"%s returned a result with an error set",
2120+
where);
21072121
_PyErr_ChainExceptions(exc, val, tb);
21082122
return NULL;
21092123
}
@@ -2136,7 +2150,7 @@ PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
21362150

21372151
Py_LeaveRecursiveCall();
21382152

2139-
return _Py_CheckFunctionResult(result, "PyObject_Call");
2153+
return _Py_CheckFunctionResult(func, result, NULL);
21402154
}
21412155

21422156
static PyObject*

Objects/methodobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds)
142142
}
143143
}
144144

145-
return _Py_CheckFunctionResult(res, "PyCFunction_Call");
145+
return _Py_CheckFunctionResult(func, res, NULL);
146146
}
147147

148148
/* Methods (the standard built-in methods, that is) */

Python/ceval.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3253,7 +3253,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
32533253
f->f_executing = 0;
32543254
tstate->frame = f->f_back;
32553255

3256-
return _Py_CheckFunctionResult(retval, "PyEval_EvalFrameEx");
3256+
return _Py_CheckFunctionResult(NULL, retval, "PyEval_EvalFrameEx");
32573257
}
32583258

32593259
static void
@@ -4251,14 +4251,14 @@ call_function(PyObject ***pp_stack, int oparg
42514251
if (flags & METH_NOARGS && na == 0) {
42524252
C_TRACE(x, (*meth)(self,NULL));
42534253

4254-
x = _Py_CheckFunctionResult(x, "call_function");
4254+
x = _Py_CheckFunctionResult(func, x, NULL);
42554255
}
42564256
else if (flags & METH_O && na == 1) {
42574257
PyObject *arg = EXT_POP(*pp_stack);
42584258
C_TRACE(x, (*meth)(self,arg));
42594259
Py_DECREF(arg);
42604260

4261-
x = _Py_CheckFunctionResult(x, "call_function");
4261+
x = _Py_CheckFunctionResult(func, x, NULL);
42624262
}
42634263
else {
42644264
err_args(func, flags, na);

0 commit comments

Comments
 (0)