@@ -854,6 +854,12 @@ func (s *Session) handleRequest(request dap.Message) {
854854 s .onGotoCheckpointRequest (request , resumeRequestLoop )
855855 }()
856856 resumeRequestLoop .wait ()
857+ case * LastValueRequest : // Custom (Undo backend)
858+ go func () {
859+ defer s .recoverPanic (request )
860+ s .onLastValueRequest (request , resumeRequestLoop )
861+ }()
862+ resumeRequestLoop .wait ()
857863 case * dap.ReverseContinueRequest : // Optional (capability 'supportsStepBack')
858864 go func () {
859865 defer s .recoverPanic (request )
@@ -3520,6 +3526,119 @@ func (s *Session) onListCheckpointsRequest(request *ListCheckpointsRequest) {
35203526 })
35213527}
35223528
3529+ func (s * Session ) rewindWithWatchpoint (thread int64 , frame int , expression string , allowNextStateChange * syncflag ) (* api.DebuggerState , bool , error ) {
3530+ wp , err := s .debugger .CreateWatchpoint (thread , frame , 0 , expression , api .WatchWrite )
3531+ if err != nil {
3532+ return nil , false , err
3533+ }
3534+
3535+ // We tell the client that the debuggee has been "continued" so it will expect a stop event.
3536+ s .send (& dap.ContinuedEvent {
3537+ Event : * newEvent ("continued" ),
3538+ Body : dap.ContinuedEventBody {
3539+ ThreadId : int (thread ),
3540+ AllThreadsContinued : true ,
3541+ },
3542+ })
3543+
3544+ // Attempt to rewind.
3545+ state , err := s .runUntilStop (api .Rewind , allowNextStateChange )
3546+
3547+ // Clear up our watchpoint - we definitely don't need it anymore.
3548+ _ , wp_err := s .debugger .ClearBreakpoint (wp )
3549+
3550+ err = errors .Join (err , wp_err )
3551+ if err != nil {
3552+ return nil , false , err
3553+ }
3554+
3555+ return state , s .debugger .StopReason () == proc .StopWatchpoint , nil
3556+ }
3557+
3558+ // onLastValueRequest handles the 'undo/lastValue' request.
3559+ // This is a custom request supported by Undo's Delve fork.
3560+ func (s * Session ) onLastValueRequest (request * LastValueRequest , allowNextStateChange * syncflag ) {
3561+ defer allowNextStateChange .raise ()
3562+
3563+ breakpoints := s .debugger .Breakpoints (true )
3564+ defer func () {
3565+ // On exit, restore the original status of all breakpoints.
3566+ for _ , bp := range breakpoints {
3567+ s .debugger .AmendBreakpoint (bp )
3568+ }
3569+ }()
3570+
3571+ sf , ok := s .stackFrameHandles .get (request .Arguments .FrameId )
3572+ // Todo: Does sf.goroutineID mean we don't need the ThreadID argument?
3573+ if ! ok {
3574+ s .sendErrorResponse (
3575+ request .Request ,
3576+ UnableToSetBreakpoints ,
3577+ "Unable to set watchpoint" ,
3578+ fmt .Sprintf ("unknown frame id %d" , request .Arguments .FrameId ),
3579+ )
3580+ return
3581+ }
3582+
3583+ // Disable all breakpoints to avoid confusion with the watchpoint we'll create.
3584+ for _ , bp := range breakpoints {
3585+ bp_disabled := * bp
3586+ bp_disabled .Disabled = true
3587+ s .debugger .AmendBreakpoint (& bp_disabled )
3588+ }
3589+
3590+ state_before , err := s .debugger .State (false )
3591+ if err != nil {
3592+ s .sendInternalErrorResponse (request .Seq , err .Error ())
3593+ }
3594+
3595+ state , found , err := s .rewindWithWatchpoint (
3596+ int64 (request .Arguments .ThreadId ),
3597+ sf .frameIndex ,
3598+ request .Arguments .Expression ,
3599+ allowNextStateChange ,
3600+ )
3601+ if err != nil {
3602+ s .sendInternalErrorResponse (request .Seq , err .Error ())
3603+ return
3604+ }
3605+
3606+ // If we didn't find a value change then return to the initial point in time.
3607+ // Todo:
3608+ // - probably need to restore current thread, frame, etc also.
3609+ // - if we were already at the start of time a strange error comes out - investigate!
3610+ if ! found {
3611+ _ , err := s .debugger .Restart (false , state_before .When , false , nil , [3 ]string {}, false )
3612+ if err != nil {
3613+ s .sendInternalErrorResponse (request .Seq , err .Error ())
3614+ }
3615+ }
3616+
3617+ s .send (& LastValueResponse {
3618+ Response : * newResponse (request .Request ),
3619+ Body : LastValueResult {Found : found },
3620+ })
3621+
3622+ // We attempt to roughly match behaviour the behaviour of stopping after a step / continue
3623+ // here, since there's no stop reason for an explicit time jump.
3624+ s .resetHandlesForStoppedEvent ()
3625+ stopped := dap.StoppedEvent {
3626+ Event : * newEvent ("stopped" ),
3627+ Body : dap.StoppedEventBody {
3628+ ThreadId : int (stoppedGoroutineID (state )),
3629+ AllThreadsStopped : true ,
3630+ },
3631+ }
3632+
3633+ if found {
3634+ stopped .Body .Reason = "data breakpoint"
3635+ } else {
3636+ stopped .Body .Reason = "step"
3637+ }
3638+
3639+ s .send (& stopped )
3640+ }
3641+
35233642// onReverseContinueRequest performs a rewind command call up to the previous
35243643// breakpoint or the start of the process
35253644// This is an optional request enabled by capability 'supportsStepBackRequest'.
0 commit comments