delve/pkg/proc/evalop/evalcompile.go

614 lines
15 KiB
Go
Raw Normal View History

proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
package evalop
import (
"bytes"
"errors"
"fmt"
"go/ast"
"go/constant"
"go/parser"
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
"go/printer"
"go/scanner"
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
"go/token"
"strconv"
"strings"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/reader"
)
var (
ErrFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'")
)
type compileCtx struct {
evalLookup
ops []Op
allowCalls bool
curCall int
}
type evalLookup interface {
FindTypeExpr(ast.Expr) (godwarf.Type, error)
HasBuiltin(string) bool
}
// CompileAST compiles the expression t into a list of instructions.
func CompileAST(lookup evalLookup, t ast.Expr) ([]Op, error) {
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
ctx := &compileCtx{evalLookup: lookup, allowCalls: true}
err := ctx.compileAST(t)
if err != nil {
return nil, err
}
err = ctx.depthCheck(1)
if err != nil {
return ctx.ops, err
}
return ctx.ops, nil
}
// Compile compiles the expression expr into a list of instructions.
// If canSet is true expressions like "x = y" are also accepted.
func Compile(lookup evalLookup, expr string, canSet bool) ([]Op, error) {
t, err := parser.ParseExpr(expr)
if err != nil {
if canSet {
eqOff, isAs := isAssignment(err)
if isAs {
return CompileSet(lookup, expr[:eqOff], expr[eqOff+1:])
}
}
return nil, err
}
return CompileAST(lookup, t)
}
func isAssignment(err error) (int, bool) {
el, isScannerErr := err.(scanner.ErrorList)
if isScannerErr && el[0].Msg == "expected '==', found '='" {
return el[0].Pos.Offset, true
}
return 0, false
}
// CompileSet compiles the expression setting lhexpr to rhexpr into a list of
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
// instructions.
func CompileSet(lookup evalLookup, lhexpr, rhexpr string) ([]Op, error) {
lhe, err := parser.ParseExpr(lhexpr)
if err != nil {
return nil, err
}
rhe, err := parser.ParseExpr(rhexpr)
if err != nil {
return nil, err
}
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
ctx := &compileCtx{evalLookup: lookup, allowCalls: true}
err = ctx.compileAST(rhe)
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
if err != nil {
return nil, err
}
if isStringLiteral(rhe) {
ctx.compileAllocLiteralString()
}
err = ctx.compileAST(lhe)
if err != nil {
return nil, err
}
ctx.pushOp(&SetValue{lhe: lhe, Rhe: rhe})
err = ctx.depthCheck(0)
if err != nil {
return ctx.ops, err
}
return ctx.ops, nil
}
func (ctx *compileCtx) compileAllocLiteralString() {
jmp := &Jump{When: JumpIfAllocStringChecksFail}
ctx.pushOp(jmp)
ctx.compileSpecialCall("runtime.mallocgc", []ast.Expr{
&ast.BasicLit{Kind: token.INT, Value: "0"},
&ast.Ident{Name: "nil"},
&ast.Ident{Name: "false"},
}, []Op{
&PushLen{},
&PushNil{},
&PushConst{constant.MakeBool(false)},
})
ctx.pushOp(&ConvertAllocToString{})
jmp.Target = len(ctx.ops)
}
func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args []Op) {
id := ctx.curCall
ctx.curCall++
ctx.pushOp(&CallInjectionStartSpecial{
id: id,
FnName: fnname,
ArgAst: argAst})
ctx.pushOp(&CallInjectionSetTarget{id: id})
for i := range args {
ctx.pushOp(args[i])
ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i})
}
ctx.pushOp(&CallInjectionComplete{id: id})
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
}
func (ctx *compileCtx) pushOp(op Op) {
ctx.ops = append(ctx.ops, op)
}
// depthCheck validates the list of instructions produced by Compile and
2023-10-19 18:04:31 +00:00
// CompileSet by performing a stack depth check.
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
// It calculates the depth of the stack at every instruction in ctx.ops and
// checks that they have enough arguments to execute. For instructions that
// can be reached through multiple paths (because of a jump) it checks that
// all paths reach the instruction with the same stack depth.
// Finally it checks that the stack depth after all instructions have
// executed is equal to endDepth.
func (ctx *compileCtx) depthCheck(endDepth int) error {
depth := make([]int, len(ctx.ops)+1) // depth[i] is the depth of the stack before i-th instruction
for i := range depth {
depth[i] = -1
}
depth[0] = 0
var err error
checkAndSet := func(j, d int) { // sets depth[j] to d after checking that we can
if depth[j] < 0 {
depth[j] = d
}
if d != depth[j] {
err = fmt.Errorf("internal debugger error: depth check error at instruction %d: expected depth %d have %d (jump target)\n%s", j, d, depth[j], Listing(depth, ctx.ops))
}
}
for i, op := range ctx.ops {
npop, npush := op.depthCheck()
if depth[i] < npop {
return fmt.Errorf("internal debugger error: depth check error at instruction %d: expected at least %d have %d\n%s", i, npop, depth[i], Listing(depth, ctx.ops))
}
d := depth[i] - npop + npush
checkAndSet(i+1, d)
if jmp, _ := op.(*Jump); jmp != nil {
checkAndSet(jmp.Target, d)
}
if err != nil {
return err
}
}
if depth[len(ctx.ops)] != endDepth {
return fmt.Errorf("internal debugger error: depth check failed: depth at the end is not %d (got %d)\n%s", depth[len(ctx.ops)], endDepth, Listing(depth, ctx.ops))
}
return nil
}
func (ctx *compileCtx) compileAST(t ast.Expr) error {
switch node := t.(type) {
case *ast.CallExpr:
return ctx.compileTypeCastOrFuncCall(node)
case *ast.Ident:
return ctx.compileIdent(node)
case *ast.ParenExpr:
// otherwise just eval recursively
return ctx.compileAST(node.X)
case *ast.SelectorExpr: // <expression>.<identifier>
switch x := node.X.(type) {
case *ast.Ident:
switch {
case x.Name == "runtime" && node.Sel.Name == "curg":
ctx.pushOp(&PushCurg{})
case x.Name == "runtime" && node.Sel.Name == "frameoff":
ctx.pushOp(&PushFrameoff{})
case x.Name == "runtime" && node.Sel.Name == "threadid":
ctx.pushOp(&PushThreadID{})
case x.Name == "runtime" && node.Sel.Name == "rangeParentOffset":
ctx.pushOp(&PushRangeParentOffset{})
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
default:
ctx.pushOp(&PushPackageVarOrSelect{Name: x.Name, Sel: node.Sel.Name})
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
}
case *ast.CallExpr:
ident, ok := x.Fun.(*ast.SelectorExpr)
if ok {
f, ok := ident.X.(*ast.Ident)
if ok && f.Name == "runtime" && ident.Sel.Name == "frame" {
switch arg := x.Args[0].(type) {
case *ast.BasicLit:
fr, err := strconv.ParseInt(arg.Value, 10, 8)
if err != nil {
return err
}
// Push local onto the stack to be evaluated in the new frame context.
ctx.pushOp(&PushLocal{Name: node.Sel.Name, Frame: fr})
return nil
default:
return fmt.Errorf("expected integer value for frame, got %v", arg)
}
}
}
return ctx.compileUnary(node.X, &Select{node.Sel.Name})
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
case *ast.BasicLit: // try to accept "package/path".varname syntax for package variables
s, err := strconv.Unquote(x.Value)
if err != nil {
return err
}
ctx.pushOp(&PushPackageVarOrSelect{Name: s, Sel: node.Sel.Name, NameIsString: true})
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
default:
return ctx.compileUnary(node.X, &Select{node.Sel.Name})
}
case *ast.TypeAssertExpr: // <expression>.(<type>)
return ctx.compileTypeAssert(node)
case *ast.IndexExpr:
return ctx.compileBinary(node.X, node.Index, nil, &Index{node})
case *ast.SliceExpr:
if node.Slice3 {
return errors.New("3-index slice expressions not supported")
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
}
return ctx.compileReslice(node)
case *ast.StarExpr:
// pointer dereferencing *<expression>
return ctx.compileUnary(node.X, &PointerDeref{node})
case *ast.UnaryExpr:
// The unary operators we support are +, - and & (note that unary * is parsed as ast.StarExpr)
switch node.Op {
case token.AND:
return ctx.compileUnary(node.X, &AddrOf{node})
default:
return ctx.compileUnary(node.X, &Unary{node})
}
case *ast.BinaryExpr:
switch node.Op {
case token.INC, token.DEC, token.ARROW:
return fmt.Errorf("operator %s not supported", node.Op.String())
}
// short circuits logical operators
var sop *Jump
switch node.Op {
case token.LAND:
sop = &Jump{When: JumpIfFalse, Node: node.X}
case token.LOR:
sop = &Jump{When: JumpIfTrue, Node: node.X}
}
err := ctx.compileBinary(node.X, node.Y, sop, &Binary{node})
if err != nil {
return err
}
if sop != nil {
sop.Target = len(ctx.ops)
ctx.pushOp(&BoolToConst{})
}
case *ast.BasicLit:
ctx.pushOp(&PushConst{constant.MakeFromLiteral(node.Value, node.Kind, 0)})
default:
return fmt.Errorf("expression %T not implemented", t)
}
return nil
}
func (ctx *compileCtx) compileTypeCastOrFuncCall(node *ast.CallExpr) error {
if len(node.Args) != 1 {
// Things that have more or less than one argument are always function calls.
return ctx.compileFunctionCall(node)
}
ambiguous := func() error {
// Ambiguous, could be a function call or a type cast, if node.Fun can be
// evaluated then try to treat it as a function call, otherwise try the
// type cast.
ctx2 := &compileCtx{evalLookup: ctx.evalLookup}
err0 := ctx2.compileAST(node.Fun)
if err0 == nil {
return ctx.compileFunctionCall(node)
}
return ctx.compileTypeCast(node, err0)
}
fnnode := node.Fun
for {
fnnode = removeParen(fnnode)
n, _ := fnnode.(*ast.StarExpr)
if n == nil {
break
}
fnnode = n.X
}
switch n := fnnode.(type) {
case *ast.BasicLit:
// It can only be a ("type string")(x) type cast
return ctx.compileTypeCast(node, nil)
case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
return ctx.compileTypeCast(node, nil)
case *ast.SelectorExpr:
if _, isident := n.X.(*ast.Ident); isident {
if typ, _ := ctx.FindTypeExpr(n); typ != nil {
return ctx.compileTypeCast(node, nil)
}
return ambiguous()
}
return ctx.compileFunctionCall(node)
case *ast.Ident:
if typ, _ := ctx.FindTypeExpr(n); typ != nil {
return ctx.compileTypeCast(node, fmt.Errorf("could not find symbol value for %s", n.Name))
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
}
return ctx.compileFunctionCall(node)
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
case *ast.IndexExpr:
// Ambiguous, could be a parametric type
switch n.X.(type) {
case *ast.Ident, *ast.SelectorExpr:
// Do the type-cast first since evaluating node.Fun could be expensive.
err := ctx.compileTypeCast(node, nil)
if err == nil || err != reader.ErrTypeNotFound {
return err
}
return ctx.compileFunctionCall(node)
default:
return ctx.compileFunctionCall(node)
}
case *ast.IndexListExpr:
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
return ctx.compileTypeCast(node, nil)
default:
// All other expressions must be function calls
return ctx.compileFunctionCall(node)
}
}
func (ctx *compileCtx) compileTypeCast(node *ast.CallExpr, ambiguousErr error) error {
err := ctx.compileAST(node.Args[0])
if err != nil {
return err
}
fnnode := node.Fun
// remove all enclosing parenthesis from the type name
fnnode = removeParen(fnnode)
targetTypeStr := exprToString(removeParen(node.Fun))
styp, err := ctx.FindTypeExpr(fnnode)
if err != nil {
switch targetTypeStr {
case "[]byte", "[]uint8":
styp = godwarf.FakeSliceType(godwarf.FakeBasicType("uint", 8))
case "[]int32", "[]rune":
styp = godwarf.FakeSliceType(godwarf.FakeBasicType("int", 32))
default:
if ambiguousErr != nil && err == reader.ErrTypeNotFound {
return fmt.Errorf("could not evaluate function or type %s: %v", exprToString(node.Fun), ambiguousErr)
}
return err
}
}
ctx.pushOp(&TypeCast{DwarfType: styp, Node: node})
return nil
}
func (ctx *compileCtx) compileBuiltinCall(builtin string, args []ast.Expr) error {
for _, arg := range args {
err := ctx.compileAST(arg)
if err != nil {
return err
}
}
ctx.pushOp(&BuiltinCall{builtin, args})
return nil
}
func (ctx *compileCtx) compileIdent(node *ast.Ident) error {
ctx.pushOp(&PushIdent{node.Name})
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
return nil
}
func (ctx *compileCtx) compileUnary(expr ast.Expr, op Op) error {
err := ctx.compileAST(expr)
if err != nil {
return err
}
ctx.pushOp(op)
return nil
}
func (ctx *compileCtx) compileTypeAssert(node *ast.TypeAssertExpr) error {
err := ctx.compileAST(node.X)
if err != nil {
return err
}
// Accept .(data) as a type assertion that always succeeds, so that users
// can access the data field of an interface without actually having to
// type the concrete type.
if idtyp, isident := node.Type.(*ast.Ident); !isident || idtyp.Name != "data" {
typ, err := ctx.FindTypeExpr(node.Type)
if err != nil {
return err
}
ctx.pushOp(&TypeAssert{typ, node})
return nil
}
ctx.pushOp(&TypeAssert{nil, node})
return nil
}
func (ctx *compileCtx) compileBinary(a, b ast.Expr, sop *Jump, op Op) error {
err := ctx.compileAST(a)
if err != nil {
return err
}
if sop != nil {
ctx.pushOp(sop)
}
err = ctx.compileAST(b)
if err != nil {
return err
}
ctx.pushOp(op)
return nil
}
func (ctx *compileCtx) compileReslice(node *ast.SliceExpr) error {
err := ctx.compileAST(node.X)
if err != nil {
return err
}
trustLen := true
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
hasHigh := false
if node.High != nil {
hasHigh = true
err = ctx.compileAST(node.High)
if err != nil {
return err
}
_, isbasiclit := node.High.(*ast.BasicLit)
trustLen = trustLen && isbasiclit
} else {
trustLen = false
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
}
if node.Low != nil {
err = ctx.compileAST(node.Low)
if err != nil {
return err
}
_, isbasiclit := node.Low.(*ast.BasicLit)
trustLen = trustLen && isbasiclit
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
} else {
ctx.pushOp(&PushConst{constant.MakeInt64(0)})
}
ctx.pushOp(&Reslice{Node: node, HasHigh: hasHigh, TrustLen: trustLen})
proc: use stack machine to evaluate expressions (#3508) * proc: use stack machine to evaluate expressions This commit splits expression evaluation into two parts. The first part (in pkg/proc/evalop/evalcompile.go) "compiles" as ast.Expr into a list of instructions (defined in pkg/proc/evalop/ops.go) for a stack machine (defined by `proc.(*evalStack)`). The second part is a stack machine (implemented by `proc.(*EvalScope).eval` and `proc.(*EvalScope).evalOne`) that has two modes of operation: in the main mode it executes inteructions from the list (by calling `evalOne`), in the second mode it executes the call injection protocol by calling `funcCallStep` repeatedly until it either the protocol finishes, needs more input from the stack machine (to set call arguments) or fails. This approach has several benefits: - it is now possible to remove the goroutine we use to evaluate expression and the channel used to communicate with the Continue loop. - every time we resume the target to execute the call injection protocol we need to update several local variables to match the changed state of the target, this is now done at the top level of the evaluation loop instead of being hidden inside a recurisive evaluator - using runtime.Pin to pin addresses returned by an injected call would allow us to use a more natural evaluation order for function calls, which would solve some bugs #3310, allow users to inspect values returned by a call injection #1599 and allow implementing some other features #1465. Doing this with the recursive evaluator, while keeping backwards compatibility with versions of Go that do not have runtime.Pin is very hard. However after this change we can simply conditionally change how compileFunctionCall works and add some opcodes. * review round 1 * review round 2
2023-10-17 18:21:59 +00:00
return nil
}
func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
if fnnode, ok := node.Fun.(*ast.Ident); ok {
if ctx.HasBuiltin(fnnode.Name) {
return ctx.compileBuiltinCall(fnnode.Name, node.Args)
}
}
if !ctx.allowCalls {
return ErrFuncCallNotAllowed
}
id := ctx.curCall
ctx.curCall++
oldAllowCalls := ctx.allowCalls
oldOps := ctx.ops
ctx.allowCalls = false
err := ctx.compileAST(node.Fun)
ctx.allowCalls = oldAllowCalls
hasFunc := false
if err != nil {
ctx.ops = oldOps
if err != ErrFuncCallNotAllowed {
return err
}
} else {
hasFunc = true
}
ctx.pushOp(&CallInjectionStart{HasFunc: hasFunc, id: id, Node: node})
// CallInjectionStart pushes true on the stack if it needs the function argument re-evaluated
var jmpif *Jump
if hasFunc {
jmpif = &Jump{When: JumpIfFalse, Pop: true}
ctx.pushOp(jmpif)
}
ctx.pushOp(&Pop{})
err = ctx.compileAST(node.Fun)
if err != nil {
return err
}
if jmpif != nil {
jmpif.Target = len(ctx.ops)
}
ctx.pushOp(&CallInjectionSetTarget{id: id})
for i, arg := range node.Args {
err := ctx.compileAST(arg)
if err != nil {
return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", exprToString(arg), i+1, exprToString(node.Fun), err)
}
if isStringLiteral(arg) {
ctx.compileAllocLiteralString()
}
ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i, ArgExpr: arg})
}
ctx.pushOp(&CallInjectionComplete{id: id})
return nil
}
func Listing(depth []int, ops []Op) string {
if depth == nil {
depth = make([]int, len(ops)+1)
}
buf := new(strings.Builder)
for i, op := range ops {
fmt.Fprintf(buf, " %3d (%2d->%2d) %#v\n", i, depth[i], depth[i+1], op)
}
return buf.String()
}
func isStringLiteral(expr ast.Expr) bool {
switch expr := expr.(type) {
case *ast.BasicLit:
return expr.Kind == token.STRING
case *ast.BinaryExpr:
if expr.Op == token.ADD {
return isStringLiteral(expr.X) && isStringLiteral(expr.Y)
}
case *ast.ParenExpr:
return isStringLiteral(expr.X)
}
return false
}
func removeParen(n ast.Expr) ast.Expr {
for {
p, ok := n.(*ast.ParenExpr)
if !ok {
break
}
n = p.X
}
return n
}
func exprToString(t ast.Expr) string {
var buf bytes.Buffer
printer.Fprint(&buf, token.NewFileSet(), t)
return buf.String()
}