Skip to content

Commit 405d74c

Browse files
committed
chore(spew): fully relinted internalized spew
Signed-off-by: Frederic BIDON <[email protected]>
1 parent 5d7eb76 commit 405d74c

File tree

6 files changed

+492
-432
lines changed

6 files changed

+492
-432
lines changed

internal/spew/bypass.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const (
3939

4040
type flag uintptr
4141

42+
//nolint:gochecknoglobals // reflect internals, set once during init
4243
var (
4344
// flagRO indicates whether the value field of a reflect.Value
4445
// is read-only.
@@ -54,21 +55,33 @@ var (
5455
// it is in the lower 5 bits.
5556
const flagKindMask = flag(0x1f)
5657

58+
// Bit positions for the reflect.Value flags field.
59+
// Different Go versions use different layouts.
60+
const (
61+
flagROBit = 5 // read-only bit position
62+
flagROBit2 = 6 // secondary read-only bit (Go 1.5+)
63+
flagAddrBit14 = 7 // addressable bit position (Go 1.4)
64+
flagAddrBit15 = 8 // addressable bit position (Go 1.5+)
65+
)
66+
5767
// Different versions of Go have used different
5868
// bit layouts for the flags type. This table
5969
// records the known combinations.
70+
//
71+
//nolint:gochecknoglobals // immutable reflect flag lookup table
6072
var okFlags = []struct {
6173
ro, addr flag
6274
}{{
6375
// From Go 1.4 to 1.5
64-
ro: 1 << 5,
65-
addr: 1 << 7,
76+
ro: 1 << flagROBit,
77+
addr: 1 << flagAddrBit14,
6678
}, {
6779
// Up to Go tip.
68-
ro: 1<<5 | 1<<6,
69-
addr: 1 << 8,
80+
ro: 1<<flagROBit | 1<<flagROBit2,
81+
addr: 1 << flagAddrBit15,
7082
}}
7183

84+
//nolint:gochecknoglobals // computed once at init from reflect internals
7285
var flagValOffset = func() uintptr {
7386
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
7487
if !ok {
@@ -79,7 +92,7 @@ var flagValOffset = func() uintptr {
7992

8093
// flagField returns a pointer to the flag field of a reflect.Value.
8194
func flagField(v *reflect.Value) *flag {
82-
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
95+
return (*flag)(unsafe.Add(unsafe.Pointer(v), flagValOffset))
8396
}
8497

8598
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
@@ -103,6 +116,8 @@ func unsafeReflectValue(v reflect.Value) reflect.Value {
103116

104117
// Sanity checks against future reflect package changes
105118
// to the type or semantics of the Value.flag field.
119+
//
120+
//nolint:gochecknoinits // validates reflect internals at startup
106121
func init() {
107122
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
108123
if !ok {

internal/spew/common.go

Lines changed: 123 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,21 @@ import (
2626
"time"
2727
)
2828

29+
// Numeric formatting constants.
30+
const (
31+
decimalBase = 10
32+
hexBase = 16
33+
hexBufSize = 18 // max uint64 hex (16 digits) + "0x" prefix
34+
float32Precision = 32
35+
float64Precision = 64
36+
complex64Precision = 32 // real/imag each float32
37+
complex128Precision = 64 // real/imag each float64
38+
)
39+
2940
// Some constants in the form of bytes to avoid string overhead. This mirrors
3041
// the technique used in the fmt package.
42+
//
43+
//nolint:gochecknoglobals // immutable byte literals used as write constants
3144
var (
3245
panicBytes = []byte("(PANIC=")
3346
plusBytes = []byte("+")
@@ -66,16 +79,16 @@ var (
6679
)
6780

6881
// hexDigits is used to map a decimal value to a hex digit.
69-
var hexDigits = "0123456789abcdef"
82+
var hexDigits = "0123456789abcdef" //nolint:gochecknoglobals // immutable lookup table
7083

7184
// catchPanic handles any panics that might occur during the handleMethods
7285
// calls.
7386
func catchPanic(w io.Writer, v reflect.Value) {
7487
_ = v // currently we do not render the panic value
7588
if err := recover(); err != nil {
76-
w.Write(panicBytes)
77-
fmt.Fprintf(w, "%v", err)
78-
w.Write(closeParenBytes)
89+
_, _ = w.Write(panicBytes)
90+
_, _ = fmt.Fprintf(w, "%v", err)
91+
_, _ = w.Write(closeParenBytes)
7992
}
8093
}
8194

@@ -140,26 +153,26 @@ func handleErrorOrStringer(cs *ConfigState, w io.Writer, v reflect.Value) (handl
140153
switch iface := v.Interface().(type) {
141154
case error:
142155
if cs.ContinueOnMethod {
143-
w.Write(openParenBytes)
144-
w.Write([]byte(iface.Error()))
145-
w.Write(closeParenBytes)
146-
w.Write(spaceBytes)
156+
_, _ = w.Write(openParenBytes)
157+
_, _ = w.Write([]byte(iface.Error()))
158+
_, _ = w.Write(closeParenBytes)
159+
_, _ = w.Write(spaceBytes)
147160
return false, true
148161
}
149162

150-
w.Write([]byte(iface.Error()))
163+
_, _ = w.Write([]byte(iface.Error()))
151164
return true, false
152165

153166
case fmt.Stringer:
154167
if cs.ContinueOnMethod {
155-
w.Write(openParenBytes)
156-
w.Write([]byte(iface.String()))
157-
w.Write(closeParenBytes)
158-
w.Write(spaceBytes)
168+
_, _ = w.Write(openParenBytes)
169+
_, _ = w.Write([]byte(iface.String()))
170+
_, _ = w.Write(closeParenBytes)
171+
_, _ = w.Write(spaceBytes)
159172
return false, true
160173
}
161174

162-
w.Write([]byte(iface.String()))
175+
_, _ = w.Write([]byte(iface.String()))
163176
return true, false
164177

165178
default:
@@ -198,41 +211,41 @@ func handleErrorOrStringer(cs *ConfigState, w io.Writer, v reflect.Value) (handl
198211
// printBool outputs a boolean value as true or false to Writer w.
199212
func printBool(w io.Writer, val bool) {
200213
if val {
201-
w.Write(trueBytes)
214+
_, _ = w.Write(trueBytes)
202215
} else {
203-
w.Write(falseBytes)
216+
_, _ = w.Write(falseBytes)
204217
}
205218
}
206219

207220
// printInt outputs a signed integer value to Writer w.
208221
func printInt(w io.Writer, val int64, base int) { //nolint: unparam // we leave base even though it is alway 10, to remain consistent with FormatInt
209-
w.Write([]byte(strconv.FormatInt(val, base)))
222+
_, _ = w.Write([]byte(strconv.FormatInt(val, base)))
210223
}
211224

212225
// printUint outputs an unsigned integer value to Writer w.
213226
func printUint(w io.Writer, val uint64, base int) {
214-
w.Write([]byte(strconv.FormatUint(val, base)))
227+
_, _ = w.Write([]byte(strconv.FormatUint(val, base)))
215228
}
216229

217230
// printFloat outputs a floating point value using the specified precision,
218231
// which is expected to be 32 or 64bit, to Writer w.
219232
func printFloat(w io.Writer, val float64, precision int) {
220-
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
233+
_, _ = w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
221234
}
222235

223236
// printComplex outputs a complex value using the specified float precision
224237
// for the real and imaginary parts to Writer w.
225238
func printComplex(w io.Writer, c complex128, floatPrecision int) {
226239
r := real(c)
227-
w.Write(openParenBytes)
228-
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
240+
_, _ = w.Write(openParenBytes)
241+
_, _ = w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
229242
i := imag(c)
230243
if i >= 0 {
231-
w.Write(plusBytes)
244+
_, _ = w.Write(plusBytes)
232245
}
233-
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
234-
w.Write(iBytes)
235-
w.Write(closeParenBytes)
246+
_, _ = w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
247+
_, _ = w.Write(iBytes)
248+
_, _ = w.Write(closeParenBytes)
236249
}
237250

238251
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
@@ -241,15 +254,15 @@ func printHexPtr(w io.Writer, p uintptr) {
241254
// Null pointer.
242255
num := uint64(p)
243256
if num == 0 {
244-
w.Write(nilAngleBytes)
257+
_, _ = w.Write(nilAngleBytes)
245258
return
246259
}
247260

248261
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
249-
buf := make([]byte, 18)
262+
buf := make([]byte, hexBufSize)
250263

251264
// It's simpler to construct the hex string right to left.
252-
base := uint64(16)
265+
base := uint64(hexBase)
253266
i := len(buf) - 1
254267
for num >= base {
255268
buf[i] = hexDigits[num%base]
@@ -266,7 +279,88 @@ func printHexPtr(w io.Writer, p uintptr) {
266279

267280
// Strip unused leading bytes.
268281
buf = buf[i:]
269-
w.Write(buf)
282+
_, _ = w.Write(buf)
283+
}
284+
285+
// tryHandleMethods checks whether the value should be handled by its
286+
// Stringer/error methods and invokes them if so. Returns true if the value
287+
// was handled.
288+
func tryHandleMethods(cs *ConfigState, w io.Writer, v reflect.Value, kind reflect.Kind) bool {
289+
if cs.DisableMethods && (!cs.EnableTimeStringer || !isTime(v)) {
290+
return false
291+
}
292+
if kind == reflect.Invalid || kind == reflect.Interface {
293+
return false
294+
}
295+
return handleMethods(cs, w, v)
296+
}
297+
298+
// ptrResolution holds the result of walking a pointer chain.
299+
type ptrResolution struct {
300+
value reflect.Value
301+
pointerChain []uintptr
302+
indirects int
303+
nilFound bool
304+
cycleFound bool
305+
}
306+
307+
// resolvePtr walks a pointer chain, dereferencing pointers and unpacking
308+
// interfaces while detecting circular references. It returns the final
309+
// resolved value along with metadata about the chain.
310+
func resolvePtr(v reflect.Value, depth int, pointers map[uintptr]int) ptrResolution {
311+
r := ptrResolution{value: v}
312+
313+
for r.value.Kind() == reflect.Pointer {
314+
if r.value.IsNil() {
315+
r.nilFound = true
316+
return r
317+
}
318+
319+
r.indirects++
320+
addr := r.value.Pointer()
321+
r.pointerChain = append(r.pointerChain, addr)
322+
if pd, ok := pointers[addr]; ok && pd < depth {
323+
r.cycleFound = true
324+
r.indirects--
325+
return r
326+
}
327+
pointers[addr] = depth
328+
329+
r.value = r.value.Elem()
330+
if r.value.Kind() == reflect.Interface {
331+
if done := r.unpackInterface(depth, pointers); done {
332+
return r
333+
}
334+
}
335+
}
336+
337+
return r
338+
}
339+
340+
// unpackInterface unpacks an interface value found during pointer resolution.
341+
// It returns true if the resolution is complete (nil or cycle found).
342+
func (r *ptrResolution) unpackInterface(depth int, pointers map[uintptr]int) bool {
343+
if r.value.IsNil() {
344+
r.nilFound = true
345+
return true
346+
}
347+
r.value = r.value.Elem()
348+
349+
if r.value.Kind() != reflect.Pointer {
350+
return false
351+
}
352+
if r.value.IsNil() {
353+
r.nilFound = true
354+
return true
355+
}
356+
addr := r.value.Pointer()
357+
if pd, ok := pointers[addr]; ok && pd <= depth {
358+
r.cycleFound = true
359+
r.indirects--
360+
return true
361+
}
362+
pointers[addr] = depth
363+
return false
270364
}
271365

272366
// valuesSorter implements sort.Interface to allow a slice of reflect.Value

internal/spew/config.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ var Config = ConfigState{ //nolint:gochecknoglobals // this is global configurat
113113
EnableTimeStringer: true,
114114
}
115115

116+
// NewDefaultConfig returns a ConfigState with the following default settings.
117+
//
118+
// Indent: " "
119+
// MaxDepth: 0
120+
// DisableMethods: false
121+
// DisablePointerMethods: false
122+
// ContinueOnMethod: false
123+
// SortKeys: false
124+
func NewDefaultConfig() *ConfigState {
125+
return &ConfigState{Indent: " "}
126+
}
127+
116128
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
117129
// passed with a Formatter interface returned by c.NewFormatter. It returns
118130
// the formatted string as a value that satisfies error. See NewFormatter
@@ -169,7 +181,7 @@ func (c *ConfigState) Fprintln(w io.Writer, a ...any) (n int, err error) {
169181
//
170182
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
171183
func (c *ConfigState) Print(a ...any) (n int, err error) {
172-
return fmt.Print(c.convertArgs(a)...)
184+
return fmt.Print(c.convertArgs(a)...) //nolint:forbidigo // public API wrapping fmt.Print
173185
}
174186

175187
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
@@ -181,7 +193,7 @@ func (c *ConfigState) Print(a ...any) (n int, err error) {
181193
//
182194
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
183195
func (c *ConfigState) Printf(format string, a ...any) (n int, err error) {
184-
return fmt.Printf(format, c.convertArgs(a)...)
196+
return fmt.Printf(format, c.convertArgs(a)...) //nolint:forbidigo // public API wrapping fmt.Printf
185197
}
186198

187199
// Println is a wrapper for fmt.Println that treats each argument as if it were
@@ -193,7 +205,7 @@ func (c *ConfigState) Printf(format string, a ...any) (n int, err error) {
193205
//
194206
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
195207
func (c *ConfigState) Println(a ...any) (n int, err error) {
196-
return fmt.Println(c.convertArgs(a)...)
208+
return fmt.Println(c.convertArgs(a)...) //nolint:forbidigo // public API wrapping fmt.Println
197209
}
198210

199211
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
@@ -302,15 +314,3 @@ func (c *ConfigState) convertArgs(args []any) (formatters []any) {
302314
}
303315
return formatters
304316
}
305-
306-
// NewDefaultConfig returns a ConfigState with the following default settings.
307-
//
308-
// Indent: " "
309-
// MaxDepth: 0
310-
// DisableMethods: false
311-
// DisablePointerMethods: false
312-
// ContinueOnMethod: false
313-
// SortKeys: false
314-
func NewDefaultConfig() *ConfigState {
315-
return &ConfigState{Indent: " "}
316-
}

0 commit comments

Comments
 (0)