python / python/cpython

FrameLocalsProxy `|=` and `update()` mishandle errors raised while merging mappings

未關閉
#153,418 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

interpreter-core type-bug
主要語言
Python
星號
77.2k
分支
35.9k
PR 合併指標
PR 指標待擷取

描述

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:

  1. framelocalsproxy_inplace_or() returns Py_NotImplemented when framelocalsproxy_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_CheckSlotResult error.
  2. framelocalsproxy_update() replaces any failure from framelocalsproxy_merge() with a generic TypeError, 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

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

從 Objects/frameobject.c 中的 framelocalsproxy_inplace_or() 和 framelocalsproxy_update() 開始,然後執行 BadDict.keys() 引發 RuntimeError 的最小重現程式。完成的標準是兩個操作都傳播原始例外,且不會在 debug build 中發生 slot failure,並為回報的行為提供回歸覆蓋。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
c, python
領域
backend
Issue 類型
缺陷
難度
3/5
預估耗時
1-2 天
活躍度
停滯
描述清晰度
描述清楚
新手友好度
25/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。