eth/tracers: do the JSON serialization via .js to capture C faults

pull/22857/head
Péter Szilágyi 4 years ago
parent ca98080798
commit 0524cede37
No known key found for this signature in database
GPG Key ID: E9AE538CEDF8293D
  1. 24
      eth/tracers/tracer.go
  2. 14
      eth/tracers/tracer_test.go

@ -505,7 +505,7 @@ func (jst *Tracer) Stop(err error) {
// call executes a method on a JS object, catching any errors, formatting and // call executes a method on a JS object, catching any errors, formatting and
// returning them as error objects. // returning them as error objects.
func (jst *Tracer) call(method string, args ...string) (json.RawMessage, error) { func (jst *Tracer) call(noret bool, method string, args ...string) (json.RawMessage, error) {
// Execute the JavaScript call and return any error // Execute the JavaScript call and return any error
jst.vm.PushString(method) jst.vm.PushString(method)
for _, arg := range args { for _, arg := range args {
@ -519,7 +519,21 @@ func (jst *Tracer) call(method string, args ...string) (json.RawMessage, error)
return nil, errors.New(err) return nil, errors.New(err)
} }
// No error occurred, extract return value and return // No error occurred, extract return value and return
return json.RawMessage(jst.vm.JsonEncode(-1)), nil if noret {
return nil, nil
}
// Push a JSON marshaller onto the stack. We can't marshal from the out-
// side because duktape can crash on large nestings and we can't catch
// C++ exceptions ourselves from Go. TODO(karalabe): Yuck, why wrap?!
jst.vm.PushString("(JSON.stringify)")
jst.vm.Eval()
jst.vm.Swap(-1, -2)
if code = jst.vm.Pcall(1); code != 0 {
err := jst.vm.SafeToString(-1)
return nil, errors.New(err)
}
return json.RawMessage(jst.vm.SafeToString(-1)), nil
} }
func wrapError(context string, err error) error { func wrapError(context string, err error) error {
@ -578,7 +592,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
*jst.errorValue = err.Error() *jst.errorValue = err.Error()
} }
if _, err := jst.call("step", "log", "db"); err != nil { if _, err := jst.call(true, "step", "log", "db"); err != nil {
jst.err = wrapError("step", err) jst.err = wrapError("step", err)
} }
} }
@ -592,7 +606,7 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
jst.errorValue = new(string) jst.errorValue = new(string)
*jst.errorValue = err.Error() *jst.errorValue = err.Error()
if _, err := jst.call("fault", "log", "db"); err != nil { if _, err := jst.call(true, "fault", "log", "db"); err != nil {
jst.err = wrapError("fault", err) jst.err = wrapError("fault", err)
} }
} }
@ -640,7 +654,7 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) {
jst.vm.PutPropString(jst.stateObject, "ctx") jst.vm.PutPropString(jst.stateObject, "ctx")
// Finalize the trace and return the results // Finalize the trace and return the results
result, err := jst.call("result", "ctx", "db") result, err := jst.call(false, "result", "ctx", "db")
if err != nil { if err != nil {
jst.err = wrapError("result", err) jst.err = wrapError("result", err)
} }

@ -78,7 +78,7 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
} }
func TestTracer(t *testing.T) { func TestTracer(t *testing.T) {
execTracer := func(code string) []byte { execTracer := func(code string) ([]byte, string) {
t.Helper() t.Helper()
ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
tracer, err := New(code, ctx.txCtx) tracer, err := New(code, ctx.txCtx)
@ -87,13 +87,14 @@ func TestTracer(t *testing.T) {
} }
ret, err := runTrace(tracer, ctx) ret, err := runTrace(tracer, ctx)
if err != nil { if err != nil {
t.Fatal(err) return nil, err.Error() // Stringify to allow comparison without nil checks
} }
return ret return ret, ""
} }
for i, tt := range []struct { for i, tt := range []struct {
code string code string
want string want string
fail string
}{ }{
{ // tests that we don't panic on bad arguments to memory access { // tests that we don't panic on bad arguments to memory access
code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", code: "{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}",
@ -116,10 +117,13 @@ func TestTracer(t *testing.T) {
}, { // tests intrinsic gas }, { // tests intrinsic gas
code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}", code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}",
want: `"100000.6.21000"`, want: `"100000.6.21000"`,
}, { // tests too deep object / serialization crash
code: "{step: function() {}, fault: function() {}, result: function() { var o={}; var x=o; for (var i=0; i<1000; i++){ o.foo={}; o=o.foo; } return x; }}",
fail: "RangeError: json encode recursion limit in server-side tracer function 'result'",
}, },
} { } {
if have := execTracer(tt.code); tt.want != string(have) { if have, err := execTracer(tt.code); tt.want != string(have) || tt.fail != err {
t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code) t.Errorf("testcase %d: expected return value to be '%s' got '%s', error to be '%s' got '%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code)
} }
} }
} }

Loading…
Cancel
Save