Using JuliaInterpreter breakpoints for custom code execution

Let’s say that I’d like to run foo(), which calls bar, but replace the bar calls with calls to yummy(). This is the standard use case for packages like Cassette and IRTools (are there others now?), but even after all these years, they still look like half-heartedly-maintained fringe packages filled with caveats (which is understandable given how complex the problem space is!) Meanwhile, JuliaInterpreter has

julia> bp = @breakpoint sum([1, 2]) any(x->x>4, a);

julia> frame, bpref = @interpret sum([1,2,5])  # should trigger breakpoint
(Frame for sum(a::AbstractArray; dims, kw...) in Base at reducedim.jl:889
c 1 889  1 ─      nothing
  2 889  │   %2 = ($(QuoteNode(NamedTuple)))()
  3 889  │   %3 = Base.pairs(%2)
⋮
a = [1, 2, 5], breakpoint(sum(a::AbstractArray; dims, kw...) in Base at reducedim.jl:889, line 889))

Would it be that unreasonable to use a breakpoint on bar, then hack into the frame to change the call stack and insert a call to yummy?

The answer seems to be yes, with this code being the critical piece:

const JInterp = JuliaInterpreter

function return_and_continue_execution(fr::JInterp.Frame, return_value)
    # This assume the `fr` is the _calling_ frame, for whatever call was breaked on, and indeed,
    # this is the case for JInterp breakpoints.
    # This code is a slimmed down version of `JInterp.maybe_reset_frame!`
    @assert !JInterp.is_leaf(fr)

    # First, abort the call to the breakpointed function
    JInterp.recycle(fr.callee)
    fr.callee = nothing

    # Then assign the value and move on to the next instruction
    JInterp.maybe_assign!(fr, return_value)
    fr.pc += 1    # maybe_reset_frame! has more complicated logic, that I don't get, but git-blame
                  # suggests that it's for kwarg funs

    return JInterp.finish_and_return!(fr)  # continue execution
end

julia> using JuliaInterpreter

julia> sump7(x) = only(sum(x)) + 7
sump7 (generic function with 1 method)

julia> bp = @breakpoint sum([10,10]);

julia> frame, _ = @interpret sump7([100,100]);

julia> return_and_continue_execution(frame, 500)
507