Skip to content

Fix out-of-range panic on dangling escape in struct key (#604) - #605

Open
chiliec wants to merge 1 commit into
goccy:masterfrom
chiliec:fix/issue-604-key-dangling-escape
Open

Fix out-of-range panic on dangling escape in struct key (#604)#605
chiliec wants to merge 1 commit into
goccy:masterfrom
chiliec:fix/issue-604-key-dangling-escape

Conversation

@chiliec

@chiliec chiliec commented Aug 23, 2026

Copy link
Copy Markdown

What

Fixes #604Unmarshal panics with index out of range when a struct is decoded from a sequence of malformed inputs, one of which is an object key ending in a dangling escape (e.g. {"\).

Root cause

decodeKeyCharByEscapedChar (internal/decoder/struct.go) handled the known escape characters and \u, but for any other byte — including the nul terminator that unmarshal appends to the source buffer — it fell through to return nil, cursor, nil: no error, no consumed characters. When a key ended with \ immediately followed by that nul (input {"\), the key scanner therefore kept advancing the cursor past the end of the buffer instead of stopping.

Because the runtime context / buffer is taken from a sync.Pool, the bytes past the end are leftovers from a previous decode. That is why the crash only appears when more than one shape of malformed input is decoded in sequence, and why it is attributed to whichever input is current rather than to the sequence — matching the report exactly. The over-read eventually lands in skipWhiteSpace at context.go:49:

panic: runtime error: index out of range [7] with length 4
github.com/goccy/go-json/internal/decoder.skipWhiteSpace(...)
	internal/decoder/context.go:49
github.com/goccy/go-json/internal/decoder.(*structDecoder).Decode(...)
	internal/decoder/struct.go:786

The value-string decoder (stringDecoder.decodeByte) already treats nul after a backslash as ErrUnexpectedEndOfJSON; the key decoder did not. This mirrors that behavior.

Fix

Add a case nul to decodeKeyCharByEscapedChar that returns ErrUnexpectedEndOfJSON("escaped string", cursor), so a dangling escape in an object key now returns a clean error instead of over-reading the buffer. +2 lines.

Tests

Added TestIssue604 in decode_test.go (same style as the existing TestIssue429, which covers the sibling \u truncation cases): decodes the reporter's exact two inputs in a 20-iteration alternating loop and asserts every call returns an error (no panic).

Validation (real results, go1.27.0 linux/amd64)

RED→GREEN — revert only the source fix (keep the test), the test panics with the exact reported stack:

$ git stash push -- internal/decoder/struct.go && go test -run TestIssue604 .
panic: runtime error: index out of range [7] with length 4
	.../internal/decoder.skipWhiteSpace(...) internal/decoder/context.go:49
	.../internal/decoder.(*structDecoder).Decode(...) internal/decoder/struct.go:786
FAIL

Restore the fix → passes:

$ go test -run 'TestIssue604|TestIssue429' -v .
--- PASS: TestIssue429 (0.00s)
--- PASS: TestIssue604 (0.00s)
ok  github.com/goccy/go-json

Full suite green, gofmt clean:

$ go test ./...
ok  github.com/goccy/go-json           1.811s
ok  github.com/goccy/go-json/internal/decoder  0.009s
ok  github.com/goccy/go-json/internal/encoder  (cached)
ok  github.com/goccy/go-json/test/cover        1.000s
ok  github.com/goccy/go-json/test/example      0.018s
$ gofmt -l internal/decoder/struct.go decode_test.go   # (no output)

First-time contributor here — happy to adjust naming, test placement, or the error label if you'd prefer a different one.

decodeKeyCharByEscapedChar silently returned no error when an object key
ended with a backslash immediately followed by the buffer's nul terminator
(e.g. `{"\`). The caller then advanced the cursor past the end of the
buffer and read reused (pooled) memory, so a later Unmarshal in the same
sequence panicked with 'index out of range' in skipWhiteSpace.

Mirror the string-value decoder, which already treats nul after a
backslash as ErrUnexpectedEndOfJSON, so a dangling escape in a key now
returns a clean error instead of over-reading the buffer.

Fixes goccy#604
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.

Unmarshal panics on a sequence of malformed inputs into a struct (state carried between calls)

1 participant