Skip to content

fix(encoding): render <nil> for typed-nil Stringer pointers instead of panicking#27

Open
SAY-5 wants to merge 1 commit intophsym:mainfrom
SAY-5:fix/stringer-nil-pointer-panic-22
Open

fix(encoding): render <nil> for typed-nil Stringer pointers instead of panicking#27
SAY-5 wants to merge 1 commit intophsym:mainfrom
SAY-5:fix/stringer-nil-pointer-panic-22

Conversation

@SAY-5
Copy link
Copy Markdown

@SAY-5 SAY-5 commented Apr 21, 2026

What

Fixes #22.

When an attribute value is a typed-nil pointer whose element type has a String method - the canonical case being (*time.Time)(nil), because time.Time.String auto-generates a pointer-receiver wrapper - the interface assertion case fmt.Stringer still matched. Calling v.String() then dispatched the method on a nil receiver and the runtime panicked with:

panic: value method time.Time.String called using nil *Time pointer

which took down the handler. In real apps this surfaced as a crash every time an optional *time.Time field was logged without being populated.

Fix

Guard the Stringer branch with reflect: if the interface value holds a nil pointer, write the literal <nil> and return - matching the default Go format for a nil Stringer. For any non-nil Stringer, or a Stringer that does not resolve to a pointer (struct-by-value implementations, custom types), behaviour is unchanged.

Tests

Added TestHandler_NilStringerPointer that passes a nil *time.Time as an attribute to the handler and asserts the output is exactly INF foobar expiration=<nil>\n rather than a panic. The test fails on master with the exact panic signature from the report; with the fix it passes.

Verification

Locally on macOS, go 1.26.2:

  • gofmt -s -l: clean
  • go vet ./...: clean
  • go test -race -count=1 ./...: full suite pass

Closes #22

…f panicking

When an attribute value is a typed-nil pointer whose element type has
a String method (the canonical case being (*time.Time)(nil), because
time.Time.String auto-generates a pointer receiver wrapper), the
interface assertion 'case fmt.Stringer' still matched, and calling
v.String() dispatched the method on a nil receiver. The runtime then
panicked with 'value method time.Time.String called using nil *Time
pointer' and took down the handler - surfacing in real apps as a
crash whenever an optional *time.Time field was unset (phsym#22).

Guard the Stringer branch with reflect: if the interface value holds
a nil pointer, write the literal <nil> and return, matching the
default Go format for a nil Stringer. For any non-nil Stringer, or a
Stringer that does not resolve to a pointer (struct-by-value
implementations, custom types), behaviour is unchanged.

Add a regression test that passes a nil *time.Time as an attribute
to the handler and asserts the output is 'INF foobar expiration=<nil>'
rather than a panic. The test fails on master with the exact panic
signature from the report.

Closes phsym#22

Signed-off-by: SAY-5 <SAY-5@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Panic when trying to log a nil *time.Time

1 participant