From 6551ca2f1da8b3f4c47fefceb601adae5c0898e4 Mon Sep 17 00:00:00 2001 From: Inhere Date: Wed, 30 Mar 2022 22:32:47 +0800 Subject: [PATCH] fix: dump.Print strck overflow error on print cyclic value --- dump/_examples/demo.go | 20 ++++++++++- dump/_examples/demo_cyclic_ref.go | 34 ++++++++++++++++++ dump/_examples/go.mod | 3 +- dump/_examples/go.sum | 12 ++----- dump/dump_test.go | 39 ++++++++++++++++++++ dump/dumper.go | 59 +++++++++++++++++++++++-------- go.mod | 1 - go.sum | 12 +------ 8 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 dump/_examples/demo_cyclic_ref.go diff --git a/dump/_examples/demo.go b/dump/_examples/demo.go index 6ac5b077c..dba902021 100644 --- a/dump/_examples/demo.go +++ b/dump/_examples/demo.go @@ -1,10 +1,28 @@ package main -import "github.com/gookit/goutil/dump" +import ( + "time" + + "github.com/gookit/goutil/dump" +) // rum demo: // go run ./dump/_examples/demo.go func main() { + val := map[string]interface{}{ + "bool": true, + "number": 1 + 1i, + "bytes": []byte{97, 98, 99}, + "lines": "multiline string\nline two", + "slice": []interface{}{1, 2}, + "time": time.Now(), + "struct": struct{ test int32 }{ + test: 13, + }, + } + val["slice"].([]interface{})[1] = val["slice"] + dump.P(val) + return // dump.Config.ShowFile = true // dump.Config.ShowMethod = true otherFunc() diff --git a/dump/_examples/demo_cyclic_ref.go b/dump/_examples/demo_cyclic_ref.go new file mode 100644 index 000000000..da8ed4071 --- /dev/null +++ b/dump/_examples/demo_cyclic_ref.go @@ -0,0 +1,34 @@ +package main + +import ( + "time" + + "github.com/gookit/goutil/dump" +) + +// rum demo: +// go run ./dump/_examples/demo_cyclic_ref.go +func main() { + a := map[string]interface{}{} + a["circular"] = map[string]interface{}{ + "a": a, + } + + // TIP: will stack overflow + // fmt.Println(a) + dump.V(a) + + val := map[string]interface{}{ + "bool": true, + "number": 1 + 1i, + "bytes": []byte{97, 98, 99}, + "lines": "first line\nsecond line", + "slice": []interface{}{1, 2}, + "time": time.Now(), + "struct": struct{ test int32 }{ + test: 13, + }, + } + val["slice"].([]interface{})[1] = val["slice"] + dump.P(val) +} diff --git a/dump/_examples/go.mod b/dump/_examples/go.mod index 9089b11a9..0e8c5e8b6 100644 --- a/dump/_examples/go.mod +++ b/dump/_examples/go.mod @@ -1,8 +1,9 @@ module dump_example -go 1.13 +go 1.14 require ( + github.com/gookit/color v1.5.0 github.com/gookit/goutil v0.0.0-00010101000000-000000000000 github.com/kortschak/utter v1.0.1 github.com/kr/pretty v0.2.1 diff --git a/dump/_examples/go.sum b/dump/_examples/go.sum index 88c93ffba..9ddf4d80f 100644 --- a/dump/_examples/go.sum +++ b/dump/_examples/go.sum @@ -1,10 +1,7 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kortschak/utter v1.0.1 h1:AJVccwLrdrikvkH0aI5JKlzZIORLpfMeGBQ5tHfIXis= github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= @@ -14,15 +11,12 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/dump/dump_test.go b/dump/dump_test.go index 8d72ad326..c0b743994 100644 --- a/dump/dump_test.go +++ b/dump/dump_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "testing" + "time" "github.com/gookit/color" "github.com/stretchr/testify/assert" @@ -293,6 +294,44 @@ func TestFormat(t *testing.T) { fmt.Println(s) } +func TestPrint_over_max_depth(t *testing.T) { + a := map[string]interface{}{} + a["circular"] = map[string]interface{}{ + "a": a, + } + + // TIP: will stack overflow + // fmt.Println(a) + + P(a) + s := Format(a) + assert.NotEmpty(t, s) + assert.Contains(t, s, "!OVER MAX DEPTH!") +} + +func TestPrint_cyclic_slice(t *testing.T) { + a := map[string]interface{}{ + "bool": true, + "number": 1 + 1i, + "bytes": []byte{97, 98, 99}, + "lines": "first line\nsecond line", + "slice": []interface{}{1, 2}, + "time": time.Now(), + "struct": struct{ test int32 }{ + test: 13, + }, + } + a["slice"].([]interface{})[1] = a["slice"] + + // TIP: will stack overflow + // fmt.Println(a) + + P(a) + s := Format(a) + assert.NotEmpty(t, s) + assert.Contains(t, s, "!CYCLIC REFERENCE!") +} + func newBuffer() *bytes.Buffer { buf := new(bytes.Buffer) diff --git a/dump/dumper.go b/dump/dumper.go index fc27b5d93..642c42e68 100644 --- a/dump/dumper.go +++ b/dump/dumper.go @@ -231,7 +231,7 @@ func (d *Dumper) printOne(v interface{}) { func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { // var isPtr bool - // if is an ptr, get real type and value + // if is a ptr, get real type and value if t.Kind() == reflect.Ptr { if v.IsNil() { d.printf("%s,\n", t.String()) @@ -248,12 +248,18 @@ func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { d.indentPrint(t.String(), ", #invalid\n") } + // if v.CanAddr() && !d.checkCyclicRef(t, v) { + // return // don't print v again + // } + if d.curDepth > d.MaxDepth { - if !v.CanInterface() { - d.printf("%s,\n", v.String()) - } else { - d.printf("%#v,\n", v.Interface()) - } + // if !v.CanInterface() { + // d.printf("%s,\n", v.String()) + // } else { + // // v.Interface() will stack overflow on cyclic refer + // d.printf("%#v,\n", v.Interface()) + // } + d.printf("%s(!OVER MAX DEPTH!),\n", v.String()) return } @@ -277,6 +283,10 @@ func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { case reflect.Complex64, reflect.Complex128: d.printf("%#v\n", v.Complex()) case reflect.Slice, reflect.Array: + if v.CanAddr() && !d.checkCyclicRef(t, v) { + break // don't print v again + } + eleNum := v.Len() lenTip := d.ColorTheme.lenTip("#len=" + strconv.Itoa(eleNum)) @@ -296,14 +306,8 @@ func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { d.indentPrint("],\n") case reflect.Struct: - if v.CanAddr() { - addr := v.UnsafeAddr() - vis := visit{addr, t} - if vd, ok := d.visited[vis]; ok && vd < d.MaxDepth { - d.indentPrint(t.String(), "{(!CYCLIC REFERENCE!)}\n") - break // don't print v again - } - d.visited[vis] = d.curDepth + if v.CanAddr() && !d.checkCyclicRef(t, v) { + break // don't print v again } d.indentPrint(d.ColorTheme.msType(t.String()), " {\n") @@ -342,6 +346,11 @@ func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { d.printf("%#v: ", key.Interface()) } + if mv.CanAddr() && !d.checkCyclicRef(mv.Type(), mv) { + d.advance(-1) + continue // don't print mv again + } + // print field value d.msValue = true d.printRValue(mv.Type(), mv) @@ -352,6 +361,10 @@ func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { d.indentPrint("},\n") case reflect.Interface: + if v.CanAddr() && !d.checkCyclicRef(t, v) { + break // don't print v again + } + switch e := v.Elem(); { case e.Kind() == reflect.Invalid: d.indentPrint("nil,\n") @@ -371,6 +384,10 @@ func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { case reflect.Invalid: d.indentPrint(t.String(), "(nil),\n") default: + if v.CanAddr() && !d.checkCyclicRef(t, v) { + break // don't print v again + } + if v.CanInterface() { d.printf("%s(%#v),\n", t.String(), v.Interface()) } else { @@ -379,6 +396,20 @@ func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) { } } +func (d *Dumper) checkCyclicRef(t reflect.Type, v reflect.Value) (goon bool) { + addr := v.UnsafeAddr() + vis := visit{addr, t} + + if vd, ok := d.visited[vis]; ok && vd < d.MaxDepth { + d.indentPrint(t.String(), "{(!CYCLIC REFERENCE!)}\n") + return false // don't print v again + } + + // record + d.visited[vis] = d.curDepth + return true +} + func (d *Dumper) print(v ...interface{}) { if d.NoColor { _, _ = fmt.Fprint(d.Output, v...) diff --git a/go.mod b/go.mod index 4f62380cc..311d2041b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/gookit/color v1.5.0 github.com/mattn/go-isatty v0.0.14 github.com/mitchellh/go-homedir v1.1.0 - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/stretchr/testify v1.7.1 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c diff --git a/go.sum b/go.sum index e41852f1a..df388b955 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gookit/color v1.5.0 h1:1Opow3+BWDwqor78DcJkJCIwnkviFi+rrOANki9BUFw= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=