github.com/aquanetwork/aquachain@v1.7.8/aqua/tracers/tracer_cgo.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // +build gccgo cgo 18 19 package tracers 20 21 import ( 22 "encoding/json" 23 "errors" 24 "fmt" 25 "math/big" 26 "sync/atomic" 27 "time" 28 "unsafe" 29 30 "gitlab.com/aquachain/aquachain/common" 31 "gitlab.com/aquachain/aquachain/common/hexutil" 32 "gitlab.com/aquachain/aquachain/common/log" 33 "gitlab.com/aquachain/aquachain/core/vm" 34 "gitlab.com/aquachain/aquachain/crypto" 35 duktape "gopkg.in/olebedev/go-duktape.v3" 36 ) 37 38 // makeSlice convert an unsafe memory pointer with the given type into a Go byte 39 // slice. 40 // 41 // Note, the returned slice uses the same memory area as the input arguments. 42 // If those are duktape stack items, popping them off **will** make the slice 43 // contents change. 44 func makeSlice(ptr unsafe.Pointer, size uint) []byte { 45 var sl = struct { 46 addr uintptr 47 len int 48 cap int 49 }{uintptr(ptr), int(size), int(size)} 50 51 return *(*[]byte)(unsafe.Pointer(&sl)) 52 } 53 54 // popSlice pops a buffer off the JavaScript stack and returns it as a slice. 55 func popSlice(ctx *duktape.Context) []byte { 56 blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1))) 57 ctx.Pop() 58 return blob 59 } 60 61 // pushBigInt create a JavaScript BigInteger in the VM. 62 func pushBigInt(n *big.Int, ctx *duktape.Context) { 63 ctx.GetGlobalString("bigInt") 64 ctx.PushString(n.String()) 65 ctx.Call(1) 66 } 67 68 // opWrapper provides a JavaScript wrapper around OpCode. 69 type opWrapper struct { 70 op vm.OpCode 71 } 72 73 // pushObject assembles a JSVM object wrapping a swappable opcode and pushes it 74 // onto the VM stack. 75 func (ow *opWrapper) pushObject(vm *duktape.Context) { 76 obj := vm.PushObject() 77 78 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(int(ow.op)); return 1 }) 79 vm.PutPropString(obj, "toNumber") 80 81 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushString(ow.op.String()); return 1 }) 82 vm.PutPropString(obj, "toString") 83 84 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushBoolean(ow.op.IsPush()); return 1 }) 85 vm.PutPropString(obj, "isPush") 86 } 87 88 // memoryWrapper provides a JavaScript wrapper around vm.Memory. 89 type memoryWrapper struct { 90 memory *vm.Memory 91 } 92 93 // slice returns the requested range of memory as a byte slice. 94 func (mw *memoryWrapper) slice(begin, end int64) []byte { 95 if mw.memory.Len() < int(end) { 96 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 97 // runtime goes belly up https://github.com/golang/go/issues/15639. 98 log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin) 99 return nil 100 } 101 return mw.memory.Get(begin, end-begin) 102 } 103 104 // getUint returns the 32 bytes at the specified address interpreted as a uint. 105 func (mw *memoryWrapper) getUint(addr int64) *big.Int { 106 if mw.memory.Len() < int(addr)+32 { 107 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 108 // runtime goes belly up https://github.com/golang/go/issues/15639. 109 log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32) 110 return new(big.Int) 111 } 112 return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32)) 113 } 114 115 // pushObject assembles a JSVM object wrapping a swappable memory and pushes it 116 // onto the VM stack. 117 func (mw *memoryWrapper) pushObject(vm *duktape.Context) { 118 obj := vm.PushObject() 119 120 // Generate the `slice` method which takes two ints and returns a buffer 121 vm.PushGoFunction(func(ctx *duktape.Context) int { 122 blob := mw.slice(int64(ctx.GetInt(-2)), int64(ctx.GetInt(-1))) 123 ctx.Pop2() 124 125 ptr := ctx.PushFixedBuffer(len(blob)) 126 copy(makeSlice(ptr, uint(len(blob))), blob[:]) 127 return 1 128 }) 129 vm.PutPropString(obj, "slice") 130 131 // Generate the `getUint` method which takes an int and returns a bigint 132 vm.PushGoFunction(func(ctx *duktape.Context) int { 133 offset := int64(ctx.GetInt(-1)) 134 ctx.Pop() 135 136 pushBigInt(mw.getUint(offset), ctx) 137 return 1 138 }) 139 vm.PutPropString(obj, "getUint") 140 } 141 142 // stackWrapper provides a JavaScript wrapper around vm.Stack. 143 type stackWrapper struct { 144 stack *vm.Stack 145 } 146 147 // peek returns the nth-from-the-top element of the stack. 148 func (sw *stackWrapper) peek(idx int) *big.Int { 149 if len(sw.stack.Data()) <= idx { 150 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 151 // runtime goes belly up https://github.com/golang/go/issues/15639. 152 log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) 153 return new(big.Int) 154 } 155 return sw.stack.Data()[len(sw.stack.Data())-idx-1] 156 } 157 158 // pushObject assembles a JSVM object wrapping a swappable stack and pushes it 159 // onto the VM stack. 160 func (sw *stackWrapper) pushObject(vm *duktape.Context) { 161 obj := vm.PushObject() 162 163 vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(len(sw.stack.Data())); return 1 }) 164 vm.PutPropString(obj, "length") 165 166 // Generate the `peek` method which takes an int and returns a bigint 167 vm.PushGoFunction(func(ctx *duktape.Context) int { 168 offset := ctx.GetInt(-1) 169 ctx.Pop() 170 171 pushBigInt(sw.peek(offset), ctx) 172 return 1 173 }) 174 vm.PutPropString(obj, "peek") 175 } 176 177 // dbWrapper provides a JavaScript wrapper around vm.Database. 178 type dbWrapper struct { 179 db vm.StateDB 180 } 181 182 // pushObject assembles a JSVM object wrapping a swappable database and pushes it 183 // onto the VM stack. 184 func (dw *dbWrapper) pushObject(vm *duktape.Context) { 185 obj := vm.PushObject() 186 187 // Push the wrapper for statedb.GetBalance 188 vm.PushGoFunction(func(ctx *duktape.Context) int { 189 pushBigInt(dw.db.GetBalance(common.BytesToAddress(popSlice(ctx))), ctx) 190 return 1 191 }) 192 vm.PutPropString(obj, "getBalance") 193 194 // Push the wrapper for statedb.GetNonce 195 vm.PushGoFunction(func(ctx *duktape.Context) int { 196 ctx.PushInt(int(dw.db.GetNonce(common.BytesToAddress(popSlice(ctx))))) 197 return 1 198 }) 199 vm.PutPropString(obj, "getNonce") 200 201 // Push the wrapper for statedb.GetCode 202 vm.PushGoFunction(func(ctx *duktape.Context) int { 203 code := dw.db.GetCode(common.BytesToAddress(popSlice(ctx))) 204 205 ptr := ctx.PushFixedBuffer(len(code)) 206 copy(makeSlice(ptr, uint(len(code))), code[:]) 207 return 1 208 }) 209 vm.PutPropString(obj, "getCode") 210 211 // Push the wrapper for statedb.GetState 212 vm.PushGoFunction(func(ctx *duktape.Context) int { 213 hash := popSlice(ctx) 214 addr := popSlice(ctx) 215 216 state := dw.db.GetState(common.BytesToAddress(addr), common.BytesToHash(hash)) 217 218 ptr := ctx.PushFixedBuffer(len(state)) 219 copy(makeSlice(ptr, uint(len(state))), state[:]) 220 return 1 221 }) 222 vm.PutPropString(obj, "getState") 223 224 // Push the wrapper for statedb.Exists 225 vm.PushGoFunction(func(ctx *duktape.Context) int { 226 ctx.PushBoolean(dw.db.Exist(common.BytesToAddress(popSlice(ctx)))) 227 return 1 228 }) 229 vm.PutPropString(obj, "exists") 230 } 231 232 // contractWrapper provides a JavaScript wrapper around vm.Contract 233 type contractWrapper struct { 234 contract *vm.Contract 235 } 236 237 // pushObject assembles a JSVM object wrapping a swappable contract and pushes it 238 // onto the VM stack. 239 func (cw *contractWrapper) pushObject(vm *duktape.Context) { 240 obj := vm.PushObject() 241 242 // Push the wrapper for contract.Caller 243 vm.PushGoFunction(func(ctx *duktape.Context) int { 244 ptr := ctx.PushFixedBuffer(20) 245 copy(makeSlice(ptr, 20), cw.contract.Caller().Bytes()) 246 return 1 247 }) 248 vm.PutPropString(obj, "getCaller") 249 250 // Push the wrapper for contract.Address 251 vm.PushGoFunction(func(ctx *duktape.Context) int { 252 ptr := ctx.PushFixedBuffer(20) 253 copy(makeSlice(ptr, 20), cw.contract.Address().Bytes()) 254 return 1 255 }) 256 vm.PutPropString(obj, "getAddress") 257 258 // Push the wrapper for contract.Value 259 vm.PushGoFunction(func(ctx *duktape.Context) int { 260 pushBigInt(cw.contract.Value(), ctx) 261 return 1 262 }) 263 vm.PutPropString(obj, "getValue") 264 265 // Push the wrapper for contract.Input 266 vm.PushGoFunction(func(ctx *duktape.Context) int { 267 blob := cw.contract.Input 268 269 ptr := ctx.PushFixedBuffer(len(blob)) 270 copy(makeSlice(ptr, uint(len(blob))), blob[:]) 271 return 1 272 }) 273 vm.PutPropString(obj, "getInput") 274 } 275 276 // Tracer provides an implementation of Tracer that evaluates a Javascript 277 // function for each VM execution step. 278 type Tracer struct { 279 inited bool // Flag whether the context was already inited from the EVM 280 281 vm *duktape.Context // Javascript VM instance 282 283 tracerObject int // Stack index of the tracer JavaScript object 284 stateObject int // Stack index of the global state to pull arguments from 285 286 opWrapper *opWrapper // Wrapper around the VM opcode 287 stackWrapper *stackWrapper // Wrapper around the VM stack 288 memoryWrapper *memoryWrapper // Wrapper around the VM memory 289 contractWrapper *contractWrapper // Wrapper around the contract object 290 dbWrapper *dbWrapper // Wrapper around the VM environment 291 292 pcValue *uint // Swappable pc value wrapped by a log accessor 293 gasValue *uint // Swappable gas value wrapped by a log accessor 294 costValue *uint // Swappable cost value wrapped by a log accessor 295 depthValue *uint // Swappable depth value wrapped by a log accessor 296 errorValue *string // Swappable error value wrapped by a log accessor 297 298 ctx map[string]interface{} // Transaction context gathered throughout execution 299 err error // Error, if one has occurred 300 301 interrupt uint32 // Atomic flag to signal execution interruption 302 reason error // Textual reason for the interruption 303 } 304 305 // New instantiates a new tracer instance. code specifies a Javascript snippet, 306 // which must evaluate to an expression returning an object with 'step', 'fault' 307 // and 'result' functions. 308 func New(code string) (*Tracer, error) { 309 // Resolve any tracers by name and assemble the tracer object 310 if tracer, ok := tracer(code); ok { 311 code = tracer 312 } 313 tracer := &Tracer{ 314 vm: duktape.New(), 315 ctx: make(map[string]interface{}), 316 opWrapper: new(opWrapper), 317 stackWrapper: new(stackWrapper), 318 memoryWrapper: new(memoryWrapper), 319 contractWrapper: new(contractWrapper), 320 dbWrapper: new(dbWrapper), 321 pcValue: new(uint), 322 gasValue: new(uint), 323 costValue: new(uint), 324 depthValue: new(uint), 325 } 326 // Set up builtins for this environment 327 tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int { 328 ctx.PushString(hexutil.Encode(popSlice(ctx))) 329 return 1 330 }) 331 tracer.vm.PushGlobalGoFunction("toWord", func(ctx *duktape.Context) int { 332 var word common.Hash 333 if ptr, size := ctx.GetBuffer(-1); ptr != nil { 334 word = common.BytesToHash(makeSlice(ptr, size)) 335 } else { 336 word = common.HexToHash(ctx.GetString(-1)) 337 } 338 ctx.Pop() 339 copy(makeSlice(ctx.PushFixedBuffer(32), 32), word[:]) 340 return 1 341 }) 342 tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int { 343 var addr common.Address 344 if ptr, size := ctx.GetBuffer(-1); ptr != nil { 345 addr = common.BytesToAddress(makeSlice(ptr, size)) 346 } else { 347 addr = common.HexToAddress(ctx.GetString(-1)) 348 } 349 ctx.Pop() 350 copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:]) 351 return 1 352 }) 353 tracer.vm.PushGlobalGoFunction("toContract", func(ctx *duktape.Context) int { 354 var from common.Address 355 if ptr, size := ctx.GetBuffer(-2); ptr != nil { 356 from = common.BytesToAddress(makeSlice(ptr, size)) 357 } else { 358 from = common.HexToAddress(ctx.GetString(-2)) 359 } 360 nonce := uint64(ctx.GetInt(-1)) 361 ctx.Pop2() 362 363 contract := crypto.CreateAddress(from, nonce) 364 copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:]) 365 return 1 366 }) 367 tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int { 368 _, ok := vm.PrecompiledContractsByzantium[common.BytesToAddress(popSlice(ctx))] 369 ctx.PushBoolean(ok) 370 return 1 371 }) 372 tracer.vm.PushGlobalGoFunction("slice", func(ctx *duktape.Context) int { 373 start, end := ctx.GetInt(-2), ctx.GetInt(-1) 374 ctx.Pop2() 375 376 blob := popSlice(ctx) 377 size := end - start 378 379 if start < 0 || start > end || end > len(blob) { 380 // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go 381 // runtime goes belly up https://github.com/golang/go/issues/15639. 382 log.Warn("Tracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size) 383 ctx.PushFixedBuffer(0) 384 return 1 385 } 386 copy(makeSlice(ctx.PushFixedBuffer(size), uint(size)), blob[start:end]) 387 return 1 388 }) 389 // Push the JavaScript tracer as object #0 onto the JSVM stack and validate it 390 if err := tracer.vm.PevalString("(" + code + ")"); err != nil { 391 log.Warn("Failed to compile tracer", "err", err) 392 return nil, err 393 } 394 tracer.tracerObject = 0 // yeah, nice, eval can't return the index itself 395 396 if !tracer.vm.GetPropString(tracer.tracerObject, "step") { 397 return nil, fmt.Errorf("Trace object must expose a function step()") 398 } 399 tracer.vm.Pop() 400 401 if !tracer.vm.GetPropString(tracer.tracerObject, "fault") { 402 return nil, fmt.Errorf("Trace object must expose a function fault()") 403 } 404 tracer.vm.Pop() 405 406 if !tracer.vm.GetPropString(tracer.tracerObject, "result") { 407 return nil, fmt.Errorf("Trace object must expose a function result()") 408 } 409 tracer.vm.Pop() 410 411 // Tracer is valid, inject the big int library to access large numbers 412 tracer.vm.EvalString(bigIntegerJS) 413 tracer.vm.PutGlobalString("bigInt") 414 415 // Push the global environment state as object #1 into the JSVM stack 416 tracer.stateObject = tracer.vm.PushObject() 417 418 logObject := tracer.vm.PushObject() 419 420 tracer.opWrapper.pushObject(tracer.vm) 421 tracer.vm.PutPropString(logObject, "op") 422 423 tracer.stackWrapper.pushObject(tracer.vm) 424 tracer.vm.PutPropString(logObject, "stack") 425 426 tracer.memoryWrapper.pushObject(tracer.vm) 427 tracer.vm.PutPropString(logObject, "memory") 428 429 tracer.contractWrapper.pushObject(tracer.vm) 430 tracer.vm.PutPropString(logObject, "contract") 431 432 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.pcValue); return 1 }) 433 tracer.vm.PutPropString(logObject, "getPC") 434 435 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.gasValue); return 1 }) 436 tracer.vm.PutPropString(logObject, "getGas") 437 438 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.costValue); return 1 }) 439 tracer.vm.PutPropString(logObject, "getCost") 440 441 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.depthValue); return 1 }) 442 tracer.vm.PutPropString(logObject, "getDepth") 443 444 tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { 445 if tracer.errorValue != nil { 446 ctx.PushString(*tracer.errorValue) 447 } else { 448 ctx.PushUndefined() 449 } 450 return 1 451 }) 452 tracer.vm.PutPropString(logObject, "getError") 453 454 tracer.vm.PutPropString(tracer.stateObject, "log") 455 456 tracer.dbWrapper.pushObject(tracer.vm) 457 tracer.vm.PutPropString(tracer.stateObject, "db") 458 459 return tracer, nil 460 } 461 462 // Stop terminates execution of the tracer at the first opportune moment. 463 func (jst *Tracer) Stop(err error) { 464 jst.reason = err 465 atomic.StoreUint32(&jst.interrupt, 1) 466 } 467 468 // call executes a method on a JS object, catching any errors, formatting and 469 // returning them as error objects. 470 func (jst *Tracer) call(method string, args ...string) (json.RawMessage, error) { 471 // Execute the JavaScript call and return any error 472 jst.vm.PushString(method) 473 for _, arg := range args { 474 jst.vm.GetPropString(jst.stateObject, arg) 475 } 476 code := jst.vm.PcallProp(jst.tracerObject, len(args)) 477 defer jst.vm.Pop() 478 479 if code != 0 { 480 err := jst.vm.SafeToString(-1) 481 return nil, errors.New(err) 482 } 483 // No error occurred, extract return value and return 484 return json.RawMessage(jst.vm.JsonEncode(-1)), nil 485 } 486 487 func wrapError(context string, err error) error { 488 var message string 489 switch err := err.(type) { 490 default: 491 message = err.Error() 492 } 493 return fmt.Errorf("%v in server-side tracer function '%v'", message, context) 494 } 495 496 // CaptureStart implements the Tracer interface to initialize the tracing operation. 497 func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error { 498 jst.ctx["type"] = "CALL" 499 if create { 500 jst.ctx["type"] = "CREATE" 501 } 502 jst.ctx["from"] = from 503 jst.ctx["to"] = to 504 jst.ctx["input"] = input 505 jst.ctx["gas"] = gas 506 jst.ctx["value"] = value 507 508 return nil 509 } 510 511 // CaptureState implements the Tracer interface to trace a single step of VM execution. 512 func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { 513 if jst.err == nil { 514 // Initialize the context if it wasn't done yet 515 if !jst.inited { 516 jst.ctx["block"] = env.BlockNumber.Uint64() 517 jst.inited = true 518 } 519 // If tracing was interrupted, set the error and stop 520 if atomic.LoadUint32(&jst.interrupt) > 0 { 521 jst.err = jst.reason 522 return nil 523 } 524 jst.opWrapper.op = op 525 jst.stackWrapper.stack = stack 526 jst.memoryWrapper.memory = memory 527 jst.contractWrapper.contract = contract 528 jst.dbWrapper.db = env.StateDB 529 530 *jst.pcValue = uint(pc) 531 *jst.gasValue = uint(gas) 532 *jst.costValue = uint(cost) 533 *jst.depthValue = uint(depth) 534 535 jst.errorValue = nil 536 if err != nil { 537 jst.errorValue = new(string) 538 *jst.errorValue = err.Error() 539 } 540 _, err := jst.call("step", "log", "db") 541 if err != nil { 542 jst.err = wrapError("step", err) 543 } 544 } 545 return nil 546 } 547 548 // CaptureFault implements the Tracer interface to trace an execution fault 549 // while running an opcode. 550 func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error { 551 if jst.err == nil { 552 // Apart from the error, everything matches the previous invocation 553 jst.errorValue = new(string) 554 *jst.errorValue = err.Error() 555 556 _, err := jst.call("fault", "log", "db") 557 if err != nil { 558 jst.err = wrapError("fault", err) 559 } 560 } 561 return nil 562 } 563 564 // CaptureEnd is called after the call finishes to finalize the tracing. 565 func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { 566 jst.ctx["output"] = output 567 jst.ctx["gasUsed"] = gasUsed 568 jst.ctx["time"] = t.String() 569 570 if err != nil { 571 jst.ctx["error"] = err.Error() 572 } 573 return nil 574 } 575 576 // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error 577 func (jst *Tracer) GetResult() (json.RawMessage, error) { 578 // Transform the context into a JavaScript object and inject into the state 579 obj := jst.vm.PushObject() 580 581 for key, val := range jst.ctx { 582 switch val := val.(type) { 583 case uint64: 584 jst.vm.PushUint(uint(val)) 585 586 case string: 587 jst.vm.PushString(val) 588 589 case []byte: 590 ptr := jst.vm.PushFixedBuffer(len(val)) 591 copy(makeSlice(ptr, uint(len(val))), val[:]) 592 593 case common.Address: 594 ptr := jst.vm.PushFixedBuffer(20) 595 copy(makeSlice(ptr, 20), val[:]) 596 597 case *big.Int: 598 pushBigInt(val, jst.vm) 599 600 default: 601 panic(fmt.Sprintf("unsupported type: %T", val)) 602 } 603 jst.vm.PutPropString(obj, key) 604 } 605 jst.vm.PutPropString(jst.stateObject, "ctx") 606 607 // Finalize the trace and return the results 608 result, err := jst.call("result", "ctx", "db") 609 if err != nil { 610 jst.err = wrapError("result", err) 611 } 612 // Clean up the JavaScript environment 613 jst.vm.DestroyHeap() 614 jst.vm.Destroy() 615 616 return result, jst.err 617 }