Skip to content

Commit 3be80aa

Browse files
committed
attributes: catch panics during typed nil formatting by fmt.Stringer
1 parent d51b3f4 commit 3be80aa

File tree

2 files changed

+46
-2
lines changed

2 files changed

+46
-2
lines changed

attributes/attributes.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ package attributes
2727

2828
import (
2929
"fmt"
30+
"reflect"
3031
"strings"
3132
)
3233

@@ -121,8 +122,26 @@ func (a *Attributes) String() string {
121122
return sb.String()
122123
}
123124

124-
func str(x any) string {
125-
if v, ok := x.(fmt.Stringer); ok {
125+
func str(x any) (s string) {
126+
defer func() {
127+
if r := recover(); r != nil {
128+
// If it panics with a nil pointer, just say "<nil>". The likeliest causes are a
129+
// [fmt.Stringer] that fails to guard against nil or a nil pointer for a
130+
// value receiver, and in either case, "<nil>" is a nice result.
131+
//
132+
// Adapted from the code in fmt/print.go.
133+
if v := reflect.ValueOf(x); v.Kind() == reflect.Pointer && v.IsNil() {
134+
s = "<nil>"
135+
return
136+
}
137+
138+
// The panic was likely not caused by fmt.Stringer.
139+
panic(r)
140+
}
141+
}()
142+
if x == nil {
143+
return "<nil>"
144+
} else if v, ok := x.(fmt.Stringer); ok {
126145
return v.String()
127146
} else if v, ok := x.(string); ok {
128147
return v

attributes/attributes_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ type stringVal struct {
2929
s string
3030
}
3131

32+
func (s stringVal) String() string {
33+
return s.s
34+
}
35+
3236
func (s stringVal) Equal(o any) bool {
3337
os, ok := o.(stringVal)
3438
return ok && s.s == os.s
@@ -57,6 +61,27 @@ func ExampleAttributes_WithValue() {
5761
// Key two: {two}
5862
}
5963

64+
func ExampleString() {
65+
type keyOne struct{}
66+
type keyTwo struct{}
67+
type keyThree struct{}
68+
type keyFour struct{}
69+
a1 := attributes.New(keyOne{}, nil) // untyped nil
70+
a2 := attributes.New(keyTwo{}, (*stringVal)(nil)) // typed nil
71+
a3 := attributes.New(keyThree{}, 1)
72+
a4 := attributes.New(keyFour{}, stringVal{s: "two"})
73+
fmt.Println("a1:", a1.String())
74+
fmt.Println("a2:", a2.String())
75+
fmt.Println("a3:", a3.String())
76+
fmt.Println("a4:", a4.String())
77+
78+
// Output:
79+
// a1: {"<%!p(attributes_test.keyOne={})>": "<nil>" }
80+
// a2: {"<%!p(attributes_test.keyTwo={})>": "<nil>" }
81+
// a3: {"<%!p(attributes_test.keyThree={})>": "<%!p(int=1)>" }
82+
// a4: {"<%!p(attributes_test.keyFour={})>": "two" }
83+
}
84+
6085
// Test that two attributes with the same content are Equal.
6186
func TestEqual(t *testing.T) {
6287
type keyOne struct{}

0 commit comments

Comments
 (0)