Skip to content

Commit 5c1a0be

Browse files
committed
implement unwinding of stack on thrown exception
1 parent b7542d2 commit 5c1a0be

File tree

2 files changed

+62
-5
lines changed

2 files changed

+62
-5
lines changed

src/commands.jl

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,22 @@ function maybe_reset_frame!(@nospecialize(recurse), frame::Frame, @nospecialize(
272272
return frame, pc
273273
end
274274

275+
# Unwind the stack until an exc is eventually caught, thereby
276+
# returning the frame that caught the exception at the pc of the catch
277+
# or rethrow the error
278+
function unwind_exception(frame::Frame, exc)
279+
while frame !== nothing
280+
if !isempty(frame.framedata.exception_frames)
281+
# Exception caught
282+
frame.pc = frame.framedata.exception_frames[end]
283+
frame.framedata.last_exception[] = exc
284+
return frame
285+
end
286+
frame = frame.caller
287+
end
288+
rethrow(exc)
289+
end
290+
275291
"""
276292
ret = debug_command(recurse, frame, cmd, rootistoplevel=false)
277293
ret = debug_command(frame, cmd, rootistoplevel=false)
@@ -294,16 +310,19 @@ or one of the 'advanced' commands
294310
Unlike other commands, the default setting for `recurse` is `Compiled()`.
295311
"""
296312
function debug_command(@nospecialize(recurse), frame::Frame, cmd::AbstractString, rootistoplevel::Bool=false)
297-
# ::Union{Val{:nc},Val{:n},Val{:se}},
298-
# Need try/catch
299313
istoplevel = rootistoplevel && frame.caller === nothing
300-
cmd == "nc" && return maybe_reset_frame!(recurse, frame, next_call!(recurse, frame, istoplevel), rootistoplevel)
301-
cmd == "n" && return maybe_reset_frame!(recurse, frame, next_line!(recurse, frame, istoplevel), rootistoplevel)
302314
if cmd == "si"
303315
stmt = pc_expr(frame)
304316
cmd = is_call(stmt) ? "s" : "se"
305317
end
306-
cmd == "se" && return maybe_reset_frame!(recurse, frame, step_expr!(recurse, frame, istoplevel), rootistoplevel)
318+
try
319+
cmd == "nc" && return maybe_reset_frame!(recurse, frame, next_call!(recurse, frame, istoplevel), rootistoplevel)
320+
cmd == "n" && return maybe_reset_frame!(recurse, frame, next_line!(recurse, frame, istoplevel), rootistoplevel)
321+
cmd == "se" && return maybe_reset_frame!(recurse, frame, step_expr!(recurse, frame, istoplevel), rootistoplevel)
322+
catch err
323+
frame = unwind_exception(frame, err)
324+
return debug_command(recurse, frame, "nc", istoplevel)
325+
end
307326
enter_generated = false
308327
if cmd == "sg"
309328
enter_generated = true

test/debug.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,42 @@ end
128128
@test get_return(frame) == 3
129129
end
130130

131+
@testset "Exceptions" begin
132+
# Don't break on caught exceptions
133+
err_caught = Any[nothing]
134+
function f_exc_outer()
135+
try
136+
f_exc_inner()
137+
catch err;
138+
err_caught[1] = err
139+
end
140+
x = 1 + 1
141+
return x
142+
end
143+
f_exc_inner() = error()
144+
fr = JuliaInterpreter.enter_call(f_exc_outer)
145+
fr, pc = debug_command(fr, "s")
146+
fr, pc = debug_command(fr, "n")
147+
fr, pc = debug_command(fr, "n")
148+
debug_command(fr, "finish")
149+
@test get_return(fr) == 2
150+
@test first(err_caught) isa ErrorException
151+
152+
# Rethrow on uncaught exceptions
153+
f_outer() = g_inner()
154+
g_inner() = error()
155+
fr = JuliaInterpreter.enter_call(f_outer)
156+
@test_throws ErrorException debug_command(fr, "finish")
157+
158+
# Break on error
159+
try
160+
JuliaInterpreter.break_on_error[] = true
161+
fr = JuliaInterpreter.enter_call(f_outer)
162+
fr, pc = debug_command(JuliaInterpreter.finish_and_return!, fr, "finish")
163+
@test fr.framecode.scope.name == :error
164+
finally
165+
JuliaInterpreter.break_on_error[] = false
166+
end
167+
end
168+
131169
# end

0 commit comments

Comments
 (0)