Skip to content

Commit

Permalink
GH-125868: Fix STORE_ATTR_WITH_HINT specialization (GH-125876)
Browse files Browse the repository at this point in the history
  • Loading branch information
markshannon authored Oct 24, 2024
1 parent c35b33b commit b61fece
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 13 deletions.
4 changes: 3 additions & 1 deletion Lib/dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,8 +778,10 @@ def _get_instructions_bytes(code, linestarts=None, line_offset=0, co_positions=N

if caches:
cache_info = []
cache_offset = offset
for name, size in _cache_format[opname[deop]].items():
data = code[offset + 2: offset + 2 + 2 * size]
data = code[cache_offset + 2: cache_offset + 2 + 2 * size]
cache_offset += size * 2
cache_info.append((name, size, data))
else:
cache_info = None
Expand Down
44 changes: 44 additions & 0 deletions Lib/test/test_opcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,50 @@ class D(dict): pass
{'a':1, 'b':2}
)

def test_125868(self):

def make_special_dict():
"""Create a dictionary an object with a this table:
index | key | value
----- | --- | -----
0 | 'b' | 'value'
1 | 'b' | NULL
"""
class A:
pass
a = A()
a.a = 1
a.b = 2
d = a.__dict__.copy()
del d['a']
del d['b']
d['b'] = "value"
return d

class NoInlineAorB:
pass
for i in range(ord('c'), ord('z')):
setattr(NoInlineAorB(), chr(i), i)

c = NoInlineAorB()
c.a = 0
c.b = 1
self.assertFalse(_testinternalcapi.has_inline_values(c))

def f(o, n):
for i in range(n):
o.b = i
# Prime f to store to dict slot 1
f(c, 100)

test_obj = NoInlineAorB()
test_obj.__dict__ = make_special_dict()
self.assertEqual(test_obj.b, "value")

#This should set x.b = 0
f(test_obj, 1)
self.assertEqual(test_obj.b, 0)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
It was possible in 3.14.0a1 only for attribute lookup to give the wrong
value. This was due to an incorrect specialization in very specific
circumstances. This is fixed in 3.14.0a2.
7 changes: 3 additions & 4 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -2303,17 +2303,16 @@ dummy_func(
assert(PyDict_CheckExact((PyObject *)dict));
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
DEOPT_IF(hint >= (size_t)dict->ma_keys->dk_nentries);
PyObject *old_value;
DEOPT_IF(!DK_IS_UNICODE(dict->ma_keys));
PyDictUnicodeEntry *ep = DK_UNICODE_ENTRIES(dict->ma_keys) + hint;
DEOPT_IF(ep->me_key != name);
PyObject *old_value = ep->me_value;
DEOPT_IF(old_value == NULL);
/* Ensure dict is GC tracked if it needs to be */
if (!_PyObject_GC_IS_TRACKED(dict) && _PyObject_GC_MAY_BE_TRACKED(PyStackRef_AsPyObjectBorrow(value))) {
_PyObject_GC_TRACK(dict);
}
old_value = ep->me_value;
PyDict_WatchEvent event = old_value == NULL ? PyDict_EVENT_ADDED : PyDict_EVENT_MODIFIED;
_PyDict_NotifyEvent(tstate->interp, event, dict, name, PyStackRef_AsPyObjectBorrow(value));
_PyDict_NotifyEvent(tstate->interp, PyDict_EVENT_MODIFIED, dict, name, PyStackRef_AsPyObjectBorrow(value));
ep->me_value = PyStackRef_AsPyObjectSteal(value);
// old_value should be DECREFed after GC track checking is done, if not, it could raise a segmentation fault,
// when dict only holds the strong reference to value in ep->me_value.
Expand Down
10 changes: 6 additions & 4 deletions Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions Python/generated_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b61fece

Please sign in to comment.