python / python/cpython

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

オープン
#153,418 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

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. リポジトリをフォークし、ブランチを切って変更します。
  4. 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 を短くまとめたダイジェスト。