Skip to content

Commit 5627d66

Browse files
committed
don't double execute descriptor on specialization fail
1 parent b73cff0 commit 5627d66

File tree

4 files changed

+94
-41
lines changed

4 files changed

+94
-41
lines changed

Include/internal/pycore_typeobject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ PyObject *_Py_slot_tp_getattr_hook(PyObject *self, PyObject *name);
100100

101101
PyObject *
102102
_PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, PyObject *name, int *meth_found);
103+
PyObject *
104+
_PySuper_LookupDescr(PyTypeObject *su_type, PyObject *su_obj, PyObject *name);
103105

104106
#ifdef __cplusplus
105107
}

Lib/test/test_opcache.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
import unittest
22

33

4+
class TestLoadSuperAttrCache(unittest.TestCase):
5+
def test_descriptor_not_double_executed_on_spec_fail(self):
6+
calls = []
7+
class Descriptor:
8+
def __get__(self, instance, owner):
9+
calls.append((instance, owner))
10+
return lambda: 1
11+
12+
class C:
13+
d = Descriptor()
14+
15+
class D(C):
16+
def f(self):
17+
return super().d()
18+
19+
d = D()
20+
21+
self.assertEqual(d.f(), 1) # warmup
22+
calls.clear()
23+
self.assertEqual(d.f(), 1) # try to specialize
24+
self.assertEqual(calls, [(d, D)])
25+
26+
427
class TestLoadAttrCache(unittest.TestCase):
528
def test_descriptor_added_after_optimization(self):
629
class Descriptor:

Objects/typeobject.c

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9357,29 +9357,26 @@ super_repr(PyObject *self)
93579357
su->type ? su->type->tp_name : "NULL");
93589358
}
93599359

9360+
/* Do a super lookup without executing descriptors or falling back to getattr
9361+
on the super object itself.
9362+
9363+
May return NULL with or without an exception set, like PyDict_GetItemWithError. */
93609364
static PyObject *
9361-
do_super_lookup(superobject *su, PyTypeObject *su_type, PyObject *su_obj,
9362-
PyTypeObject *su_obj_type, PyObject *name, int *meth_found)
9365+
_super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *name)
93639366
{
9364-
PyTypeObject *starttype;
93659367
PyObject *mro, *res;
93669368
Py_ssize_t i, n;
9367-
int temp_su = 0;
9368-
9369-
starttype = su_obj_type;
9370-
if (starttype == NULL)
9371-
goto skip;
93729369

93739370
/* We want __class__ to return the class of the super object
93749371
(i.e. super, or a subclass), not the class of su->obj. */
93759372
if (PyUnicode_Check(name) &&
93769373
PyUnicode_GET_LENGTH(name) == 9 &&
93779374
_PyUnicode_Equal(name, &_Py_ID(__class__)))
9378-
goto skip;
9375+
return NULL;
93799376

9380-
mro = starttype->tp_mro;
9377+
mro = su_obj_type->tp_mro;
93819378
if (mro == NULL)
9382-
goto skip;
9379+
return NULL;
93839380

93849381
assert(PyTuple_Check(mro));
93859382
n = PyTuple_GET_SIZE(mro);
@@ -9391,9 +9388,9 @@ do_super_lookup(superobject *su, PyTypeObject *su_type, PyObject *su_obj,
93919388
}
93929389
i++; /* skip su->type (if any) */
93939390
if (i >= n)
9394-
goto skip;
9391+
return NULL;
93959392

9396-
/* keep a strong reference to mro because starttype->tp_mro can be
9393+
/* keep a strong reference to mro because su_obj_type->tp_mro can be
93979394
replaced during PyDict_GetItemWithError(dict, name) */
93989395
Py_INCREF(mro);
93999396
do {
@@ -9404,22 +9401,6 @@ do_super_lookup(superobject *su, PyTypeObject *su_type, PyObject *su_obj,
94049401
res = PyDict_GetItemWithError(dict, name);
94059402
if (res != NULL) {
94069403
Py_INCREF(res);
9407-
if (meth_found && _PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
9408-
*meth_found = 1;
9409-
}
9410-
else {
9411-
descrgetfunc f = Py_TYPE(res)->tp_descr_get;
9412-
if (f != NULL) {
9413-
PyObject *res2;
9414-
res2 = f(res,
9415-
/* Only pass 'obj' param if this is instance-mode super
9416-
(See SF ID #743627) */
9417-
(su_obj == (PyObject *)starttype) ? NULL : su_obj,
9418-
(PyObject *)starttype);
9419-
Py_SETREF(res, res2);
9420-
}
9421-
}
9422-
94239404
Py_DECREF(mro);
94249405
return res;
94259406
}
@@ -9431,6 +9412,43 @@ do_super_lookup(superobject *su, PyTypeObject *su_type, PyObject *su_obj,
94319412
i++;
94329413
} while (i < n);
94339414
Py_DECREF(mro);
9415+
return NULL;
9416+
}
9417+
9418+
static PyObject *
9419+
do_super_lookup(superobject *su, PyTypeObject *su_type, PyObject *su_obj,
9420+
PyTypeObject *su_obj_type, PyObject *name, int *meth_found)
9421+
{
9422+
PyObject *res;
9423+
int temp_su = 0;
9424+
9425+
if (su_obj_type == NULL) {
9426+
goto skip;
9427+
}
9428+
9429+
res = _super_lookup_descr(su_type, su_obj_type, name);
9430+
if (res != NULL) {
9431+
if (meth_found && _PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
9432+
*meth_found = 1;
9433+
}
9434+
else {
9435+
descrgetfunc f = Py_TYPE(res)->tp_descr_get;
9436+
if (f != NULL) {
9437+
PyObject *res2;
9438+
res2 = f(res,
9439+
/* Only pass 'obj' param if this is instance-mode super
9440+
(See SF ID #743627) */
9441+
(su_obj == (PyObject *)su_obj_type) ? NULL : su_obj,
9442+
(PyObject *)su_obj_type);
9443+
Py_SETREF(res, res2);
9444+
}
9445+
}
9446+
9447+
return res;
9448+
}
9449+
else if (PyErr_Occurred()) {
9450+
return NULL;
9451+
}
94349452

94359453
skip:
94369454
if (su == NULL) {
@@ -9520,6 +9538,18 @@ _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, PyObject *name, int *me
95209538
return res;
95219539
}
95229540

9541+
PyObject *
9542+
_PySuper_LookupDescr(PyTypeObject *su_type, PyObject *su_obj, PyObject *name)
9543+
{
9544+
PyTypeObject *su_obj_type = supercheck(su_type, su_obj);
9545+
if (su_obj_type == NULL) {
9546+
return NULL;
9547+
}
9548+
PyObject *res = _super_lookup_descr(su_type, su_obj_type, name);
9549+
Py_DECREF(su_obj_type);
9550+
return res;
9551+
}
9552+
95239553
static PyObject *
95249554
super_descr_get(PyObject *self, PyObject *obj, PyObject *type)
95259555
{

Python/specialize.c

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ _PyCode_Quicken(PyCodeObject *code)
327327
#define SPEC_FAIL_SUPER_BAD_CLASS 10
328328
#define SPEC_FAIL_SUPER_SHADOWED 11
329329
#define SPEC_FAIL_SUPER_NOT_METHOD 12
330-
#define SPEC_FAIL_SUPER_ERROR 13
330+
#define SPEC_FAIL_SUPER_ERROR_OR_NOT_FOUND 13
331331

332332
/* Attributes */
333333

@@ -533,22 +533,20 @@ _Py_Specialize_LoadSuperAttr(PyObject *global_super, PyObject *class, PyObject *
533533
goto fail;
534534
}
535535
PyTypeObject *tp = (PyTypeObject *)class;
536-
int meth_found = 0;
537-
PyObject *res = _PySuper_Lookup(tp, self, name, &meth_found);
536+
PyObject *res = _PySuper_LookupDescr(tp, self, name);
538537
if (res == NULL) {
539-
SPECIALIZATION_FAIL(LOAD_SUPER_ATTR, SPEC_FAIL_SUPER_ERROR);
538+
SPECIALIZATION_FAIL(LOAD_SUPER_ATTR, SPEC_FAIL_SUPER_ERROR_OR_NOT_FOUND);
540539
PyErr_Clear();
541540
goto fail;
542541
}
543-
if (!meth_found) {
544-
SPECIALIZATION_FAIL(LOAD_SUPER_ATTR, SPEC_FAIL_SUPER_NOT_METHOD);
545-
goto fail;
542+
if (_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
543+
write_u32(cache->class_version, tp->tp_version_tag);
544+
write_u32(cache->self_type_version, Py_TYPE(self)->tp_version_tag);
545+
write_obj(cache->method, res); // borrowed
546+
instr->op.code = LOAD_SUPER_ATTR_METHOD;
547+
goto success;
546548
}
547-
write_u32(cache->class_version, tp->tp_version_tag);
548-
write_u32(cache->self_type_version, Py_TYPE(self)->tp_version_tag);
549-
write_obj(cache->method, res); // borrowed
550-
instr->op.code = LOAD_SUPER_ATTR_METHOD;
551-
goto success;
549+
SPECIALIZATION_FAIL(LOAD_SUPER_ATTR, SPEC_FAIL_SUPER_NOT_METHOD);
552550

553551
fail:
554552
STAT_INC(LOAD_SUPER_ATTR, failure);

0 commit comments

Comments
 (0)