Skip to content

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

Open
SAY-5 wants to merge 1 commit into
phsym: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 into
phsym:mainfrom
SAY-5:fix/stringer-nil-pointer-panic-22

Conversation

@SAY-5

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

Copy link
Copy Markdown

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>
@SAY-5 SAY-5 force-pushed the fix/stringer-nil-pointer-panic-22 branch from fb35b8e to 0d8f44c Compare May 31, 2026 02:39
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