github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/compiler/debug.go (about) 1 package compiler 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "go/ast" 8 "go/types" 9 "sort" 10 "strconv" 11 "strings" 12 "unicode" 13 "unicode/utf8" 14 15 "github.com/nspcc-dev/neo-go/pkg/crypto/hash" 16 "github.com/nspcc-dev/neo-go/pkg/smartcontract" 17 "github.com/nspcc-dev/neo-go/pkg/smartcontract/binding" 18 "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" 19 "github.com/nspcc-dev/neo-go/pkg/util" 20 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 21 ) 22 23 // DebugInfo represents smart-contract debug information. 24 type DebugInfo struct { 25 MainPkg string `json:"-"` 26 Hash util.Uint160 `json:"hash"` 27 Documents []string `json:"documents"` 28 Methods []MethodDebugInfo `json:"methods"` 29 // NamedTypes are exported structured types that have some name (even 30 // if the original structure doesn't) and a number of internal fields. 31 NamedTypes map[string]binding.ExtendedType `json:"-"` 32 // Events are the events that contract is allowed to emit and that have to 33 // be presented in the resulting contract manifest and debug info file. 34 Events []EventDebugInfo `json:"events"` 35 // EmittedEvents contains events occurring in code, i.e. events emitted 36 // via runtime.Notify(...) call in the contract code if they have constant 37 // names and doesn't have ellipsis arguments. EmittedEvents are not related 38 // to the debug info and are aimed to serve bindings generation. 39 EmittedEvents map[string][]EmittedEventInfo `json:"-"` 40 // InvokedContracts contains foreign contract invocations. 41 InvokedContracts map[util.Uint160][]string `json:"-"` 42 // StaticVariables contains a list of static variable names and types. 43 StaticVariables []string `json:"static-variables"` 44 } 45 46 // MethodDebugInfo represents smart-contract's method debug information. 47 type MethodDebugInfo struct { 48 // ID is the actual name of the method. 49 ID string `json:"id"` 50 // Name is the name of the method with the first letter in a lowercase 51 // together with the namespace it belongs to. We need to keep the first letter 52 // lowercased to match manifest standards. 53 Name DebugMethodName `json:"name"` 54 // IsExported defines whether the method is exported. 55 IsExported bool `json:"-"` 56 // IsFunction defines whether the method has no receiver. 57 IsFunction bool `json:"-"` 58 // Range is the range of smart-contract's opcodes corresponding to the method. 59 Range DebugRange `json:"range"` 60 // Parameters is a list of the method's parameters. 61 Parameters []DebugParam `json:"params"` 62 // ReturnType is the method's return type. 63 ReturnType string `json:"return"` 64 // ReturnTypeReal is the method's return type as specified in Go code. 65 ReturnTypeReal binding.Override `json:"-"` 66 // ReturnTypeExtended is the method's return type with additional data. 67 ReturnTypeExtended *binding.ExtendedType `json:"-"` 68 // ReturnTypeSC is a return type to use in manifest. 69 ReturnTypeSC smartcontract.ParamType `json:"-"` 70 Variables []string `json:"variables"` 71 // SeqPoints is a map between source lines and byte-code instruction offsets. 72 SeqPoints []DebugSeqPoint `json:"sequence-points"` 73 } 74 75 // DebugMethodName is a combination of a namespace and name. 76 type DebugMethodName struct { 77 Namespace string 78 Name string 79 } 80 81 // EventDebugInfo represents smart-contract's event debug information. 82 type EventDebugInfo struct { 83 ID string `json:"id"` 84 // Name is a human-readable event name in a format "{namespace},{name}". 85 Name string `json:"name"` 86 Parameters []DebugParam `json:"params"` 87 } 88 89 // DebugSeqPoint represents break-point for debugger. 90 type DebugSeqPoint struct { 91 // Opcode is an opcode's address. 92 Opcode int 93 // Document is an index of file where sequence point occurs. 94 Document int 95 // StartLine is the first line of the break-pointed statement. 96 StartLine int 97 // StartCol is the first column of the break-pointed statement. 98 StartCol int 99 // EndLine is the last line of the break-pointed statement. 100 EndLine int 101 // EndCol is the last column of the break-pointed statement. 102 EndCol int 103 } 104 105 // DebugRange represents the method's section in bytecode. 106 type DebugRange struct { 107 Start uint16 108 End uint16 109 } 110 111 // DebugParam represents the variable's name and type. 112 type DebugParam struct { 113 Name string `json:"name"` 114 Type string `json:"type"` 115 RealType binding.Override `json:"-"` 116 ExtendedType *binding.ExtendedType `json:"-"` 117 TypeSC smartcontract.ParamType `json:"-"` 118 } 119 120 // EmittedEventInfo describes information about single emitted event got from 121 // the contract code. It has the map of extended types used as the parameters to 122 // runtime.Notify(...) call (if any) and the parameters info itself. 123 type EmittedEventInfo struct { 124 ExtTypes map[string]binding.ExtendedType 125 Params []DebugParam 126 } 127 128 func (c *codegen) saveSequencePoint(n ast.Node) { 129 name := "init" 130 if c.scope != nil { 131 name = c.scope.name 132 } 133 134 fset := c.buildInfo.config.Fset 135 start := fset.Position(n.Pos()) 136 end := fset.Position(n.End()) 137 c.sequencePoints[name] = append(c.sequencePoints[name], DebugSeqPoint{ 138 Opcode: c.prog.Len(), 139 Document: c.docIndex[start.Filename], 140 StartLine: start.Line, 141 StartCol: start.Column, 142 EndLine: end.Line, 143 EndCol: end.Column, 144 }) 145 } 146 147 func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo { 148 d := &DebugInfo{ 149 Hash: hash.Hash160(contract), 150 MainPkg: c.mainPkg.Name, 151 Events: []EventDebugInfo{}, 152 Documents: c.documents, 153 StaticVariables: c.staticVariables, 154 } 155 if c.initEndOffset > 0 { 156 d.Methods = append(d.Methods, MethodDebugInfo{ 157 ID: manifest.MethodInit, 158 Name: DebugMethodName{ 159 Name: manifest.MethodInit, 160 Namespace: c.mainPkg.Name, 161 }, 162 IsExported: true, 163 IsFunction: true, 164 Range: DebugRange{ 165 Start: 0, 166 End: uint16(c.initEndOffset), 167 }, 168 ReturnType: "Void", 169 ReturnTypeSC: smartcontract.VoidType, 170 SeqPoints: c.sequencePoints["init"], 171 Variables: c.initVariables, 172 }) 173 } 174 if c.deployEndOffset >= 0 { 175 d.Methods = append(d.Methods, MethodDebugInfo{ 176 ID: manifest.MethodDeploy, 177 Name: DebugMethodName{ 178 Name: manifest.MethodDeploy, 179 Namespace: c.mainPkg.Name, 180 }, 181 IsExported: true, 182 IsFunction: true, 183 Range: DebugRange{ 184 Start: uint16(c.initEndOffset + 1), 185 End: uint16(c.deployEndOffset), 186 }, 187 Parameters: []DebugParam{ 188 { 189 Name: "data", 190 Type: "Any", 191 TypeSC: smartcontract.AnyType, 192 }, 193 { 194 Name: "isUpdate", 195 Type: "Boolean", 196 TypeSC: smartcontract.BoolType, 197 }, 198 }, 199 ReturnType: "Void", 200 ReturnTypeSC: smartcontract.VoidType, 201 SeqPoints: c.sequencePoints[manifest.MethodDeploy], 202 Variables: c.deployVariables, 203 }) 204 } 205 206 var fnames = make([]string, 0, len(c.funcs)) 207 for name, scope := range c.funcs { 208 if scope.rng.Start == scope.rng.End { 209 continue 210 } 211 fnames = append(fnames, name) 212 } 213 sort.Strings(fnames) 214 d.NamedTypes = make(map[string]binding.ExtendedType) 215 for _, name := range fnames { 216 m := c.methodInfoFromScope(name, c.funcs[name], d.NamedTypes) 217 d.Methods = append(d.Methods, *m) 218 } 219 d.EmittedEvents = c.emittedEvents 220 d.InvokedContracts = c.invokedContracts 221 return d 222 } 223 224 func (c *codegen) registerDebugVariable(name string, expr ast.Expr) { 225 _, vt, _, _ := c.scAndVMTypeFromExpr(expr, nil) 226 if c.scope == nil { 227 c.staticVariables = append(c.staticVariables, name+","+vt.String()) 228 return 229 } 230 c.scope.variables = append(c.scope.variables, name+","+vt.String()) 231 } 232 233 func (c *codegen) methodInfoFromScope(name string, scope *funcScope, exts map[string]binding.ExtendedType) *MethodDebugInfo { 234 ps := scope.decl.Type.Params 235 params := make([]DebugParam, 0, ps.NumFields()) 236 for i := range ps.List { 237 for j := range ps.List[i].Names { 238 st, vt, rt, et := c.scAndVMTypeFromExpr(ps.List[i].Type, exts) 239 params = append(params, DebugParam{ 240 Name: ps.List[i].Names[j].Name, 241 Type: vt.String(), 242 ExtendedType: et, 243 RealType: rt, 244 TypeSC: st, 245 }) 246 } 247 } 248 ss := strings.Split(name, ".") 249 name = ss[len(ss)-1] 250 r, n := utf8.DecodeRuneInString(name) 251 st, vt, rt, et := c.scAndVMReturnTypeFromScope(scope, exts) 252 253 return &MethodDebugInfo{ 254 ID: name, 255 Name: DebugMethodName{ 256 Name: string(unicode.ToLower(r)) + name[n:], 257 Namespace: scope.pkg.Name(), 258 }, 259 IsExported: scope.decl.Name.IsExported(), 260 IsFunction: scope.decl.Recv == nil, 261 Range: scope.rng, 262 Parameters: params, 263 ReturnType: vt, 264 ReturnTypeExtended: et, 265 ReturnTypeReal: rt, 266 ReturnTypeSC: st, 267 SeqPoints: c.sequencePoints[name], 268 Variables: scope.variables, 269 } 270 } 271 272 func (c *codegen) scAndVMReturnTypeFromScope(scope *funcScope, exts map[string]binding.ExtendedType) (smartcontract.ParamType, string, binding.Override, *binding.ExtendedType) { 273 results := scope.decl.Type.Results 274 switch results.NumFields() { 275 case 0: 276 return smartcontract.VoidType, "Void", binding.Override{}, nil 277 case 1: 278 st, vt, s, et := c.scAndVMTypeFromExpr(results.List[0].Type, exts) 279 return st, vt.String(), s, et 280 default: 281 // multiple return values are not supported in debugger 282 return smartcontract.AnyType, "Any", binding.Override{}, nil 283 } 284 } 285 286 func scAndVMInteropTypeFromExpr(named *types.Named, isPointer bool) (smartcontract.ParamType, stackitem.Type, binding.Override, *binding.ExtendedType) { 287 name := named.Obj().Name() 288 pkg := named.Obj().Pkg().Name() 289 switch pkg { 290 case "ledger", "management": 291 switch name { 292 case "ParameterType", "SignerScope", "WitnessAction", "WitnessConditionType", "VMState": 293 return smartcontract.IntegerType, stackitem.IntegerT, binding.Override{TypeName: "int"}, nil 294 } 295 // Block, Transaction, Contract. 296 typeName := pkg + "." + name 297 et := &binding.ExtendedType{Base: smartcontract.ArrayType, Name: typeName} 298 if isPointer { 299 typeName = "*" + typeName 300 } 301 return smartcontract.ArrayType, stackitem.ArrayT, binding.Override{ 302 Package: named.Obj().Pkg().Path(), 303 TypeName: typeName, 304 }, et 305 case "interop": 306 if name != "Interface" { 307 over := binding.Override{ 308 Package: interopPrefix, 309 TypeName: "interop." + name, 310 } 311 switch name { 312 case "Hash160": 313 return smartcontract.Hash160Type, stackitem.ByteArrayT, over, nil 314 case "Hash256": 315 return smartcontract.Hash256Type, stackitem.ByteArrayT, over, nil 316 case "PublicKey": 317 return smartcontract.PublicKeyType, stackitem.ByteArrayT, over, nil 318 case "Signature": 319 return smartcontract.SignatureType, stackitem.ByteArrayT, over, nil 320 } 321 } 322 } 323 return smartcontract.InteropInterfaceType, 324 stackitem.InteropT, 325 binding.Override{TypeName: "any"}, 326 &binding.ExtendedType{Base: smartcontract.InteropInterfaceType, Interface: "iterator"} // Temporarily all interops are iterators. 327 } 328 329 func (c *codegen) scAndVMTypeFromExpr(typ ast.Expr, exts map[string]binding.ExtendedType) (smartcontract.ParamType, stackitem.Type, binding.Override, *binding.ExtendedType) { 330 return c.scAndVMTypeFromType(c.typeOf(typ), exts) 331 } 332 333 func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.ExtendedType) (smartcontract.ParamType, stackitem.Type, binding.Override, *binding.ExtendedType) { 334 if t == nil { 335 return smartcontract.AnyType, stackitem.AnyT, binding.Override{TypeName: "any"}, nil 336 } 337 338 var isPtr bool 339 340 named, isNamed := t.(*types.Named) 341 if !isNamed { 342 var ptr *types.Pointer 343 if ptr, isPtr = t.(*types.Pointer); isPtr { 344 named, isNamed = ptr.Elem().(*types.Named) 345 } 346 } 347 if isNamed { 348 if isInteropPath(named.String()) { 349 st, vt, over, et := scAndVMInteropTypeFromExpr(named, isPtr) 350 if et != nil && et.Base == smartcontract.ArrayType && exts != nil && exts[et.Name].Name != et.Name { 351 _ = c.genStructExtended(named.Underlying().(*types.Struct), et.Name, exts) 352 } 353 return st, vt, over, et 354 } 355 } 356 if ptr, isPtr := t.(*types.Pointer); isPtr { 357 t = ptr.Elem() 358 } 359 var over binding.Override 360 switch t := t.Underlying().(type) { 361 case *types.Basic: 362 info := t.Info() 363 switch { 364 case info&types.IsInteger != 0: 365 over.TypeName = "int" 366 return smartcontract.IntegerType, stackitem.IntegerT, over, nil 367 case info&types.IsBoolean != 0: 368 over.TypeName = "bool" 369 return smartcontract.BoolType, stackitem.BooleanT, over, nil 370 case info&types.IsString != 0: 371 over.TypeName = "string" 372 return smartcontract.StringType, stackitem.ByteArrayT, over, nil 373 default: 374 over.TypeName = "any" 375 return smartcontract.AnyType, stackitem.AnyT, over, nil 376 } 377 case *types.Map: 378 et := &binding.ExtendedType{ 379 Base: smartcontract.MapType, 380 } 381 et.Key, _, _, _ = c.scAndVMTypeFromType(t.Key(), exts) 382 vt, _, over, vet := c.scAndVMTypeFromType(t.Elem(), exts) 383 et.Value = vet 384 if et.Value == nil { 385 et.Value = &binding.ExtendedType{Base: vt} 386 } 387 over.TypeName = "map[" + t.Key().String() + "]" + over.TypeName 388 return smartcontract.MapType, stackitem.MapT, over, et 389 case *types.Struct: 390 var extName string 391 if isNamed { 392 over.Package = named.Obj().Pkg().Path() 393 over.TypeName = named.Obj().Pkg().Name() + "." + named.Obj().Name() 394 _ = c.genStructExtended(t, over.TypeName, exts) 395 extName = over.TypeName 396 } else { 397 name := "unnamed" 398 if exts != nil { 399 for exts[name].Name == name { 400 name = name + "X" 401 } 402 _ = c.genStructExtended(t, name, exts) 403 } 404 // For bindings configurator this structure becomes named in fact. Its name 405 // is "unnamed[X...X]". 406 extName = name 407 } 408 return smartcontract.ArrayType, stackitem.StructT, over, 409 &binding.ExtendedType{ // Value-less, refer to exts. 410 Base: smartcontract.ArrayType, 411 Name: extName, 412 } 413 414 case *types.Slice: 415 if isByte(t.Elem()) { 416 over.TypeName = "[]byte" 417 return smartcontract.ByteArrayType, stackitem.ByteArrayT, over, nil 418 } 419 et := &binding.ExtendedType{ 420 Base: smartcontract.ArrayType, 421 } 422 vt, _, over, vet := c.scAndVMTypeFromType(t.Elem(), exts) 423 et.Value = vet 424 if et.Value == nil { 425 et.Value = &binding.ExtendedType{ 426 Base: vt, 427 } 428 } 429 if over.TypeName != "" { 430 over.TypeName = "[]" + over.TypeName 431 } 432 return smartcontract.ArrayType, stackitem.ArrayT, over, et 433 default: 434 over.TypeName = "any" 435 return smartcontract.AnyType, stackitem.AnyT, over, nil 436 } 437 } 438 439 func (c *codegen) genStructExtended(t *types.Struct, name string, exts map[string]binding.ExtendedType) *binding.ExtendedType { 440 var et *binding.ExtendedType 441 if exts != nil { 442 if exts[name].Name != name { 443 et = &binding.ExtendedType{ 444 Base: smartcontract.ArrayType, 445 Name: name, 446 Fields: make([]binding.FieldExtendedType, t.NumFields()), 447 } 448 exts[name] = *et // Prefill to solve recursive structures. 449 for i := range et.Fields { 450 field := t.Field(i) 451 ft, _, _, fet := c.scAndVMTypeFromType(field.Type(), exts) 452 if fet == nil { 453 et.Fields[i].ExtendedType.Base = ft 454 } else { 455 et.Fields[i].ExtendedType = *fet 456 } 457 et.Fields[i].Field = field.Name() 458 } 459 exts[name] = *et // Set real structure data. 460 } else { 461 et = new(binding.ExtendedType) 462 *et = exts[name] 463 } 464 } 465 return et 466 } 467 468 // MarshalJSON implements the json.Marshaler interface. 469 func (d *DebugRange) MarshalJSON() ([]byte, error) { 470 return []byte(`"` + strconv.FormatUint(uint64(d.Start), 10) + `-` + 471 strconv.FormatUint(uint64(d.End), 10) + `"`), nil 472 } 473 474 // UnmarshalJSON implements the json.Unmarshaler interface. 475 func (d *DebugRange) UnmarshalJSON(data []byte) error { 476 startS, endS, err := parsePairJSON(data, "-") 477 if err != nil { 478 return err 479 } 480 start, err := strconv.ParseUint(startS, 10, 16) 481 if err != nil { 482 return err 483 } 484 end, err := strconv.ParseUint(endS, 10, 16) 485 if err != nil { 486 return err 487 } 488 489 d.Start = uint16(start) 490 d.End = uint16(end) 491 492 return nil 493 } 494 495 // MarshalJSON implements the json.Marshaler interface. 496 func (d *DebugParam) MarshalJSON() ([]byte, error) { 497 return []byte(`"` + d.Name + `,` + d.Type + `"`), nil 498 } 499 500 // UnmarshalJSON implements the json.Unmarshaler interface. 501 func (d *DebugParam) UnmarshalJSON(data []byte) error { 502 startS, endS, err := parsePairJSON(data, ",") 503 if err != nil { 504 return err 505 } 506 507 d.Name = startS 508 d.Type = endS 509 510 return nil 511 } 512 513 // ToManifestParameter converts DebugParam to manifest.Parameter. 514 func (d *DebugParam) ToManifestParameter() manifest.Parameter { 515 return manifest.Parameter{ 516 Name: d.Name, 517 Type: d.TypeSC, 518 } 519 } 520 521 // ToManifestMethod converts MethodDebugInfo to manifest.Method. 522 func (m *MethodDebugInfo) ToManifestMethod() manifest.Method { 523 var ( 524 result manifest.Method 525 ) 526 parameters := make([]manifest.Parameter, len(m.Parameters)) 527 for i, p := range m.Parameters { 528 parameters[i] = p.ToManifestParameter() 529 } 530 result.Name = m.Name.Name 531 result.Offset = int(m.Range.Start) 532 result.Parameters = parameters 533 result.ReturnType = m.ReturnTypeSC 534 return result 535 } 536 537 // MarshalJSON implements the json.Marshaler interface. 538 func (d *DebugMethodName) MarshalJSON() ([]byte, error) { 539 return []byte(`"` + d.Namespace + `,` + d.Name + `"`), nil 540 } 541 542 // UnmarshalJSON implements the json.Unmarshaler interface. 543 func (d *DebugMethodName) UnmarshalJSON(data []byte) error { 544 startS, endS, err := parsePairJSON(data, ",") 545 if err != nil { 546 return err 547 } 548 549 d.Namespace = startS 550 d.Name = endS 551 552 return nil 553 } 554 555 // MarshalJSON implements the json.Marshaler interface. 556 func (d *DebugSeqPoint) MarshalJSON() ([]byte, error) { 557 s := fmt.Sprintf("%d[%d]%d:%d-%d:%d", d.Opcode, d.Document, 558 d.StartLine, d.StartCol, d.EndLine, d.EndCol) 559 return []byte(`"` + s + `"`), nil 560 } 561 562 // UnmarshalJSON implements the json.Unmarshaler interface. 563 func (d *DebugSeqPoint) UnmarshalJSON(data []byte) error { 564 _, err := fmt.Sscanf(string(data), `"%d[%d]%d:%d-%d:%d"`, 565 &d.Opcode, &d.Document, &d.StartLine, &d.StartCol, &d.EndLine, &d.EndCol) 566 return err 567 } 568 569 func parsePairJSON(data []byte, sep string) (string, string, error) { 570 var s string 571 if err := json.Unmarshal(data, &s); err != nil { 572 return "", "", err 573 } 574 ss := strings.SplitN(s, sep, 2) 575 if len(ss) != 2 { 576 return "", "", errors.New("invalid range format") 577 } 578 return ss[0], ss[1], nil 579 } 580 581 // ConvertToManifest converts a contract to the manifest.Manifest struct for debugger. 582 // Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038. 583 func (di *DebugInfo) ConvertToManifest(o *Options) (*manifest.Manifest, error) { 584 methods := make([]manifest.Method, 0) 585 for _, method := range di.Methods { 586 if method.IsExported && method.IsFunction && method.Name.Namespace == di.MainPkg { 587 mMethod := method.ToManifestMethod() 588 for i := range o.SafeMethods { 589 if mMethod.Name == o.SafeMethods[i] { 590 mMethod.Safe = true 591 break 592 } 593 } 594 methods = append(methods, mMethod) 595 } 596 } 597 598 result := manifest.NewManifest(o.Name) 599 if o.ContractSupportedStandards != nil { 600 result.SupportedStandards = o.ContractSupportedStandards 601 } 602 events := make([]manifest.Event, len(o.ContractEvents)) 603 for i, e := range o.ContractEvents { 604 params := make([]manifest.Parameter, len(e.Parameters)) 605 for j, p := range e.Parameters { 606 params[j] = p.Parameter 607 } 608 events[i] = manifest.Event{ 609 Name: o.ContractEvents[i].Name, 610 Parameters: params, 611 } 612 } 613 result.ABI = manifest.ABI{ 614 Methods: methods, 615 Events: events, 616 } 617 if result.ABI.Events == nil { 618 result.ABI.Events = make([]manifest.Event, 0) 619 } 620 result.Permissions = o.Permissions 621 for name, emitName := range o.Overloads { 622 m := result.ABI.GetMethod(name, -1) 623 if m == nil { 624 return nil, fmt.Errorf("overload for method %s was provided but it wasn't found", name) 625 } 626 if result.ABI.GetMethod(emitName, -1) == nil { 627 return nil, fmt.Errorf("overload with target method %s was provided but it wasn't found", emitName) 628 } 629 630 realM := result.ABI.GetMethod(emitName, len(m.Parameters)) 631 if realM != nil { 632 return nil, fmt.Errorf("conflict overload for %s: "+ 633 "multiple methods with the same number of parameters", name) 634 } 635 m.Name = emitName 636 // Check the resulting name against set of safe methods. 637 for i := range o.SafeMethods { 638 if m.Name == o.SafeMethods[i] { 639 m.Safe = true 640 break 641 } 642 } 643 } 644 return result, nil 645 }