Catch panics when calling GoString like fmt %#v does (#87)
This handles a few cases (similar to how fmt %#v does):
- A GoString method on a value receiver, called with a nil pointer
- A GoString method on a pointer receiver that doesn't check for nil
- A GoString method that panics in some other way
Because Go 1.17 added a method Time.GoString with value receiver, this
broke structs that had *time.Time fields with nil values (which is
common!).
Also added a bunch of tests for these cases.
Fixes #77
Co-authored-by: Jordan Barrett <jordan.barrett@canonical.com>
diff --git a/.gitignore b/.gitignore
index 1f0a99f..b2d4781 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
_go*
_test*
_obj
+/.idea
diff --git a/formatter.go b/formatter.go
index 249f089..8e6969c 100644
--- a/formatter.go
+++ b/formatter.go
@@ -92,6 +92,24 @@
typ reflect.Type
}
+func (p *printer) catchPanic(v reflect.Value, method string) {
+ if r := recover(); r != nil {
+ if v.Kind() == reflect.Ptr && v.IsNil() {
+ writeByte(p, '(')
+ io.WriteString(p, v.Type().String())
+ io.WriteString(p, ")(nil)")
+ return
+ }
+ writeByte(p, '(')
+ io.WriteString(p, v.Type().String())
+ io.WriteString(p, ")(PANIC=calling method ")
+ io.WriteString(p, strconv.Quote(method))
+ io.WriteString(p, ": ")
+ fmt.Fprint(p, r)
+ writeByte(p, ')')
+ }
+}
+
func (p *printer) printValue(v reflect.Value, showType, quote bool) {
if p.depth > 10 {
io.WriteString(p, "!%v(DEPTH EXCEEDED)")
@@ -101,6 +119,7 @@
if v.IsValid() && v.CanInterface() {
i := v.Interface()
if goStringer, ok := i.(fmt.GoStringer); ok {
+ defer p.catchPanic(v, "GoString")
io.WriteString(p, goStringer.GoString())
return
}
diff --git a/formatter_test.go b/formatter_test.go
index f23c700..6e27b27 100644
--- a/formatter_test.go
+++ b/formatter_test.go
@@ -5,6 +5,7 @@
"io"
"strings"
"testing"
+ "time"
"unsafe"
)
@@ -97,7 +98,7 @@
`[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`,
},
{F(5), "pretty.F(5)"},
- { NewStructWithPrivateFields("foo"), `NewStructWithPrivateFields("foo")`},
+ {NewStructWithPrivateFields("foo"), `NewStructWithPrivateFields("foo")`},
{
SA{&T{1, 2}, T{3, 4}},
`pretty.SA{
@@ -176,6 +177,41 @@
},
}`,
},
+ {(*time.Time)(nil), "(*time.Time)(nil)"},
+ {&ValueGoString{"vgs"}, `VGS vgs`},
+ {(*ValueGoString)(nil), `(*pretty.ValueGoString)(nil)`},
+ {(*VGSWrapper)(nil), `(*pretty.VGSWrapper)(nil)`},
+ {&PointerGoString{"pgs"}, `PGS pgs`},
+ {(*PointerGoString)(nil), "(*pretty.PointerGoString)(nil)"},
+ {&PanicGoString{"oops!"}, `(*pretty.PanicGoString)(PANIC=calling method "GoString": oops!)`},
+}
+
+type ValueGoString struct {
+ s string
+}
+
+func (g ValueGoString) GoString() string {
+ return "VGS " + g.s
+}
+
+type VGSWrapper struct {
+ ValueGoString
+}
+
+type PointerGoString struct {
+ s string
+}
+
+func (g *PointerGoString) GoString() string {
+ return "PGS " + g.s
+}
+
+type PanicGoString struct {
+ s string
+}
+
+func (g *PanicGoString) GoString() string {
+ panic(g.s)
}
func TestGoSyntax(t *testing.T) {