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

gh-96092: Fix traceback.walk_stack(None) skipping too many frames #129330

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ammaraskar
Copy link
Member

@ammaraskar ammaraskar commented Jan 27, 2025

Fixes #96092. Please see my comment in #99015 (comment) for more details on the root cause analysis of this issue.


This changes the walk_stack generator to capture the stack when walk_stack is called, rather than when it is first iterated over. Since this is technically a breaking change in behavior, I added a versionchanged to the documentation. In practice, this is unlikely to break anyone, you would have been needing to store the result of walk_stack and expecting it to change.

It also adds a direct test for what the innermost frame walk_stack returns is.


📚 Documentation preview 📚: https://cpython-previews--129330.org.readthedocs.build/

As it says in its documentation, walk_stack was meant to just
follow `f.f_back` like other functions in the traceback module.
Instead it was previously doing `f.f_back.f_back` and then this
changed to `f_back.f_back.f_back.f_back' in Python 3.11 breaking
its behavior for external users.

This happened because the walk_stack function never really had
any good direct tests and its only consumer in the traceback module was
`extract_stack` which passed the result into `StackSummary.extract`.
As a generator, it was previously capturing the state of the stack
when it was first iterated over, rather than the stack when `walk_stack`
was called. Meaning when called inside the two method deep
`extract` and `extract_stack` calls, two `f_back`s were needed.
When 3.11 modified the sequence of calls in `extract`, two more
`f_back`s were needed to make the tests happy.

This changes the generator to capture the stack when `walk_stack` is
called, rather than when it is first iterated over. Since this is
technically a breaking change in behavior, there is a versionchanged
to the documentation. In practice, this is unlikely to break anyone,
you would have been needing to store the result of `walk_stack` and
expecting it to change.
@ammaraskar
Copy link
Member Author

ammaraskar commented Jan 27, 2025

+@pablogsal as a reviewer. This was a bug I accidentally introduced in PEP 657's implementation

Finally getting around to fixing it. Technically the fix is a change in behavior itself but it is unlikely to break anyone, they would have had to store the result of walk_stack, e.g

walker = traceback.walk_stack(None)
...
"""
frames = list(walker)
# ^ Previously this would return the frames
# as of when the `list` was called.
# Now returns the frames where `traceback.walk_stack` was called.

A quick search of traceback.walk_stack shows that most people either pass it into StackSummary.extract or directly iterate over it as soon as its called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

traceback.walk_stack(None) does not behave the same as traceback.walk_stack(inspect.currentframe())
2 participants