#1431·bun

newArrayParser indexes b[0] without length check, panicking on empty scan input

Author: krishna3554Created Sep 10, 2026Updated Sep 10, 2026

Summary

newArrayParser in dialect/pgdialect/array_parser.go indexes b[0] without checking length, so scanning an empty (zero-length) value panics with index out of range instead of returning a parse error. Scan paths must never panic.

Location

  • File: dialect/pgdialect/array_parser.go
  • Function: newArrayParser(b []byte)
  • Callers: dialect/pgdialect/array.goarrayScanner closure plus scanStringSliceValue/decodeStringSlice-adjacent slice scanners (all do p := newArrayParser(b) after toBytes(src))

Relevant code path (static analysis of current master):

func newArrayParser(b []byte) *arrayParser {
    p := new(arrayParser)
    if b[0] == 'n' {   // panics when len(b) == 0
        ...
    }
    if len(b) < 2 || ... {
        p.err = ...
    }
    ...
}

Contrast with newHStoreParser in the same package, which correctly guards: if len(b) != 0 && (len(b) < 6 || b[0] != '"').

Problem

src == nil is handled before toBytes, but src == "" (empty string) or empty bytes are not nil. toBytes("") yields a zero-length slice, and newArrayParser immediately dereferences b[0], panicking. Since this sits in the sql.Scanner path (arrayScannernewArrayParser(b)for p.Next()), a single unexpected empty value from the driver crashes the whole process instead of surfacing p.Err().

Trigger / Reproduction

Based on static analysis (no execution performed):

  1. Scan a value where toBytes(src) returns zero-length b (e.g. src is "" / empty []byte) into a slice/array destination via the pgdialect array scanner.
  2. newArrayParser(b) evaluates b[0] with len(b)==0 → runtime panic: index out of range [0] with length 0.

Existing TestArrayParser covers {}, {""}, [], etc., but has no empty-input case, consistent with the missing guard.

Note: this is a static-analysis finding; I did not run a live PostgreSQL instance.

Expected Behavior

Empty input should produce a controlled parse error (via p.err, surfaced through p.Err()) or be treated as an empty/NULL array per package convention — never a panic. Scan must always return error on bad input.

Actual Behavior

Empty input causes an unrecoverable panic in the scan path.

Impact

  • Any application scanning rows can be crashed by one unexpected empty array value (fail-closed data becomes fail-crash process).
  • Inconsistent with the sibling hstore parser and with Go sql.Scanner contracts.

Suggested Direction

  • Add a len(b) == 0 guard at the top of newArrayParser that sets p.err (e.g. pgdialect: can't parse array: "") and returns early, mirroring the existing len(b) < 2 error path. Optionally add an empty-input case to TestArrayParser asserting Err() != nil and no panic.

Evidence

  • Source via API: array_parser.go:newArrayParser unguarded b[0]; hstore_parser.go:newHStoreParser guarded len(b) check; array.go shows all scan closures funnel through newArrayParser(b) after only a src == nil check; array_parser_test.go shows no empty-input coverage.
  • Duplicate check: issue search for arrayParser empty panic returns total_count: 0, and the open-issue list contains no array-parser panic report — no apparent duplicate.

Classification

  • FACT: newArrayParser dereferences b[0] before any length check (verified in source via API).
  • INFERENCE: zero-length scan input therefore panics instead of returning an error.
  • HYPOTHESIS: a length guard returning a parse error fixes the crash with no behavior change for valid inputs.