Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions _fixtures/delvecatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import "fmt"

func main() {
for i, iface := range []any{12, "test", nil, 2.2, "hello", 7} {
fmt.Println(i, iface)
}
}
23 changes: 20 additions & 3 deletions pkg/proc/breakpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,15 +482,32 @@ func evalBreakpointCondition(tgt *Target, thread Thread, cond ast.Expr) (bool, e
return true, err
}
}
v, err := scope.evalAST(cond)
flags := scope.evalopFlags()
flags |= evalop.BreakpointCondition
ops, err := evalop.CompileAST(scopeToEvalLookup{scope}, cond, flags)
if err != nil {
return true, err
}
stack := &evalStack{}
stack.eval(scope, ops)
v, err := stack.result(nil)
if err != nil {
if stack.disabledErrors {
return false, nil
}
return true, fmt.Errorf("error evaluating expression: %v", err)
}
if v.Kind != reflect.Bool {
if stack.disabledErrors {
return false, nil
}
return true, errors.New("condition expression not boolean")
}
v.loadValue(loadFullValue)
if v.Unreadable != nil {
if stack.disabledErrors {
return false, nil
}
return true, fmt.Errorf("condition expression unreadable: %v", v.Unreadable)
}
return constant.BoolVal(v.Value), nil
Expand Down Expand Up @@ -1188,7 +1205,7 @@ func breakpointConditionSatisfiable(lbpmap map[int]*LogicalBreakpoint, lbp *Logi
return 0, false
}
ident, ok := selx.X.(*ast.Ident)
if !ok || ident.Name != evalop.BreakpointHitCountVarNamePackage || selx.Sel.Name != evalop.BreakpointHitCountVarName {
if !ok || ident.Name != evalop.DelvePackage || selx.Sel.Name != evalop.BreakpointHitCountVarName {
return 0, false
}
lit, ok := idx.Index.(*ast.BasicLit)
Expand Down Expand Up @@ -1274,7 +1291,7 @@ func breakpointConditionUsesHitCounts(lbp *LogicalBreakpoint) bool {
if ok {
ident, ok := seln.X.(*ast.Ident)
if ok {
if ident.Name == evalop.BreakpointHitCountVarNamePackage && seln.Sel.Name == evalop.BreakpointHitCountVarName {
if ident.Name == evalop.DelvePackage && seln.Sel.Name == evalop.BreakpointHitCountVarName {
r = true
return false
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/proc/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,7 @@ type evalStack struct {
curthread Thread
lastRetiredFncall *functionCallState
debugPinner *Variable
disabledErrors bool
}

func (s *evalStack) push(v *Variable) {
Expand Down Expand Up @@ -1328,6 +1329,9 @@ func (stack *evalStack) executeOp() {
case *evalop.PushNewFakeVariable:
stack.pushNewFakeVariable(scope, op.Type)

case *evalop.DisableErrors:
stack.disabledErrors = true

default:
stack.err = fmt.Errorf("internal debugger error: unknown eval opcode: %#v", op)
}
Expand Down
29 changes: 23 additions & 6 deletions pkg/proc/evalop/evalcompile.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import (
)

var (
ErrFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'")
ErrFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'")
errFuncCallNotAllowedLitAlloc = errors.New("literal can not be allocated because function calls are not allowed without using 'call'")
)

const (
BreakpointHitCountVarNamePackage = "delve"
DelvePackage = "delve"
BreakpointHitCountVarName = "bphitcount"
BreakpointHitCountVarNameQualified = BreakpointHitCountVarNamePackage + "." + BreakpointHitCountVarName
BreakpointHitCountVarNameQualified = DelvePackage + "." + BreakpointHitCountVarName
DebugPinnerFunctionName = "runtime.debugPinnerV1"
)

Expand All @@ -48,8 +48,9 @@ type evalLookup interface {
type Flags uint8

const (
CanSet Flags = 1 << iota // Assignment is allowed
HasDebugPinner // runtime.debugPinner is available
CanSet Flags = 1 << iota // Assignment is allowed
HasDebugPinner // runtime.debugPinner is available
BreakpointCondition // This expression is used as a breakpoint condition
)

// CompileAST compiles the expression t into a list of instructions.
Expand Down Expand Up @@ -292,6 +293,19 @@ func (ctx *compileCtx) depthCheck(endDepth int) error {
func (ctx *compileCtx) compileAST(t ast.Expr, toplevel bool) error {
switch node := t.(type) {
case *ast.CallExpr:
if f, ok := node.Fun.(*ast.SelectorExpr); ok && ctx.flags&BreakpointCondition != 0 {
const catch = "catch"
if ident, ok := f.X.(*ast.Ident); ok && ident.Name == DelvePackage && f.Sel.Name == catch {
if !toplevel {
return fmt.Errorf("%s.%s can only be used at toplevel", DelvePackage, catch)
}
if len(node.Args) != 1 {
return fmt.Errorf("wrong number of arguments for %s.%s", DelvePackage, catch)
}
ctx.pushOp(&DisableErrors{})
return ctx.compileAST(node.Args[0], false)
}
}
return ctx.compileTypeCastOrFuncCall(node, toplevel)

case *ast.Ident:
Expand All @@ -317,7 +331,7 @@ func (ctx *compileCtx) compileAST(t ast.Expr, toplevel bool) error {
case x.Name == "runtime" && node.Sel.Name == "rangeParentOffset":
ctx.pushOp(&PushRangeParentOffset{})

case x.Name == BreakpointHitCountVarNamePackage && node.Sel.Name == BreakpointHitCountVarName:
case x.Name == DelvePackage && node.Sel.Name == BreakpointHitCountVarName:
ctx.pushOp(&PushBreakpointHitCount{})

default:
Expand All @@ -329,6 +343,9 @@ func (ctx *compileCtx) compileAST(t ast.Expr, toplevel bool) error {
if ok {
f, ok := ident.X.(*ast.Ident)
if ok && f.Name == "runtime" && ident.Sel.Name == "frame" {
if len(x.Args) != 1 {
return fmt.Errorf("wrong number of arguments for runtime.frame")
}
switch arg := x.Args[0].(type) {
case *ast.BasicLit:
fr, err := strconv.ParseInt(arg.Value, 10, 8)
Expand Down
6 changes: 6 additions & 0 deletions pkg/proc/evalop/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,9 @@ type PushNewFakeVariable struct {
}

func (*PushNewFakeVariable) depthCheck() (npop, npush int) { return 0, 1 }

// DisableErrors disables error reporing for the rest of the execution
type DisableErrors struct {
}

func (*DisableErrors) depthCheck() (npop, npush int) { return 0, 0 }
10 changes: 10 additions & 0 deletions pkg/proc/proc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5897,3 +5897,13 @@ func TestChainedBreakpoint(t *testing.T) {
}
})
}

func TestDelveCatch(t *testing.T) {
withTestProcess("delvecatch", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 7)
bp.UserBreaklet().Cond, _ = parser.ParseExpr(`delve.catch(iface.(data) == "hello")`)
assertNoError(grp.Continue(), t, "Continue")
assertLineNumber(p, t, 7, "continue")
assertVariable(t, evalVariable(p, t, "i"), varTest{name: "i", preserveName: true, value: "4", varType: "int"})
})
}
Loading