Skip to content

Commit

Permalink
Catch panics when calling GoString like fmt %#v does
Browse files Browse the repository at this point in the history
This handles a few cases (similar to how fmt %#v does):

1. A GoString method on a value receiver, called with a nil pointer
2. A GoString method on a pointer receiver that doesn't check for nil
3. 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 kr#77
  • Loading branch information
benhoyt committed Aug 18, 2022
1 parent d928460 commit 93720a3
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 1 deletion.
19 changes: 19 additions & 0 deletions formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,24 @@ type visit struct {
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=")
io.WriteString(p, method)
io.WriteString(p, " method: ")
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)")
Expand All @@ -101,6 +119,7 @@ func (p *printer) printValue(v reflect.Value, showType, quote bool) {
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
}
Expand Down
33 changes: 32 additions & 1 deletion formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"strings"
"testing"
"time"
"unsafe"
)

Expand Down Expand Up @@ -97,7 +98,7 @@ var gosyntax = []test{
`[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`,
},
{F(5), "pretty.F(5)"},
{ NewStructWithPrivateFields("foo"), `NewStructWithPrivateFields("foo")`},
{NewStructWithPrivateFields("foo"), `NewStructWithPrivateFields("foo")`},
{
SA{&T{1, 2}, T{3, 4}},
`pretty.SA{
Expand Down Expand Up @@ -176,6 +177,36 @@ var gosyntax = []test{
},
}`,
},
{(*time.Time)(nil), "(*time.Time)(nil)"},
{&ValueGoString{"vgs"}, `VGS vgs`},
{(*ValueGoString)(nil), `(*pretty.ValueGoString)(nil)`},
{&PointerGoString{"pgs"}, `PGS pgs`},
{(*PointerGoString)(nil), "(*pretty.PointerGoString)(nil)"},
{&PanicGoString{"oops!"}, "(*pretty.PanicGoString)(PANIC=GoString method: oops!)"},
}

type ValueGoString struct {
s string
}

func (g ValueGoString) GoString() string {
return "VGS " + g.s
}

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) {
Expand Down

0 comments on commit 93720a3

Please sign in to comment.