mirror of
https://github.com/python/cpython.git
synced 2025-07-23 03:05:38 +00:00
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.
This commit is contained in:
parent
6921c13bbb
commit
efde146b0c
6 changed files with 93 additions and 12 deletions
|
@ -267,8 +267,9 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
|
||||||
PyObject *args, PyObject *kw);
|
PyObject *args, PyObject *kw);
|
||||||
|
|
||||||
#ifndef Py_LIMITED_API
|
#ifndef Py_LIMITED_API
|
||||||
PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *obj,
|
PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *func,
|
||||||
const char *func_name);
|
PyObject *result,
|
||||||
|
const char *where);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -6,10 +6,12 @@ import pickle
|
||||||
import random
|
import random
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import textwrap
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import MISSING_C_DOCSTRINGS
|
from test.support import MISSING_C_DOCSTRINGS
|
||||||
|
from test.script_helper import assert_python_failure
|
||||||
try:
|
try:
|
||||||
import _posixsubprocess
|
import _posixsubprocess
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -21,6 +23,9 @@ except ImportError:
|
||||||
# Skip this test if the _testcapi module isn't available.
|
# Skip this test if the _testcapi module isn't available.
|
||||||
_testcapi = support.import_module('_testcapi')
|
_testcapi = support.import_module('_testcapi')
|
||||||
|
|
||||||
|
# Were we compiled --with-pydebug or with #define Py_DEBUG?
|
||||||
|
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
|
||||||
|
|
||||||
|
|
||||||
def testfunction(self):
|
def testfunction(self):
|
||||||
"""some doc"""
|
"""some doc"""
|
||||||
|
@ -167,6 +172,45 @@ class CAPITest(unittest.TestCase):
|
||||||
o @= m1
|
o @= m1
|
||||||
self.assertEqual(o, ("matmul", 42, m1))
|
self.assertEqual(o, ("matmul", 42, m1))
|
||||||
|
|
||||||
|
def test_return_null_without_error(self):
|
||||||
|
# Issue #23571: A function must not return NULL without setting an
|
||||||
|
# error
|
||||||
|
if Py_DEBUG:
|
||||||
|
code = textwrap.dedent("""
|
||||||
|
import _testcapi
|
||||||
|
from test import support
|
||||||
|
|
||||||
|
with support.SuppressCrashReport():
|
||||||
|
_testcapi.return_null_without_error()
|
||||||
|
""")
|
||||||
|
rc, out, err = assert_python_failure('-c', code)
|
||||||
|
self.assertIn(b'_Py_CheckFunctionResult: Assertion', err)
|
||||||
|
else:
|
||||||
|
with self.assertRaises(SystemError) as cm:
|
||||||
|
_testcapi.return_null_without_error()
|
||||||
|
self.assertRegex(str(cm.exception),
|
||||||
|
'return_null_without_error.* '
|
||||||
|
'returned NULL without setting an error')
|
||||||
|
|
||||||
|
def test_return_result_with_error(self):
|
||||||
|
# Issue #23571: A function must not return a result with an error set
|
||||||
|
if Py_DEBUG:
|
||||||
|
code = textwrap.dedent("""
|
||||||
|
import _testcapi
|
||||||
|
from test import support
|
||||||
|
|
||||||
|
with support.SuppressCrashReport():
|
||||||
|
_testcapi.return_result_with_error()
|
||||||
|
""")
|
||||||
|
rc, out, err = assert_python_failure('-c', code)
|
||||||
|
self.assertIn(b'_Py_CheckFunctionResult: Assertion', err)
|
||||||
|
else:
|
||||||
|
with self.assertRaises(SystemError) as cm:
|
||||||
|
_testcapi.return_result_with_error()
|
||||||
|
self.assertRegex(str(cm.exception),
|
||||||
|
'return_result_with_error.* '
|
||||||
|
'returned a result with an error set')
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(threading, 'Threading required for this test.')
|
@unittest.skipUnless(threading, 'Threading required for this test.')
|
||||||
class TestPendingCalls(unittest.TestCase):
|
class TestPendingCalls(unittest.TestCase):
|
||||||
|
|
|
@ -3360,6 +3360,24 @@ pymarshal_read_object_from_file(PyObject* self, PyObject *args)
|
||||||
return Py_BuildValue("Nl", obj, pos);
|
return Py_BuildValue("Nl", obj, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
return_null_without_error(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
/* invalid call: return NULL without setting an error,
|
||||||
|
* _Py_CheckFunctionResult() must detect such bug at runtime. */
|
||||||
|
PyErr_Clear();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
return_result_with_error(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
/* invalid call: return a result with an error set,
|
||||||
|
* _Py_CheckFunctionResult() must detect such bug at runtime. */
|
||||||
|
PyErr_SetNone(PyExc_ValueError);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef TestMethods[] = {
|
static PyMethodDef TestMethods[] = {
|
||||||
{"raise_exception", raise_exception, METH_VARARGS},
|
{"raise_exception", raise_exception, METH_VARARGS},
|
||||||
|
@ -3519,6 +3537,10 @@ static PyMethodDef TestMethods[] = {
|
||||||
pymarshal_read_last_object_from_file, METH_VARARGS},
|
pymarshal_read_last_object_from_file, METH_VARARGS},
|
||||||
{"pymarshal_read_object_from_file",
|
{"pymarshal_read_object_from_file",
|
||||||
pymarshal_read_object_from_file, METH_VARARGS},
|
pymarshal_read_object_from_file, METH_VARARGS},
|
||||||
|
{"return_null_without_error",
|
||||||
|
return_null_without_error, METH_NOARGS},
|
||||||
|
{"return_result_with_error",
|
||||||
|
return_result_with_error, METH_NOARGS},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2074,10 +2074,12 @@ PyObject_CallObject(PyObject *o, PyObject *a)
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject*
|
PyObject*
|
||||||
_Py_CheckFunctionResult(PyObject *result, const char *func_name)
|
_Py_CheckFunctionResult(PyObject *func, PyObject *result, const char *where)
|
||||||
{
|
{
|
||||||
int err_occurred = (PyErr_Occurred() != NULL);
|
int err_occurred = (PyErr_Occurred() != NULL);
|
||||||
|
|
||||||
|
assert((func != NULL) ^ (where != NULL));
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
/* In debug mode: abort() with an assertion error. Use two different
|
/* In debug mode: abort() with an assertion error. Use two different
|
||||||
assertions, so if an assertion fails, it's possible to know
|
assertions, so if an assertion fails, it's possible to know
|
||||||
|
@ -2090,8 +2092,14 @@ _Py_CheckFunctionResult(PyObject *result, const char *func_name)
|
||||||
|
|
||||||
if (result == NULL) {
|
if (result == NULL) {
|
||||||
if (!err_occurred) {
|
if (!err_occurred) {
|
||||||
|
if (func)
|
||||||
PyErr_Format(PyExc_SystemError,
|
PyErr_Format(PyExc_SystemError,
|
||||||
"NULL result without error in %s", func_name);
|
"%R returned NULL without setting an error",
|
||||||
|
func);
|
||||||
|
else
|
||||||
|
PyErr_Format(PyExc_SystemError,
|
||||||
|
"%s returned NULL without setting an error",
|
||||||
|
where);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2102,8 +2110,14 @@ _Py_CheckFunctionResult(PyObject *result, const char *func_name)
|
||||||
|
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
|
|
||||||
|
if (func)
|
||||||
PyErr_Format(PyExc_SystemError,
|
PyErr_Format(PyExc_SystemError,
|
||||||
"result with error in %s", func_name);
|
"%R returned a result with an error set",
|
||||||
|
func);
|
||||||
|
else
|
||||||
|
PyErr_Format(PyExc_SystemError,
|
||||||
|
"%s returned a result with an error set",
|
||||||
|
where);
|
||||||
_PyErr_ChainExceptions(exc, val, tb);
|
_PyErr_ChainExceptions(exc, val, tb);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -2136,7 +2150,7 @@ PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
|
||||||
|
|
||||||
Py_LeaveRecursiveCall();
|
Py_LeaveRecursiveCall();
|
||||||
|
|
||||||
return _Py_CheckFunctionResult(result, "PyObject_Call");
|
return _Py_CheckFunctionResult(func, result, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
|
|
|
@ -142,7 +142,7 @@ PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _Py_CheckFunctionResult(res, "PyCFunction_Call");
|
return _Py_CheckFunctionResult(func, res, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Methods (the standard built-in methods, that is) */
|
/* Methods (the standard built-in methods, that is) */
|
||||||
|
|
|
@ -3253,7 +3253,7 @@ exit_eval_frame:
|
||||||
f->f_executing = 0;
|
f->f_executing = 0;
|
||||||
tstate->frame = f->f_back;
|
tstate->frame = f->f_back;
|
||||||
|
|
||||||
return _Py_CheckFunctionResult(retval, "PyEval_EvalFrameEx");
|
return _Py_CheckFunctionResult(NULL, retval, "PyEval_EvalFrameEx");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -4251,14 +4251,14 @@ call_function(PyObject ***pp_stack, int oparg
|
||||||
if (flags & METH_NOARGS && na == 0) {
|
if (flags & METH_NOARGS && na == 0) {
|
||||||
C_TRACE(x, (*meth)(self,NULL));
|
C_TRACE(x, (*meth)(self,NULL));
|
||||||
|
|
||||||
x = _Py_CheckFunctionResult(x, "call_function");
|
x = _Py_CheckFunctionResult(func, x, NULL);
|
||||||
}
|
}
|
||||||
else if (flags & METH_O && na == 1) {
|
else if (flags & METH_O && na == 1) {
|
||||||
PyObject *arg = EXT_POP(*pp_stack);
|
PyObject *arg = EXT_POP(*pp_stack);
|
||||||
C_TRACE(x, (*meth)(self,arg));
|
C_TRACE(x, (*meth)(self,arg));
|
||||||
Py_DECREF(arg);
|
Py_DECREF(arg);
|
||||||
|
|
||||||
x = _Py_CheckFunctionResult(x, "call_function");
|
x = _Py_CheckFunctionResult(func, x, NULL);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
err_args(func, flags, na);
|
err_args(func, flags, na);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue