diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 85b0b16f4..062dc185f 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -49,6 +49,9 @@ type codegen struct { labels map[labelWithType]uint16 // A list of nested label names together with evaluation stack depth. labelList []labelWithStackSize + // inlineLabelOffsets contains size of labelList at the start of inline call processing. + // For such calls we need to drop only newly created part of stack. + inlineLabelOffsets []int // A label for the for-loop being currently visited. currentFor string @@ -607,7 +610,11 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.setLabel(l) cnt := 0 - for i := range c.labelList { + start := 0 + if len(c.inlineLabelOffsets) > 0 { + start = c.inlineLabelOffsets[len(c.inlineLabelOffsets)-1] + } + for i := start; i < len(c.labelList); i++ { cnt += c.labelList[i].sz } c.dropItems(cnt) diff --git a/pkg/compiler/inline.go b/pkg/compiler/inline.go index 4998d250e..bc326fb2d 100644 --- a/pkg/compiler/inline.go +++ b/pkg/compiler/inline.go @@ -16,6 +16,14 @@ import ( // // } func (c *codegen) inlineCall(f *funcScope, n *ast.CallExpr) { + labelSz := len(c.labelList) + offSz := len(c.inlineLabelOffsets) + c.inlineLabelOffsets = append(c.inlineLabelOffsets, labelSz) + defer func() { + c.inlineLabelOffsets = c.inlineLabelOffsets[:offSz] + c.labelList = c.labelList[:labelSz] + }() + pkg := c.buildInfo.program.Package(f.pkg.Path()) sig := c.typeOf(n.Fun).(*types.Signature) diff --git a/pkg/compiler/inline_test.go b/pkg/compiler/inline_test.go index 9db10359c..61b7f1acc 100644 --- a/pkg/compiler/inline_test.go +++ b/pkg/compiler/inline_test.go @@ -115,6 +115,56 @@ func TestInline(t *testing.T) { }) } +func TestInlineInLoop(t *testing.T) { + t.Run("simple", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/binary" + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline" + func Main() int { + sum := 0 + values := []int{10, 11} + for _, v := range values { + binary.Itoa(v, 10) + sum += inline.VarSum(1, 2, 3, 4) + } + return sum + }` + eval(t, src, big.NewInt(20)) + }) + t.Run("check clean stack on return", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/binary" + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline" + func Main() int { + values := []int{10, 11, 12} + for _, v := range values { + binary.Itoa(v, 10) + if v == 11 { + return inline.VarSum(2, 20, 200) + } + } + return 0 + }` + eval(t, src, big.NewInt(222)) + }) +} + +func TestInlineInSwitch(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/compiler/testdata/inline" + func Main() int { + switch inline.VarSum(1, 2) { + case inline.VarSum(3, 1): + return 10 + case inline.VarSum(4, -1): + return 11 + default: + return 12 + } + }` + eval(t, src, big.NewInt(11)) +} + func TestInlineGlobalVariable(t *testing.T) { t.Run("simple", func(t *testing.T) { src := `package foo diff --git a/pkg/compiler/vm_test.go b/pkg/compiler/vm_test.go index bb16a2238..0e92a3133 100644 --- a/pkg/compiler/vm_test.go +++ b/pkg/compiler/vm_test.go @@ -107,6 +107,7 @@ func newStoragePlugin() *storagePlugin { s.interops[interopnames.ToID([]byte(interopnames.SystemStoragePut))] = s.Put s.interops[interopnames.ToID([]byte(interopnames.SystemStorageGetContext))] = s.GetContext s.interops[interopnames.ToID([]byte(interopnames.SystemRuntimeNotify))] = s.Notify + s.interops[interopnames.ToID([]byte(interopnames.SystemBinaryItoa))] = s.Itoa return s } @@ -122,6 +123,13 @@ func (s *storagePlugin) syscallHandler(v *vm.VM, id uint32) error { return errors.New("syscall not found") } +func (s *storagePlugin) Itoa(v *vm.VM) error { + n := v.Estack().Pop().BigInt() + base := v.Estack().Pop().BigInt() + v.Estack().PushVal(n.Text(int(base.Int64()))) + return nil +} + func (s *storagePlugin) Notify(v *vm.VM) error { name := v.Estack().Pop().String() item := stackitem.NewArray(v.Estack().Pop().Array())