Describe the bug
We've encountered panics when encoding composite types and arrays due to nil checks not handling non-nil interfaces with nil values. Since the nil check returns a false negative later code goes on to call a value receiver method and panic.
Here are a few places where we've encountered them:
-
-
Replacing these checks with one that also uses reflection fixes these panics. It's likely other codecs have similar code paths.
To Reproduce
The below example shows cases that cause both the array and composite codecs to trigger a panic.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
type Child struct {
field1 string
field2 int32
}
type Parent struct {
field1 int32
field2 *Child
}
type ParentWithSlice struct {
field1 int32
field2 []*Child
}
func main() {
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
_, err = conn.Exec(context.Background(),
`DROP TYPE IF EXISTS parent_with_slice_t CASCADE;
DROP TYPE IF EXISTS parent_t CASCADE;
DROP TYPE IF EXISTS child_t CASCADE;
CREATE TYPE child_t AS ( field1 text, field2 int4 );
CREATE TYPE parent_t AS ( field1 int4, field2 child_t );
CREATE TYPE parent_with_slice_t AS ( field1 int4, field2 child_t[] );
CREATE OR REPLACE PROCEDURE do_something(parent parent_t)
LANGUAGE plpgsql
AS $$
BEGIN
END;
$$;
CREATE OR REPLACE PROCEDURE do_something_else(parent parent_with_slice_t)
LANGUAGE plpgsql
AS $$
BEGIN
END;
$$;`)
if err != nil {
log.Fatal(err)
}
types, err := conn.LoadTypes(context.Background(), []string{"child_t", "parent_t", "parent_with_slice_t"})
if err != nil {
log.Fatal(err)
}
conn.TypeMap().RegisterTypes(types)
// composite codec panic
input1 := Parent{
field1: 1,
field2: nil,
}
_, err = conn.Exec(context.Background(), "CALL do_something( $1 )", input1)
if err != nil {
log.Fatal(err)
}
// array codec panic
input2 := ParentWithSlice{
field1: 1,
field2: []*Child{nil},
}
_, err = conn.Exec(context.Background(), "CALL do_something_else( $1 )", input2)
if err != nil {
log.Fatal(err)
}
}
func (value Child) IsNull() bool { return false }
func (value Child) Index(i int) any {
switch i {
case 0:
return value.field1
case 1:
return value.field2
}
return fmt.Errorf("%s unknown field requested - %d is out of bounds", "Child", i)
}
var _ pgtype.CompositeIndexGetter = Child{}
func (value Parent) IsNull() bool { return false }
func (value Parent) Index(i int) any {
switch i {
case 0:
return value.field1
case 1:
return value.field2
}
return fmt.Errorf("%s unknown field requested - %d is out of bounds", "Parent", i)
}
var _ pgtype.CompositeIndexGetter = Parent{}
func (value ParentWithSlice) IsNull() bool { return false }
func (value ParentWithSlice) Index(i int) any {
switch i {
case 0:
return value.field1
case 1:
return value.field2
}
return fmt.Errorf("%s unknown field requested - %d is out of bounds", "ParentWithSlice", i)
}
var _ pgtype.CompositeIndexGetter = ParentWithSlice{}
Please run your example with the race detector enabled. For example, go run -race main.go or go test -race.
Expected behavior
The above program to be able to encode the input values and run successfully.
Actual behavior
Executing the above go program results in a panic
panic: value method main.Child.IsNull called using nil *Child pointer
goroutine 1 [running]:
main.(*Child).IsNull(0xc10e80?)
<autogenerated>:1 +0x1b
github.com/jackc/pgx/v5/pgtype.(*encodePlanCompositeCodecCompositeIndexGetterToBinary).Encode(0xc000222190, {0x808960?, 0x0}, {0xc000236600, 0x18, 0x80})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/pgtype/composite.go:80 +0xba
github.com/jackc/pgx/v5/pgtype.(*CompositeBinaryBuilder).AppendValue(0xc0001779e0, 0x511d, {0x808960, 0x0})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/pgtype/composite.go:495 +0x36a
github.com/jackc/pgx/v5/pgtype.(*encodePlanCompositeCodecCompositeIndexGetterToBinary).Encode(0xc000222180, {0x8550e0?, 0xc000222170}, {0xc000236600, 0x0, 0x80})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/pgtype/composite.go:86 +0x1e6
github.com/jackc/pgx/v5/pgtype.(*Map).Encode(0xc000134600, 0x5120, 0x1, {0x8550e0, 0xc000222170}, {0xc000236600, 0x0, 0x80})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/pgtype/pgtype.go:1961 +0x142
github.com/jackc/pgx/v5.(*ExtendedQueryBuilder).encodeExtendedParamValue(0xc00015d980, 0xc000134600?, 0x5120?, 0x1?, {0x8550e0?, 0xc000222170?})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/extended_query_builder.go:125 +0xc6
github.com/jackc/pgx/v5.(*ExtendedQueryBuilder).appendParam(0xc00015d980, 0xc0001a2540?, 0x5120?, 0x1, {0x8550e0?, 0xc000222170?})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/extended_query_builder.go:79 +0x189
github.com/jackc/pgx/v5.(*ExtendedQueryBuilder).appendParam(0xc00015d980, 0xc000134600, 0x5120, 0xc0?, {0x8550e0, 0xc000222170})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/extended_query_builder.go:59 +0x10a
github.com/jackc/pgx/v5.(*ExtendedQueryBuilder).Build(0xc00015d980, 0xc000134600, 0xc00024e1e0, {0xc000222160, 0x1, 0xc000232600?})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/extended_query_builder.go:40 +0x3d0
github.com/jackc/pgx/v5.(*Conn).execPrepared(0xc00015d8c0, {0x938ed0, 0xc43360}, 0xc00024e1e0, {0xc000222160?, 0x892104?, 0x17?})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/conn.go:581 +0x5f
github.com/jackc/pgx/v5.(*Conn).exec(0xc00015d8c0, {0x938ed0, 0xc43360}, {0x892104, 0x17}, {0xc000222160?, 0x10?, 0x10?})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/conn.go:523 +0x370
github.com/jackc/pgx/v5.(*Conn).Exec(0xc00015d8c0, {0x938ed0?, 0xc43360?}, {0x892104, 0x17}, {0xc000222160, 0x1, 0x1})
/home/jaltavilla/go/pkg/mod/github.com/jackc/pgx/v5@v5.7.6/conn.go:466 +0x117
main.main()
/home/jaltavilla/dev/MGP-Server/pastegres-repro/main.go:55 +0x2ad
Version
- Go: go version go1.23.5 linux/amd64
- PostgreSQL: PostgreSQL 17.6 (Debian 17.6-1.pgdg11+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
- pgx: require github.com/jackc/pgx/v5 v5.7.6
Describe the bug
We've encountered panics when encoding composite types and arrays due to nil checks not handling non-nil interfaces with nil values. Since the nil check returns a false negative later code goes on to call a value receiver method and panic.
Here are a few places where we've encountered them:
pgx/pgtype/array_codec.go
Line 134 in 4c1308c
pgx/pgtype/composite.go
Line 479 in 4c1308c
Replacing these checks with one that also uses reflection fixes these panics. It's likely other codecs have similar code paths.
To Reproduce
The below example shows cases that cause both the array and composite codecs to trigger a panic.
Please run your example with the race detector enabled. For example,
go run -race main.goorgo test -race.Expected behavior
The above program to be able to encode the input values and run successfully.
Actual behavior
Executing the above go program results in a panic
Version