Skip to content

Commit

Permalink
proc: allow function calls to appear inside an expression (#1503)
Browse files Browse the repository at this point in the history
The initial implementation of the 'call' command required the
function call to be the root expression, i.e. something like:

	double(3) + 1

was not allowed, because the root expression was the binary operator
'+', not the function call.

With this change expressions like the one above and others are
allowed.

This is the first step necessary to implement nested function calls
(where the result of a function call is used as argument to another
function call).

This is implemented by replacing proc.CallFunction with
proc.EvalExpressionWithCalls. EvalExpressionWithCalls will run
proc.(*EvalScope).EvalExpression in a different goroutine. This
goroutine, the 'eval' goroutine, will communicate with the main
goroutine of the debugger by means of two channels: continueRequest
and continueCompleted.

The eval goroutine evaluates the expression recursively, when
a function call is encountered it takes care of setting up the
function call on the target program and writes a request to the
continueRequest channel, this causes the 'main' goroutine to restart
the target program by calling proc.Continue.

Whenever Continue encounters a breakpoint that belongs to the
function call injection protocol (runtime.debugCallV1 and associated
functions) it writes to continueCompleted which resumes the 'eval'
goroutine.

The 'eval' goroutine takes care of implementing the function call
injection protocol.

When the expression is fully evaluated the 'eval' goroutine will
write a special message to 'continueRequest' signaling that the
expression evaluation is terminated which will cause Continue to
return to the user.

Updates #119
  • Loading branch information
aarzilli authored and derekparker committed May 9, 2019
1 parent f3b149b commit c30a333
Show file tree
Hide file tree
Showing 11 changed files with 347 additions and 90 deletions.
17 changes: 16 additions & 1 deletion _fixtures/fncall.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ func escapeArg(pa2 *a2struct) {
globalPA2 = pa2
}

func square(x int) int {
return x * x
}

func intcallpanic(a int) int {
if a == 0 {
panic("panic requested")
}
return a
}

func onetwothree(n int) []int {
return []int{n + 1, n + 2, n + 3}
}

func main() {
one, two := 1, 2
intslice := []int{1, 2, 3}
Expand All @@ -98,5 +113,5 @@ func main() {
runtime.Breakpoint()
call1(one, two)
fn2clos(2)
fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2)
fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2, square, intcallpanic, onetwothree)
}
24 changes: 11 additions & 13 deletions pkg/proc/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ var errOperationOnSpecialFloat = errors.New("operations on non-finite floats not

// EvalExpression returns the value of the given expression.
func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable, error) {
if scope.callCtx != nil {
// makes sure that the other goroutine won't wait forever if we make a mistake
defer close(scope.callCtx.continueRequest)
}
t, err := parser.ParseExpr(expr)
if err != nil {
scope.callCtx.doReturn(nil, err)
return nil, err
}

Expand All @@ -33,12 +38,14 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable,
ev, err = scope.evalAST(t)
}
if err != nil {
scope.callCtx.doReturn(nil, err)
return nil, err
}
ev.loadValue(cfg)
if ev.Name == "" {
ev.Name = expr
}
scope.callCtx.doReturn(ev, nil)
return ev, nil
}

Expand Down Expand Up @@ -174,20 +181,11 @@ func (scope *EvalScope) evalAST(t ast.Expr) (*Variable, error) {
case *ast.CallExpr:
if len(node.Args) == 1 {
v, err := scope.evalTypeCast(node)
if err == nil {
return v, nil
}
_, isident := node.Fun.(*ast.Ident)
// we don't support function calls at the moment except for a few
// builtin functions so just return the type error here if the function
// isn't an identifier.
// More sophisticated logic will be required when function calls
// are implemented.
if err != reader.TypeNotFoundErr || !isident {
if err == nil || err != reader.TypeNotFoundErr {
return v, err
}
}
return scope.evalBuiltinCall(node)
return scope.evalFunctionCall(node)

case *ast.Ident:
return scope.evalIdent(node)
Expand Down Expand Up @@ -395,7 +393,7 @@ func convertInt(n uint64, signed bool, size int64) uint64 {
func (scope *EvalScope) evalBuiltinCall(node *ast.CallExpr) (*Variable, error) {
fnnode, ok := node.Fun.(*ast.Ident)
if !ok {
return nil, fmt.Errorf("function calls are not supported")
return nil, nil
}

args := make([]*Variable, len(node.Args))
Expand All @@ -421,7 +419,7 @@ func (scope *EvalScope) evalBuiltinCall(node *ast.CallExpr) (*Variable, error) {
return realBuiltin(args, node.Args)
}

return nil, fmt.Errorf("function calls are not supported")
return nil, nil
}

func capBuiltin(args []*Variable, nodeargs []ast.Expr) (*Variable, error) {
Expand Down
Loading

0 comments on commit c30a333

Please sign in to comment.