Skip to content

Commit 756ae77

Browse files
committed
Validate instance slot for specialised attribute is NULL
This ensures that we correct specialise class attribute loads even if a slot is defined but not set.
1 parent 9d22a53 commit 756ae77

11 files changed

Lines changed: 295 additions & 17 deletions

File tree

‎Include/internal/pycore_opcode_metadata.h‎

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

‎Include/internal/pycore_uop_ids.h‎

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

‎Include/internal/pycore_uop_metadata.h‎

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

‎Lib/test/test_opcache.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,6 +1760,33 @@ def set_value(n):
17601760
set_value(_testinternalcapi.SPECIALIZATION_COOLDOWN)
17611761
self.assert_no_opcode(set_value, "STORE_ATTR_WITH_HINT")
17621762

1763+
@cpython_only
1764+
@requires_specialization
1765+
def test_load_attr_class_default_not_set_on_instance(self):
1766+
class C:
1767+
timeout = 30
1768+
1769+
def set_timeout(self, value):
1770+
self.timeout = value # puts "timeout" in C's shared keys
1771+
1772+
c = C() # never sets timeout, so c.timeout is the class default
1773+
1774+
@reset_code
1775+
def get_timeout(n):
1776+
for _ in range(n):
1777+
value = c.timeout
1778+
return value
1779+
1780+
self.assertEqual(get_timeout(_testinternalcapi.SPECIALIZATION_THRESHOLD), 30)
1781+
# Must load the class attribute, not specialize as an instance
1782+
# attribute load that misses on every execution.
1783+
self.assert_specialized(get_timeout, "LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES")
1784+
self.assert_no_opcode(get_timeout, "LOAD_ATTR_INSTANCE_VALUE")
1785+
1786+
# Setting the attribute on the instance must be seen.
1787+
c.set_timeout(5)
1788+
self.assertEqual(get_timeout(1), 5)
1789+
17631790
@cpython_only
17641791
@requires_specialization
17651792
def test_to_bool(self):

‎Modules/_testinternalcapi/test_cases.c.h‎

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

‎Python/bytecodes.c‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4197,6 +4197,15 @@ dummy_func(
41974197
DEAD(exc);
41984198
}
41994199

4200+
/* Slots are created even if not assigend to */
4201+
op(_GUARD_SHADOWING_SLOT_EMPTY, (slot_offset/1, owner -- owner)) {
4202+
if (slot_offset != 0) {
4203+
PyObject *owner_o = PyStackRef_AsPyObjectBorrow(owner);
4204+
PyObject **value_ptr = (PyObject **)(((char *)owner_o) + slot_offset);
4205+
EXIT_IF(FT_ATOMIC_LOAD_PTR_RELAXED(*value_ptr) != NULL);
4206+
}
4207+
}
4208+
42004209
op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self)) {
42014210
assert(oparg & 1);
42024211
/* Cached method object */
@@ -4213,7 +4222,8 @@ dummy_func(
42134222
_RECORD_TOS_TYPE +
42144223
_GUARD_TYPE_VERSION +
42154224
_CHECK_MANAGED_OBJECT_HAS_VALUES +
4216-
unused/2 +
4225+
_GUARD_SHADOWING_SLOT_EMPTY +
4226+
unused/1 +
42174227
_LOAD_ATTR_METHOD_WITH_VALUES;
42184228

42194229
op(_LOAD_ATTR_METHOD_NO_DICT, (descr/4, owner -- attr, self)) {
@@ -4247,7 +4257,8 @@ dummy_func(
42474257
_RECORD_TOS_TYPE +
42484258
_GUARD_TYPE_VERSION +
42494259
_CHECK_MANAGED_OBJECT_HAS_VALUES +
4250-
unused/2 +
4260+
_GUARD_SHADOWING_SLOT_EMPTY +
4261+
unused/1 +
42514262
_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES;
42524263

42534264
op(_LOAD_ATTR_NONDESCRIPTOR_NO_DICT, (descr/4, owner -- attr)) {

‎Python/executor_cases.c.h‎

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

‎Python/generated_cases.c.h‎

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

‎Python/optimizer_bytecodes.c‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,12 @@ dummy_func(void) {
10281028
_POP_TOP, _NOP);
10291029
}
10301030

1031+
op(_GUARD_SHADOWING_SLOT_EMPTY, (slot_offset/1, owner -- owner)) {
1032+
if (slot_offset == 0) {
1033+
ADD_OP(_NOP, 0, 0);
1034+
}
1035+
}
1036+
10311037
op(_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, (descr/4, owner -- attr)) {
10321038
(void)descr;
10331039
PyTypeObject *type = sym_get_type(owner);

‎Python/optimizer_cases.c.h‎

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

0 commit comments

Comments
 (0)