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

Setting f_trace_opcodes to True can lead to f_lineno being removed in some cases (using breakpoint()/pdb.set_trace()) #127321

Open
Viicos opened this issue Nov 27, 2024 · 6 comments
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@Viicos
Copy link
Contributor

Viicos commented Nov 27, 2024

Bug report

Bug description:

Since #118579 (introduced in 3.13), Bdb.set_trace will call Bdb.set_stepinstr instead of Bdb.set_step (on L406):

cpython/Lib/bdb.py

Lines 389 to 407 in 6d3b520

def set_trace(self, frame=None):
"""Start debugging from frame.
If frame is not specified, debugging starts from caller's frame.
"""
sys.settrace(None)
if frame is None:
frame = sys._getframe().f_back
self.reset()
self.enterframe = frame
while frame:
frame.f_trace = self.trace_dispatch
self.botframe = frame
self.frame_trace_lines_opcodes[frame] = (frame.f_trace_lines, frame.f_trace_opcodes)
# We need f_trace_lines == True for the debugger to work
frame.f_trace_lines = True
frame = frame.f_back
self.set_stepinstr()
sys.settrace(self.trace_dispatch)

This ends up setting f_trace_opcodes to True on all the frames of the stack.

This is fine for most use cases, but for some reason, this removes the f_lineno attribute of frames in exotic setups using breakpoint():

any(some_cond for el in it) and breakpoint()
# -> Warning: lineno is None

[1, 2] and breakpoint()
# -> Warning: lineno is None

True and breakpoint()
# Fine, lineno available

I'm using these inline conditions a lot to conditionally add a breakpoint, and not having access to the line number is annoying as many commands (such as list) will fail because they expect frame.f_lineno to not be None (and thus crashes and exits the debugger).

I'm not familiar with opcodes and how this interacts with frames. It is expected for the f_lineno to be lost here?

CPython versions tested on:

3.13, 3.14

Operating systems tested on:

Linux

@Viicos Viicos added the type-bug An unexpected behavior, bug, or error label Nov 27, 2024
@picnixz picnixz added the stdlib Python modules in the Lib dir label Nov 27, 2024
@picnixz
Copy link
Contributor

picnixz commented Nov 27, 2024

cc @gaogaotiantian

@gaogaotiantian
Copy link
Member

I'm not able to reproduce this on either main or 3.13. Could you make a full example?

[1, 2] and breakpoint()
> /home/gaogaotiantian/programs/mycpython/scrabble.py(1)<module>()
-> [1, 2] and breakpoint()
(Pdb) list
  1  -> [1, 2] and breakpoint()
[EOF]
(Pdb) $_frame.f_lineno
1
(Pdb) 

@Viicos
Copy link
Contributor Author

Viicos commented Nov 27, 2024

Ok took some time to find how to reproduce, but turns out you need at least one extra statement after the breakpoint call. So something like:

[1,2] and breakpoint()

a = 1

should work.

@gaogaotiantian
Copy link
Member

Ah, interesting. Worked for me, thanks!

@gaogaotiantian
Copy link
Member

@iritkatriel the bytecode of

[1, 2] and breakpoint()
a = 2

is

   0           RESUME                   0

   1           LOAD_SMALL_INT           1
               LOAD_SMALL_INT           2
               BUILD_LIST               2
               COPY                     1
               TO_BOOL
               POP_JUMP_IF_FALSE        7 (to L1)
               POP_TOP
               LOAD_NAME                0 (breakpoint)
               PUSH_NULL
               CALL                     0

  --   L1:     POP_TOP

   2           LOAD_SMALL_INT           2
               STORE_NAME               1 (a)
               LOAD_CONST               0 (None)
               RETURN_VALUE

The label L1 is the short circuit label. Is it expected that the bytecode is not associated with any line number? I mean I can understand that the label itself is probably not generated directly by the code, just want to confirm if this is a desired behavior. If so, I'll need to work on some workaround for pdb.

@iritkatriel
Copy link
Member

The POP_TOP is there because we just compiled an expression (and nothing is consuming its result from the stack). If the POP_TOP was only reachable via fall through, the line number of the previous instruction would propagate to it. But here it's reachable via a jump as well (and it seems we don't check that the jump was on the same line as the fall through, but even if we did this would fail in the case where the boolean expression spans more than one line).

There are situations (such as return and raise instructions) where we force instructions to have a line number even if it requires making multiple copies of some instructions. But this is not one of those cases. Why is it a problem here that there is no line number?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

4 participants