cmd, core, eth/tracers: support fancier js tracing (#15516)
* cmd, core, eth/tracers: support fancier js tracing
* eth, internal/web3ext: rework trace API, concurrency, chain tracing
* eth/tracers: add three more JavaScript tracers
* eth/tracers, vendor: swap ottovm to duktape for tracing
* core, eth, internal: finalize call tracer and needed extras
* eth, tests: prestate tracer, call test suite, rewinding
* vendor: fix windows builds for tracer js engine
* vendor: temporary duktape fix
* eth/tracers: fix up 4byte and evmdis tracer
* vendor: pull in latest duktape with my upstream fixes
* eth: fix some review comments
* eth: rename rewind to reexec to make it more obvious
* core/vm: terminate tracing using defers
2017-12-21 14:56:11 +03:00
|
|
|
package duktape
|
|
|
|
|
|
|
|
/*
|
|
|
|
#cgo !windows CFLAGS: -std=c99 -O3 -Wall -fomit-frame-pointer -fstrict-aliasing
|
|
|
|
#cgo windows CFLAGS: -O3 -Wall -fomit-frame-pointer -fstrict-aliasing
|
|
|
|
#cgo linux LDFLAGS: -lm
|
|
|
|
#cgo freebsd LDFLAGS: -lm
|
2018-03-07 15:47:09 +03:00
|
|
|
#cgo openbsd LDFLAGS: -lm
|
cmd, core, eth/tracers: support fancier js tracing (#15516)
* cmd, core, eth/tracers: support fancier js tracing
* eth, internal/web3ext: rework trace API, concurrency, chain tracing
* eth/tracers: add three more JavaScript tracers
* eth/tracers, vendor: swap ottovm to duktape for tracing
* core, eth, internal: finalize call tracer and needed extras
* eth, tests: prestate tracer, call test suite, rewinding
* vendor: fix windows builds for tracer js engine
* vendor: temporary duktape fix
* eth/tracers: fix up 4byte and evmdis tracer
* vendor: pull in latest duktape with my upstream fixes
* eth: fix some review comments
* eth: rename rewind to reexec to make it more obvious
* core/vm: terminate tracing using defers
2017-12-21 14:56:11 +03:00
|
|
|
|
|
|
|
#include "duktape.h"
|
|
|
|
#include "duk_logging.h"
|
|
|
|
#include "duk_print_alert.h"
|
|
|
|
#include "duk_module_duktape.h"
|
|
|
|
#include "duk_console.h"
|
|
|
|
extern duk_ret_t goFunctionCall(duk_context *ctx);
|
|
|
|
extern void goFinalizeCall(duk_context *ctx);
|
|
|
|
*/
|
|
|
|
import "C"
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"sync"
|
|
|
|
"unsafe"
|
|
|
|
)
|
|
|
|
|
|
|
|
var reFuncName = regexp.MustCompile("^[a-z_][a-z0-9_]*([A-Z_][a-z0-9_]*)*$")
|
|
|
|
|
|
|
|
const (
|
|
|
|
goFunctionPtrProp = "\xff" + "goFunctionPtrProp"
|
|
|
|
goContextPtrProp = "\xff" + "goContextPtrProp"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Context struct {
|
|
|
|
*context
|
|
|
|
}
|
|
|
|
|
|
|
|
// transmute replaces the value from Context with the value of pointer
|
|
|
|
func (c *Context) transmute(p unsafe.Pointer) {
|
|
|
|
*c = *(*Context)(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// this is a pojo containing only the values of the Context
|
|
|
|
type context struct {
|
|
|
|
sync.Mutex
|
|
|
|
duk_context *C.duk_context
|
|
|
|
fnIndex *functionIndex
|
|
|
|
timerIndex *timerIndex
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns plain initialized duktape context object
|
|
|
|
// See: http://duktape.org/api.html#duk_create_heap_default
|
|
|
|
func New() *Context {
|
|
|
|
d := &Context{
|
|
|
|
&context{
|
|
|
|
duk_context: C.duk_create_heap(nil, nil, nil, nil, nil),
|
|
|
|
fnIndex: newFunctionIndex(),
|
|
|
|
timerIndex: &timerIndex{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := d.duk_context
|
|
|
|
C.duk_logging_init(ctx, 0)
|
|
|
|
C.duk_print_alert_init(ctx, 0)
|
|
|
|
C.duk_module_duktape_init(ctx)
|
|
|
|
C.duk_console_init(ctx, 0)
|
|
|
|
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flags is a set of flags for controlling the behaviour of duktape.
|
|
|
|
type Flags struct {
|
|
|
|
Logging uint
|
|
|
|
PrintAlert uint
|
|
|
|
Console uint
|
|
|
|
}
|
|
|
|
|
|
|
|
// FlagConsoleProxyWrapper is a Console flag.
|
|
|
|
// Use a proxy wrapper to make undefined methods (console.foo()) no-ops.
|
|
|
|
const FlagConsoleProxyWrapper = 1 << 0
|
|
|
|
|
|
|
|
// FlagConsoleFlush is a Console flag.
|
|
|
|
// Flush output after every call.
|
|
|
|
const FlagConsoleFlush = 1 << 1
|
|
|
|
|
|
|
|
// NewWithFlags returns plain initialized duktape context object
|
|
|
|
// You can control the behaviour of duktape by setting flags.
|
|
|
|
// See: http://duktape.org/api.html#duk_create_heap_default
|
|
|
|
func NewWithFlags(flags *Flags) *Context {
|
|
|
|
d := &Context{
|
|
|
|
&context{
|
|
|
|
duk_context: C.duk_create_heap(nil, nil, nil, nil, nil),
|
|
|
|
fnIndex: newFunctionIndex(),
|
|
|
|
timerIndex: &timerIndex{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := d.duk_context
|
|
|
|
C.duk_logging_init(ctx, C.duk_uint_t(flags.Logging))
|
|
|
|
C.duk_print_alert_init(ctx, C.duk_uint_t(flags.PrintAlert))
|
|
|
|
C.duk_module_duktape_init(ctx)
|
|
|
|
C.duk_console_init(ctx, C.duk_uint_t(flags.Console))
|
|
|
|
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
func contextFromPointer(ctx *C.duk_context) *Context {
|
|
|
|
return &Context{&context{duk_context: ctx}}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PushGlobalGoFunction push the given function into duktape global object
|
|
|
|
// Returns non-negative index (relative to stack bottom) of the pushed function
|
|
|
|
// also returns error if the function name is invalid
|
|
|
|
func (d *Context) PushGlobalGoFunction(name string, fn func(*Context) int) (int, error) {
|
|
|
|
if !reFuncName.MatchString(name) {
|
|
|
|
return -1, errors.New("Malformed function name '" + name + "'")
|
|
|
|
}
|
|
|
|
|
|
|
|
d.PushGlobalObject()
|
|
|
|
idx := d.PushGoFunction(fn)
|
|
|
|
d.PutPropString(-2, name)
|
|
|
|
d.Pop()
|
|
|
|
|
|
|
|
return idx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PushGoFunction push the given function into duktape stack, returns non-negative
|
|
|
|
// index (relative to stack bottom) of the pushed function
|
|
|
|
func (d *Context) PushGoFunction(fn func(*Context) int) int {
|
|
|
|
funPtr := d.fnIndex.add(fn)
|
|
|
|
ctxPtr := contexts.add(d)
|
|
|
|
|
|
|
|
idx := d.PushCFunction((*[0]byte)(C.goFunctionCall), C.DUK_VARARGS)
|
|
|
|
d.PushCFunction((*[0]byte)(C.goFinalizeCall), 1)
|
|
|
|
d.PushPointer(funPtr)
|
|
|
|
d.PutPropString(-2, goFunctionPtrProp)
|
|
|
|
d.PushPointer(ctxPtr)
|
|
|
|
d.PutPropString(-2, goContextPtrProp)
|
|
|
|
d.SetFinalizer(-2)
|
|
|
|
|
|
|
|
d.PushPointer(funPtr)
|
|
|
|
d.PutPropString(-2, goFunctionPtrProp)
|
|
|
|
d.PushPointer(ctxPtr)
|
|
|
|
d.PutPropString(-2, goContextPtrProp)
|
|
|
|
|
|
|
|
return idx
|
|
|
|
}
|
|
|
|
|
|
|
|
//export goFunctionCall
|
|
|
|
func goFunctionCall(cCtx *C.duk_context) C.duk_ret_t {
|
|
|
|
d := contextFromPointer(cCtx)
|
|
|
|
|
|
|
|
funPtr, ctx := d.getFunctionPtrs()
|
|
|
|
d.transmute(unsafe.Pointer(ctx))
|
|
|
|
|
|
|
|
result := d.fnIndex.get(funPtr)(d)
|
|
|
|
|
|
|
|
return C.duk_ret_t(result)
|
|
|
|
}
|
|
|
|
|
|
|
|
//export goFinalizeCall
|
|
|
|
func goFinalizeCall(cCtx *C.duk_context) {
|
|
|
|
d := contextFromPointer(cCtx)
|
|
|
|
|
|
|
|
funPtr, ctx := d.getFunctionPtrs()
|
|
|
|
d.transmute(unsafe.Pointer(ctx))
|
|
|
|
|
|
|
|
d.fnIndex.delete(funPtr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Context) getFunctionPtrs() (unsafe.Pointer, *Context) {
|
|
|
|
d.PushCurrentFunction()
|
|
|
|
d.GetPropString(-1, goFunctionPtrProp)
|
|
|
|
funPtr := d.GetPointer(-1)
|
|
|
|
|
|
|
|
d.Pop()
|
|
|
|
|
|
|
|
d.GetPropString(-1, goContextPtrProp)
|
|
|
|
ctx := contexts.get(d.GetPointer(-1))
|
|
|
|
d.Pop2()
|
|
|
|
return funPtr, ctx
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destroy destroy all the references to the functions and freed the pointers
|
|
|
|
func (d *Context) Destroy() {
|
|
|
|
d.fnIndex.destroy()
|
|
|
|
contexts.delete(d)
|
|
|
|
}
|
|
|
|
|
|
|
|
type Error struct {
|
|
|
|
Type string
|
|
|
|
Message string
|
|
|
|
FileName string
|
|
|
|
LineNumber int
|
|
|
|
Stack string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *Error) Error() string {
|
|
|
|
return fmt.Sprintf("%s: %s", e.Type, e.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
type Type int
|
|
|
|
|
|
|
|
func (t Type) IsNone() bool { return t == TypeNone }
|
|
|
|
func (t Type) IsUndefined() bool { return t == TypeUndefined }
|
|
|
|
func (t Type) IsNull() bool { return t == TypeNull }
|
|
|
|
func (t Type) IsBool() bool { return t == TypeBoolean }
|
|
|
|
func (t Type) IsNumber() bool { return t == TypeNumber }
|
|
|
|
func (t Type) IsString() bool { return t == TypeString }
|
|
|
|
func (t Type) IsObject() bool { return t == TypeObject }
|
|
|
|
func (t Type) IsBuffer() bool { return t == TypeBuffer }
|
|
|
|
func (t Type) IsPointer() bool { return t == TypePointer }
|
|
|
|
func (t Type) IsLightFunc() bool { return t == TypeLightFunc }
|
|
|
|
|
|
|
|
func (t Type) String() string {
|
|
|
|
switch t {
|
|
|
|
case TypeNone:
|
|
|
|
return "None"
|
|
|
|
case TypeUndefined:
|
|
|
|
return "Undefined"
|
|
|
|
case TypeNull:
|
|
|
|
return "Null"
|
|
|
|
case TypeBoolean:
|
|
|
|
return "Boolean"
|
|
|
|
case TypeNumber:
|
|
|
|
return "Number"
|
|
|
|
case TypeString:
|
|
|
|
return "String"
|
|
|
|
case TypeObject:
|
|
|
|
return "Object"
|
|
|
|
case TypeBuffer:
|
|
|
|
return "Buffer"
|
|
|
|
case TypePointer:
|
|
|
|
return "Pointer"
|
|
|
|
case TypeLightFunc:
|
|
|
|
return "LightFunc"
|
|
|
|
default:
|
|
|
|
return "Unknown"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type functionIndex struct {
|
|
|
|
functions map[unsafe.Pointer]func(*Context) int
|
|
|
|
sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
type timerIndex struct {
|
|
|
|
c float64
|
|
|
|
sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *timerIndex) get() float64 {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
t.c++
|
|
|
|
return t.c
|
|
|
|
}
|
|
|
|
|
|
|
|
func newFunctionIndex() *functionIndex {
|
|
|
|
return &functionIndex{
|
|
|
|
functions: make(map[unsafe.Pointer]func(*Context) int, 0),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *functionIndex) add(fn func(*Context) int) unsafe.Pointer {
|
|
|
|
ptr := C.malloc(1)
|
|
|
|
|
|
|
|
i.Lock()
|
|
|
|
i.functions[ptr] = fn
|
|
|
|
i.Unlock()
|
|
|
|
|
|
|
|
return ptr
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *functionIndex) get(ptr unsafe.Pointer) func(*Context) int {
|
|
|
|
i.RLock()
|
|
|
|
fn := i.functions[ptr]
|
|
|
|
i.RUnlock()
|
|
|
|
|
|
|
|
return fn
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *functionIndex) delete(ptr unsafe.Pointer) {
|
|
|
|
i.Lock()
|
|
|
|
delete(i.functions, ptr)
|
|
|
|
i.Unlock()
|
|
|
|
|
|
|
|
C.free(ptr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *functionIndex) destroy() {
|
|
|
|
i.Lock()
|
|
|
|
|
|
|
|
for ptr, _ := range i.functions {
|
|
|
|
delete(i.functions, ptr)
|
|
|
|
C.free(ptr)
|
|
|
|
}
|
|
|
|
i.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
type ctxIndex struct {
|
|
|
|
sync.RWMutex
|
|
|
|
ctxs map[unsafe.Pointer]*Context
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ci *ctxIndex) add(ctx *Context) unsafe.Pointer {
|
|
|
|
|
|
|
|
ci.RLock()
|
|
|
|
for ptr, ctxPtr := range ci.ctxs {
|
|
|
|
if ctxPtr == ctx {
|
|
|
|
ci.RUnlock()
|
|
|
|
return ptr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ci.RUnlock()
|
|
|
|
|
|
|
|
ci.Lock()
|
|
|
|
for ptr, ctxPtr := range ci.ctxs {
|
|
|
|
if ctxPtr == ctx {
|
|
|
|
ci.Unlock()
|
|
|
|
return ptr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ptr := C.malloc(1)
|
|
|
|
ci.ctxs[ptr] = ctx
|
|
|
|
ci.Unlock()
|
|
|
|
|
|
|
|
return ptr
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ci *ctxIndex) get(ptr unsafe.Pointer) *Context {
|
|
|
|
ci.RLock()
|
|
|
|
ctx := ci.ctxs[ptr]
|
|
|
|
ci.RUnlock()
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ci *ctxIndex) delete(ctx *Context) {
|
|
|
|
ci.Lock()
|
|
|
|
for ptr, ctxPtr := range ci.ctxs {
|
|
|
|
if ctxPtr == ctx {
|
|
|
|
delete(ci.ctxs, ptr)
|
|
|
|
C.free(ptr)
|
|
|
|
ci.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
panic(fmt.Sprintf("context (%p) doesn't exist", ctx))
|
|
|
|
}
|
|
|
|
|
|
|
|
var contexts *ctxIndex
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
contexts = &ctxIndex{
|
|
|
|
ctxs: make(map[unsafe.Pointer]*Context),
|
|
|
|
}
|
|
|
|
}
|