Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

traceback.walk_stack(None) does not behave the same as traceback.walk_stack(inspect.currentframe()) #96092

Open
VincentVanlaer opened this issue Aug 18, 2022 · 5 comments
Labels
3.10 only security fixes 3.11 only security fixes 3.12 bugs and security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@VincentVanlaer
Copy link

VincentVanlaer commented Aug 18, 2022

Bug report

traceback.walk_stack(None) omits the calling frame, making it equivalent to traceback.walk_stack(inspect.currentframe().f_back). Given the documentation just says "If f is None, the current stack is used.", I would have expected traceback.walk_stack(None) to be equivalent to traceback.walk_stack(inspect.currentframe()).

Example code

The following code

import traceback
import inspect

def abc():
    print(list(traceback.walk_stack(None)))
    print(list(traceback.walk_stack(inspect.currentframe())))
    print(list(traceback.walk_stack(inspect.currentframe().f_back)))

abc()

has as output

[(<frame at 0x7f66ac559a40, file '/home/vincenttc/data/temp/test/test.py', line 10, code <module>>, 10)]
[(<frame at 0x7f66ac55fa60, file '/home/vincenttc/data/temp/test/test.py', line 7, code abc>, 7), (<frame at 0x7f66ac559a40, file '/home/vincenttc/data/temp/test/test.py', line 10, code <module>>, 10)]
[(<frame at 0x7f66ac559a40, file '/home/vincenttc/data/temp/test/test.py', line 10, code <module>>, 10)]

Note that the frame for abc is missing in the first and last line.

Your environment

  • CPython versions tested on: 3.8.10 (Ubuntu 20.04), 3.10.5 (Arch Linux)
@VincentVanlaer VincentVanlaer added the type-bug An unexpected behavior, bug, or error label Aug 18, 2022
@mdboom mdboom added 3.10 only security fixes 3.8 only security fixes labels Aug 19, 2022
@iritkatriel
Copy link
Member

On main your code doesn't work at all:

>>> import traceback
>>> import inspect
>>> 
>>> def abc():
...     print(list(traceback.walk_stack(None)))
...     print(list(traceback.walk_stack(inspect.currentframe())))
...     print(list(traceback.walk_stack(inspect.currentframe().f_back)))
... 
>>> abc()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in abc
  File "/Users/iritkatriel/src/cpython/Lib/traceback.py", line 333, in walk_stack
    f = sys._getframe().f_back.f_back.f_back.f_back
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'f_back'

I see that this function changed in

commit 5644c7b3ffd49bed58dc095be6e6148e0bb4431e
Author: Ammar Askar <ammar@ammaraskar.com>
Date:   Sun Jul 4 19:14:33 2021 -0400

    bpo-43950: Print columns in tracebacks (PEP 657) (GH-26958)
    
    The traceback.c and traceback.py mechanisms now utilize the newly added code.co_positions and PyCode_Addr2Location
    to print carets on the specific expressions involved in a traceback.
    
    Co-authored-by: Pablo Galindo <Pablogsal@gmail.com>
    Co-authored-by: Ammar Askar <ammar@ammaraskar.com>
    Co-authored-by: Batuhan Taskaya <batuhanosmantaskaya@gmail.com>

This part:

@@ -309,7 +321,7 @@ def walk_stack(f):
     current stack is used. Usually used with StackSummary.extract.
     """
     if f is None:
-        f = sys._getframe().f_back.f_back
+        f = sys._getframe().f_back.f_back.f_back.f_back
     while f is not None:
         yield f, f.f_lineno
         f = f.f_back

CC @ammaraskar @pablogsal @isidentical

@iritkatriel iritkatriel added 3.11 only security fixes 3.12 bugs and security fixes stdlib Python modules in the Lib dir and removed 3.8 only security fixes labels Sep 5, 2022
@pablogsal
Copy link
Member

I am currently in release hell, @ammaraskar @isidentical can you take a look?

@ammaraskar
Copy link
Member

ammaraskar commented Nov 2, 2022

Added details on the root cause here: #99015 (comment)

It looks like traceback.walk_stack was never really meant to be called without immediately using StackSummary.extract on it. It is a generator so the:

    if f is None:
        f = sys._getframe().f_back.f_back

is a bit misleading, those lines don't run when you call traceback.walk_stack but only when you start iterating over the generator.

So if you do something like:

def outer():
    return inner()
def inner():
    return traceback.walk_stack(None)

frames = outer()


def print_frames(f):
    print(list(f))

print_frames(frames)

You might expect it to print module -> outer -> inner but it would actually get module -> print_frames because that is where the walk_stack generator is consumed.


My intuition is we should fix walk_stack to return a generator that iterates over the frames from when it is called, rather than iterated over. While this is a breaking change, it seems like walk_stack(None) is almost never called in the stdlib or tests without an immediate StackSummary.extract nor did I find anything in a quick search over Github.

graingert added a commit to graingert/distributed that referenced this issue Nov 3, 2022
see python/cpython#96092
and it's not really intended for use outside of StackSummary.extract,
see python/cpython#99015 (comment)
@ammaraskar
Copy link
Member

Thanks for the link. That use case is fine too since the generator is iterated over immediately. People tend to either pass into StackSummary.extract or iterate over it without storing the generator.

graingert added a commit to graingert/distributed that referenced this issue Nov 9, 2022
see python/cpython#96092
and it's not really intended for use outside of StackSummary.extract,
see python/cpython#99015 (comment)
graingert added a commit to graingert/distributed that referenced this issue Nov 17, 2022
see python/cpython#96092
and it's not really intended for use outside of StackSummary.extract,
see python/cpython#99015 (comment)
graingert added a commit to graingert/distributed that referenced this issue Nov 23, 2022
see python/cpython#96092
and it's not really intended for use outside of StackSummary.extract,
see python/cpython#99015 (comment)
graingert added a commit to graingert/distributed that referenced this issue Nov 24, 2022
see python/cpython#96092
and it's not really intended for use outside of StackSummary.extract,
see python/cpython#99015 (comment)
graingert added a commit to graingert/distributed that referenced this issue Nov 30, 2022
see python/cpython#96092
and it's not really intended for use outside of StackSummary.extract,
see python/cpython#99015 (comment)
graingert added a commit to dask/distributed that referenced this issue Dec 14, 2022
* use sys.executable for python binary

* pass the desired frame to traceback.walk_stack for 3.11

see python/cpython#96092
and it's not really intended for use outside of StackSummary.extract,
see python/cpython#99015 (comment)

* the GC overhead now depends on _PyType_PreHeaderSize

faster-cpython/ideas#125
python/cpython@8319114#diff-a3a5c73931235f7f344c072dc755d6508e13923db3f5d581c5e88652075871cbR1684

* GH-6785: asyncio.wait no longer calls ensure_future

python/cpython#95601

* test on python 3.11

* exclude macos py 3.10
bkeryan added a commit to ni/measurement-plugin-python that referenced this issue Oct 29, 2023
In Python 3.11 and later, `traceback.walk_stack(None)` skips more stack frames than in the past: python/cpython#96092
dixonjoel pushed a commit to ni/measurement-plugin-python that referenced this issue Oct 30, 2023
* service: Include TS code module dir in .env search

* examples: Update teststand_nivisa_dmm.py to search for `.env` starting with the code module's parent directory

* tests: Fix mypy errors and last-minute rename errors

* service: Work around lack of PurePath.is_relative_to() in Python 3.8

* service: Make _get_nims_path return package dir, not __init__.py path

* service: Fix _get_caller_path() with Python >= 3.11

In Python 3.11 and later, `traceback.walk_stack(None)` skips more stack frames than in the past: python/cpython#96092

* tests: Make pytest path check less specific
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.10 only security fixes 3.11 only security fixes 3.12 bugs and security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
6 participants