FrameLocalsProxy `|=` and `update()` mishandle errors raised while merging mappings
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 77.2k
- Forks
- 35.9k
- PR merge metrics
- PR metrics pending
Description
Bug report
Bug description:
Bug description
FrameLocalsProxy does not correctly propagate exceptions raised while merging a dict subclass or another supported mapping-like object.
There are two related problems in Objects/frameobject.c:
framelocalsproxy_inplace_or()returnsPy_NotImplementedwhenframelocalsproxy_merge()fails. If the merge failed because Python code raised an exception, the slot returns a normal object while an exception is still set. In a pydebug build this triggers a fatal_Py_CheckSlotResulterror.framelocalsproxy_update()replaces any failure fromframelocalsproxy_merge()with a genericTypeError, masking the original exception.
This is a sub-issue of https://github.com/python/cpython/issues/146102 with gist details
A minimal reproducer is below.
import operator
import sys
class BadDict(dict):
def keys(self):
raise RuntimeError("keys() failed!")
def repro_update():
locs = sys._getframe().f_locals
print(type(locs))
try:
locs.update(BadDict())
except TypeError as exc:
print("BUG: update() masked RuntimeError as TypeError:", exc)
except RuntimeError as exc:
print("OK: update() propagated RuntimeError:", exc)
def repro_inplace_or():
locs = sys._getframe().f_locals
print(type(locs))
try:
operator.ior(locs, BadDict())
except RuntimeError as exc:
print("OK: |= propagated RuntimeError:", exc)
except BaseException as exc:
print("BUG: |= raised wrong exception:", type(exc).__name__, exc)
else:
print("BUG: |= swallowed RuntimeError")
repro_update()
repro_inplace_or()
operator.ior(locs, BadDict()) is used to exercise the same nb_inplace_or slot as locs |= BadDict().
Actual behavior
For .update(), the original RuntimeError from BadDict.keys() is replaced with a generic TypeError:
<class 'FrameLocalsProxy'>
BUG: update() masked RuntimeError as TypeError: update() argument must be dict or another FrameLocalsProxy
For |= on a pydebug build, the interpreter aborts because the slot reports success while an exception remains set:
<class 'FrameLocalsProxy'>
Fatal Python error: _Py_CheckSlotResult: Slot |= of type FrameLocalsProxy succeeded with an exception set
Python runtime state: initialized
Traceback (most recent call last):
File "/tmp/repro_ior.py", line 6, in keys
raise RuntimeError("keys() failed!")
RuntimeError: keys() failed!
Aborted (core dumped)
Expected behavior
Both FrameLocalsProxy.update() and FrameLocalsProxy.__ior__() should propagate the original exception raised while merging:
<class 'FrameLocalsProxy'>
OK: update() propagated RuntimeError: keys() failed!
<class 'FrameLocalsProxy'>
OK: |= propagated RuntimeError: keys() failed!
I will submit a PR to fix this
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Linked PRs
- gh-153421
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in Objects/frameobject.c at framelocalsproxy_inplace_or() and framelocalsproxy_update(), then run the minimal reproducer with BadDict.keys() raising RuntimeError. Done means both operations propagate the original exception without a debug-build slot failure, with regression coverage for the reported behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100