Skip to content

Commit 1114f39

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

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-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 { // NOTE: typed nils will not be caught by this check
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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ func (s stringVal) Equal(o any) bool {
3434
return ok && s.s == os.s
3535
}
3636

37+
type stringerVal struct {
38+
s string
39+
}
40+
41+
func (s stringerVal) String() string {
42+
return s.s
43+
}
44+
3745
func ExampleAttributes() {
3846
type keyOne struct{}
3947
type keyTwo struct{}
@@ -57,6 +65,33 @@ func ExampleAttributes_WithValue() {
5765
// Key two: {two}
5866
}
5967

68+
func ExampleAttributes_String() {
69+
type key struct{}
70+
var typedNil *stringerVal
71+
a1 := attributes.New(key{}, typedNil) // typed nil implements [fmt.Stringer]
72+
a2 := attributes.New(key{}, (*stringerVal)(nil)) // typed nil implements [fmt.Stringer]
73+
a3 := attributes.New(key{}, (*stringVal)(nil)) // typed nil not implements [fmt.Stringer]
74+
a4 := attributes.New(key{}, nil) // untyped nil
75+
a5 := attributes.New(key{}, 1)
76+
a6 := attributes.New(key{}, stringerVal{s: "two"})
77+
a7 := attributes.New(key{}, stringVal{s: "two"})
78+
fmt.Println("a1:", a1.String())
79+
fmt.Println("a2:", a2.String())
80+
fmt.Println("a3:", a3.String())
81+
fmt.Println("a4:", a4.String())
82+
fmt.Println("a5:", a5.String())
83+
fmt.Println("a6:", a6.String())
84+
fmt.Println("a7:", a7.String())
85+
// Output:
86+
// a1: {"<%!p(attributes_test.key={})>": "<nil>" }
87+
// a2: {"<%!p(attributes_test.key={})>": "<nil>" }
88+
// a3: {"<%!p(attributes_test.key={})>": "<0x0>" }
89+
// a4: {"<%!p(attributes_test.key={})>": "<nil>" }
90+
// a5: {"<%!p(attributes_test.key={})>": "<%!p(int=1)>" }
91+
// a6: {"<%!p(attributes_test.key={})>": "two" }
92+
// a7: {"<%!p(attributes_test.key={})>": "<%!p(attributes_test.stringVal={two})>" }
93+
}
94+
6095
// Test that two attributes with the same content are Equal.
6196
func TestEqual(t *testing.T) {
6297
type keyOne struct{}

0 commit comments

Comments
 (0)