github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/compiler/debug_test.go (about)

     1  package compiler
     2  
     3  import (
     4  	"encoding/json"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/nspcc-dev/neo-go/internal/testserdes"
     9  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    10  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/binding"
    11  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    12  	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestCodeGen_DebugInfo(t *testing.T) {
    18  	src := `package foo
    19  	import "github.com/nspcc-dev/neo-go/pkg/interop"
    20  	import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
    21  	import "github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
    22  var staticVar int
    23  func init() {
    24  	a := 1
    25  	_ = a
    26  }
    27  func init() {
    28  	x := ""
    29  	_ = x
    30  	staticVar = 1
    31  }
    32  func Main(op string) bool {
    33  	var s string
    34  	_ = s
    35  	res := MethodInt(op)
    36  	_ = MethodString()
    37  	_ = MethodByteArray()
    38  	_ = MethodArray()
    39  	_ = MethodStruct()
    40  	_ = MethodConcat("a", "b", "c")
    41  	_ = unexportedMethod()
    42  	return res == 42
    43  }
    44  
    45  func MethodInt(a string) int {
    46  	if a == "get42" {
    47  		return 42
    48  	}
    49  	return 3
    50  }
    51  func MethodConcat(a, b string, c string) string{
    52  	return a + b + c
    53  }
    54  func MethodString() string { return "" }
    55  func MethodByteArray() []byte { return nil }
    56  func MethodArray() []bool { return nil }
    57  func MethodStruct() struct{} { return struct{}{} }
    58  func unexportedMethod() int { return 1 }
    59  func MethodParams(addr interop.Hash160, h interop.Hash256,
    60  	sig interop.Signature, pub interop.PublicKey,
    61  	inter interop.Interface,
    62  	ctx storage.Context, tx ledger.Transaction) bool {
    63  	return true
    64  }
    65  type MyStruct struct {}
    66  func (ms MyStruct) MethodOnStruct() { }
    67  func (ms *MyStruct) MethodOnPointerToStruct() { }
    68  func _deploy(data any, isUpdate bool) { x := 1; _ = x }
    69  `
    70  
    71  	ne, d, err := CompileWithOptions("foo.go", strings.NewReader(src), nil)
    72  	require.NoError(t, err)
    73  	require.NotNil(t, d)
    74  
    75  	t.Run("return types", func(t *testing.T) {
    76  		returnTypes := map[string]string{
    77  			"MethodInt":    "Integer",
    78  			"MethodConcat": "ByteString",
    79  			"MethodString": "ByteString", "MethodByteArray": "ByteString",
    80  			"MethodArray": "Array", "MethodStruct": "Struct",
    81  			"Main":                    "Boolean",
    82  			"unexportedMethod":        "Integer",
    83  			"MethodOnStruct":          "Void",
    84  			"MethodOnPointerToStruct": "Void",
    85  			"MethodParams":            "Boolean",
    86  			"_deploy":                 "Void",
    87  			manifest.MethodInit:       "Void",
    88  		}
    89  		for i := range d.Methods {
    90  			name := d.Methods[i].ID
    91  			assert.Equal(t, returnTypes[name], d.Methods[i].ReturnType)
    92  		}
    93  	})
    94  
    95  	t.Run("variables", func(t *testing.T) {
    96  		vars := map[string][]string{
    97  			"Main":                {"s,ByteString", "res,Integer"},
    98  			manifest.MethodInit:   {"a,Integer", "x,ByteString"},
    99  			manifest.MethodDeploy: {"x,Integer"},
   100  		}
   101  		for i := range d.Methods {
   102  			v, ok := vars[d.Methods[i].ID]
   103  			if ok {
   104  				require.Equal(t, v, d.Methods[i].Variables)
   105  			}
   106  		}
   107  	})
   108  
   109  	t.Run("static variables", func(t *testing.T) {
   110  		require.Equal(t, []string{"staticVar,Integer"}, d.StaticVariables)
   111  	})
   112  
   113  	t.Run("param types", func(t *testing.T) {
   114  		paramTypes := map[string][]DebugParam{
   115  			"_deploy": {
   116  				{
   117  					Name:   "data",
   118  					Type:   "Any",
   119  					TypeSC: smartcontract.AnyType,
   120  				},
   121  				{
   122  					Name:   "isUpdate",
   123  					Type:   "Boolean",
   124  					TypeSC: smartcontract.BoolType,
   125  				},
   126  			},
   127  			"MethodInt": {{
   128  				Name: "a",
   129  				Type: "ByteString",
   130  				RealType: binding.Override{
   131  					TypeName: "string",
   132  				},
   133  				TypeSC: smartcontract.StringType,
   134  			}},
   135  			"MethodConcat": {
   136  				{
   137  					Name: "a",
   138  					Type: "ByteString",
   139  					RealType: binding.Override{
   140  						TypeName: "string",
   141  					},
   142  					TypeSC: smartcontract.StringType,
   143  				},
   144  				{
   145  					Name: "b",
   146  					Type: "ByteString",
   147  					RealType: binding.Override{
   148  						TypeName: "string",
   149  					},
   150  					TypeSC: smartcontract.StringType,
   151  				},
   152  				{
   153  					Name: "c",
   154  					Type: "ByteString",
   155  					RealType: binding.Override{
   156  						TypeName: "string",
   157  					},
   158  					TypeSC: smartcontract.StringType,
   159  				},
   160  			},
   161  			"Main": {{
   162  				Name: "op",
   163  				Type: "ByteString",
   164  				RealType: binding.Override{
   165  					TypeName: "string",
   166  				},
   167  				TypeSC: smartcontract.StringType,
   168  			}},
   169  		}
   170  		for i := range d.Methods {
   171  			v, ok := paramTypes[d.Methods[i].ID]
   172  			if ok {
   173  				require.Equal(t, v, d.Methods[i].Parameters)
   174  			}
   175  		}
   176  	})
   177  
   178  	// basic check that last instruction of every method is indeed RET
   179  	for i := range d.Methods {
   180  		index := d.Methods[i].Range.End
   181  		require.True(t, int(index) < len(ne.Script))
   182  		require.EqualValues(t, opcode.RET, ne.Script[index])
   183  	}
   184  
   185  	t.Run("convert to Manifest", func(t *testing.T) {
   186  		p := manifest.NewPermission(manifest.PermissionWildcard)
   187  		p.Methods.Add("randomMethod")
   188  
   189  		actual, err := d.ConvertToManifest(&Options{
   190  			Name:        "MyCTR",
   191  			SafeMethods: []string{"methodInt", "methodString"},
   192  			Permissions: []manifest.Permission{*p},
   193  		})
   194  		require.NoError(t, err)
   195  		expected := &manifest.Manifest{
   196  			Name: "MyCTR",
   197  			ABI: manifest.ABI{
   198  				Methods: []manifest.Method{
   199  					{
   200  						Name:       manifest.MethodInit,
   201  						Parameters: []manifest.Parameter{},
   202  						ReturnType: smartcontract.VoidType,
   203  					},
   204  					{
   205  						Name: "_deploy",
   206  						Parameters: []manifest.Parameter{
   207  							manifest.NewParameter("data", smartcontract.AnyType),
   208  							manifest.NewParameter("isUpdate", smartcontract.BoolType),
   209  						},
   210  						ReturnType: smartcontract.VoidType,
   211  					},
   212  					{
   213  						Name: "main",
   214  						Parameters: []manifest.Parameter{
   215  							manifest.NewParameter("op", smartcontract.StringType),
   216  						},
   217  						ReturnType: smartcontract.BoolType,
   218  					},
   219  					{
   220  						Name: "methodInt",
   221  						Parameters: []manifest.Parameter{
   222  							{
   223  								Name: "a",
   224  								Type: smartcontract.StringType,
   225  							},
   226  						},
   227  						ReturnType: smartcontract.IntegerType,
   228  						Safe:       true,
   229  					},
   230  					{
   231  						Name:       "methodString",
   232  						Parameters: []manifest.Parameter{},
   233  						ReturnType: smartcontract.StringType,
   234  						Safe:       true,
   235  					},
   236  					{
   237  						Name:       "methodByteArray",
   238  						Parameters: []manifest.Parameter{},
   239  						ReturnType: smartcontract.ByteArrayType,
   240  					},
   241  					{
   242  						Name:       "methodArray",
   243  						Parameters: []manifest.Parameter{},
   244  						ReturnType: smartcontract.ArrayType,
   245  					},
   246  					{
   247  						Name:       "methodStruct",
   248  						Parameters: []manifest.Parameter{},
   249  						ReturnType: smartcontract.ArrayType,
   250  					},
   251  					{
   252  						Name: "methodConcat",
   253  						Parameters: []manifest.Parameter{
   254  							{
   255  								Name: "a",
   256  								Type: smartcontract.StringType,
   257  							},
   258  							{
   259  								Name: "b",
   260  								Type: smartcontract.StringType,
   261  							},
   262  							{
   263  								Name: "c",
   264  								Type: smartcontract.StringType,
   265  							},
   266  						},
   267  						ReturnType: smartcontract.StringType,
   268  					},
   269  					{
   270  						Name: "methodParams",
   271  						Parameters: []manifest.Parameter{
   272  							manifest.NewParameter("addr", smartcontract.Hash160Type),
   273  							manifest.NewParameter("h", smartcontract.Hash256Type),
   274  							manifest.NewParameter("sig", smartcontract.SignatureType),
   275  							manifest.NewParameter("pub", smartcontract.PublicKeyType),
   276  							manifest.NewParameter("inter", smartcontract.InteropInterfaceType),
   277  							manifest.NewParameter("ctx", smartcontract.InteropInterfaceType),
   278  							manifest.NewParameter("tx", smartcontract.ArrayType),
   279  						},
   280  						ReturnType: smartcontract.BoolType,
   281  					},
   282  				},
   283  				Events: []manifest.Event{},
   284  			},
   285  			Groups:      []manifest.Group{},
   286  			Permissions: []manifest.Permission{*p},
   287  			Trusts: manifest.WildPermissionDescs{
   288  				Value: []manifest.PermissionDesc{},
   289  			},
   290  			Extra: json.RawMessage("null"),
   291  		}
   292  		require.Equal(t, len(expected.ABI.Methods), len(actual.ABI.Methods))
   293  		for _, exp := range expected.ABI.Methods {
   294  			md := actual.ABI.GetMethod(exp.Name, len(exp.Parameters))
   295  			require.NotNil(t, md)
   296  			require.Equal(t, exp.Name, md.Name)
   297  			require.Equal(t, exp.Parameters, md.Parameters)
   298  			require.Equal(t, exp.ReturnType, md.ReturnType)
   299  			require.Equal(t, exp.Safe, md.Safe)
   300  		}
   301  		require.Equal(t, expected.ABI.Events, actual.ABI.Events)
   302  		require.Equal(t, expected.Groups, actual.Groups)
   303  		require.Equal(t, expected.Permissions, actual.Permissions)
   304  		require.Equal(t, expected.Trusts, actual.Trusts)
   305  		require.Equal(t, expected.Extra, actual.Extra)
   306  	})
   307  }
   308  
   309  func TestSequencePoints(t *testing.T) {
   310  	src := `package foo
   311  	func Main(op string) bool {
   312  		if op == "123" {
   313  			return true
   314  		}
   315  		return false
   316  	}`
   317  
   318  	_, d, err := CompileWithOptions("foo.go", strings.NewReader(src), nil)
   319  	require.NoError(t, err)
   320  	require.NotNil(t, d)
   321  
   322  	require.Equal(t, 1, len(d.Documents))
   323  	require.True(t, strings.HasSuffix(d.Documents[0], "foo.go"))
   324  
   325  	// Main func has 2 return on 4-th and 6-th lines.
   326  	ps := d.Methods[0].SeqPoints
   327  	require.Equal(t, 2, len(ps))
   328  	require.Equal(t, 4, ps[0].StartLine)
   329  	require.Equal(t, 6, ps[1].StartLine)
   330  }
   331  
   332  func TestDebugInfo_MarshalJSON(t *testing.T) {
   333  	d := &DebugInfo{
   334  		Documents: []string{"/path/to/file"},
   335  		Methods: []MethodDebugInfo{
   336  			{
   337  				ID: "id1",
   338  				Name: DebugMethodName{
   339  					Namespace: "default",
   340  					Name:      "method1",
   341  				},
   342  				Range: DebugRange{Start: 10, End: 20},
   343  				Parameters: []DebugParam{
   344  					{Name: "param1", Type: "Integer"},
   345  					{Name: "ok", Type: "Boolean"},
   346  				},
   347  				ReturnType: "ByteString",
   348  				Variables:  []string{},
   349  				SeqPoints: []DebugSeqPoint{
   350  					{
   351  						Opcode:    123,
   352  						Document:  1,
   353  						StartLine: 2,
   354  						StartCol:  3,
   355  						EndLine:   4,
   356  						EndCol:    5,
   357  					},
   358  				},
   359  			},
   360  		},
   361  		Events: []EventDebugInfo{},
   362  	}
   363  
   364  	testserdes.MarshalUnmarshalJSON(t, d, new(DebugInfo))
   365  }
   366  
   367  func TestManifestOverload(t *testing.T) {
   368  	src := `package foo
   369  	func Main() int {
   370  		return 1
   371  	}
   372  	func Add3() int {
   373  		return Add3Aux(0)
   374  	}
   375  	func Add3Aux(a int) int {
   376  		return a + 3
   377  	}
   378  	func Add3Aux2(b int) int {
   379  		return b + 3
   380  	}
   381  	func Add4() int {
   382  		return 4
   383  	}`
   384  
   385  	_, di, err := CompileWithOptions("foo.go", strings.NewReader(src), nil)
   386  	require.NoError(t, err)
   387  
   388  	m, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"add3Aux": "add3"}})
   389  	require.NoError(t, err)
   390  	require.NoError(t, m.ABI.IsValid())
   391  	require.NotNil(t, m.ABI.GetMethod("add3", 0))
   392  	require.NotNil(t, m.ABI.GetMethod("add3", 1))
   393  	require.Nil(t, m.ABI.GetMethod("add3Aux", 1))
   394  
   395  	t.Run("missing method", func(t *testing.T) {
   396  		_, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"miss": "add3"}})
   397  		require.Error(t, err)
   398  	})
   399  	t.Run("parameter conflict", func(t *testing.T) {
   400  		_, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"add4": "add3"}})
   401  		require.Error(t, err)
   402  	})
   403  	t.Run("parameter conflict, overload", func(t *testing.T) {
   404  		_, err := di.ConvertToManifest(&Options{Overloads: map[string]string{
   405  			"add3Aux":  "add3",
   406  			"add3Aux2": "add3",
   407  		}})
   408  		require.Error(t, err)
   409  	})
   410  	t.Run("missing target method", func(t *testing.T) {
   411  		_, err := di.ConvertToManifest(&Options{Overloads: map[string]string{"add4": "add5"}})
   412  		require.Error(t, err)
   413  	})
   414  }