proc: do not pin function call returns for toplevel call (#3925)

For the toplevel function call do not perform pinning since the
function results are unpinned immediately.
This commit is contained in:
Alessandro Arzilli
2025-03-05 17:58:01 +01:00
committed by GitHub
parent 0b821e4516
commit 2685a42bc0
5 changed files with 165 additions and 103 deletions

View File

@ -27,7 +27,8 @@ type compileCtx struct {
allowCalls bool
curCall int
flags Flags
firstCall bool
pinnerUsed bool
hasCalls bool
}
type evalLookup interface {
@ -45,13 +46,13 @@ const (
// CompileAST compiles the expression t into a list of instructions.
func CompileAST(lookup evalLookup, t ast.Expr, flags Flags) ([]Op, error) {
ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags, firstCall: true}
err := ctx.compileAST(t)
ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags}
err := ctx.compileAST(t, true)
if err != nil {
return nil, err
}
ctx.compileDebugUnpin()
ctx.compileDebugPinnerSetupTeardown()
err = ctx.depthCheck(1)
if err != nil {
@ -96,23 +97,25 @@ func CompileSet(lookup evalLookup, lhexpr, rhexpr string, flags Flags) ([]Op, er
return nil, err
}
ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags, firstCall: true}
err = ctx.compileAST(rhe)
ctx := &compileCtx{evalLookup: lookup, allowCalls: true, flags: flags}
err = ctx.compileAST(rhe, false)
if err != nil {
return nil, err
}
if isStringLiteral(rhe) {
if isStringLiteralThatNeedsAlloc(rhe) {
ctx.compileAllocLiteralString()
}
err = ctx.compileAST(lhe)
err = ctx.compileAST(lhe, false)
if err != nil {
return nil, err
}
ctx.pushOp(&SetValue{lhe: lhe, Rhe: rhe})
ctx.compileDebugPinnerSetupTeardown()
err = ctx.depthCheck(0)
if err != nil {
return ctx.ops, err
@ -132,23 +135,31 @@ func (ctx *compileCtx) compileAllocLiteralString() {
&PushLen{},
&PushNil{},
&PushConst{constant.MakeBool(false)},
}, true)
}, specialCallDoPinning|specialCallIsStringAlloc)
ctx.pushOp(&ConvertAllocToString{})
jmp.Target = len(ctx.ops)
}
func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args []Op, doPinning bool) {
if doPinning {
ctx.compileGetDebugPinner()
}
type specialCallFlags uint8
const (
specialCallDoPinning specialCallFlags = 1 << iota
specialCallIsStringAlloc
specialCallComplainAboutStringAlloc
)
func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args []Op, flags specialCallFlags) {
doPinning := flags&specialCallDoPinning != 0
id := ctx.curCall
ctx.curCall++
ctx.pushOp(&CallInjectionStartSpecial{
id: id,
FnName: fnname,
ArgAst: argAst})
ArgAst: argAst,
ComplainAboutStringAlloc: flags&specialCallComplainAboutStringAlloc != 0})
ctx.pushOp(&CallInjectionSetTarget{id: id})
for i := range args {
@ -160,6 +171,13 @@ func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args
doPinning = doPinning && (ctx.flags&HasDebugPinner != 0)
if doPinning {
ctx.pinnerUsed = true
if flags&specialCallIsStringAlloc == 0 {
ctx.hasCalls = true
}
}
ctx.pushOp(&CallInjectionComplete{id: id, DoPinning: doPinning})
if doPinning {
@ -167,25 +185,40 @@ func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args
}
}
func (ctx *compileCtx) compileGetDebugPinner() {
if ctx.firstCall && ctx.flags&HasDebugPinner != 0 {
ctx.compileSpecialCall(DebugPinnerFunctionName, []ast.Expr{}, []Op{}, false)
ctx.pushOp(&SetDebugPinner{})
ctx.firstCall = false
func (ctx *compileCtx) compileDebugPinnerSetupTeardown() {
if !ctx.pinnerUsed {
return
}
}
func (ctx *compileCtx) compileDebugUnpin() {
if !ctx.firstCall && ctx.flags&HasDebugPinner != 0 {
// Prepend debug pinner allocation
mainops := ctx.ops
ctx.ops = []Op{}
flags := specialCallFlags(0)
if !ctx.hasCalls {
flags = specialCallComplainAboutStringAlloc
}
ctx.compileSpecialCall(DebugPinnerFunctionName, []ast.Expr{}, []Op{}, flags)
ctx.pushOp(&SetDebugPinner{})
// Adjust jump destinations
for _, op := range mainops {
switch op := op.(type) {
case *Jump:
op.Target += len(ctx.ops)
}
}
ctx.ops = append(ctx.ops, mainops...)
// Append debug pinner deallocation
ctx.compileSpecialCall("runtime.(*Pinner).Unpin", []ast.Expr{
&ast.Ident{Name: "debugPinner"},
}, []Op{
&PushDebugPinner{},
}, false)
}, 0)
ctx.pushOp(&Pop{})
ctx.pushOp(&PushNil{})
ctx.pushOp(&SetDebugPinner{})
}
}
func (ctx *compileCtx) pushOp(op Op) {
@ -247,17 +280,17 @@ func (ctx *compileCtx) depthCheck(endDepth int) error {
return nil
}
func (ctx *compileCtx) compileAST(t ast.Expr) error {
func (ctx *compileCtx) compileAST(t ast.Expr, toplevel bool) error {
switch node := t.(type) {
case *ast.CallExpr:
return ctx.compileTypeCastOrFuncCall(node)
return ctx.compileTypeCastOrFuncCall(node, toplevel)
case *ast.Ident:
return ctx.compileIdent(node)
case *ast.ParenExpr:
// otherwise just eval recursively
return ctx.compileAST(node.X)
return ctx.compileAST(node.X, false)
case *ast.SelectorExpr: // <expression>.<identifier>
switch x := node.X.(type) {
@ -367,10 +400,10 @@ func (ctx *compileCtx) compileAST(t ast.Expr) error {
return nil
}
func (ctx *compileCtx) compileTypeCastOrFuncCall(node *ast.CallExpr) error {
func (ctx *compileCtx) compileTypeCastOrFuncCall(node *ast.CallExpr, toplevel bool) error {
if len(node.Args) != 1 {
// Things that have more or less than one argument are always function calls.
return ctx.compileFunctionCall(node)
return ctx.compileFunctionCall(node, toplevel)
}
ambiguous := func() error {
@ -378,9 +411,9 @@ func (ctx *compileCtx) compileTypeCastOrFuncCall(node *ast.CallExpr) error {
// 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)
err0 := ctx2.compileAST(node.Fun, false)
if err0 == nil {
return ctx.compileFunctionCall(node)
return ctx.compileFunctionCall(node, toplevel)
}
return ctx.compileTypeCast(node, err0)
}
@ -408,12 +441,12 @@ func (ctx *compileCtx) compileTypeCastOrFuncCall(node *ast.CallExpr) error {
}
return ambiguous()
}
return ctx.compileFunctionCall(node)
return ctx.compileFunctionCall(node, toplevel)
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))
}
return ctx.compileFunctionCall(node)
return ctx.compileFunctionCall(node, toplevel)
case *ast.IndexExpr:
// Ambiguous, could be a parametric type
switch n.X.(type) {
@ -423,20 +456,20 @@ func (ctx *compileCtx) compileTypeCastOrFuncCall(node *ast.CallExpr) error {
if err == nil || err != reader.ErrTypeNotFound {
return err
}
return ctx.compileFunctionCall(node)
return ctx.compileFunctionCall(node, toplevel)
default:
return ctx.compileFunctionCall(node)
return ctx.compileFunctionCall(node, toplevel)
}
case *ast.IndexListExpr:
return ctx.compileTypeCast(node, nil)
default:
// All other expressions must be function calls
return ctx.compileFunctionCall(node)
return ctx.compileFunctionCall(node, toplevel)
}
}
func (ctx *compileCtx) compileTypeCast(node *ast.CallExpr, ambiguousErr error) error {
err := ctx.compileAST(node.Args[0])
err := ctx.compileAST(node.Args[0], false)
if err != nil {
return err
}
@ -468,7 +501,7 @@ func (ctx *compileCtx) compileTypeCast(node *ast.CallExpr, ambiguousErr error) e
func (ctx *compileCtx) compileBuiltinCall(builtin string, args []ast.Expr) error {
for _, arg := range args {
err := ctx.compileAST(arg)
err := ctx.compileAST(arg, false)
if err != nil {
return err
}
@ -483,7 +516,7 @@ func (ctx *compileCtx) compileIdent(node *ast.Ident) error {
}
func (ctx *compileCtx) compileUnary(expr ast.Expr, op Op) error {
err := ctx.compileAST(expr)
err := ctx.compileAST(expr, false)
if err != nil {
return err
}
@ -492,7 +525,7 @@ func (ctx *compileCtx) compileUnary(expr ast.Expr, op Op) error {
}
func (ctx *compileCtx) compileTypeAssert(node *ast.TypeAssertExpr) error {
err := ctx.compileAST(node.X)
err := ctx.compileAST(node.X, false)
if err != nil {
return err
}
@ -512,14 +545,14 @@ func (ctx *compileCtx) compileTypeAssert(node *ast.TypeAssertExpr) error {
}
func (ctx *compileCtx) compileBinary(a, b ast.Expr, sop *Jump, op Op) error {
err := ctx.compileAST(a)
err := ctx.compileAST(a, false)
if err != nil {
return err
}
if sop != nil {
ctx.pushOp(sop)
}
err = ctx.compileAST(b)
err = ctx.compileAST(b, false)
if err != nil {
return err
}
@ -528,7 +561,7 @@ func (ctx *compileCtx) compileBinary(a, b ast.Expr, sop *Jump, op Op) error {
}
func (ctx *compileCtx) compileReslice(node *ast.SliceExpr) error {
err := ctx.compileAST(node.X)
err := ctx.compileAST(node.X, false)
if err != nil {
return err
}
@ -537,7 +570,7 @@ func (ctx *compileCtx) compileReslice(node *ast.SliceExpr) error {
hasHigh := false
if node.High != nil {
hasHigh = true
err = ctx.compileAST(node.High)
err = ctx.compileAST(node.High, false)
if err != nil {
return err
}
@ -548,7 +581,7 @@ func (ctx *compileCtx) compileReslice(node *ast.SliceExpr) error {
}
if node.Low != nil {
err = ctx.compileAST(node.Low)
err = ctx.compileAST(node.Low, false)
if err != nil {
return err
}
@ -562,7 +595,7 @@ func (ctx *compileCtx) compileReslice(node *ast.SliceExpr) error {
return nil
}
func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr, toplevel bool) error {
if fnnode, ok := node.Fun.(*ast.Ident); ok {
if ctx.HasBuiltin(fnnode.Name) {
return ctx.compileBuiltinCall(fnnode.Name, node.Args)
@ -576,7 +609,7 @@ func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
ctx.curCall++
if ctx.flags&HasDebugPinner != 0 {
return ctx.compileFunctionCallWithPinning(node, id)
return ctx.compileFunctionCallWithPinning(node, id, toplevel)
}
return ctx.compileFunctionCallNoPinning(node, id)
@ -588,7 +621,7 @@ func (ctx *compileCtx) compileFunctionCallNoPinning(node *ast.CallExpr, id int)
oldAllowCalls := ctx.allowCalls
oldOps := ctx.ops
ctx.allowCalls = false
err := ctx.compileAST(node.Fun)
err := ctx.compileAST(node.Fun, false)
ctx.allowCalls = oldAllowCalls
hasFunc := false
if err != nil {
@ -608,7 +641,7 @@ func (ctx *compileCtx) compileFunctionCallNoPinning(node *ast.CallExpr, id int)
ctx.pushOp(jmpif)
}
ctx.pushOp(&Pop{})
err = ctx.compileAST(node.Fun)
err = ctx.compileAST(node.Fun, false)
if err != nil {
return err
}
@ -619,11 +652,11 @@ func (ctx *compileCtx) compileFunctionCallNoPinning(node *ast.CallExpr, id int)
ctx.pushOp(&CallInjectionSetTarget{id: id})
for i, arg := range node.Args {
err := ctx.compileAST(arg)
err := ctx.compileAST(arg, false)
if err != nil {
return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", astutil.ExprToString(arg), i+1, astutil.ExprToString(node.Fun), err)
}
if isStringLiteral(arg) {
if isStringLiteralThatNeedsAlloc(arg) {
ctx.compileAllocLiteralString()
}
ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i, ArgExpr: arg})
@ -636,17 +669,20 @@ func (ctx *compileCtx) compileFunctionCallNoPinning(node *ast.CallExpr, id int)
// compileFunctionCallWithPinning compiles a function call when runtime.debugPinner
// is available in the target.
func (ctx *compileCtx) compileFunctionCallWithPinning(node *ast.CallExpr, id int) error {
ctx.compileGetDebugPinner()
func (ctx *compileCtx) compileFunctionCallWithPinning(node *ast.CallExpr, id int, toplevel bool) error {
if !toplevel {
ctx.pinnerUsed = true
}
ctx.hasCalls = true
err := ctx.compileAST(node.Fun)
err := ctx.compileAST(node.Fun, false)
if err != nil {
return err
}
for i, arg := range node.Args {
err := ctx.compileAST(arg)
if isStringLiteral(arg) {
err := ctx.compileAST(arg, false)
if isStringLiteralThatNeedsAlloc(arg) {
ctx.compileAllocLiteralString()
}
if err != nil {
@ -664,9 +700,11 @@ func (ctx *compileCtx) compileFunctionCallWithPinning(node *ast.CallExpr, id int
ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i, ArgExpr: arg})
}
ctx.pushOp(&CallInjectionComplete{id: id, DoPinning: true})
ctx.pushOp(&CallInjectionComplete{id: id, DoPinning: !toplevel})
if !toplevel {
ctx.compilePinningLoop(id)
}
return nil
}
@ -682,7 +720,7 @@ func (ctx *compileCtx) compilePinningLoop(id int) {
}, []Op{
&PushDebugPinner{},
nil,
}, false)
}, 0)
ctx.pushOp(&Pop{})
ctx.pushOp(&Jump{When: JumpAlways, Target: loopStart})
jmp.Target = len(ctx.ops)
@ -700,16 +738,16 @@ func Listing(depth []int, ops []Op) string {
return buf.String()
}
func isStringLiteral(expr ast.Expr) bool {
func isStringLiteralThatNeedsAlloc(expr ast.Expr) bool {
switch expr := expr.(type) {
case *ast.BasicLit:
return expr.Kind == token.STRING
return expr.Kind == token.STRING && expr.Value != `""`
case *ast.BinaryExpr:
if expr.Op == token.ADD {
return isStringLiteral(expr.X) && isStringLiteral(expr.Y)
return isStringLiteralThatNeedsAlloc(expr.X) && isStringLiteralThatNeedsAlloc(expr.Y)
}
case *ast.ParenExpr:
return isStringLiteral(expr.X)
return isStringLiteralThatNeedsAlloc(expr.X)
}
return false
}

View File

@ -285,6 +285,8 @@ type CallInjectionStartSpecial struct {
id int
FnName string
ArgAst []ast.Expr
ComplainAboutStringAlloc bool // if this call injection can not be made complain specifically about string allocation
}
func (*CallInjectionStartSpecial) depthCheck() (npop, npush int) { return 0, 1 }

View File

@ -1062,11 +1062,19 @@ func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64
}
func (scope *EvalScope) callInjectionStartSpecial(stack *evalStack, op *evalop.CallInjectionStartSpecial, curthread Thread) bool {
if op.ComplainAboutStringAlloc && scope.callCtx == nil {
stack.err = errFuncCallNotAllowedStrAlloc
return false
}
fnv, err := scope.findGlobalInternal(op.FnName)
if fnv == nil {
if err == nil {
if op.ComplainAboutStringAlloc {
err = errFuncCallNotAllowedStrAlloc
} else {
err = fmt.Errorf("function %s not found", op.FnName)
}
}
stack.err = err
return false
}

View File

@ -1284,8 +1284,8 @@ func TestCallFunction(t *testing.T) {
{"call1(one+two, 4)", []string{":int:7"}, nil, 0},
{"callpanic()", []string{`~panic:interface {}:interface {}(string) "callpanic panicked"`}, nil, 0},
{`stringsJoin(nil, "")`, []string{`:string:""`}, nil, 0},
{`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil, 1},
{`stringsJoin(stringslice, "~~")`, []string{`:string:"one~~two~~three"`}, nil, 2},
{`stringsJoin(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil, 0},
{`stringsJoin(stringslice, "~~")`, []string{`:string:"one~~two~~three"`}, nil, 1},
{`stringsJoin(s1, comma)`, nil, errors.New(`could not find symbol value for s1`), 1},
{`stringsJoin(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string"), 1},
{`noreturncall(2)`, nil, nil, 0},
@ -1299,15 +1299,15 @@ func TestCallFunction(t *testing.T) {
// Call types tests (methods, function pointers, etc.)
// The following set of calls was constructed using https://docs.google.com/document/d/1bMwCey-gmqZVTpRax-ESeVuZGmjwbocYs1iHplK-cjo/pub as a reference
{`a.VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil, 1}, // direct call of a method with value receiver / on a value
{`a.VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil, 0}, // direct call of a method with value receiver / on a value
{`a.PRcvr(2)`, []string{`:string:"2 - 3 = -1"`}, nil, 1}, // direct call of a method with pointer receiver / on a value
{`pa.VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil, 1}, // direct call of a method with value receiver / on a pointer
{`pa.PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil, 1}, // direct call of a method with pointer receiver / on a pointer
{`a.PRcvr(2)`, []string{`:string:"2 - 3 = -1"`}, nil, 0}, // direct call of a method with pointer receiver / on a value
{`pa.VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil, 0}, // direct call of a method with value receiver / on a pointer
{`pa.PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil, 0}, // direct call of a method with pointer receiver / on a pointer
{`vable_pa.VRcvr(6)`, []string{`:string:"6 + 6 = 12"`}, nil, 1}, // indirect call of method on interface / containing value with value method
{`pable_pa.PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil, 1}, // indirect call of method on interface / containing pointer with value method
{`vable_a.VRcvr(5)`, []string{`:string:"5 + 3 = 8"`}, nil, 1}, // indirect call of method on interface / containing pointer with pointer method
{`vable_pa.VRcvr(6)`, []string{`:string:"6 + 6 = 12"`}, nil, 0}, // indirect call of method on interface / containing value with value method
{`pable_pa.PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil, 0}, // indirect call of method on interface / containing pointer with value method
{`vable_a.VRcvr(5)`, []string{`:string:"5 + 3 = 8"`}, nil, 0}, // indirect call of method on interface / containing pointer with pointer method
{`pa.nonexistent()`, nil, errors.New("pa has no member nonexistent"), 0},
{`a.nonexistent()`, nil, errors.New("a has no member nonexistent"), 0},
@ -1316,14 +1316,14 @@ func TestCallFunction(t *testing.T) {
{`pable_pa.nonexistent()`, nil, errors.New("pable_pa has no member nonexistent"), 0},
{`fn2glob(10, 20)`, []string{":int:30"}, nil, 0}, // indirect call of func value / set to top-level func
{`fn2clos(11)`, []string{`:string:"1 + 6 + 11 = 18"`}, nil, 1}, // indirect call of func value / set to func literal
{`fn2clos(12)`, []string{`:string:"2 + 6 + 12 = 20"`}, nil, 1},
{`fn2valmeth(13)`, []string{`:string:"13 + 6 = 19"`}, nil, 1}, // indirect call of func value / set to value method
{`fn2ptrmeth(14)`, []string{`:string:"14 - 6 = 8"`}, nil, 1}, // indirect call of func value / set to pointer method
{`fn2clos(11)`, []string{`:string:"1 + 6 + 11 = 18"`}, nil, 0}, // indirect call of func value / set to func literal
{`fn2clos(12)`, []string{`:string:"2 + 6 + 12 = 20"`}, nil, 0},
{`fn2valmeth(13)`, []string{`:string:"13 + 6 = 19"`}, nil, 0}, // indirect call of func value / set to value method
{`fn2ptrmeth(14)`, []string{`:string:"14 - 6 = 8"`}, nil, 0}, // indirect call of func value / set to pointer method
{"fn2nil()", nil, errors.New("nil pointer dereference"), 0},
{"ga.PRcvr(2)", []string{`:string:"2 - 0 = 2"`}, nil, 1},
{"ga.PRcvr(2)", []string{`:string:"2 - 0 = 2"`}, nil, 0},
{"x.CallMe()", nil, nil, 0},
{"x2.CallMe(5)", []string{":int:25"}, nil, 0},
@ -1332,10 +1332,10 @@ func TestCallFunction(t *testing.T) {
// Nested function calls tests
{`onetwothree(intcallpanic(2))`, []string{`:[]int:[]int len: 3, cap: 3, [3,4,5]`}, nil, 1},
{`onetwothree(intcallpanic(2))`, []string{`:[]int:[]int len: 3, cap: 3, [3,4,5]`}, nil, 0},
{`onetwothree(intcallpanic(0))`, []string{`~panic:interface {}:interface {}(string) "panic requested"`}, nil, 0},
{`onetwothree(intcallpanic(2)+1)`, []string{`:[]int:[]int len: 3, cap: 3, [4,5,6]`}, nil, 1},
{`onetwothree(intcallpanic("not a number"))`, nil, errors.New("can not convert \"not a number\" constant to int"), 1},
{`onetwothree(intcallpanic(2)+1)`, []string{`:[]int:[]int len: 3, cap: 3, [4,5,6]`}, nil, 0},
{`onetwothree(intcallpanic("not a number"))`, nil, altErrors("can not convert \"not a number\" constant to int", "literal string can not be allocated because function calls are not allowed without using 'call'"), 1},
// Variable setting tests
{`pa2 = getAStructPtr(8); pa2`, []string{`pa2:*main.astruct:*main.astruct {X: 8}`}, nil, 1},
@ -1355,14 +1355,14 @@ func TestCallFunction(t *testing.T) {
var testcases112 = []testCaseCallFunction{
// string allocation requires trusted argument order, which we don't have in Go 1.11
{`stringsJoin(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 2},
{`stringsJoin(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 1},
{`str = "a new string"; str`, []string{`str:string:"a new string"`}, nil, 1},
// support calling optimized functions
{`strings.Join(nil, "")`, []string{`:string:""`}, nil, 0},
{`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil, 1},
{`strings.Join(stringslice, comma)`, []string{`:string:"one,two,three"`}, nil, 0},
{`strings.Join(intslice, comma)`, nil, errors.New("can not convert value of type []int to []string"), 1},
{`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 2},
{`strings.Join(stringslice, ",")`, []string{`:string:"one,two,three"`}, nil, 1},
{`strings.LastIndexByte(stringslice[1], 'w')`, []string{":int:1"}, nil, 0},
{`strings.LastIndexByte(stringslice[1], 'o')`, []string{":int:2"}, nil, 0},
{`d.Base.Method()`, []string{`:int:4`}, nil, 0},
@ -1374,15 +1374,15 @@ func TestCallFunction(t *testing.T) {
// Method calls on a value returned by a function
{`getAStruct(3).VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil, 1}, // direct call of a method with value receiver / on a value
{`getAStruct(3).VRcvr(1)`, []string{`:string:"1 + 3 = 4"`}, nil, 0}, // direct call of a method with value receiver / on a value
{`getAStruct(3).PRcvr(2)`, nil, errors.New("could not set call receiver: cannot use getAStruct(3).PRcvr as argument pa in function main.(*astruct).PRcvr: stack object passed to escaping pointer: pa"), 0}, // direct call of a method with pointer receiver / on a value
{`getAStructPtr(6).VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil, 2}, // direct call of a method with value receiver / on a pointer
{`getAStructPtr(6).PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil, 2}, // direct call of a method with pointer receiver / on a pointer
{`getAStructPtr(6).VRcvr(3)`, []string{`:string:"3 + 6 = 9"`}, nil, 1}, // direct call of a method with value receiver / on a pointer
{`getAStructPtr(6).PRcvr(4)`, []string{`:string:"4 - 6 = -2"`}, nil, 1}, // direct call of a method with pointer receiver / on a pointer
{`getVRcvrableFromAStruct(3).VRcvr(6)`, []string{`:string:"6 + 3 = 9"`}, nil, 3}, // indirect call of method on interface / containing value with value method
{`getPRcvrableFromAStructPtr(6).PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil, 3}, // indirect call of method on interface / containing pointer with value method
{`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil, 3}, // indirect call of method on interface / containing pointer with pointer method
{`getVRcvrableFromAStruct(3).VRcvr(6)`, []string{`:string:"6 + 3 = 9"`}, nil, 2}, // indirect call of method on interface / containing value with value method
{`getPRcvrableFromAStructPtr(6).PRcvr(7)`, []string{`:string:"7 - 6 = 1"`}, nil, 2}, // indirect call of method on interface / containing pointer with value method
{`getVRcvrableFromAStructPtr(6).VRcvr(5)`, []string{`:string:"5 + 6 = 11"`}, nil, 2}, // indirect call of method on interface / containing pointer with pointer method
}
var testcasesBefore114After112 = []testCaseCallFunction{
@ -1394,11 +1394,11 @@ func TestCallFunction(t *testing.T) {
}
var testcases117 = []testCaseCallFunction{
{`regabistacktest("one", "two", "three", "four", "five", 4)`, []string{`:string:"onetwo"`, `:string:"twothree"`, `:string:"threefour"`, `:string:"fourfive"`, `:string:"fiveone"`, ":uint8:8"}, nil, 10},
{`regabistacktest("one", "two", "three", "four", "five", 4)`, []string{`:string:"onetwo"`, `:string:"twothree"`, `:string:"threefour"`, `:string:"fourfive"`, `:string:"fiveone"`, ":uint8:8"}, nil, 5},
{`regabistacktest2(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)`, []string{":int:3", ":int:5", ":int:7", ":int:9", ":int:11", ":int:13", ":int:15", ":int:17", ":int:19", ":int:11"}, nil, 0},
{`issue2698.String()`, []string{`:string:"1 2 3 4"`}, nil, 1},
{`issue3364.String()`, []string{`:string:"1 2"`}, nil, 1},
{`regabistacktest3(rast3, 5)`, []string{`:[10]string:[10]string ["onetwo","twothree","threefour","fourfive","fivesix","sixseven","sevenheight","heightnine","nineten","tenone"]`, ":uint8:15"}, nil, 10},
{`issue2698.String()`, []string{`:string:"1 2 3 4"`}, nil, 0},
{`issue3364.String()`, []string{`:string:"1 2"`}, nil, 0},
{`regabistacktest3(rast3, 5)`, []string{`:[10]string:[10]string ["onetwo","twothree","threefour","fourfive","fivesix","sixseven","sevenheight","heightnine","nineten","tenone"]`, ":uint8:15"}, nil, 0},
{`floatsum(1, 2)`, []string{":float64:3"}, nil, 0},
}
@ -1486,9 +1486,23 @@ func testCallFunctionIntl(t *testing.T, grp *proc.TargetGroup, p *proc.Target, t
if err == nil {
t.Fatalf("call %q: expected error %q, got no error", tc.expr, tc.err.Error())
}
switch e := tc.err.(type) {
case *altError:
ok := false
for _, tgtErr := range e.errs {
if tgtErr == err.Error() {
ok = true
break
}
}
if !ok {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
default:
if tc.err.Error() != err.Error() {
t.Fatalf("call %q: expected error %q, got %q", tc.expr, tc.err.Error(), err.Error())
}
}
return
}

View File

@ -6157,7 +6157,7 @@ func TestSetVariable(t *testing.T) {
tester.expectSetVariable(barRef, "Baz", "42")
tester.evaluate("bar", `main.FooBar {Baz: 42, Bur: "lorem"}`, hasChildren)
tester.failSetVariable(barRef, "Baz", `"string"`, "can not convert")
tester.failSetVariable(barRef, "Baz", `"string"`, "can not")
// int
checkVarExact(t, locals, -1, "a2", "a2", "6", "int", noChildren)