From b7b0b05cc671d1ecc5e6d0a0aba3635091b42961 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Sat, 18 Jul 2026 14:02:28 +0200 Subject: [PATCH] Stop at breakpoints on a function's initial statements (#134) Two places used to skip such breakpoints: - `@make_frame` advanced to the first call with `maybe_next_call!`, executing any breakpointed non-call statement before it. - `@run` unconditionally executed `c`, which steps over a breakpoint on the statement the frame is already stopped at, so a breakpoint on the first statement never fired. Reimplements #148 by pfitzseb on current master. Co-Authored-By: Claude Fable 5 --- src/Debugger.jl | 7 ++++++- src/repl.jl | 4 +++- test/misc.jl | 16 ++++++++++++++++ test/ui.jl | 16 ++++++++++++++-- test/ui/bp_first_statement.multiout | 24 ++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 test/ui/bp_first_statement.multiout diff --git a/src/Debugger.jl b/src/Debugger.jl index f38fba6..706d36d 100644 --- a/src/Debugger.jl +++ b/src/Debugger.jl @@ -97,7 +97,12 @@ function _make_frame(mod, arg) frame === nothing && error("failed to enter the function, perhaps it is set to run in compiled mode") frame = JuliaInterpreter.maybe_step_through_kwprep!(frame) frame = JuliaInterpreter.maybe_step_through_wrapper!(frame) - JuliaInterpreter.maybe_next_call!(frame) + # Advance to the first call, but stop earlier if a breakpoint is set on + # one of the initial statements (#134) + JuliaInterpreter.maybe_next_until!(frame) do fr + JuliaInterpreter.shouldbreak(fr, fr.pc) || + JuliaInterpreter.is_call_or_return(JuliaInterpreter.pc_expr(fr)) + end frame end end diff --git a/src/repl.jl b/src/repl.jl index f5dbd59..fa64c3a 100644 --- a/src/repl.jl +++ b/src/repl.jl @@ -155,7 +155,9 @@ function RunDebugger(frame, repl = nothing, terminal = nothing; initial_continue state.standard_keymap = keymaps panel.keymap_dict = LineEdit.keymap([repl_switch;state.standard_keymap]) - if initial_continue + # If a breakpoint is set on the statement we are already stopped at, `c` would + # step over it, so stay put and show the prompt instead (#134) + if initial_continue && !JuliaInterpreter.shouldbreak(state.frame, state.frame.pc) try execute_command(state, Val(:c), "c") catch err diff --git a/test/misc.jl b/test/misc.jl index 5713bdc..a65640a 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -224,3 +224,19 @@ execute_command(state, Val{:bp}(), """bp add lfdshfds""") @test !_iscall(:(f.(1, 2))) @test !_iscall(:(identity() do; end)) end + +# Issue #134: a breakpoint on one of the initial statements of a function should +# not be stepped over when entering the frame +@testset "breakpoint on first statement" begin + function f_bp_first(x) + y = x + 1 + return y * 2 + end + JuliaInterpreter.breakpoint(f_bp_first) + try + frame = @make_frame f_bp_first(1) + @test JuliaInterpreter.shouldbreak(frame, frame.pc) + finally + JuliaInterpreter.remove() + end +end diff --git a/test/ui.jl b/test/ui.jl index e225ce7..e2b3fe5 100644 --- a/test/ui.jl +++ b/test/ui.jl @@ -140,12 +140,12 @@ end Vector{UInt8}(codeunits(actual_decorator))) end - function run_terminal_test(frame, commands, validation) + function run_terminal_test(frame, commands, validation; initial_continue=false) run_debugger = function (emuterm) repl = REPL.LineEditREPL(emuterm, true) repl.interface = REPL.setup_interface(repl) repl.specialdisplay = REPL.REPLDisplay(repl) - RunDebugger(frame, repl, emuterm) + RunDebugger(frame, repl, emuterm; initial_continue=initial_continue) end output_path = joinpath(@__DIR__, validation) if get(ENV, "DEBUGGER_UPDATE_UI_SNAPSHOTS", "false") == "true" @@ -195,6 +195,18 @@ end ["bt\n", "c\n"], "ui/big_repr_ui.multiout") + # Issue #134: `@run` should stop at a breakpoint on the first statement + # instead of continuing past it + JuliaInterpreter.breakpoint(mysum) + try + run_terminal_test(@make_frame(mysum([1,2,3])), + ["c\n"], + "ui/bp_first_statement.multiout"; + initial_continue=true) + finally + JuliaInterpreter.remove() + end + was_breaking_on_error = JuliaInterpreter.break_on_error[] Debugger.break_on(:error) try diff --git a/test/ui/bp_first_statement.multiout b/test/ui/bp_first_statement.multiout new file mode 100644 index 0000000..2cf409a --- /dev/null +++ b/test/ui/bp_first_statement.multiout @@ -0,0 +1,24 @@ +++++++++++++++++++++++++++++++++++++++++++++++++++ +|In mysum(x) at ui.jl:94 +| 94 function mysum(x) +|> 95 s = 0 +| 96 for v in x +| 97 s += v +| 98 end +| 99 return s +| 100 end +| +|About to run: 0 +|1|debug> +-------------------------------------------------- +|AAAAAAAAAAAAAAAAAAAAAAA +|AAAAAAAAAAAAAAAAAAAAAAA +|BBBBBBAAAAAAAAA +|AAAAAAAAAAAAAAAAAAAA +|CCCCCCAAAAAAAAAAAAAA +|AAAAAAAAAAAAA +|AAAAAAAAAAAAAAAAAA +|AAAAAAAAA +| +|AAAAAAAAAAAAAAA +|DDDDDDDDD \ No newline at end of file