Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4726,6 +4726,33 @@ class FakeStr:
with self.assertRaises(TypeError):
str.__add__(fake_str, "abc")

def test_specialized_method_calls_check_types(self):
# https://github.com/python/cpython/issues/92063
class Thing:
pass
thing = Thing()
for i in range(20):
with self.assertRaises(TypeError):
# PRECALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS
list.sort(thing)
for i in range(20):
with self.assertRaises(TypeError):
# PRECALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS
str.split(thing)
for i in range(20):
with self.assertRaises(TypeError):
# PRECALL_NO_KW_METHOD_DESCRIPTOR_NOARGS
str.upper(thing)
for i in range(20):
with self.assertRaises(TypeError):
# PRECALL_NO_KW_METHOD_DESCRIPTOR_FAST
str.strip(thing)
from collections import deque
for i in range(20):
with self.assertRaises(TypeError):
# PRECALL_NO_KW_METHOD_DESCRIPTOR_O
deque.append(thing, thing)

def test_repr_as_str(self):
# Issue #11603: crash or infinite loop when rebinding __str__ as
# __repr__.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The ``PRECALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS`` instruction
now ensures methods are called only on objects of the correct type.
43 changes: 27 additions & 16 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -5254,11 +5254,15 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
assert(call_shape.kwnames == NULL);
int is_meth = is_method(stack_pointer, oparg);
int total_args = oparg + is_meth;
PyObject *callable = PEEK(total_args + 1);
PyMethodDescrObject *callable =
(PyMethodDescrObject *)PEEK(total_args + 1);
DEOPT_IF(total_args != 2, PRECALL);
DEOPT_IF(!Py_IS_TYPE(callable, &PyMethodDescr_Type), PRECALL);
PyMethodDef *meth = ((PyMethodDescrObject *)callable)->d_method;
PyMethodDef *meth = callable->d_method;
DEOPT_IF(meth->ml_flags != METH_O, PRECALL);
PyObject *arg = TOP();
PyObject *self = SECOND();
DEOPT_IF(!Py_IS_TYPE(self, callable->d_common.d_type), PRECALL);
STAT_INC(PRECALL, hit);
SKIP_CALL();
PyCFunction cfunc = meth->ml_meth;
Expand All @@ -5267,8 +5271,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
if (_Py_EnterRecursiveCall(tstate, " while calling a Python object")) {
goto error;
}
PyObject *arg = TOP();
PyObject *self = SECOND();
PyObject *res = cfunc(self, arg);
_Py_LeaveRecursiveCall(tstate);
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
Expand All @@ -5287,17 +5289,22 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
TARGET(PRECALL_METHOD_DESCRIPTOR_FAST_WITH_KEYWORDS) {
int is_meth = is_method(stack_pointer, oparg);
int total_args = oparg + is_meth;
PyObject *callable = PEEK(total_args + 1);
PyMethodDescrObject *callable =
(PyMethodDescrObject *)PEEK(total_args + 1);
DEOPT_IF(!Py_IS_TYPE(callable, &PyMethodDescr_Type), PRECALL);
PyMethodDef *meth = ((PyMethodDescrObject *)callable)->d_method;
PyMethodDef *meth = callable->d_method;
DEOPT_IF(meth->ml_flags != (METH_FASTCALL|METH_KEYWORDS), PRECALL);
PyTypeObject *d_type = callable->d_common.d_type;
PyObject *self = PEEK(total_args);
DEOPT_IF(!Py_IS_TYPE(self, d_type), PRECALL);
STAT_INC(PRECALL, hit);
SKIP_CALL();
int nargs = total_args-1;
STACK_SHRINK(nargs);
_PyCFunctionFastWithKeywords cfunc = (_PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth;
PyObject *self = TOP();
PyObject *res = cfunc(self, stack_pointer, nargs - KWNAMES_LEN(), call_shape.kwnames);
_PyCFunctionFastWithKeywords cfunc =
(_PyCFunctionFastWithKeywords)(void(*)(void))meth->ml_meth;
PyObject *res = cfunc(self, stack_pointer, nargs - KWNAMES_LEN(),
call_shape.kwnames);
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
call_shape.kwnames = NULL;

Expand All @@ -5322,9 +5329,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
int is_meth = is_method(stack_pointer, oparg);
int total_args = oparg + is_meth;
DEOPT_IF(total_args != 1, PRECALL);
PyObject *callable = SECOND();
PyMethodDescrObject *callable = (PyMethodDescrObject *)SECOND();
DEOPT_IF(!Py_IS_TYPE(callable, &PyMethodDescr_Type), PRECALL);
PyMethodDef *meth = ((PyMethodDescrObject *)callable)->d_method;
PyMethodDef *meth = callable->d_method;
PyObject *self = TOP();
DEOPT_IF(!Py_IS_TYPE(self, callable->d_common.d_type), PRECALL);
DEOPT_IF(meth->ml_flags != METH_NOARGS, PRECALL);
STAT_INC(PRECALL, hit);
SKIP_CALL();
Expand All @@ -5334,7 +5343,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
if (_Py_EnterRecursiveCall(tstate, " while calling a Python object")) {
goto error;
}
PyObject *self = TOP();
PyObject *res = cfunc(self, NULL);
_Py_LeaveRecursiveCall(tstate);
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
Expand All @@ -5353,17 +5361,20 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
assert(call_shape.kwnames == NULL);
int is_meth = is_method(stack_pointer, oparg);
int total_args = oparg + is_meth;
PyObject *callable = PEEK(total_args + 1);
PyMethodDescrObject *callable =
(PyMethodDescrObject *)PEEK(total_args + 1);
/* Builtin METH_FASTCALL methods, without keywords */
DEOPT_IF(!Py_IS_TYPE(callable, &PyMethodDescr_Type), PRECALL);
PyMethodDef *meth = ((PyMethodDescrObject *)callable)->d_method;
PyMethodDef *meth = callable->d_method;
DEOPT_IF(meth->ml_flags != METH_FASTCALL, PRECALL);
PyObject *self = PEEK(total_args);
DEOPT_IF(!Py_IS_TYPE(self, callable->d_common.d_type), PRECALL);
STAT_INC(PRECALL, hit);
SKIP_CALL();
_PyCFunctionFast cfunc = (_PyCFunctionFast)(void(*)(void))meth->ml_meth;
_PyCFunctionFast cfunc =
(_PyCFunctionFast)(void(*)(void))meth->ml_meth;
int nargs = total_args-1;
STACK_SHRINK(nargs);
PyObject *self = TOP();
PyObject *res = cfunc(self, stack_pointer, nargs);
assert((res != NULL) ^ (_PyErr_Occurred(tstate) != NULL));
/* Clear the stack of the arguments. */
Expand Down