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 }