Skip to content

Commit 586947b

Browse files
committed
Accept any as input and add comments.
1 parent ddccec9 commit 586947b

File tree

1 file changed

+113
-57
lines changed

1 file changed

+113
-57
lines changed

internal/engine/eval/rego/debug.go

+113-57
Original file line numberDiff line numberDiff line change
@@ -24,40 +24,25 @@ import (
2424
"github.com/mindersec/minder/pkg/engine/v1/interfaces"
2525
)
2626

27-
func MakeEventHandler(ch chan<- *debug.Event) func(debug.Event) {
27+
func makeEventHandler(ch chan<- *debug.Event) func(debug.Event) {
2828
return func(event debug.Event) {
2929
ch <- &event
3030
}
3131
}
3232

33-
func MakeTracingEventHandler(ch chan<- *debug.Event) func(debug.Event) {
33+
//nolint:unused
34+
func makeTracingEventHandler(ch chan<- *debug.Event) func(debug.Event) {
3435
return func(event debug.Event) {
3536
fmt.Fprintf(os.Stderr, "%+v\n", event)
3637
ch <- &event
3738
}
3839
}
3940

40-
func (ds *debugSession) WaitFor(
41-
ctx context.Context,
42-
eventTypes ...debug.EventType,
43-
) *debug.Event {
44-
for {
45-
select {
46-
case e := <-ds.ch:
47-
if slices.Contains(eventTypes, e.Type) {
48-
return e
49-
}
50-
case <-ctx.Done():
51-
return nil
52-
}
53-
}
54-
}
55-
5641
var (
5742
errEmptySource = errors.New("empty source code")
58-
errInvalidInput = errors.New("invalid input")
5943
errInvalidInstr = errors.New("invalid instruction")
6044
errInvalidBP = errors.New("invalid breakpoint")
45+
errUserAbort = errors.New("user abort")
6146
)
6247

6348
// Debug implements an interactive debugger for REGO-based evaluators.
@@ -67,9 +52,6 @@ func (e *Evaluator) Debug(
6752
input *Input,
6853
funcs ...func(*rego.Rego),
6954
) error {
70-
ctx, cancel := context.WithCancel(ctx)
71-
defer cancel()
72-
7355
allOpts := make([]func(*rego.Rego), 0, len(e.regoOpts)+len(funcs))
7456
allOpts = append(allOpts, e.regoOpts...)
7557
allOpts = append(allOpts, funcs...)
@@ -80,7 +62,7 @@ func (e *Evaluator) Debug(
8062
withInput(input),
8163
withQuery(e.reseval.getQueryString()),
8264
withOpts(allOpts...),
83-
withTracingEventHandler(),
65+
// withTracingEventHandler(),
8466
)
8567
if err != nil {
8668
return fmt.Errorf("error initializing debugger: %w", err)
@@ -93,7 +75,7 @@ type debugSession struct {
9375
prompt string
9476
src string
9577
lines int
96-
input *Input
78+
input any
9779
query string
9880
opts []debug.LaunchOption
9981
ch chan *debug.Event
@@ -125,11 +107,7 @@ func withSource(src string) debugSessionOption {
125107

126108
func withInput(input any) debugSessionOption {
127109
return func(ds *debugSession) error {
128-
inner, ok := input.(*Input)
129-
if !ok {
130-
return fmt.Errorf("%w: wrong type %T", errInvalidInput, input)
131-
}
132-
ds.input = inner
110+
ds.input = input
133111
return nil
134112
}
135113
}
@@ -159,11 +137,20 @@ func withOpts(opts ...func(*rego.Rego)) debugSessionOption {
159137
}
160138
}
161139

140+
//nolint:unused
162141
func withTracingEventHandler() debugSessionOption {
163142
return func(ds *debugSession) error {
143+
// NOTE: this channel must be buffered, because REGO
144+
// interpreter emits several events that we're
145+
// currently handling in the same thread of execition
146+
// of the CLI interface.
147+
//
148+
// The solution would be handling CLI events and
149+
// debuggee events asynchronously, but we're not there
150+
// yet.
164151
ch := make(chan *debug.Event, 10)
165152
ds.ch = ch
166-
ds.handler = MakeTracingEventHandler(ch)
153+
ds.handler = makeTracingEventHandler(ch)
167154
return nil
168155
}
169156
}
@@ -180,20 +167,62 @@ func newDebugSession(
180167
}
181168

182169
if ds.handler == nil {
170+
// NOTE: this channel must be buffered, because REGO
171+
// interpreter emits several events that we're
172+
// currently handling in the same thread of execition
173+
// of the CLI interface.
174+
//
175+
// The solution would be handling CLI events and
176+
// debuggee events asynchronously, but we're not there
177+
// yet.
183178
ch := make(chan *debug.Event, 10)
184179
ds.ch = ch
185-
ds.handler = MakeEventHandler(ch)
180+
ds.handler = makeEventHandler(ch)
186181
}
187182

188183
return ds, nil
189184
}
190185

186+
func (ds *debugSession) waitFor(
187+
ctx context.Context,
188+
eventTypes ...debug.EventType,
189+
) *debug.Event {
190+
for {
191+
select {
192+
case e := <-ds.ch:
193+
if slices.Contains(eventTypes, e.Type) {
194+
return e
195+
}
196+
case <-ctx.Done():
197+
return nil
198+
}
199+
}
200+
}
201+
191202
func (ds *debugSession) startDebugger(
192203
ctx context.Context,
193204
) error {
194205
debugger := debug.NewDebugger(
195206
debug.SetEventHandler(ds.handler),
196207
)
208+
// This combination of flags provides roughly the same user
209+
// experience as one would have while debugging imperative
210+
// languages using a standard debugger like lldb or gdb.
211+
//
212+
// Specifically, `StopOnEntry` stops when entering an
213+
// expression, which is like stepping through some, but not
214+
// all, lines and even inside the same line multiple times in
215+
// the case of list/set comprehensions, while `StopOnFail`
216+
// results in stopping at all expressions producing a `false`
217+
// value, which is similar to the previous case in that it
218+
// stops every time a check fails during a list/set
219+
// comprehension.
220+
//
221+
// The previous descriptions must be taken with a grain of
222+
// salt and are likely missing useful cases. That said, the
223+
// described cases are hardly seen when debugging imperative
224+
// languages, which is the user experience we want to provide
225+
// at the moment. Of course, this might change in the future.
197226
launchProps := debug.LaunchEvalProperties{
198227
LaunchProperties: debug.LaunchProperties{
199228
StopOnEntry: false,
@@ -242,11 +271,11 @@ func (ds *debugSession) Start(ctx context.Context) error {
242271
}
243272
fmt.Fprintf(&b, "Restarted")
244273
case line == "c":
245-
if err := ds.session.Resume(thr); err != nil {
274+
if err := ds.session.ResumeAll(); err != nil {
246275
return fmt.Errorf("error resuming execution: %w", err)
247276
}
248277

249-
evt := ds.WaitFor(ctx,
278+
evt := ds.waitFor(ctx,
250279
debug.ExceptionEventType,
251280
debug.StoppedEventType,
252281
debug.StdoutEventType,
@@ -280,6 +309,9 @@ func (ds *debugSession) Start(ctx context.Context) error {
280309
case debug.TerminatedEventType:
281310
fmt.Fprintf(&b, "\nTerminated\n")
282311
}
312+
case line == "q":
313+
return errUserAbort
314+
283315
case line == "locals":
284316
if err := printLocals(&b, ds.session, thr); err != nil {
285317
return fmt.Errorf("error printing locals: %w", err)
@@ -343,11 +375,23 @@ func (ds *debugSession) Start(ctx context.Context) error {
343375
return fmt.Errorf("error getting stack trace: %w", err)
344376
}
345377
if loc := getCurrentLocation(stack); loc != nil {
346-
loc.Row += 1 // let's hope it always exists...
347-
loc.Col = 0
378+
// Unfortunately, getting the column
379+
// right is tricky, since source-level
380+
// breakpoints only look at line
381+
// numbers in the REGO interpreter, so
382+
// the safest assumption is starting
383+
// from 0.
384+
//
385+
// It would be great if the frame
386+
// struct contained details about the
387+
// position in the source.
388+
nextloc := location.Location{
389+
Row: loc.Row + 1, // let's hope it always exists...
390+
Col: 0,
391+
}
348392

349393
// add internal breakpoint
350-
bp, err := ds.session.AddBreakpoint(*loc)
394+
bp, err := ds.session.AddBreakpoint(nextloc)
351395
if err != nil {
352396
return fmt.Errorf("error setting breakpoint: %w", err)
353397
}
@@ -357,7 +401,7 @@ func (ds *debugSession) Start(ctx context.Context) error {
357401
return fmt.Errorf("error resuming execution: %w", err)
358402
}
359403

360-
evt := ds.WaitFor(ctx, debug.StoppedEventType)
404+
evt := ds.waitFor(ctx, debug.StoppedEventType)
361405
stack, err := ds.session.StackTrace(evt.Thread)
362406
if err != nil {
363407
return fmt.Errorf("error getting stack trace: %w", err)
@@ -374,43 +418,39 @@ func (ds *debugSession) Start(ctx context.Context) error {
374418
case line == "s",
375419
line == "sv":
376420
if err := ds.session.StepOver(thr); err != nil {
377-
panic(err)
421+
return fmt.Errorf("error on step-over: %w", err)
378422
}
379-
evt := ds.WaitFor(ctx, debug.StoppedEventType)
423+
evt := ds.waitFor(ctx, debug.StoppedEventType)
380424
stack, err := ds.session.StackTrace(evt.Thread)
381425
if err != nil {
382426
return fmt.Errorf("error getting stack trace: %w", err)
383427
}
384428
printSource(&b, ds.src, stack)
385429
case line == "si":
386-
go func() {
387-
if err := ds.session.StepIn(thr); err != nil {
388-
panic(err)
389-
}
390-
}()
391-
evt := ds.WaitFor(ctx, debug.StoppedEventType)
430+
if err := ds.session.StepIn(thr); err != nil {
431+
return fmt.Errorf("error on step-in: %w", err)
432+
}
433+
evt := ds.waitFor(ctx, debug.StoppedEventType)
392434
stack, err := ds.session.StackTrace(evt.Thread)
393435
if err != nil {
394436
return fmt.Errorf("error getting stack trace: %w", err)
395437
}
396438
printSource(&b, ds.src, stack)
397439
case line == "so":
398-
go func() {
399-
if err := ds.session.StepOut(thr); err != nil {
400-
panic(err)
401-
}
402-
}()
403-
evt := ds.WaitFor(ctx, debug.StoppedEventType)
440+
if err := ds.session.StepOut(thr); err != nil {
441+
return fmt.Errorf("error on step-out: %w", err)
442+
}
443+
evt := ds.waitFor(ctx, debug.StoppedEventType)
404444
stack, err := ds.session.StackTrace(evt.Thread)
405445
if err != nil {
406446
return fmt.Errorf("error getting stack trace: %w", err)
407447
}
408448
printSource(&b, ds.src, stack)
409-
case line == "q":
410-
return fmt.Errorf("user abort")
449+
411450
case line == "h",
412451
line == "help":
413452
printHelp(&b)
453+
414454
case strings.HasPrefix(line, "p"):
415455
varname, err := toVarName(line)
416456
if err != nil {
@@ -433,6 +473,7 @@ func (ds *debugSession) Start(ctx context.Context) error {
433473
if err := printVar(&b, r, ds.session, thr); err != nil {
434474
return fmt.Errorf("error printing variables: %w", err)
435475
}
476+
436477
case strings.HasPrefix(line, "b"):
437478
loc, err := toLocation(line, ds.lines)
438479
if err != nil {
@@ -627,10 +668,24 @@ func printSource(b *strings.Builder, src string, stack debug.StackTrace) {
627668
for idx, line := range strings.Split(src, "\n") {
628669
fmt.Fprintf(b, "%*d: %s", padding, idx+1, line)
629670
if idx+1 == loc.Row {
630-
theline := strings.Split(string(loc.Text), "\n")[0]
671+
// `theline` is the very first line of
672+
// the expression starting at the
673+
// given position.
674+
//
675+
// In REGO expressions can span
676+
// multiple lines (for example, rules
677+
// do), but we really are interested
678+
// in underlining only the first line
679+
// of the given expression.
680+
//
681+
// For weird underlyining starting
682+
// from column 0 of the line, see
683+
// comment on setting source-level
684+
// breakpoints.
685+
theline := strings.Split(line, "\n")[0]
631686
fmt.Fprintf(b, "\n%s%s",
632-
strings.Repeat(" ", loc.Col+int(padding)+2-1),
633-
cli.SimpleBoldStyle.Render(strings.Repeat("^", len(theline))),
687+
strings.Repeat(" ", int(padding)+2+loc.Col-1),
688+
cli.SimpleBoldStyle.Render(strings.Repeat("^", len(theline)-loc.Col+1)),
634689
)
635690
}
636691
fmt.Fprintln(b)
@@ -654,6 +709,7 @@ func printLocals(b *strings.Builder, s debug.Session, thrID debug.ThreadID) erro
654709
}
655710

656711
if len(trace) == 0 {
712+
fmt.Fprintln(b, "No locals")
657713
return nil
658714
}
659715

@@ -843,7 +899,7 @@ Breakpoints:
843899
clearall/cla -- clear all breakpoints
844900
845901
Stepping:
846-
n ------------- next line
902+
n/next--------- next line
847903
s/sv ---------- step over
848904
so ------------ step out
849905
si ------------ step into

0 commit comments

Comments
 (0)