From cbf26f315cfb83d49f548b750e8b9a73897b8efb Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 9 Dec 2020 13:14:21 +0300 Subject: [PATCH] compiler: save both VM and smartcontract types VM types are used in debugger, while smartcontract ones are used in manifest. We can't save only one of them, because conversion in either side is lossy: 1. VM has `Array` and `Struct` but smartcontract only has `Array`. 2. Smartcontract has `Hash160` etc, which are all `ByteString` or `Buffer` in VM. And to spice things a bit more, return type in debugger can be `Void`, which corresponds to no real stackitem type (as it must exist). --- examples/events/events.go | 30 ++++++++++ examples/events/events.yml | 23 ++++++++ pkg/compiler/codegen.go | 3 +- pkg/compiler/debug.go | 111 +++++++++++++++++++++---------------- pkg/compiler/debug_test.go | 40 +++++++------ 5 files changed, 140 insertions(+), 67 deletions(-) create mode 100644 examples/events/events.go create mode 100644 examples/events/events.yml diff --git a/examples/events/events.go b/examples/events/events.go new file mode 100644 index 000000000..28254ee4a --- /dev/null +++ b/examples/events/events.go @@ -0,0 +1,30 @@ +package events + +import ( + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" +) + +// NotifySomeBytes emits notification with ByteArray. +func NotifySomeBytes(arg []byte) { + runtime.Notify("SomeBytes", arg) +} + +// NotifySomeInteger emits notification with Integer. +func NotifySomeInteger(arg int) { + runtime.Notify("SomeInteger", arg) +} + +// NotifySomeString emits notification with String. +func NotifySomeString(arg string) { + runtime.Notify("SomeString", arg) +} + +// NotifySomeMap emits notification with Map. +func NotifySomeMap(arg map[string]int) { + runtime.Notify("SomeMap", arg) +} + +// NotifySomeArray emits notification with Array. +func NotifySomeArray(arg []int) { + runtime.Notify("SomeArray", arg) +} diff --git a/examples/events/events.yml b/examples/events/events.yml new file mode 100644 index 000000000..51d25cdde --- /dev/null +++ b/examples/events/events.yml @@ -0,0 +1,23 @@ +name: "Event types example" +supportedstandards: [] +events: + - name: SomeBytes + parameters: + - name: bytes + type: ByteArray + - name: SomeInteger + parameters: + - name: int + type: Integer + - name: SomeString + parameters: + - name: str + type: String + - name: SomeMap + parameters: + - name: m + type: Map + - name: SomeArray + parameters: + - name: a + type: Array \ No newline at end of file diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 06548e535..a8f2a925e 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -877,7 +877,8 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { tv := c.typeAndValueOf(n.Args[0]) params := make([]string, 0, len(n.Args[1:])) for _, p := range n.Args[1:] { - params = append(params, c.scTypeFromExpr(p)) + st, _ := c.scAndVMTypeFromExpr(p) + params = append(params, st.String()) } // Sometimes event name is stored in a var. // Skip in this case. diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 42cdf88c3..1676a0a3e 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -13,6 +13,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // DebugInfo represents smart-contract debug information. @@ -42,8 +43,10 @@ type MethodDebugInfo struct { // Parameters is a list of method's parameters. Parameters []DebugParam `json:"params"` // ReturnType is method's return type. - ReturnType string `json:"return"` - Variables []string `json:"variables"` + ReturnType string `json:"return"` + // ReturnTypeSC is return type to use in manifest. + ReturnTypeSC smartcontract.ParamType `json:"-"` + Variables []string `json:"variables"` // SeqPoints is a map between source lines and byte-code instruction offsets. SeqPoints []DebugSeqPoint `json:"sequence-points"` } @@ -86,8 +89,9 @@ type DebugRange struct { // DebugParam represents variables's name and type. type DebugParam struct { - Name string `json:"name"` - Type string `json:"type"` + Name string `json:"name"` + Type string `json:"type"` + TypeSC smartcontract.ParamType `json:"-"` } func (c *codegen) saveSequencePoint(n ast.Node) { @@ -128,8 +132,9 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo { Start: 0, End: uint16(c.initEndOffset), }, - ReturnType: "Void", - SeqPoints: c.sequencePoints["init"], + ReturnType: "Void", + ReturnTypeSC: smartcontract.VoidType, + SeqPoints: c.sequencePoints["init"], }) } if c.deployEndOffset >= 0 { @@ -146,11 +151,13 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo { End: uint16(c.deployEndOffset), }, Parameters: []DebugParam{{ - Name: "isUpdate", - Type: "Boolean", + Name: "isUpdate", + Type: "Boolean", + TypeSC: smartcontract.BoolType, }}, - ReturnType: "Void", - SeqPoints: c.sequencePoints[manifest.MethodDeploy], + ReturnType: "Void", + ReturnTypeSC: smartcontract.VoidType, + SeqPoints: c.sequencePoints[manifest.MethodDeploy], }) } for name, scope := range c.funcs { @@ -169,8 +176,8 @@ func (c *codegen) registerDebugVariable(name string, expr ast.Expr) { // do not save globals for now return } - typ := c.scTypeFromExpr(expr) - c.scope.variables = append(c.scope.variables, name+","+typ) + _, vt := c.scAndVMTypeFromExpr(expr) + c.scope.variables = append(c.scope.variables, name+","+vt.String()) } func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebugInfo { @@ -178,48 +185,53 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu params := make([]DebugParam, 0, ps.NumFields()) for i := range ps.List { for j := range ps.List[i].Names { + st, vt := c.scAndVMTypeFromExpr(ps.List[i].Type) params = append(params, DebugParam{ - Name: ps.List[i].Names[j].Name, - Type: c.scTypeFromExpr(ps.List[i].Type), + Name: ps.List[i].Names[j].Name, + Type: vt.String(), + TypeSC: st, }) } } ss := strings.Split(name, ".") name = ss[len(ss)-1] r, n := utf8.DecodeRuneInString(name) + st, vt := c.scAndVMReturnTypeFromScope(scope) return &MethodDebugInfo{ ID: name, Name: DebugMethodName{ Name: string(unicode.ToLower(r)) + name[n:], Namespace: scope.pkg.Name(), }, - IsExported: scope.decl.Name.IsExported(), - IsFunction: scope.decl.Recv == nil, - Range: scope.rng, - Parameters: params, - ReturnType: c.scReturnTypeFromScope(scope), - SeqPoints: c.sequencePoints[name], - Variables: scope.variables, + IsExported: scope.decl.Name.IsExported(), + IsFunction: scope.decl.Recv == nil, + Range: scope.rng, + Parameters: params, + ReturnType: vt, + ReturnTypeSC: st, + SeqPoints: c.sequencePoints[name], + Variables: scope.variables, } } -func (c *codegen) scReturnTypeFromScope(scope *funcScope) string { +func (c *codegen) scAndVMReturnTypeFromScope(scope *funcScope) (smartcontract.ParamType, string) { results := scope.decl.Type.Results switch results.NumFields() { case 0: - return "Void" + return smartcontract.VoidType, "Void" case 1: - return c.scTypeFromExpr(results.List[0].Type) + st, vt := c.scAndVMTypeFromExpr(results.List[0].Type) + return st, vt.String() default: // multiple return values are not supported in debugger - return "Any" + return smartcontract.AnyType, "Any" } } -func (c *codegen) scTypeFromExpr(typ ast.Expr) string { +func (c *codegen) scAndVMTypeFromExpr(typ ast.Expr) (smartcontract.ParamType, stackitem.Type) { t := c.typeOf(typ) if c.typeOf(typ) == nil { - return "Any" + return smartcontract.AnyType, stackitem.AnyT } if named, ok := t.(*types.Named); ok { if isInteropPath(named.String()) { @@ -227,13 +239,22 @@ func (c *codegen) scTypeFromExpr(typ ast.Expr) string { pkg := named.Obj().Pkg().Name() switch pkg { case "blockchain", "contract": - return "Array" // Block, Transaction, Contract + return smartcontract.ArrayType, stackitem.ArrayT // Block, Transaction, Contract case "interop": if name != "Interface" { - return name + switch name { + case "Hash160": + return smartcontract.Hash160Type, stackitem.ByteArrayT + case "Hash256": + return smartcontract.Hash256Type, stackitem.ByteArrayT + case "PublicKey": + return smartcontract.PublicKeyType, stackitem.ByteArrayT + case "Signature": + return smartcontract.SignatureType, stackitem.ByteArrayT + } } } - return "InteropInterface" + return smartcontract.InteropInterfaceType, stackitem.InteropT } } switch t := t.Underlying().(type) { @@ -241,25 +262,25 @@ func (c *codegen) scTypeFromExpr(typ ast.Expr) string { info := t.Info() switch { case info&types.IsInteger != 0: - return "Integer" + return smartcontract.IntegerType, stackitem.IntegerT case info&types.IsBoolean != 0: - return "Boolean" + return smartcontract.BoolType, stackitem.BooleanT case info&types.IsString != 0: - return "String" + return smartcontract.StringType, stackitem.ByteArrayT default: - return "Any" + return smartcontract.AnyType, stackitem.AnyT } case *types.Map: - return "Map" + return smartcontract.MapType, stackitem.MapT case *types.Struct: - return "Struct" + return smartcontract.ArrayType, stackitem.StructT case *types.Slice: if isByte(t.Elem()) { - return "ByteString" + return smartcontract.ByteArrayType, stackitem.ByteArrayT } - return "Array" + return smartcontract.ArrayType, stackitem.ArrayT default: - return "Any" + return smartcontract.AnyType, stackitem.AnyT } } @@ -310,13 +331,9 @@ func (d *DebugParam) UnmarshalJSON(data []byte) error { // ToManifestParameter converts DebugParam to manifest.Parameter func (d *DebugParam) ToManifestParameter() (manifest.Parameter, error) { - pType, err := smartcontract.ParseParamType(d.Type) - if err != nil { - return manifest.Parameter{}, err - } return manifest.Parameter{ Name: d.Name, - Type: pType, + Type: d.TypeSC, }, nil } @@ -333,14 +350,10 @@ func (m *MethodDebugInfo) ToManifestMethod() (manifest.Method, error) { return result, err } } - returnType, err := smartcontract.ParseParamType(m.ReturnType) - if err != nil { - return result, err - } result.Name = m.Name.Name result.Offset = int(m.Range.Start) result.Parameters = parameters - result.ReturnType = returnType + result.ReturnType = m.ReturnTypeSC return result, nil } diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index 858af186c..f7c5a6211 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -71,8 +71,8 @@ func _deploy(isUpdate bool) {} t.Run("return types", func(t *testing.T) { returnTypes := map[string]string{ "MethodInt": "Integer", - "MethodConcat": "String", - "MethodString": "String", "MethodByteArray": "ByteString", + "MethodConcat": "ByteString", + "MethodString": "ByteString", "MethodByteArray": "ByteString", "MethodArray": "Array", "MethodStruct": "Struct", "Main": "Boolean", "unexportedMethod": "Integer", @@ -89,7 +89,7 @@ func _deploy(isUpdate bool) {} t.Run("variables", func(t *testing.T) { vars := map[string][]string{ - "Main": {"s,String", "res,Integer"}, + "Main": {"s,ByteString", "res,Integer"}, } for i := range d.Methods { v, ok := vars[d.Methods[i].ID] @@ -102,30 +102,36 @@ func _deploy(isUpdate bool) {} t.Run("param types", func(t *testing.T) { paramTypes := map[string][]DebugParam{ "_deploy": {{ - Name: "isUpdate", - Type: "Boolean", + Name: "isUpdate", + Type: "Boolean", + TypeSC: smartcontract.BoolType, }}, "MethodInt": {{ - Name: "a", - Type: "String", + Name: "a", + Type: "ByteString", + TypeSC: smartcontract.StringType, }}, "MethodConcat": { { - Name: "a", - Type: "String", + Name: "a", + Type: "ByteString", + TypeSC: smartcontract.StringType, }, { - Name: "b", - Type: "String", + Name: "b", + Type: "ByteString", + TypeSC: smartcontract.StringType, }, { - Name: "c", - Type: "String", + Name: "c", + Type: "ByteString", + TypeSC: smartcontract.StringType, }, }, "Main": {{ - Name: "op", - Type: "String", + Name: "op", + Type: "ByteString", + TypeSC: smartcontract.StringType, }}, } for i := range d.Methods { @@ -307,8 +313,8 @@ func TestDebugInfo_MarshalJSON(t *testing.T) { }, Range: DebugRange{Start: 10, End: 20}, Parameters: []DebugParam{ - {"param1", "Integer"}, - {"ok", "Boolean"}, + {Name: "param1", Type: "Integer"}, + {Name: "ok", Type: "Boolean"}, }, ReturnType: "ByteString", Variables: []string{},