Skip to content

Commit 30e5469

Browse files
committed
dap: implement variable references
Implement variable references to inspect the state of a stack frame. Variable reference ids are composed of two sections. A thread mask that is the first 8 bytes and the remainder is an increasing number that gets reset each time a thread is resumed. This allows the adapter to know which thread to delegate the variables request to and allows the variable references to still remain confined to each thread. An int32 is used for this because variable references need to be in the range of (0, 2^32). At the moment, only the platform variables and some of the exec operations for an operation. These are labeled as "arguments" to the stack frame. Signed-off-by: Jonathan A. Sternberg <[email protected]>
1 parent 5db46fd commit 30e5469

File tree

5 files changed

+313
-53
lines changed

5 files changed

+313
-53
lines changed

dap/adapter.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ func (d *Adapter[C]) newThread(ctx Context, name string) (t *thread) {
216216
sourceMap: &d.sourceMap,
217217
breakpointMap: d.breakpointMap,
218218
idPool: d.idPool,
219+
variables: newVariableReferences(),
219220
}
220221
d.threads[t.id] = t
221222
d.nextThreadID++
@@ -240,6 +241,11 @@ func (d *Adapter[C]) getThread(id int) (t *thread) {
240241

241242
func (d *Adapter[C]) deleteThread(ctx Context, t *thread) {
242243
d.threadsMu.Lock()
244+
if t := d.threads[t.id]; t != nil {
245+
if t.variables != nil {
246+
t.variables.Reset()
247+
}
248+
}
243249
delete(d.threads, t.id)
244250
d.threadsMu.Unlock()
245251

@@ -252,6 +258,18 @@ func (d *Adapter[C]) deleteThread(ctx Context, t *thread) {
252258
}
253259
}
254260

261+
func (d *Adapter[T]) getThreadByFrameID(id int) (t *thread) {
262+
d.threadsMu.RLock()
263+
defer d.threadsMu.RUnlock()
264+
265+
for _, t := range d.threads {
266+
if t.hasFrame(id) {
267+
return t
268+
}
269+
}
270+
return nil
271+
}
272+
255273
type evaluateRequest struct {
256274
name string
257275
c gateway.Client
@@ -330,6 +348,35 @@ func (d *Adapter[C]) StackTrace(c Context, req *dap.StackTraceRequest, resp *dap
330348
return nil
331349
}
332350

351+
func (d *Adapter[C]) Scopes(c Context, req *dap.ScopesRequest, resp *dap.ScopesResponse) error {
352+
t := d.getThreadByFrameID(req.Arguments.FrameId)
353+
if t == nil {
354+
return errors.Errorf("no such frame id: %d", req.Arguments.FrameId)
355+
}
356+
357+
resp.Body.Scopes = t.Scopes(req.Arguments.FrameId)
358+
for i, s := range resp.Body.Scopes {
359+
resp.Body.Scopes[i].VariablesReference = (t.id << 24) | s.VariablesReference
360+
}
361+
return nil
362+
}
363+
364+
func (d *Adapter[C]) Variables(c Context, req *dap.VariablesRequest, resp *dap.VariablesResponse) error {
365+
tid := req.Arguments.VariablesReference >> 24
366+
367+
t := d.getThread(tid)
368+
if t == nil {
369+
return errors.Errorf("no such thread: %d", tid)
370+
}
371+
372+
varRef := req.Arguments.VariablesReference & ((1 << 24) - 1)
373+
resp.Body.Variables = t.Variables(varRef)
374+
for i, ref := range resp.Body.Variables {
375+
resp.Body.Variables[i].VariablesReference = (tid << 24) | ref.VariablesReference
376+
}
377+
return nil
378+
}
379+
333380
func (d *Adapter[C]) Source(c Context, req *dap.SourceRequest, resp *dap.SourceResponse) error {
334381
fname := req.Arguments.Source.Path
335382

@@ -378,6 +425,8 @@ func (d *Adapter[C]) dapHandler() Handler {
378425
Disconnect: d.Disconnect,
379426
Threads: d.Threads,
380427
StackTrace: d.StackTrace,
428+
Scopes: d.Scopes,
429+
Variables: d.Variables,
381430
Source: d.Source,
382431
}
383432
}

dap/handler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ type Handler struct {
5555
Restart HandlerFunc[*dap.RestartRequest, *dap.RestartResponse]
5656
Threads HandlerFunc[*dap.ThreadsRequest, *dap.ThreadsResponse]
5757
StackTrace HandlerFunc[*dap.StackTraceRequest, *dap.StackTraceResponse]
58+
Scopes HandlerFunc[*dap.ScopesRequest, *dap.ScopesResponse]
59+
Variables HandlerFunc[*dap.VariablesRequest, *dap.VariablesResponse]
5860
Evaluate HandlerFunc[*dap.EvaluateRequest, *dap.EvaluateResponse]
5961
Source HandlerFunc[*dap.SourceRequest, *dap.SourceResponse]
6062
}

dap/server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ func (s *Server) handleMessage(c Context, m dap.Message) (dap.ResponseMessage, e
125125
return s.h.Threads.Do(c, req)
126126
case *dap.StackTraceRequest:
127127
return s.h.StackTrace.Do(c, req)
128+
case *dap.ScopesRequest:
129+
return s.h.Scopes.Do(c, req)
130+
case *dap.VariablesRequest:
131+
return s.h.Variables.Do(c, req)
128132
case *dap.EvaluateRequest:
129133
return s.h.Evaluate.Do(c, req)
130134
case *dap.SourceRequest:

dap/thread.go

Lines changed: 50 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type thread struct {
2626
idPool *idPool
2727
sourceMap *sourceMap
2828
breakpointMap *breakpointMap
29+
variables *variableReferences
2930

3031
// Inputs to the evaluate call.
3132
c gateway.Client
@@ -48,11 +49,10 @@ type thread struct {
4849
mu sync.Mutex
4950

5051
// Attributes set when a thread is paused.
51-
rCtx *build.ResultHandle
52-
curPos digest.Digest
53-
54-
// Lazy attributes that are set when a thread is paused.
55-
stackTrace []dap.StackFrame
52+
rCtx *build.ResultHandle
53+
curPos digest.Digest
54+
stackTrace []int32
55+
frames map[int32]*frame
5656
}
5757

5858
type region struct {
@@ -154,6 +154,7 @@ func (t *thread) pause(c Context, err error, event dap.StoppedEventBody) <-chan
154154
}
155155
}
156156
}
157+
t.collectStackTrace()
157158

158159
event.ThreadId = t.id
159160
c.C() <- &dap.StoppedEvent{
@@ -178,18 +179,7 @@ func (t *thread) resume(step stepType) {
178179
if t.paused == nil {
179180
return
180181
}
181-
182-
if t.rCtx != nil {
183-
t.rCtx.Done()
184-
t.rCtx = nil
185-
}
186-
187-
if t.stackTrace != nil {
188-
for _, frame := range t.stackTrace {
189-
t.idPool.Put(int64(frame.Id))
190-
}
191-
t.stackTrace = nil
192-
}
182+
t.releaseState()
193183

194184
t.paused <- step
195185
close(t.paused)
@@ -207,10 +197,23 @@ func (t *thread) StackTrace() []dap.StackFrame {
207197
return []dap.StackFrame{}
208198
}
209199

210-
if t.stackTrace == nil {
211-
t.stackTrace = t.makeStackTrace()
200+
frames := make([]dap.StackFrame, len(t.stackTrace))
201+
for i, id := range t.stackTrace {
202+
frames[i] = t.frames[id].StackFrame
212203
}
213-
return t.stackTrace
204+
return frames
205+
}
206+
207+
func (t *thread) Scopes(frameID int) []dap.Scope {
208+
t.mu.Lock()
209+
defer t.mu.Unlock()
210+
211+
frame := t.frames[int32(frameID)]
212+
return frame.Scopes()
213+
}
214+
215+
func (t *thread) Variables(id int) []dap.Variable {
216+
return t.variables.Get(id)
214217
}
215218

216219
func (t *thread) getLLBState(ctx Context) error {
@@ -502,15 +505,16 @@ func (t *thread) solve(ctx context.Context, target digest.Digest) (gateway.Refer
502505
return res.SingleRef()
503506
}
504507

505-
func (t *thread) newStackFrame() dap.StackFrame {
506-
return dap.StackFrame{
507-
Id: int(t.idPool.Get()),
508+
func (t *thread) releaseState() {
509+
if t.rCtx != nil {
510+
t.rCtx.Done()
511+
t.rCtx = nil
508512
}
513+
t.stackTrace = nil
514+
t.frames = nil
509515
}
510516

511-
func (t *thread) makeStackTrace() []dap.StackFrame {
512-
var frames []dap.StackFrame
513-
517+
func (t *thread) collectStackTrace() {
514518
region := t.regionsByDigest[t.curPos]
515519
r := t.regions[region]
516520

@@ -519,45 +523,38 @@ func (t *thread) makeStackTrace() []dap.StackFrame {
519523
digests = digests[:index+1]
520524
}
521525

526+
t.frames = make(map[int32]*frame)
522527
for i := len(digests) - 1; i >= 0; i-- {
523528
dgst := digests[i]
524529

525-
frame := t.newStackFrame()
530+
frame := &frame{}
531+
frame.Id = int(t.idPool.Get())
532+
526533
if meta, ok := t.def.Metadata[dgst]; ok {
527-
fillStackFrameMetadata(&frame, meta)
534+
frame.setNameFromMeta(meta)
528535
}
529536
if loc, ok := t.def.Source.Locations[string(dgst)]; ok {
530-
t.fillStackFrameLocation(&frame, loc)
537+
frame.fillLocation(t.def, loc, t.sourcePath)
531538
}
532-
frames = append(frames, frame)
533-
}
534-
return frames
535-
}
536539

537-
func fillStackFrameMetadata(frame *dap.StackFrame, meta llb.OpMetadata) {
538-
if name, ok := meta.Description["llb.customname"]; ok {
539-
frame.Name = name
540-
} else if cmd, ok := meta.Description["com.docker.dockerfile.v1.command"]; ok {
541-
frame.Name = cmd
540+
if op := t.ops[dgst]; op != nil {
541+
frame.fillVarsFromOp(op, t.variables)
542+
}
543+
t.stackTrace = append(t.stackTrace, int32(frame.Id))
544+
t.frames[int32(frame.Id)] = frame
542545
}
543-
// TODO: should we infer the name from somewhere else?
544546
}
545547

546-
func (t *thread) fillStackFrameLocation(frame *dap.StackFrame, loc *pb.Locations) {
547-
for _, l := range loc.Locations {
548-
for _, r := range l.Ranges {
549-
frame.Line = int(r.Start.Line)
550-
frame.Column = int(r.Start.Character)
551-
frame.EndLine = int(r.End.Line)
552-
frame.EndColumn = int(r.End.Character)
553-
554-
info := t.def.Source.Infos[l.SourceIndex]
555-
frame.Source = &dap.Source{
556-
Path: filepath.Join(t.sourcePath, info.Filename),
557-
}
558-
return
559-
}
548+
func (t *thread) hasFrame(id int) bool {
549+
t.mu.Lock()
550+
defer t.mu.Unlock()
551+
552+
if t.paused == nil {
553+
return false
560554
}
555+
556+
_, ok := t.frames[int32(id)]
557+
return ok
561558
}
562559

563560
func pop[S ~[]E, E any](s *S) E {

0 commit comments

Comments
 (0)