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).
This commit is contained in:
Evgenii Stratonikov 2020-12-09 13:14:21 +03:00
parent 3d0ed6eac3
commit cbf26f315c
5 changed files with 140 additions and 67 deletions

30
examples/events/events.go Normal file
View file

@ -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)
}

View file

@ -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

View file

@ -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.

View file

@ -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
}

View file

@ -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{},