1
1
"""
2
- ret = finish!(recurse, frame, istoplevel=false)
3
- ret = finish!(frame, istoplevel=false)
2
+ pc = finish!(recurse, frame, istoplevel=false)
3
+ pc = finish!(frame, istoplevel=false)
4
4
5
- Run `frame` until execution terminates. `ret ` is either `nothing` (if execution terminates
5
+ Run `frame` until execution terminates. `pc ` is either `nothing` (if execution terminates
6
6
when it hits a `return` statement) or a reference to a breakpoint.
7
7
In the latter case, `leaf(frame)` returns the frame in which it hit the breakpoint.
8
8
@@ -22,8 +22,8 @@ finish!(frame::Frame, istoplevel::Bool=false) = finish!(finish_and_return!, fram
22
22
ret = finish_and_return!(recurse, frame, istoplevel::Bool=false)
23
23
ret = finish_and_return!(frame, istoplevel::Bool=false)
24
24
25
- Call [`JuliaInterpreter.finish!`](@ref) and pass back the return value. If execution
26
- pauses at a breakpoint, the reference to the breakpoint is returned .
25
+ Call [`JuliaInterpreter.finish!`](@ref) and pass back the return value `ret` . If execution
26
+ pauses at a breakpoint, `ret` is the reference to the breakpoint.
27
27
"""
28
28
function finish_and_return! (@nospecialize (recurse), frame:: Frame , istoplevel:: Bool = false )
29
29
pc = finish! (recurse, frame, istoplevel)
33
33
finish_and_return! (frame:: Frame , istoplevel:: Bool = false ) = finish_and_return! (finish_and_return!, frame, istoplevel)
34
34
35
35
"""
36
- bpref = dummy_breakpoint(recurse, frame::Frame)
36
+ bpref = dummy_breakpoint(recurse, frame::Frame, istoplevel )
37
37
38
- Return a fake breakpoint. This can be useful as the `recurse` argument to `evaluate_call!`
39
- (or any of the higher-order commands) to ensure that you return immediately after stepping
40
- into a call.
38
+ Return a fake breakpoint. `dummy_breakpoint` can be useful as the `recurse` argument to
39
+ `evaluate_call!` (or any of the higher-order commands) to ensure that you return immediately
40
+ after stepping into a call.
41
41
"""
42
- dummy_breakpoint (@nospecialize (recurse), frame:: Frame ) = BreakpointRef (frame. framecode, 0 )
42
+ dummy_breakpoint (@nospecialize (recurse), frame:: Frame , istoplevel ) = BreakpointRef (frame. framecode, 0 )
43
43
44
44
"""
45
- ret = finish_stack!(recurse, frame, istoplevel =false)
46
- ret = finish_stack!(frame, istoplevel =false)
45
+ ret = finish_stack!(recurse, frame, rootistoplevel =false)
46
+ ret = finish_stack!(frame, rootistoplevel =false)
47
47
48
48
Unwind the callees of `frame`, finishing each before returning to the caller.
49
- `frame` itself is also finished
50
- If execution hits a breakpoint, `ret` will be a reference to the breakpoint.
49
+ `frame` itself is also finished. `rootistoplevel` should be true if the root frame is top-level.
50
+
51
+ `ret` is typically the returned value. If execution hits a breakpoint, `ret` will be a
52
+ reference to the breakpoint.
51
53
"""
52
- function finish_stack! (@nospecialize (recurse), frame:: Frame , istoplevel :: Bool = false )
54
+ function finish_stack! (@nospecialize (recurse), frame:: Frame , rootistoplevel :: Bool = false )
53
55
frame0 = frame
54
56
frame = leaf (frame)
55
57
while true
58
+ istoplevel = rootistoplevel && frame. caller === nothing
56
59
ret = finish_and_return! (recurse, frame, istoplevel)
57
60
isa (ret, BreakpointRef) && return ret
58
61
frame === frame0 && return ret
93
96
next_until! (predicate, frame:: Frame , istoplevel:: Bool = false ) =
94
97
next_until! (predicate, finish_and_return!, frame, istoplevel)
95
98
99
+ """
100
+ pc = next_call!(recurse, frame, istoplevel=false)
101
+ pc = next_call!(frame, istoplevel=false)
102
+
103
+ Execute the current statement. Continue stepping through `frame` until the next
104
+ `:return` or `:call` expression.
105
+ """
96
106
next_call! (@nospecialize (recurse), frame:: Frame , istoplevel:: Bool = false ) =
97
107
next_until! (is_call_or_return, recurse, frame, istoplevel)
98
108
next_call! (frame:: Frame , istoplevel:: Bool = false ) = next_call! (finish_and_return!, frame, istoplevel)
99
109
100
110
"""
101
- maybe_next_call!(predicate, frame, istoplevel=false)
111
+ pc = maybe_next_call!(recurse, frame, istoplevel=false)
112
+ pc = maybe_next_call!(frame, istoplevel=false)
102
113
103
- Return the current statement of `frame` if it is a `:return` or `:call` expression.
114
+ Return the current program counter of `frame` if it is a `:return` or `:call` expression.
104
115
Otherwise, step through the statements of `frame` until the next `:return` or `:call` expression.
105
116
"""
106
117
function maybe_next_call! (@nospecialize (recurse), frame:: Frame , istoplevel:: Bool = false )
@@ -112,8 +123,8 @@ maybe_next_call!(frame::Frame, istoplevel::Bool=false) =
112
123
maybe_next_call! (finish_and_return!, frame, istoplevel)
113
124
114
125
"""
115
- through_methoddef_or_done!(recurse, frame)
116
- through_methoddef_or_done!(frame)
126
+ pc = through_methoddef_or_done!(recurse, frame)
127
+ pc = through_methoddef_or_done!(frame)
117
128
118
129
Runs `frame` at top level until it either finishes (e.g., hits a `return` statement)
119
130
or defines a new method.
@@ -144,11 +155,20 @@ function changed_line!(expr, line, fls)
144
155
end
145
156
end
146
157
158
+ """
159
+ pc = next_line!(recurse, frame, istoplevel=false)
160
+ pc = next_line!(frame, istoplevel=false)
161
+
162
+ Execute until reaching the first call of the next line of the source code.
163
+ Upon return, `pc` is either the new program counter, `nothing` if a `return` is reached,
164
+ or a `BreakpointRef` if it encountered a wrapper call. In the latter case, call `leaf(frame)`
165
+ to obtain the new execution frame.
166
+ """
147
167
function next_line! (@nospecialize (recurse), frame:: Frame , istoplevel:: Bool = false )
148
- initial = linenumber (frame)
149
- first = true
150
168
pc = frame. pc
151
- while linenumber (frame, pc) == initial
169
+ initialline, initialfile = linenumber (frame, pc), getfile (frame, pc)
170
+ first = true
171
+ while linenumber (frame, pc) == initialline && getfile (frame, pc) == initialfile
152
172
# If this is a return node, interrupt execution
153
173
expr = pc_expr (frame, pc)
154
174
(! first && isexpr (expr, :return )) && return pc
@@ -177,13 +197,170 @@ function next_line!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false
177
197
expr = pc_expr (frame)
178
198
end
179
199
# Signal that we've switched frames
180
- switched && return BreakpointRef (frame. framecode, frame. pc)
200
+ if switched
201
+ pc = next_line! (recurse, frame, false )
202
+ pc === nothing && error (" confusing next_line!" )
203
+ lframe = leaf (frame)
204
+ return isa (pc, BreakpointRef) ? pc : BreakpointRef (lframe. framecode, lframe. pc)
205
+ end
181
206
else
182
207
pc = step_expr! (recurse, frame, istoplevel)
183
208
(pc === nothing || isa (pc, BreakpointRef)) && return pc
184
209
end
185
210
shouldbreak (frame, pc) && return BreakpointRef (frame. framecode, pc)
186
211
end
187
- maybe_next_call! (recurse, frame, pc )
212
+ maybe_next_call! (recurse, frame, istoplevel )
188
213
end
189
214
next_line! (frame:: Frame , istoplevel:: Bool = false ) = next_line! (finish_and_return!, frame, istoplevel)
215
+
216
+ """
217
+ cframe = maybe_step_through_wrapper!(recurse, frame)
218
+ cframe = maybe_step_through_wrapper!(frame)
219
+
220
+ Return the new frame of execution, potentially stepping through "wrapper" methods like those
221
+ that supply default positional arguments or handle keywords. `cframe` is the leaf frame from
222
+ which execution should start.
223
+ """
224
+ function maybe_step_through_wrapper! (@nospecialize (recurse), frame:: Frame )
225
+ code = frame. framecode
226
+ stmts, scope = code. src. code, code. scope:: Method
227
+ length (stmts) < 2 && return frame
228
+ last = stmts[end - 1 ]
229
+ isexpr (last, :(= )) && (last = last. args[2 ])
230
+ is_kw = isa (scope, Method) && startswith (String (Base. unwrap_unionall (scope. sig). parameters[1 ]. name. name), " #kw" )
231
+ if is_kw || isexpr (last, :call ) && any (x-> x== Core. SlotNumber (1 ), last. args)
232
+ # If the last expr calls #self# or passes it to an implementation method,
233
+ # this is a wrapper function that we might want to step through
234
+ while frame. pc != length (stmts)- 1
235
+ pc = next_call! (recurse, frame, false ) # since we're in a Method we're not at toplevel
236
+ end
237
+ ret = evaluate_call! (dummy_breakpoint, frame, last)
238
+ @assert isa (ret, BreakpointRef)
239
+ return maybe_step_through_wrapper! (recurse, callee (frame))
240
+ end
241
+ return frame
242
+ end
243
+ maybe_step_through_wrapper! (frame:: Frame ) = maybe_step_through_wrapper! (finish_and_return!, frame)
244
+
245
+ """
246
+ ret = maybe_reset_frame!(recurse, frame, pc, rootistoplevel)
247
+
248
+ Perform a return to the caller, or descend to the level of a breakpoint.
249
+ `pc` is the return state from the previous command (e.g., `next_call!` or similar).
250
+ `rootistoplevel` should be true if the root frame is top-level.
251
+
252
+ `ret` will be `nothing` if we have just completed a top-level frame. Otherwise,
253
+
254
+ cframe, cpc = ret
255
+
256
+ where `cframe` is the frame from which execution should continue and `cpc` is the state
257
+ of `cframe` (the program counter, a `BreakpointRef`, or `nothing`).
258
+ """
259
+ function maybe_reset_frame! (@nospecialize (recurse), frame:: Frame , @nospecialize (pc), rootistoplevel:: Bool )
260
+ isa (pc, BreakpointRef) && return leaf (frame), pc
261
+ if pc === nothing
262
+ val = get_return (frame)
263
+ recycle (frame)
264
+ frame = caller (frame)
265
+ frame === nothing && return nothing
266
+ frame. callee = nothing
267
+ maybe_assign! (frame, val)
268
+ frame. pc += 1
269
+ pc = maybe_next_call! (recurse, frame, rootistoplevel && frame. caller=== nothing )
270
+ return maybe_reset_frame! (recurse, frame, pc, rootistoplevel)
271
+ end
272
+ return frame, pc
273
+ end
274
+
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
+ recycle (frame)
287
+ frame = caller (frame)
288
+ frame === nothing || (frame. callee = nothing )
289
+ end
290
+ rethrow (exc)
291
+ end
292
+
293
+ """
294
+ ret = debug_command(recurse, frame, cmd, rootistoplevel=false)
295
+ ret = debug_command(frame, cmd, rootistoplevel=false)
296
+
297
+ Perform one "debugger" command. `cmd` should be one of:
298
+
299
+ - "n": advance to the next line
300
+ - "s": step into the next call
301
+ - "c": continue execution until termination or reaching a breakpoint
302
+ - "finish": finish the current frame and return to the parent
303
+
304
+ or one of the 'advanced' commands
305
+
306
+ - "nc": step forward to the next call
307
+ - "se": execute a single statement
308
+ - "si": execute a single statement, stepping in if it's a call
309
+ - "sg": step into the generator of a generated function
310
+
311
+ `rootistoplevel` and `ret` are as described for [`JuliaInterpreter.maybe_reset_frame!`](@ref).
312
+ """
313
+ function debug_command (@nospecialize (recurse), frame:: Frame , cmd:: AbstractString , rootistoplevel:: Bool = false )
314
+ istoplevel = rootistoplevel && frame. caller === nothing
315
+ if cmd == " si"
316
+ stmt = pc_expr (frame)
317
+ cmd = is_call (stmt) ? " s" : " se"
318
+ end
319
+ try
320
+ cmd == " nc" && return maybe_reset_frame! (recurse, frame, next_call! (recurse, frame, istoplevel), rootistoplevel)
321
+ cmd == " n" && return maybe_reset_frame! (recurse, frame, next_line! (recurse, frame, istoplevel), rootistoplevel)
322
+ cmd == " se" && return maybe_reset_frame! (recurse, frame, step_expr! (recurse, frame, istoplevel), rootistoplevel)
323
+
324
+ enter_generated = false
325
+ if cmd == " sg"
326
+ enter_generated = true
327
+ cmd = " s"
328
+ end
329
+ if cmd == " s"
330
+ pc = maybe_next_call! (recurse, frame, istoplevel)
331
+ (isa (pc, BreakpointRef) || pc === nothing ) && return maybe_reset_frame! (recurse, frame, pc, rootistoplevel)
332
+ stmt0 = stmt = pc_expr (frame, pc)
333
+ isexpr (stmt0, :return ) && return maybe_reset_frame! (recurse, frame, nothing , rootistoplevel)
334
+ if isexpr (stmt, :(= ))
335
+ stmt = stmt. args[2 ]
336
+ end
337
+ local ret
338
+ try
339
+ ret = evaluate_call! (dummy_breakpoint, frame, stmt; enter_generated= enter_generated)
340
+ catch err
341
+ ret = handle_err (recurse, frame, err)
342
+ return isa (ret, BreakpointRef) ? (leaf (frame), ret) : ret
343
+ end
344
+ isa (ret, BreakpointRef) && return maybe_reset_frame! (recurse, frame, ret, rootistoplevel)
345
+ maybe_assign! (frame, stmt0, ret)
346
+ frame. pc += 1
347
+ return frame, frame. pc
348
+ end
349
+ if cmd == " c"
350
+ r = root (frame)
351
+ ret = finish_stack! (recurse, r, rootistoplevel)
352
+ return isa (ret, BreakpointRef) ? (leaf (r), ret) : nothing
353
+ end
354
+ cmd == " finish" && return maybe_reset_frame! (recurse, frame, finish! (recurse, frame, istoplevel), rootistoplevel)
355
+ catch err
356
+ frame = unwind_exception (frame, err)
357
+ if cmd == " c"
358
+ return debug_command (recurse, frame, " c" , istoplevel)
359
+ else
360
+ return debug_command (recurse, frame, " nc" , istoplevel)
361
+ end
362
+ end
363
+ throw (ArgumentError (" command $cmd not recognized" ))
364
+ end
365
+ debug_command (frame:: Frame , cmd:: AbstractString , rootistoplevel:: Bool = false ) =
366
+ debug_command (finish_and_return!, frame, cmd, rootistoplevel)
0 commit comments