Skip to content

Commit 8cefc49

Browse files
committed
gh-149180: avoid double checking tp_as_number, tp_as_sequence and tp_as_mapping
1 parent 12a20da commit 8cefc49

16 files changed

Lines changed: 286 additions & 221 deletions

Include/internal/pycore_abstract.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ extern "C" {
1212
static inline int
1313
_PyIndex_Check(PyObject *obj)
1414
{
15-
PyNumberMethods *tp_as_number = Py_TYPE(obj)->tp_as_number;
16-
return (tp_as_number != NULL && tp_as_number->nb_index != NULL);
15+
return Py_TYPE(obj)->tp_as_number->nb_index != NULL;
1716
}
1817

1918
// Exported for external JIT support

Include/internal/pycore_typeobject.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,29 @@ extern "C" {
3535
#define _Py_MAX_GLOBAL_TYPE_VERSION_TAG (_Py_TYPE_BASE_VERSION_TAG - 1)
3636

3737

38+
extern const PyNumberMethods _PyType_EmptyNumberMethods;
39+
extern const PySequenceMethods _PyType_EmptySequenceMethods;
40+
extern const PyMappingMethods _PyType_EmptyMappingMethods;
41+
42+
static inline int
43+
_PyType_HasOwnNumberMethods(PyTypeObject *tp)
44+
{
45+
return tp->tp_as_number != &_PyType_EmptyNumberMethods;
46+
}
47+
48+
static inline int
49+
_PyType_HasOwnSequenceMethods(PyTypeObject *tp)
50+
{
51+
return tp->tp_as_sequence != &_PyType_EmptySequenceMethods;
52+
}
53+
54+
static inline int
55+
_PyType_HasOwnMappingMethods(PyTypeObject *tp)
56+
{
57+
return tp->tp_as_mapping != &_PyType_EmptyMappingMethods;
58+
}
59+
60+
3861
/* runtime lifecycle */
3962

4063
extern PyStatus _PyTypes_InitTypes(PyInterpreterState *);

Lib/test/test_descr.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from copy import deepcopy
1717
from contextlib import redirect_stdout
1818
from test import support
19+
from test.support import import_helper
1920
from test.support.script_helper import assert_python_ok
2021

2122
try:
@@ -6319,5 +6320,139 @@ class IntSubclass(int):
63196320
weakref_descriptor.__get__(IntSubclass(), IntSubclass)
63206321

63216322

6323+
class TestSubtableDispatchInvariants(unittest.TestCase):
6324+
6325+
def test_int_times_list(self):
6326+
self.assertEqual(2 * [1], [1, 1])
6327+
6328+
def test_list_times_int(self):
6329+
self.assertEqual([1] * 2, [1, 1])
6330+
6331+
def test_int_times_tuple(self):
6332+
self.assertEqual(2 * (1,), (1, 1))
6333+
6334+
def test_tuple_times_int(self):
6335+
self.assertEqual((1,) * 2, (1, 1))
6336+
6337+
def test_int_times_str(self):
6338+
self.assertEqual(2 * "ab", "abab")
6339+
6340+
def test_str_times_int(self):
6341+
self.assertEqual("ab" * 2, "abab")
6342+
6343+
def test_int_times_bytes(self):
6344+
self.assertEqual(2 * b"ab", b"abab")
6345+
6346+
def test_int_imul_list(self):
6347+
a = 2
6348+
a *= [1]
6349+
self.assertEqual(a, [1, 1])
6350+
6351+
def test_list_imul_int(self):
6352+
a = [1]
6353+
a *= 2
6354+
self.assertEqual(a, [1, 1])
6355+
6356+
def test_str_imul_int(self):
6357+
a = "ab"
6358+
a *= 2
6359+
self.assertEqual(a, "abab")
6360+
6361+
def test_class_without_mul_imul_seq_raises(self):
6362+
class NoSeq:
6363+
pass
6364+
a = NoSeq()
6365+
with self.assertRaises(TypeError):
6366+
a *= [1]
6367+
6368+
def test_class_with_empty_seq_methods_imul_seq_raises(self):
6369+
class HasMulOnly:
6370+
def __mul__(self, other):
6371+
return NotImplemented
6372+
def __index__(self):
6373+
return 2
6374+
a = HasMulOnly()
6375+
with self.assertRaises(TypeError):
6376+
a *= [1]
6377+
6378+
def test_int_getitem_raises_type_error(self):
6379+
x = 5
6380+
with self.assertRaises(TypeError):
6381+
x[0]
6382+
6383+
def test_int_setitem_raises_type_error(self):
6384+
x = 5
6385+
with self.assertRaises(TypeError):
6386+
x[0] = 1
6387+
6388+
def test_object_setitem_does_not_coerce_index(self):
6389+
class BadIndex:
6390+
called = False
6391+
6392+
def __index__(self):
6393+
self.called = True
6394+
raise AssertionError("__index__ called")
6395+
6396+
obj = object()
6397+
key = BadIndex()
6398+
with self.assertRaisesRegex(TypeError,
6399+
"does not support item assignment"):
6400+
obj[key] = 1
6401+
self.assertFalse(key.called)
6402+
6403+
def test_object_delitem_does_not_coerce_index(self):
6404+
class BadIndex:
6405+
called = False
6406+
6407+
def __index__(self):
6408+
self.called = True
6409+
raise AssertionError("__index__ called")
6410+
6411+
obj = object()
6412+
key = BadIndex()
6413+
with self.assertRaisesRegex(TypeError,
6414+
"does not support item deletion"):
6415+
del obj[key]
6416+
self.assertFalse(key.called)
6417+
6418+
def test_object_bool_default(self):
6419+
class C:
6420+
pass
6421+
self.assertTrue(bool(C()))
6422+
6423+
def test_object_bool_with_len(self):
6424+
class C:
6425+
def __len__(self):
6426+
return 0
6427+
self.assertFalse(bool(C()))
6428+
6429+
def test_object_bool_with_dunder_bool(self):
6430+
class C:
6431+
def __bool__(self):
6432+
return False
6433+
self.assertFalse(bool(C()))
6434+
6435+
def test_sequence_check_int_is_false(self):
6436+
_testlimitedcapi = import_helper.import_module("_testlimitedcapi")
6437+
self.assertFalse(_testlimitedcapi.sequence_check(5))
6438+
6439+
def test_mapping_check_int_is_false(self):
6440+
_testlimitedcapi = import_helper.import_module("_testlimitedcapi")
6441+
self.assertFalse(_testlimitedcapi.mapping_check(5))
6442+
6443+
def test_int_index_protocol(self):
6444+
class MyInt(int):
6445+
pass
6446+
a = [10, 20, 30]
6447+
self.assertEqual(a[MyInt(1)], 20)
6448+
6449+
def test_class_without_index_protocol_subscript_raises(self):
6450+
class NoIndex:
6451+
pass
6452+
a = [10, 20, 30]
6453+
with self.assertRaises(TypeError):
6454+
a[NoIndex()]
6455+
6456+
63226457
if __name__ == "__main__":
63236458
unittest.main()

Modules/_bisectmodule.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ get_sq_item(PyObject *s)
3535
// The parts of PySequence_GetItem that we only need to do once
3636
PyTypeObject *tp = Py_TYPE(s);
3737
PySequenceMethods *m = tp->tp_as_sequence;
38-
if (m && m->sq_item) {
38+
if (m->sq_item) {
3939
return m->sq_item;
4040
}
4141
const char *msg;
42-
if (tp->tp_as_mapping && tp->tp_as_mapping->mp_subscript) {
42+
if (tp->tp_as_mapping->mp_subscript) {
4343
msg = "%.200s is not a sequence";
4444
}
4545
else {

Modules/_testinternalcapi/test_cases.c.h

Lines changed: 0 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)