python / python/cpython

Stack trace of re-raised exception is wrong

未關閉
#116,862 5 則留言 2 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

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

描述

Bug report

Bug description:

If you catch an exception and hold on to it for later handling, and then re-raise the exception, the stack trace is wrong (the stack is a blend of the two stacks).

A simple example:

def err_returning_call() -> Exception:
    try:
        raise ValueError('foo')
    except Exception as err:
        return err

def raise_err(err):
    raise err

err = err_returning_call()

raise_err(err)

Produces a traceback with:

$ python simplest_cpython_bug.py 
Traceback (most recent call last):
  File "example.py", line 14, in <module>
    raise_err(err)
    ~~~~~~~~~^^^^^
  File "example.py", line 10, in raise_err
    raise err
  File "example.py", line 3, in err_returning_call
    raise ValueError('foo')
ValueError: foo

Note that the previously caught exception (raise err) produces a traceback that mixes the original stack and the new stack, rather than a clean traceback from the point where it is re-raised.


Raising the exception from another (adapting err_returning_call to raise ValueError('another') from err) produces a logical stack trace:

$ ./build/bin/python3 simplest_cpython_bug.py 
Traceback (most recent call last):
  File "example.py", line 3, in err_returning_call
    raise ValueError('foo')
ValueError: foo

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "example.py", line 14, in <module>
    raise_err(err)
    ~~~~~~~~~^^^^^
  File "example.py", line 10, in raise_err
    raise ValueError('another') from err
ValueError: another

The deeper the stacks, the more confusing the tb. It also seems to drop an important step in the traceback (even if we use the raise new_error from original_error pattern):

def root_call():
    return err_returning_call()

def err_returning_call() -> Exception:
    try:
        error_raising()
    except Exception as err:
        return err

def error_raising():
    raise ValueError('foo')


err = root_call()


def raise_err(err):
    raise err

def indirect_raise_err(err):
    raise_err(err)


indirect_raise_err(err)

With the result:

Traceback (most recent call last):
  File "example.py", line 24, in <module>
    indirect_raise_err(err)
    ~~~~~~~~~~~~~~~~~~^^^^^
  File "example.py", line 21, in indirect_raise_err
    raise_err(err)
    ~~~~~~~~~^^^^^
  File "example.py", line 18, in raise_err
    raise err
  File "example.py", line 6, in err_returning_call
    error_raising()
    ~~~~~~~~~~~~~^^
  File "example.py", line 11, in error_raising
    raise ValueError('foo')
ValueError: foo

Note that with the raise from included, the traceback is:

Traceback (most recent call last):
  File "example.py", line 6, in err_returning_call
    error_raising()
    ~~~~~~~~~~~~~^^
  File "example.py", line 11, in error_raising
    raise ValueError('foo')
ValueError: foo

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "example.py", line 24, in <module>
    indirect_raise_err(err)
    ~~~~~~~~~~~~~~~~~~^^^^^
  File "example.py", line 21, in indirect_raise_err
    raise_err(err)
    ~~~~~~~~~^^^^^
  File "example.py", line 18, in raise_err
    raise ValueError('bar') from err
ValueError: bar

Notice how even in this case, the original traceback is missing the root_call call (possibly reasonable?).

The expected behaviour would be to simply raise the exception with the stack trace of the final raise call. This would be analogous to what happens when you capture an exception in a variable without raising it:

exception = ValueError('not raised directly')

raise exception

Which produces the following traceback:

Traceback (most recent call last):
  File "/media/important/github/python/cpython/./exception_var.py", line 3, in <module>
    raise exception
ValueError: not raised directly

This report is against main (at 2cf18a4430 - the latest commit on 15/3/2024), but applies all the way back to Python 3.7 at least.

CPython versions tested on:

3.8, 3.9, 3.10, 3.11, 3.12, 3.13, CPython main branch

Operating systems tested on:

Linux

貢獻指南

開啟貢獻指南

從這裡開始

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

研究方向

Start by reproducing the behavior with the issue's err_returning_call, raise_err, and indirect_raise_err examples across the described CPython versions. Trace how a caught exception's traceback changes when it is raised again, and compare that with raising a newly created exception. Done means a re-raised exception reports only the final raise call while explicit exception chaining retains the expected separate tracebacks.

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

評估

技術堆疊
python
領域
backend
Issue 類型
缺陷
難度
5/5
預估耗時
一週以上
活躍度
停滯
描述清晰度
描述清楚
新手友好度
35/100

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

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