Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Debugger.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Base.@kwdef mutable struct DebuggerState
frame::Union{Nothing, Frame}
level::Int = 1
broke_on_error::Bool = false
# The exception the session stopped for (see `break_on`), while `broke_on_error`
error_stopped_for = nothing
watch_list::Vector = WATCH_LIST
lowered_status::Bool = false
interp::Interpreter = RecursiveInterpreter()
Expand Down
19 changes: 19 additions & 0 deletions src/commands.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ function assert_allow_step(state)
return true
end

# Whether an exception thrown in `frame` is caught by it or by one of its callers
function is_caught_in_frames(frame::Union{Nothing,Frame})
while frame !== nothing
isempty(frame.framedata.exception_frames) || return true
frame = caller(frame)
end
return false
end

invalid_command(state, cmd) = execute_command(state, Val(:_), cmd) # print error

function show_breakpoint(io::IO, bp::BreakpointRef, state::DebuggerState)
Expand Down Expand Up @@ -40,6 +49,15 @@ function execute_command(state::DebuggerState, v::Union{Val{:c},Val{:nc},Val{:n}
kwargs = Dict(:line => line)
end
end
if state.broke_on_error && state.level == 1 && is_caught_in_frames(state.frame)
# Stopped for an exception that an interpreted frame catches (`break_on(:throw)`):
# resume by unwinding into the handler. `c` continues from there; the other
# commands stop at the start of the `catch` block.
state.frame = JuliaInterpreter.unwind_exception(state.frame, state.error_stopped_for)
state.broke_on_error = false
state.error_stopped_for = nothing
cmd == "c" || return true
end
assert_allow_step(state) || return false
cmd == "so" && (cmd = "finish")
cmd == "u" && (cmd = "until")
Expand All @@ -58,6 +76,7 @@ function execute_command(state::DebuggerState, v::Union{Val{:c},Val{:nc},Val{:n}
end
if pc.err !== nothing
state.broke_on_error = true
state.error_stopped_for = pc.err
end
end
if pc_expr(state.frame) === nothing # happens when stopping for @bp
Expand Down
57 changes: 57 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -501,3 +501,60 @@ end
Debugger.set_highlight(prev_highlight)
end
end

function f_caught_throws(xs)
n = 0
for x in xs
v = try
parse(Int, x)
catch
-1
end
n += v
end
return n
end
f_uncaught_throw(x) = parse(Int, x)

# With `break_on(:throw)`, stopping for an exception that the code catches must
# not end the session: stepping resumes in the `catch` block
@testset "continue after breaking on a caught throw" begin
try
JuliaInterpreter.break_on(:throw)
state = dummy_state(JuliaInterpreter.enter_call(f_caught_throws, ["a", "1", "b"]))
execute_command(state, Val(:c), "c")
@test state.broke_on_error
@test occursin("'a'", sprint(showerror, state.error_stopped_for))
# `c` continues past the handler to the next throw
@test execute_command(state, Val(:c), "c")
@test state.broke_on_error
@test occursin("'b'", sprint(showerror, state.error_stopped_for))
# `n` stops in the `catch` block of the catching frame
@test execute_command(state, Val(:n), "n")
@test !state.broke_on_error
@test state.frame.framecode.scope.name === :f_caught_throws
@test JuliaInterpreter.caller(state.frame) === nothing
execute_command(state, Val(:c), "c")
@test state.frame === nothing
@test state.overall_result == -1
finally
JuliaInterpreter.break_off(:throw)
end
end

@testset "no stepping after breaking on an uncaught error" begin
try
JuliaInterpreter.break_on(:error)
state = dummy_state(JuliaInterpreter.enter_call(f_uncaught_throw, "a"))
execute_command(state, Val(:c), "c")
@test state.broke_on_error
frame = state.frame
@test !redirect_stderr(devnull) do
execute_command(state, Val(:c), "c")
end
@test state.broke_on_error
@test state.frame === frame
finally
JuliaInterpreter.break_off(:error)
end
end
Loading