Skip to content

Commit a294092

Browse files
committed
Properly stop at line 1 in frame eval mode. Fixes #995
1 parent db43846 commit a294092

8 files changed

Lines changed: 55 additions & 15 deletions

File tree

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_cython.c

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_cython.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,7 @@ cdef class PyDBFrame:
10021002
new_frame = frame
10031003
stop_reason = CMD_SET_FUNCTION_BREAK
10041004

1005-
elif not is_return and info.pydev_state != 2 and breakpoints_for_file is not None and line in breakpoints_for_file:
1005+
elif is_line and info.pydev_state != 2 and breakpoints_for_file is not None and line in breakpoints_for_file:
10061006
breakpoint = breakpoints_for_file[line]
10071007
new_frame = frame
10081008
stop = True

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,7 @@ def trace_dispatch(self, frame, event, arg):
869869
new_frame = frame
870870
stop_reason = CMD_SET_FUNCTION_BREAK
871871

872-
elif not is_return and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None and line in breakpoints_for_file:
872+
elif is_line and info.pydev_state != STATE_SUSPEND and breakpoints_for_file is not None and line in breakpoints_for_file:
873873
breakpoint = breakpoints_for_file[line]
874874
new_frame = frame
875875
stop = True

src/debugpy/_vendored/pydevd/_pydevd_frame_eval/pydevd_modify_bytecode.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ def get_instructions_to_add(
8787
'''
8888
# Good reference to how things work regarding line numbers and jumps:
8989
# https://github.com/python/cpython/blob/3.6/Objects/lnotab_notes.txt
90+
91+
# Usually use a stop line -1, but if that'd be 0, using line +1 is ok too.
92+
spurious_line = stop_at_line - 1
93+
if spurious_line <= 0:
94+
spurious_line = stop_at_line + 1
95+
9096
label = Label()
9197
return [
9298
# -- if _pydev_needs_stop_at_break():
@@ -99,10 +105,10 @@ def get_instructions_to_add(
99105
#
100106
# Note that this has line numbers -1 so that when the NOP just below
101107
# is executed we have a spurious line event.
102-
Instr("LOAD_CONST", _pydev_stop_at_break, lineno=stop_at_line - 1),
103-
Instr("LOAD_CONST", stop_at_line, lineno=stop_at_line - 1),
104-
Instr("CALL_FUNCTION", 1, lineno=stop_at_line - 1),
105-
Instr("POP_TOP", lineno=stop_at_line - 1),
108+
Instr("LOAD_CONST", _pydev_stop_at_break, lineno=spurious_line),
109+
Instr("LOAD_CONST", stop_at_line, lineno=spurious_line),
110+
Instr("CALL_FUNCTION", 1, lineno=spurious_line),
111+
Instr("POP_TOP", lineno=spurious_line),
106112

107113
# Reason for the NOP: Python will give us a 'line' trace event whenever we forward jump to
108114
# the first instruction of a line, so, in the case where we haven't added a programmatic
@@ -259,6 +265,12 @@ def insert_pydevd_breaks(
259265
# if code_to_modify.co_firstlineno in breakpoint_lines:
260266
# return False, code_to_modify
261267

268+
for line in breakpoint_lines:
269+
if line <= 0:
270+
# The first line is line 1, so, a break at line 0 is not valid.
271+
pydev_log.info('Trying to add breakpoint in invalid line: %s', line)
272+
return False, code_to_modify
273+
262274
try:
263275
b = bytecode.Bytecode.from_code(code_to_modify)
264276

src/debugpy/_vendored/pydevd/pydevd.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,9 @@ def do_wait_suspend(self, thread, frame, event, arg, exception_type=None): # @U
19951995
thread_id = get_current_thread_id(thread)
19961996

19971997
# print('do_wait_suspend %s %s %s %s %s %s (%s)' % (frame.f_lineno, frame.f_code.co_name, frame.f_code.co_filename, event, arg, constant_to_str(thread.additional_info.pydev_step_cmd), constant_to_str(thread.additional_info.pydev_original_step_cmd)))
1998+
# print('--- stack ---')
1999+
# print(traceback.print_stack(file=sys.stdout))
2000+
# print('--- end stack ---')
19982001

19992002
# Send the suspend message
20002003
message = thread.additional_info.pydev_message

src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_subprocess_and_fork.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ def breaknow():
88

99

1010
if '--fork-in-subprocess' in sys.argv:
11+
popen = None
1112
if sys.platform == 'win32':
1213
popen = subprocess.Popen([sys.executable, __file__, '--forked'])
1314
pid = popen.pid
@@ -20,6 +21,16 @@ def breaknow():
2021
print('currently in pid: %s, ppid: %s' % (os.getpid(), ppid))
2122
print('os.fork returned', pid)
2223
breaknow()
24+
# i.e.: wait so that we check for the retcode so that we don't get a traceback such as the one
25+
# below (as that code in __del__ will only be called if returncode is None).
26+
# Traceback (most recent call last):
27+
# File "C:\hostedtoolcache\windows\Python\3.9.13\x64\lib\subprocess.py", line 1055, in __del__
28+
# self._internal_poll(_deadstate=_maxsize)
29+
# File "C:\hostedtoolcache\windows\Python\3.9.13\x64\lib\subprocess.py", line 1457, in _internal_poll
30+
# if _WaitForSingleObject(self._handle, 0) == _WAIT_OBJECT_0:
31+
# OSError: [WinError 6] The handle is invalid
32+
if popen is not None:
33+
popen.wait(20)
2334

2435
elif '--forked' in sys.argv:
2536
try:

src/debugpy/_vendored/pydevd/tests_python/test_debugger.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2274,9 +2274,9 @@ def test_case_method_single_line(case_setup):
22742274
writer.write_add_breakpoint(writer.get_line_index_with_content('Break here'), 'None')
22752275
writer.write_make_initial_run()
22762276

2277-
for _ in range(5):
2278-
# We'll hit the same breakpoint 5 times (method creation,
2279-
# (enter method, exit method) * 2.
2277+
for _ in range(3):
2278+
# We'll hit the same breakpoint 3 times (method creation,
2279+
# method line for each call).
22802280
hit = writer.wait_for_breakpoint_hit()
22812281

22822282
writer.write_run_thread(hit.thread_id)

src/debugpy/_vendored/pydevd/tests_python/test_frame_eval_and_tracing.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,3 +262,17 @@ def test_generator_code_cache(case_setup_force_frame_eval):
262262
writer.write_run_thread(hit.thread_id)
263263

264264
writer.finished_ok = True
265+
266+
267+
def test_break_line_1(case_setup_force_frame_eval):
268+
with case_setup_force_frame_eval.test_file('_debugger_case_yield_from.py') as writer:
269+
break1_line = 1
270+
break1_id = writer.write_add_breakpoint(break1_line, 'None')
271+
writer.write_make_initial_run()
272+
273+
hit = writer.wait_for_breakpoint_hit(line=break1_line)
274+
assert hit.suspend_type == "frame_eval"
275+
276+
writer.write_run_thread(hit.thread_id)
277+
278+
writer.finished_ok = True

0 commit comments

Comments
 (0)