Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from May 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 19 additions & 5 deletions eth/tracers/tracer.go
Expand Up @@ -505,7 +505,7 @@ func (jst *Tracer) Stop(err error) {

// call executes a method on a JS object, catching any errors, formatting and
// 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
jst.vm.PushString(method)
for _, arg := range args {
Expand All @@ -519,7 +519,21 @@ func (jst *Tracer) call(method string, args ...string) (json.RawMessage, error)
return nil, errors.New(err)
}
// 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 {
Expand Down Expand Up @@ -578,7 +592,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
*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)
}
}
Expand All @@ -592,7 +606,7 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost
jst.errorValue = new(string)
*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)
}
}
Expand Down Expand Up @@ -640,7 +654,7 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) {
jst.vm.PutPropString(jst.stateObject, "ctx")

// 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 {
jst.err = wrapError("result", err)
}
Expand Down
14 changes: 9 additions & 5 deletions eth/tracers/tracer_test.go
Expand Up @@ -78,7 +78,7 @@ func runTrace(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) {
}

func TestTracer(t *testing.T) {
execTracer := func(code string) []byte {
execTracer := func(code string) ([]byte, string) {
t.Helper()
ctx := &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}}
tracer, err := New(code, ctx.txCtx)
Expand All @@ -87,13 +87,14 @@ func TestTracer(t *testing.T) {
}
ret, err := runTrace(tracer, ctx)
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 {
code string
want string
fail string
}{
{ // 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; }}",
Expand All @@ -116,10 +117,13 @@ func TestTracer(t *testing.T) {
}, { // tests intrinsic gas
code: "{depths: [], step: function() {}, fault: function() {}, result: function(ctx) { return ctx.gasPrice+'.'+ctx.gasUsed+'.'+ctx.intrinsicGas; }}",
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) {
t.Errorf("testcase %d: expected return value to be %s got %s\n\tcode: %v", i, tt.want, string(have), tt.code)
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', error to be '%s' got '%s'\n\tcode: %v", i, tt.want, string(have), tt.fail, err, tt.code)
}
}
}
Expand Down