提交 60830c2b 编写于 作者: A Alessandro Arzilli 提交者: Derek Parker

More Function Calls, parts 2 (#1504)

* proc: support nested function calls

Changes the code in fncall.go to support nested function calls.

This changes delays argument evaluation until after we have used
the call injection protocol to allocate an argument frame. When
evaluating the parse tree of an expression we'll initiate each
function call we find on the way down and then complete the function
call on the way up.

For example. in:

	f(g(x))

we will:

1. initiate the call injection protocol for f(...)
2. progress it until the point where we have space for the arguments
   of 'f' (i.e. when we receive the debugCallAXCompleteCall message
   from the target runtime)
3. inititate the call injection protocol for g(...)
4. progress it until the point where we have space for the arguments
   of 'g'
5. copy the value of x into the argument frame of 'g'
6. finish the call to g(...)
7. copy the return value of g(x) into the argument frame of 'f'
8. finish the call to f(...)

Updates #119

* proc: bugfix: closure addr was wrong for non-closure functions
上级 3215dc2d
......@@ -91,6 +91,32 @@ func onetwothree(n int) []int {
return []int{n + 1, n + 2, n + 3}
}
func curriedAdd(n int) func(int) int {
return func(m int) int {
return n + m
}
}
func getAStruct(n int) astruct {
return astruct{X: n}
}
func getAStructPtr(n int) *astruct {
return &astruct{X: n}
}
func getVRcvrableFromAStruct(n int) VRcvrable {
return astruct{X: n}
}
func getPRcvrableFromAStructPtr(n int) PRcvrable {
return &astruct{X: n}
}
func getVRcvrableFromAStructPtr(n int) VRcvrable {
return &astruct{X: n}
}
func main() {
one, two := 1, 2
intslice := []int{1, 2, 3}
......@@ -113,5 +139,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, square, intcallpanic, onetwothree)
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, curriedAdd, getAStruct, getAStructPtr, getVRcvrableFromAStruct, getPRcvrableFromAStructPtr, getVRcvrableFromAStructPtr)
}
......@@ -39,6 +39,7 @@ const (
debugCallFunctionNamePrefix1 = "debugCall"
debugCallFunctionNamePrefix2 = "runtime.debugCall"
debugCallFunctionName = "runtime.debugCallV1"
maxArgFrameSize = 65535
)
var (
......@@ -61,19 +62,26 @@ type functionCallState struct {
savedRegs Registers
// err contains a saved error
err error
// expr is the expression being evaluated
expr *ast.CallExpr
// fn is the function that is being called
fn *Function
// receiver is the receiver argument for the function
receiver *Variable
// closureAddr is the address of the closure being called
closureAddr uint64
// argmem contains the argument frame of this function call
argmem []byte
// formalArgs are the formal arguments of fn
formalArgs []funcCallArg
// argFrameSize contains the size of the arguments
argFrameSize int64
// retvars contains the return variables after the function call terminates without panic'ing
retvars []*Variable
// retLoadCfg is the load configuration used to load return values
retLoadCfg *LoadConfig
// panicvar is a variable used to store the value of the panic, if the
// called function panics.
panicvar *Variable
// lateCallFailure is set to true if the function call could not be
// completed after we started evaluating the arguments.
lateCallFailure bool
}
type callContext struct {
......@@ -220,14 +228,6 @@ func (scope *EvalScope) evalFunctionCall(node *ast.CallExpr) (*Variable, error)
if !p.Common().fncallEnabled {
return nil, errFuncCallUnsupportedBackend
}
if p.Common().callInProgress {
return nil, errFuncCallInProgress
}
p.Common().callInProgress = true
defer func() {
p.Common().callInProgress = false
}()
dbgcallfn := bi.LookupFunc[debugCallFunctionName]
if dbgcallfn == nil {
......@@ -257,12 +257,12 @@ func (scope *EvalScope) evalFunctionCall(node *ast.CallExpr) (*Variable, error)
return nil, errFuncCallUnsupportedBackend
}
fn, closureAddr, argvars, err := scope.funcCallEvalExpr(node)
if err != nil {
return nil, err
fncall := functionCallState{
expr: node,
savedRegs: regs,
}
argmem, err := funcCallArgFrame(fn, argvars, g, bi, scope.callCtx.checkEscape)
err = funcCallEvalFuncExpr(scope, &fncall, false)
if err != nil {
return nil, err
}
......@@ -271,19 +271,11 @@ func (scope *EvalScope) evalFunctionCall(node *ast.CallExpr) (*Variable, error)
return nil, err
}
// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
if err := writePointer(bi, g.Thread, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(len(argmem))); err != nil {
if err := writePointer(bi, g.Thread, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
return nil, err
}
fncall := functionCallState{
savedRegs: regs,
fn: fn,
closureAddr: closureAddr,
argmem: argmem,
retLoadCfg: &scope.callCtx.retLoadCfg,
}
fncallLog("function call initiated %v frame size %d\n", fn, len(argmem))
fncallLog("function call initiated %v frame size %d\n", fncall.fn, fncall.argFrameSize)
spoff := int64(scope.Regs.Uint64Val(scope.Regs.SPRegNum)) - int64(g.stackhi)
bpoff := int64(scope.Regs.Uint64Val(scope.Regs.BPRegNum)) - int64(g.stackhi)
......@@ -391,48 +383,69 @@ func callOP(bi *BinaryInfo, thread Thread, regs Registers, callAddr uint64) erro
return thread.SetPC(callAddr)
}
// funcCallEvalExpr evaluates expr, which must be a function call, returns
// the function being called and its arguments.
func (scope *EvalScope) funcCallEvalExpr(callexpr *ast.CallExpr) (fn *Function, closureAddr uint64, argvars []*Variable, err error) {
// funcCallEvalFuncExpr evaluates expr.Fun and returns the function that we're trying to call.
// If allowCalls is false function calls will be disabled even if scope.callCtx != nil
func funcCallEvalFuncExpr(scope *EvalScope, fncall *functionCallState, allowCalls bool) error {
bi := scope.BinInfo
fnvar, err := scope.evalAST(callexpr.Fun)
if err != nil {
return nil, 0, nil, err
if !allowCalls {
callCtx := scope.callCtx
scope.callCtx = nil
defer func() {
scope.callCtx = callCtx
}()
}
fnvar, err := scope.evalAST(fncall.expr.Fun)
if err == errFuncCallNotAllowed {
// we can't determine the frame size because callexpr.Fun can't be
// evaluated without enabling function calls, just set up an argument
// frame for the maximum possible argument size.
fncall.argFrameSize = maxArgFrameSize
return nil
} else if err != nil {
return err
}
if fnvar.Kind != reflect.Func {
return nil, 0, nil, fmt.Errorf("expression %q is not a function", exprToString(callexpr.Fun))
return fmt.Errorf("expression %q is not a function", exprToString(fncall.expr.Fun))
}
fnvar.loadValue(LoadConfig{false, 0, 0, 0, 0, 0})
if fnvar.Unreadable != nil {
return nil, 0, nil, fnvar.Unreadable
return fnvar.Unreadable
}
if fnvar.Base == 0 {
return nil, 0, nil, errors.New("nil pointer dereference")
return errors.New("nil pointer dereference")
}
fn = bi.PCToFunc(uint64(fnvar.Base))
if fn == nil {
return nil, 0, nil, fmt.Errorf("could not find DIE for function %q", exprToString(callexpr.Fun))
fncall.fn = bi.PCToFunc(uint64(fnvar.Base))
if fncall.fn == nil {
return fmt.Errorf("could not find DIE for function %q", exprToString(fncall.expr.Fun))
}
if !fn.cu.isgo {
return nil, 0, nil, errNotAGoFunction
if !fncall.fn.cu.isgo {
return errNotAGoFunction
}
fncall.closureAddr = fnvar.closureAddr
fncall.argFrameSize, fncall.formalArgs, err = funcCallArgs(fncall.fn, bi, false)
if err != nil {
return err
}
argnum := len(fncall.expr.Args)
argvars = make([]*Variable, 0, len(callexpr.Args)+1)
if len(fnvar.Children) > 0 {
// receiver argument
argvars = append(argvars, &fnvar.Children[0])
argnum++
fncall.receiver = &fnvar.Children[0]
fncall.receiver.Name = exprToString(fncall.expr.Fun)
}
for i := range callexpr.Args {
argvar, err := scope.evalAST(callexpr.Args[i])
if err != nil {
return nil, 0, nil, err
}
argvar.Name = exprToString(callexpr.Args[i])
argvars = append(argvars, argvar)
if argnum > len(fncall.formalArgs) {
return errTooManyArguments
}
if argnum < len(fncall.formalArgs) {
return errNotEnoughArguments
}
return fn, fnvar.funcvalAddr(), argvars, nil
return nil
}
type funcCallArg struct {
......@@ -442,44 +455,58 @@ type funcCallArg struct {
isret bool
}
// funcCallArgFrame checks type and pointer escaping for the arguments and
// returns the argument frame.
func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo, checkEscape bool) (argmem []byte, err error) {
argFrameSize, formalArgs, err := funcCallArgs(fn, bi, false)
if err != nil {
return nil, err
}
if len(actualArgs) > len(formalArgs) {
return nil, errTooManyArguments
// funcCallEvalArgs evaluates the arguments of the function call, copying
// the into the argument frame starting at argFrameAddr.
func funcCallEvalArgs(scope *EvalScope, fncall *functionCallState, argFrameAddr uint64) error {
g := scope.callCtx.p.SelectedGoroutine()
if g == nil {
// this should never happen
return errNoGoroutine
}
if len(actualArgs) < len(formalArgs) {
return nil, errNotEnoughArguments
if fncall.receiver != nil {
err := funcCallCopyOneArg(g, scope, fncall, fncall.receiver, &fncall.formalArgs[0], argFrameAddr)
if err != nil {
return err
}
fncall.formalArgs = fncall.formalArgs[1:]
}
// constructs arguments frame
argmem = make([]byte, argFrameSize)
argmemWriter := &bufferMemoryReadWriter{argmem}
for i := range formalArgs {
formalArg := &formalArgs[i]
actualArg := actualArgs[i]
for i := range fncall.formalArgs {
formalArg := &fncall.formalArgs[i]
if checkEscape {
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
return nil, fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fn.Name, err)
}
actualArg, err := scope.evalAST(fncall.expr.Args[i])
if err != nil {
return fmt.Errorf("error evaluating %q as argument %s in function %s: %v", exprToString(fncall.expr.Args[i]), formalArg.name, fncall.fn.Name, err)
}
actualArg.Name = exprToString(fncall.expr.Args[i])
err = funcCallCopyOneArg(g, scope, fncall, actualArg, formalArg, argFrameAddr)
if err != nil {
return err
}
}
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
// by convertToEface.
return nil
}
formalArgVar := newVariable(formalArg.name, uintptr(formalArg.off+fakeAddress), formalArg.typ, bi, argmemWriter)
if err := formalArgVar.setValue(actualArg, actualArg.Name); err != nil {
return nil, err
func funcCallCopyOneArg(g *G, scope *EvalScope, fncall *functionCallState, actualArg *Variable, formalArg *funcCallArg, argFrameAddr uint64) error {
if scope.callCtx.checkEscape {
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
if err := escapeCheck(actualArg, formalArg.name, g); err != nil {
return fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fncall.fn.Name, err)
}
}
return argmem, nil
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
// by convertToEface.
formalArgVar := newVariable(formalArg.name, uintptr(formalArg.off+int64(argFrameAddr)), formalArg.typ, scope.BinInfo, scope.Mem)
if err := formalArgVar.setValue(actualArg, actualArg.Name); err != nil {
return err
}
return nil
}
func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize int64, formalArgs []funcCallArg, err error) {
......@@ -585,8 +612,8 @@ const (
)
// funcCallStep executes one step of the function call injection protocol.
func funcCallStep(scope *EvalScope, fncall *functionCallState) bool {
p := scope.callCtx.p
func funcCallStep(callScope *EvalScope, fncall *functionCallState) bool {
p := callScope.callCtx.p
bi := p.BinInfo()
thread := p.CurrentThread()
......@@ -624,14 +651,29 @@ func funcCallStep(scope *EvalScope, fncall *functionCallState) bool {
fncall.err = fmt.Errorf("%v", constant.StringVal(errvar.Value))
case debugCallAXCompleteCall:
// write arguments to the stack, call final function
n, err := thread.WriteMemory(uintptr(regs.SP()), fncall.argmem)
if err != nil {
fncall.err = fmt.Errorf("could not write arguments: %v", err)
}
if n != len(fncall.argmem) {
fncall.err = fmt.Errorf("short argument write: %d %d", n, len(fncall.argmem))
// evaluate arguments of the target function, copy them into its argument frame and call the function
if fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0 {
// if we couldn't figure out which function we are calling before
// (because the function we are calling is the return value of a call to
// another function) now we have to figure it out by recursively
// evaluating the function calls.
// This also needs to be done if the function call has a receiver
// argument or a closure address (because those addresses could be on the stack
// and have changed position between the start of the call and now).
err := funcCallEvalFuncExpr(callScope, fncall, true)
if err != nil {
fncall.err = err
fncall.lateCallFailure = true
break
}
//TODO: double check that function call size isn't too big
}
// instead of evaluating the arguments we start first by pushing the call
// on the stack, this is the opposite of what would happen normally but
// it's necessary because otherwise the GC wouldn't be able to deal with
// the argument frame.
if fncall.closureAddr != 0 {
// When calling a function pointer we must set the DX register to the
// address of the function pointer itself.
......@@ -639,6 +681,16 @@ func funcCallStep(scope *EvalScope, fncall *functionCallState) bool {
}
callOP(bi, thread, regs, fncall.fn.Entry)
err := funcCallEvalArgs(callScope, fncall, regs.SP())
if err != nil {
// rolling back the call, note: this works because we called regs.Copy() above
thread.SetSP(regs.SP())
thread.SetPC(regs.PC())
fncall.err = err
fncall.lateCallFailure = true
break
}
case debugCallAXRestoreRegisters:
// runtime requests that we restore the registers (all except pc and sp),
// this is also the last step of the function call protocol.
......@@ -659,19 +711,19 @@ func funcCallStep(scope *EvalScope, fncall *functionCallState) bool {
case debugCallAXReadReturn:
// read return arguments from stack
if fncall.retLoadCfg == nil || fncall.panicvar != nil {
if fncall.panicvar != nil || fncall.lateCallFailure {
break
}
scope, err := ThreadScope(thread)
retScope, err := ThreadScope(thread)
if err != nil {
fncall.err = fmt.Errorf("could not get return values: %v", err)
break
}
// pretend we are still inside the function we called
fakeFunctionEntryScope(scope, fncall.fn, int64(regs.SP()), regs.SP()-uint64(bi.Arch.PtrSize()))
fakeFunctionEntryScope(retScope, fncall.fn, int64(regs.SP()), regs.SP()-uint64(bi.Arch.PtrSize()))
fncall.retvars, err = scope.Locals()
fncall.retvars, err = retScope.Locals()
if err != nil {
fncall.err = fmt.Errorf("could not get return values: %v", err)
break
......@@ -680,20 +732,17 @@ func funcCallStep(scope *EvalScope, fncall *functionCallState) bool {
return (v.Flags & VariableReturnArgument) != 0
})
loadValues(fncall.retvars, *fncall.retLoadCfg)
loadValues(fncall.retvars, callScope.callCtx.retLoadCfg)
case debugCallAXReadPanic:
// read panic value from stack
if fncall.retLoadCfg == nil {
return false
}
fncall.panicvar, err = readTopstackVariable(thread, regs, "interface {}", *fncall.retLoadCfg)
fncall.panicvar, err = readTopstackVariable(thread, regs, "interface {}", callScope.callCtx.retLoadCfg)
if err != nil {
fncall.err = fmt.Errorf("could not get panic: %v", err)
break
}
fncall.panicvar.Name = "~panic"
fncall.panicvar.loadValue(*fncall.retLoadCfg)
fncall.panicvar.loadValue(callScope.callCtx.retLoadCfg)
if fncall.panicvar.Unreadable != nil {
fncall.err = fmt.Errorf("could not get panic: %v", fncall.panicvar.Unreadable)
break
......
......@@ -122,12 +122,6 @@ type CommonProcess struct {
// pkg/proc/fncall.go for a description of how this works.
continueCompleted chan<- struct{}
continueRequest <-chan continueRequest
// callInProgress is true when a function call is being injected in the
// target process.
// This is only used to prevent nested function calls, it should be removed
// when we add support for them.
callInProgress bool
}
// NewCommonProcess returns a struct with fields common across
......
......@@ -143,18 +143,3 @@ func DereferenceMemory(mem MemoryReadWriter) MemoryReadWriter {
}
return mem
}
// bufferMemoryReadWriter is dummy a MemoryReadWriter backed by a []byte.
type bufferMemoryReadWriter struct {
buf []byte
}
func (mem *bufferMemoryReadWriter) ReadMemory(buf []byte, addr uintptr) (n int, err error) {
copy(buf, mem.buf[addr-fakeAddress:][:len(buf)])
return len(buf), nil
}
func (mem *bufferMemoryReadWriter) WriteMemory(addr uintptr, data []byte) (written int, err error) {
copy(mem.buf[addr-fakeAddress:], data)
return len(data), nil
}
......@@ -103,6 +103,9 @@ type Variable struct {
stride int64
fieldType godwarf.Type
// closureAddr is the closure address for function variables (0 for non-closures)
closureAddr uint64
// number of elements to skip when loading a map
mapSkip int
......@@ -1698,18 +1701,18 @@ func (v *Variable) writeCopy(srcv *Variable) error {
func (v *Variable) readFunctionPtr() {
// dereference pointer to find function pc
fnaddr := v.funcvalAddr()
v.closureAddr = v.funcvalAddr()
if v.Unreadable != nil {
return
}
if fnaddr == 0 {
if v.closureAddr == 0 {
v.Base = 0
v.Value = constant.MakeString("")
return
}
val := make([]byte, v.bi.Arch.PtrSize())
_, err := v.mem.ReadMemory(val, uintptr(fnaddr))
_, err := v.mem.ReadMemory(val, uintptr(v.closureAddr))
if err != nil {
v.Unreadable = err
return
......
......@@ -1085,14 +1085,16 @@ func TestIssue1075(t *testing.T) {
})
}
type testCaseCallFunction struct {
expr string // call expression to evaluate
outs []string // list of return parameters in this format: <param name>:<param type>:<param value>
err error // if not nil should return an error
}
func TestCallFunction(t *testing.T) {
protest.MustSupportFunctionCalls(t, testBackend)
var testcases = []struct {
expr string // call expression to evaluate
outs []string // list of return parameters in this format: <param name>:<param type>:<param value>
err error // if not nil should return an error
}{
var testcases = []testCaseCallFunction{
// Basic function call injection tests
{"call1(one, two)", []string{":int:3"}, nil},
......@@ -1139,14 +1141,33 @@ func TestCallFunction(t *testing.T) {
{"ga.PRcvr(2)", []string{`:string:"2 - 0 = 2"`}, nil},
// Nested function calls
{`onetwothree(intcallpanic(2))`, []string{`:[]int:[]int len: 3, cap: 3, [3,4,5]`}, nil},
{`onetwothree(intcallpanic(0))`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil},
{`onetwothree(intcallpanic(2)+1)`, []string{`:[]int:[]int len: 3, cap: 3, [4,5,6]`}, nil},
{`onetwothree(intcallpanic("not a number"))`, nil, errors.New("can not convert \"not a number\" constant to int")},
// Escape tests
{"escapeArg(&a2)", nil, errors.New("cannot use &a2 as argument pa2 in function main.escapeArg: stack object passed to escaping pointer: pa2")},
{"-unsafe escapeArg(&a2)", nil, nil}, // LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
}
const unsafePrefix = "-unsafe "
var testcases113 = []testCaseCallFunction{
{`curriedAdd(2)(3)`, []string{`:int:5`}, nil},
// Method calls on a value returned by a function
{`getAStruct(3).VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil}, // direct call of a method with value receiver / on a value
{`getAStruct(3).PRcvr(2)`, nil, errors.New("cannot use getAStruct(3).PRcvr as argument pa in function main.(*astruct).PRcvr: stack object passed to escaping pointer: pa")}, // direct call of a method with pointer receiver / on a value
{`getAStructPtr(6).VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil}, // direct call of a method with value receiver / on a pointer
{`getAStructPtr(6).PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil}, // direct call of a method with pointer receiver / on a pointer
{`getVRcvrableFromAStruct(3).VRcvr(6)`, []string{`:string:"6 + 3 = 9"`}, nil}, // indirect call of method on interface / containing value with value method
{`getPRcvrableFromAStructPtr(6).PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil}, // indirect call of method on interface / containing pointer with value method
{`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil}, // indirect call of method on interface / containing pointer with pointer method
}
withTestProcess("fncall", t, func(p proc.Process, fixture protest.Fixture) {
_, err := proc.FindFunctionLocation(p, "runtime.debugCallV1", true, 0)
......@@ -1155,61 +1176,76 @@ func TestCallFunction(t *testing.T) {
}
assertNoError(proc.Continue(p), t, "Continue()")
for _, tc := range testcases {
expr := tc.expr
checkEscape := true
if strings.HasPrefix(expr, unsafePrefix) {
expr = expr[len(unsafePrefix):]
checkEscape = false
}
t.Logf("call %q", tc.expr)
err := proc.EvalExpressionWithCalls(p, expr, pnormalLoadConfig, checkEscape)
if tc.err != nil {
t.Logf("\terr = %v\n", err)
if err == nil {
t.Fatalf("call %q: expected error %q, got no error", tc.expr, tc.err.Error())
}
if tc.err.Error() != err.Error() {
t.Fatalf("call %q: expected error %q, got %q", tc.expr, tc.err.Error(), err.Error())
}
continue
}
testCallFunction(t, p, tc)
}
if err != nil {
t.Fatalf("call %q: error %q", tc.expr, err.Error())
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
for _, tc := range testcases113 {
testCallFunction(t, p, tc)
}
}
retvalsVar := p.CurrentThread().Common().ReturnValues(pnormalLoadConfig)
retvals := make([]*api.Variable, len(retvalsVar))
// LEAVE THIS AS THE LAST ITEM, IT BREAKS THE TARGET PROCESS!!!
testCallFunction(t, p, testCaseCallFunction{"-unsafe escapeArg(&a2)", nil, nil})
})
}
for i := range retvals {
retvals[i] = api.ConvertVar(retvalsVar[i])
}
func testCallFunction(t *testing.T, p proc.Process, tc testCaseCallFunction) {
const unsafePrefix = "-unsafe "
for i := range retvals {
t.Logf("\t%s = %s", retvals[i].Name, retvals[i].SinglelineString())
}
expr := tc.expr
checkEscape := true
if strings.HasPrefix(expr, unsafePrefix) {
expr = expr[len(unsafePrefix):]
checkEscape = false
}
t.Logf("call %q", tc.expr)
err := proc.EvalExpressionWithCalls(p, expr, pnormalLoadConfig, checkEscape)
if tc.err != nil {
t.Logf("\terr = %v\n", err)
if err == nil {
t.Fatalf("call %q: expected error %q, got no error", tc.expr, tc.err.Error())
}
if tc.err.Error() != err.Error() {
t.Fatalf("call %q: expected error %q, got %q", tc.expr, tc.err.Error(), err.Error())
}
return
}
if len(retvals) != len(tc.outs) {
t.Fatalf("call %q: wrong number of return parameters", tc.expr)
}
if err != nil {
t.Fatalf("call %q: error %q", tc.expr, err.Error())
}
for i := range retvals {
outfields := strings.SplitN(tc.outs[i], ":", 3)
tgtName, tgtType, tgtValue := outfields[0], outfields[1], outfields[2]
retvalsVar := p.CurrentThread().Common().ReturnValues(pnormalLoadConfig)
retvals := make([]*api.Variable, len(retvalsVar))
if tgtName != "" && tgtName != retvals[i].Name {
t.Fatalf("call %q output parameter %d: expected name %q, got %q", tc.expr, i, tgtName, retvals[i].Name)
}
for i := range retvals {
retvals[i] = api.ConvertVar(retvalsVar[i])
}
if retvals[i].Type != tgtType {
t.Fatalf("call %q, output parameter %d: expected type %q, got %q", tc.expr, i, tgtType, retvals[i].Type)
}
if cvs := retvals[i].SinglelineString(); cvs != tgtValue {
t.Fatalf("call %q, output parameter %d: expected value %q, got %q", tc.expr, i, tgtValue, cvs)
}
}
for i := range retvals {
t.Logf("\t%s = %s", retvals[i].Name, retvals[i].SinglelineString())
}
if len(retvals) != len(tc.outs) {
t.Fatalf("call %q: wrong number of return parameters", tc.expr)
}
for i := range retvals {
outfields := strings.SplitN(tc.outs[i], ":", 3)
tgtName, tgtType, tgtValue := outfields[0], outfields[1], outfields[2]
if tgtName != "" && tgtName != retvals[i].Name {
t.Fatalf("call %q output parameter %d: expected name %q, got %q", tc.expr, i, tgtName, retvals[i].Name)
}
})
if retvals[i].Type != tgtType {
t.Fatalf("call %q, output parameter %d: expected type %q, got %q", tc.expr, i, tgtType, retvals[i].Type)
}
if cvs := retvals[i].SinglelineString(); cvs != tgtValue {
t.Fatalf("call %q, output parameter %d: expected value %q, got %q", tc.expr, i, tgtValue, cvs)
}
}
}
func TestIssue1531(t *testing.T) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册