@@ -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
3144var (
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.
7386func 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.
199212func 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.
208221func 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.
213226func 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.
219232func 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.
225238func 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
0 commit comments