public inbox for [email protected]  
help / color / mirror / Atom feed
BUG #19491: Segmentation fault triggered by IS NULL
3+ messages / 3 participants
[nested] [flat]

* BUG #19491: Segmentation fault triggered by IS NULL
@ 2026-05-22 06:27 PG Bug reporting form <[email protected]>
  2026-05-25 15:12 ` Re: BUG #19491: Segmentation fault triggered by IS NULL Ayush Tiwari <[email protected]>
  0 siblings, 1 reply; 3+ messages in thread

From: PG Bug reporting form @ 2026-05-22 06:27 UTC (permalink / raw)
  To: [email protected]; +Cc: [email protected]

The following bug has been logged on the website:

Bug reference:      19491
Logged by:          Chi Zhang
Email address:      [email protected]
PostgreSQL version: 18.4
Operating system:   Ubuntu 24.04
Description:        

Hi,

I found that the following test case triggers a segmentation fault.

```
CREATE SCHEMA IF NOT EXISTS poc;

CREATE FUNCTION poc.mystring_in(cstring)
    RETURNS poc.mystring
    AS 'textin' LANGUAGE internal IMMUTABLE STRICT;

CREATE FUNCTION poc.mystring_out(poc.mystring)
    RETURNS cstring
    AS 'textout' LANGUAGE internal IMMUTABLE STRICT;

CREATE TYPE poc.mystring (
    INPUT = poc.mystring_in,
    OUTPUT = poc.mystring_out,
    LIKE = text,
    CATEGORY = 'S'
);

SELECT '{"a":1}'::poc.mystring IS JSON;

DROP SCHEMA IF EXISTS poc CASCADE;
```

This is the output:

```
sqlancer=# CREATE SCHEMA IF NOT EXISTS poc;
CREATE SCHEMA
sqlancer=# CREATE FUNCTION poc.mystring_in(cstring)
    RETURNS poc.mystring
    AS 'textin' LANGUAGE internal IMMUTABLE STRICT;
NOTICE:  type "poc.mystring" is not yet defined
DETAIL:  Creating a shell type definition.
CREATE FUNCTION
sqlancer=# CREATE FUNCTION poc.mystring_out(poc.mystring)
    RETURNS cstring
    AS 'textout' LANGUAGE internal IMMUTABLE STRICT;
NOTICE:  argument type poc.mystring is only a shell
LINE 1: CREATE FUNCTION poc.mystring_out(poc.mystring)
                                         ^
CREATE FUNCTION
sqlancer=# CREATE TYPE poc.mystring (
    INPUT = poc.mystring_in,
    OUTPUT = poc.mystring_out,
    LIKE = text,
    CATEGORY = 'S'
);
CREATE TYPE
sqlancer=# SELECT '{"a":1}'::poc.mystring IS JSON;
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Succeeded.
```

This is the log:

```
2026-05-22 06:19:00.811 UTC [1] LOG:  starting PostgreSQL 18.1 (Debian
18.1-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19)
14.2.0, 64-bit
2026-05-22 06:19:00.811 UTC [1] LOG:  listening on IPv4 address "0.0.0.0",
port 5432
2026-05-22 06:19:00.811 UTC [1] LOG:  listening on IPv6 address "::", port
5432
2026-05-22 06:19:00.816 UTC [1] LOG:  listening on Unix socket
"/var/run/postgresql/.s.PGSQL.5432"
2026-05-22 06:19:00.825 UTC [32] LOG:  database system was shut down at
2026-05-22 06:18:54 UTC
2026-05-22 06:19:00.830 UTC [1] LOG:  database system is ready to accept
connections
2026-05-22 06:21:48.272 UTC [1] LOG:  client backend (PID 42) was terminated
by signal 11: Segmentation fault
2026-05-22 06:21:48.272 UTC [1] DETAIL:  Failed process was running: SELECT
'{"a":1}'::poc.mystring IS JSON;
2026-05-22 06:21:48.272 UTC [1] LOG:  terminating any other active server
processes
2026-05-22 06:21:48.273 UTC [1] LOG:  all server processes terminated;
reinitializing
2026-05-22 06:21:48.324 UTC [49] LOG:  database system was interrupted; last
known up at 2026-05-22 06:19:00 UTC
2026-05-22 06:21:48.428 UTC [49] LOG:  database system was not properly shut
down; automatic recovery in progress
2026-05-22 06:21:48.431 UTC [49] LOG:  redo starts at 0/1C07F50
2026-05-22 06:21:48.432 UTC [49] LOG:  invalid record length at 0/1C1AE88:
expected at least 24, got 0
2026-05-22 06:21:48.432 UTC [49] LOG:  redo done at 0/1C1AE00 system usage:
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
2026-05-22 06:21:48.437 UTC [50] LOG:  checkpoint starting: end-of-recovery
immediate wait
2026-05-22 06:21:48.450 UTC [50] LOG:  checkpoint complete: wrote 15 buffers
(0.1%), wrote 3 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled;
write=0.003 s, sync=0.004 s, total=0.016 s; sync files=16, longest=0.002 s,
average=0.001 s; distance=75 kB, estimate=75 kB; lsn=0/1C1AE88, redo
lsn=0/1C1AE88
2026-05-22 06:21:48.455 UTC [1] LOG:  database system is ready to accept
connections
```

I can reproduce this on the github commit
d8af73010033cb8ad6c941942d6b03d74d7e4f7e








^ permalink  raw  reply  [nested|flat] 3+ messages in thread

* Re: BUG #19491: Segmentation fault triggered by IS NULL
  2026-05-22 06:27 BUG #19491: Segmentation fault triggered by IS NULL PG Bug reporting form <[email protected]>
@ 2026-05-25 15:12 ` Ayush Tiwari <[email protected]>
  2026-05-25 16:36   ` Re: BUG #19491: Segmentation fault triggered by IS NULL Srinath Reddy Sadipiralla <[email protected]>
  0 siblings, 1 reply; 3+ messages in thread

From: Ayush Tiwari @ 2026-05-25 15:12 UTC (permalink / raw)
  To: [email protected]; [email protected]; Álvaro Herrera <[email protected]>

Hi,

On Mon, 25 May 2026 at 18:57, PG Bug reporting form <[email protected]>
wrote:

> The following bug has been logged on the website:
>
> Bug reference:      19491
> Logged by:          Chi Zhang
> Email address:      [email protected]
> PostgreSQL version: 18.4
> Operating system:   Ubuntu 24.04
> Description:
>
> Hi,
>
> I found that the following test case triggers a segmentation fault.
>
> ```
> CREATE SCHEMA IF NOT EXISTS poc;
>
> CREATE FUNCTION poc.mystring_in(cstring)
>     RETURNS poc.mystring
>     AS 'textin' LANGUAGE internal IMMUTABLE STRICT;
>
> CREATE FUNCTION poc.mystring_out(poc.mystring)
>     RETURNS cstring
>     AS 'textout' LANGUAGE internal IMMUTABLE STRICT;
>
> CREATE TYPE poc.mystring (
>     INPUT = poc.mystring_in,
>     OUTPUT = poc.mystring_out,
>     LIKE = text,
>     CATEGORY = 'S'
> );
>
> SELECT '{"a":1}'::poc.mystring IS JSON;
>
> DROP SCHEMA IF EXISTS poc CASCADE;
> ```
>
> This is the output:
>
> ```
> sqlancer=# CREATE SCHEMA IF NOT EXISTS poc;
> CREATE SCHEMA
> sqlancer=# CREATE FUNCTION poc.mystring_in(cstring)
>     RETURNS poc.mystring
>     AS 'textin' LANGUAGE internal IMMUTABLE STRICT;
> NOTICE:  type "poc.mystring" is not yet defined
> DETAIL:  Creating a shell type definition.
> CREATE FUNCTION
> sqlancer=# CREATE FUNCTION poc.mystring_out(poc.mystring)
>     RETURNS cstring
>     AS 'textout' LANGUAGE internal IMMUTABLE STRICT;
> NOTICE:  argument type poc.mystring is only a shell
> LINE 1: CREATE FUNCTION poc.mystring_out(poc.mystring)
>                                          ^
> CREATE FUNCTION
> sqlancer=# CREATE TYPE poc.mystring (
>     INPUT = poc.mystring_in,
>     OUTPUT = poc.mystring_out,
>     LIKE = text,
>     CATEGORY = 'S'
> );
> CREATE TYPE
> sqlancer=# SELECT '{"a":1}'::poc.mystring IS JSON;
> server closed the connection unexpectedly
>         This probably means the server terminated abnormally
>         before or while processing the request.
> The connection to the server was lost. Attempting reset: Succeeded.
> ```
>

Thanks for the report.  I was able to reproduce this on HEAD with your SQL.
The crash is in the executor while building expression state for the IS JSON
predicate -- ExecInitExprRec() ends up being called with a NULL node:

  #0  ExecInitExprRec(node=0x0, ...)        execExpr.c:966
  #1  ExecInitExprRec(<JsonIsPredicate>)    execExpr.c:2507
  #2  ExecBuildProjectionInfo(...)          execExpr.c:511

The NULL comes from transformJsonParseArg().  For UNKNOWN or string-category
input types it implicitly coerces to text via coerce_to_target_type() and
then sets exprtype to TEXTOID  without checking if the coercion succeeded.
poc.mystring is in CATEGORY = 'S' but has no implicit cast to text, so
coerce_to_target_type() returns NULL; the parser still claims it's text and
that NULL ends up as the JsonIsPredicate subject.

String-category alone isn't a promise of text-coercibility -- adding
CREATE CAST (poc.mystring AS text) ... AS IMPLICIT makes the same query
works fine.  I guess the parser just shouldn't assume the coercion worked?

The smallest fix I could see is to only update expr / exprtype when
coerce_to_target_type() returns non-NULL.  Then transformJsonIsPredicate()
(and the similar JSON() WITH UNIQUE KEYS path) raise their existing
"cannot use type X" errors instead of crashing:

  ERROR:  cannot use type poc.mystring in IS JSON predicate

Attached is a small patch with that fix and a regression test in sqljson.

Blame points at 6ee30209a6f1 (March 2023), so this looks like it goes back
to v16.

Regards,
Ayush


Attachments:

  [application/octet-stream] v1-0001-Avoid-NULL-IS-JSON-expressions-after-failed-text-.patch (5.6K, 3-v1-0001-Avoid-NULL-IS-JSON-expressions-after-failed-text-.patch)
  download | inline diff:
From 1ac215fb7a7910b866e383ed4dae0623e1585678 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <[email protected]>
Date: Mon, 25 May 2026 15:05:45 +0000
Subject: [PATCH v1] Avoid NULL IS JSON expressions after failed text coercion

transformJsonParseArg() tries to coerce UNKNOWN and string-category input
types to text before building JSON parse and IS JSON expressions.  It did
not check whether coerce_to_target_type() succeeded before replacing the
expression and setting exprtype to TEXTOID.

For a user-defined string-category type without an implicit cast to text,
coerce_to_target_type() returns NULL.  IS JSON then stores that NULL as
the predicate subject, and the executor crashes while initializing
expression state.

Only replace the expression when coercion succeeds.  Otherwise leave the
original type in place so callers raise the existing datatype errors.
---
 src/backend/parser/parse_expr.c       | 16 +++++++----
 src/test/regress/expected/sqljson.out | 39 +++++++++++++++++++++++++++
 src/test/regress/sql/sqljson.sql      | 20 ++++++++++++++
 3 files changed, 70 insertions(+), 5 deletions(-)

diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f1003e57fb2..8b8a1e985d6 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4199,11 +4199,17 @@ transformJsonParseArg(ParseState *pstate, Node *jsexpr, JsonFormat *format,
 
 		if (*exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING)
 		{
-			expr = coerce_to_target_type(pstate, expr, *exprtype,
-										 TEXTOID, -1,
-										 COERCION_IMPLICIT,
-										 COERCE_IMPLICIT_CAST, -1);
-			*exprtype = TEXTOID;
+			Node	   *coerced_expr;
+
+			coerced_expr = coerce_to_target_type(pstate, expr, *exprtype,
+												 TEXTOID, -1,
+												 COERCION_IMPLICIT,
+												 COERCE_IMPLICIT_CAST, -1);
+			if (coerced_expr != NULL)
+			{
+				expr = coerced_expr;
+				*exprtype = TEXTOID;
+			}
 		}
 
 		if (format->encoding != JS_ENC_DEFAULT)
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 143d961c077..ecfa03a76a2 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -1470,6 +1470,45 @@ LINE 1: SELECT NULL::jd5 IS JSON WITH UNIQUE KEYS;
 -- domain constraint violation during cast
 SELECT a::jd2 IS JSON WITH UNIQUE KEYS as col1 FROM (VALUES('{"a": 1, "a": 2}')) s(a); -- error
 ERROR:  value for domain jd2 violates check constraint "jd2_check"
+-- A user-defined string-category type with no implicit cast to text must
+-- produce a clean error rather than crash for IS JSON / JSON() input
+-- (per bug #19491).
+CREATE FUNCTION sqljson_mystr_in(cstring) RETURNS sqljson_mystr
+	AS 'textin' LANGUAGE internal IMMUTABLE STRICT;
+NOTICE:  type "sqljson_mystr" is not yet defined
+DETAIL:  Creating a shell type definition.
+CREATE FUNCTION sqljson_mystr_out(sqljson_mystr) RETURNS cstring
+	AS 'textout' LANGUAGE internal IMMUTABLE STRICT;
+NOTICE:  argument type sqljson_mystr is only a shell
+LINE 1: CREATE FUNCTION sqljson_mystr_out(sqljson_mystr) RETURNS cst...
+                                          ^
+CREATE TYPE sqljson_mystr (
+	INPUT = sqljson_mystr_in,
+	OUTPUT = sqljson_mystr_out,
+	LIKE = text,
+	CATEGORY = 'S'
+);
+SELECT '{"a":1}'::sqljson_mystr IS JSON;                -- error
+ERROR:  cannot use type sqljson_mystr in IS JSON predicate
+LINE 1: SELECT '{"a":1}'::sqljson_mystr IS JSON;
+               ^
+SELECT JSON('{"a":1}'::sqljson_mystr WITH UNIQUE KEYS); -- error
+ERROR:  cannot use non-string types with WITH UNIQUE KEYS clause
+LINE 1: SELECT JSON('{"a":1}'::sqljson_mystr WITH UNIQUE KEYS);
+               ^
+-- An implicit cast to text lets the same query work normally.
+CREATE CAST (sqljson_mystr AS text) WITHOUT FUNCTION AS IMPLICIT;
+SELECT '{"a":1}'::sqljson_mystr IS JSON;
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TYPE sqljson_mystr CASCADE;
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to function sqljson_mystr_in(cstring)
+drop cascades to function sqljson_mystr_out(sqljson_mystr)
+drop cascades to cast from sqljson_mystr to text
 -- view creation and deparsing with domain IS JSON
 CREATE VIEW domain_isjson AS
 WITH cte(a) AS (VALUES('{"a": 1, "a": 2}'))
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index ed044d81fdd..689af6f0681 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -559,6 +559,26 @@ SELECT NULL::jd5 IS JSON WITH UNIQUE KEYS; -- error
 -- domain constraint violation during cast
 SELECT a::jd2 IS JSON WITH UNIQUE KEYS as col1 FROM (VALUES('{"a": 1, "a": 2}')) s(a); -- error
 
+-- A user-defined string-category type with no implicit cast to text must
+-- produce a clean error rather than crash for IS JSON / JSON() input
+-- (per bug #19491).
+CREATE FUNCTION sqljson_mystr_in(cstring) RETURNS sqljson_mystr
+	AS 'textin' LANGUAGE internal IMMUTABLE STRICT;
+CREATE FUNCTION sqljson_mystr_out(sqljson_mystr) RETURNS cstring
+	AS 'textout' LANGUAGE internal IMMUTABLE STRICT;
+CREATE TYPE sqljson_mystr (
+	INPUT = sqljson_mystr_in,
+	OUTPUT = sqljson_mystr_out,
+	LIKE = text,
+	CATEGORY = 'S'
+);
+SELECT '{"a":1}'::sqljson_mystr IS JSON;                -- error
+SELECT JSON('{"a":1}'::sqljson_mystr WITH UNIQUE KEYS); -- error
+-- An implicit cast to text lets the same query work normally.
+CREATE CAST (sqljson_mystr AS text) WITHOUT FUNCTION AS IMPLICIT;
+SELECT '{"a":1}'::sqljson_mystr IS JSON;
+DROP TYPE sqljson_mystr CASCADE;
+
 -- view creation and deparsing with domain IS JSON
 CREATE VIEW domain_isjson AS
 WITH cte(a) AS (VALUES('{"a": 1, "a": 2}'))
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 3+ messages in thread

* Re: BUG #19491: Segmentation fault triggered by IS NULL
  2026-05-22 06:27 BUG #19491: Segmentation fault triggered by IS NULL PG Bug reporting form <[email protected]>
  2026-05-25 15:12 ` Re: BUG #19491: Segmentation fault triggered by IS NULL Ayush Tiwari <[email protected]>
@ 2026-05-25 16:36   ` Srinath Reddy Sadipiralla <[email protected]>
  0 siblings, 0 replies; 3+ messages in thread

From: Srinath Reddy Sadipiralla @ 2026-05-25 16:36 UTC (permalink / raw)
  To: Ayush Tiwari <[email protected]>; +Cc: [email protected]; [email protected]; Álvaro Herrera <[email protected]>

Hi,

On Mon, May 25, 2026 at 8:43 PM Ayush Tiwari <[email protected]>
wrote:

> Hi,
>
> On Mon, 25 May 2026 at 18:57, PG Bug reporting form <
> [email protected]> wrote:
>
>> The following bug has been logged on the website:
>>
>> Bug reference:      19491
>> Logged by:          Chi Zhang
>> Email address:      [email protected]
>> PostgreSQL version: 18.4
>> Operating system:   Ubuntu 24.04
>> Description:
>>
>> Hi,
>>
>> I found that the following test case triggers a segmentation fault.
>>
>> ```
>> CREATE SCHEMA IF NOT EXISTS poc;
>>
>> CREATE FUNCTION poc.mystring_in(cstring)
>>     RETURNS poc.mystring
>>     AS 'textin' LANGUAGE internal IMMUTABLE STRICT;
>>
>> CREATE FUNCTION poc.mystring_out(poc.mystring)
>>     RETURNS cstring
>>     AS 'textout' LANGUAGE internal IMMUTABLE STRICT;
>>
>> CREATE TYPE poc.mystring (
>>     INPUT = poc.mystring_in,
>>     OUTPUT = poc.mystring_out,
>>     LIKE = text,
>>     CATEGORY = 'S'
>> );
>>
>> SELECT '{"a":1}'::poc.mystring IS JSON;
>>
>> DROP SCHEMA IF EXISTS poc CASCADE;
>> ```
>>
>> This is the output:
>>
>> ```
>> sqlancer=# CREATE SCHEMA IF NOT EXISTS poc;
>> CREATE SCHEMA
>> sqlancer=# CREATE FUNCTION poc.mystring_in(cstring)
>>     RETURNS poc.mystring
>>     AS 'textin' LANGUAGE internal IMMUTABLE STRICT;
>> NOTICE:  type "poc.mystring" is not yet defined
>> DETAIL:  Creating a shell type definition.
>> CREATE FUNCTION
>> sqlancer=# CREATE FUNCTION poc.mystring_out(poc.mystring)
>>     RETURNS cstring
>>     AS 'textout' LANGUAGE internal IMMUTABLE STRICT;
>> NOTICE:  argument type poc.mystring is only a shell
>> LINE 1: CREATE FUNCTION poc.mystring_out(poc.mystring)
>>                                          ^
>> CREATE FUNCTION
>> sqlancer=# CREATE TYPE poc.mystring (
>>     INPUT = poc.mystring_in,
>>     OUTPUT = poc.mystring_out,
>>     LIKE = text,
>>     CATEGORY = 'S'
>> );
>> CREATE TYPE
>> sqlancer=# SELECT '{"a":1}'::poc.mystring IS JSON;
>> server closed the connection unexpectedly
>>         This probably means the server terminated abnormally
>>         before or while processing the request.
>> The connection to the server was lost. Attempting reset: Succeeded.
>> ```
>>
>
> Thanks for the report.  I was able to reproduce this on HEAD with your SQL.
> The crash is in the executor while building expression state for the IS
> JSON
> predicate -- ExecInitExprRec() ends up being called with a NULL node:
>
>   #0  ExecInitExprRec(node=0x0, ...)        execExpr.c:966
>   #1  ExecInitExprRec(<JsonIsPredicate>)    execExpr.c:2507
>   #2  ExecBuildProjectionInfo(...)          execExpr.c:511
>
> The NULL comes from transformJsonParseArg().  For UNKNOWN or
> string-category
> input types it implicitly coerces to text via coerce_to_target_type() and
> then sets exprtype to TEXTOID  without checking if the coercion succeeded.
> poc.mystring is in CATEGORY = 'S' but has no implicit cast to text, so
> coerce_to_target_type() returns NULL; the parser still claims it's text and
> that NULL ends up as the JsonIsPredicate subject.
>

yeah , this was the reason.


>
> String-category alone isn't a promise of text-coercibility -- adding
> CREATE CAST (poc.mystring AS text) ... AS IMPLICIT makes the same query
> works fine.  I guess the parser just shouldn't assume the coercion worked?
>
> The smallest fix I could see is to only update expr / exprtype when
> coerce_to_target_type() returns non-NULL.  Then transformJsonIsPredicate()
> (and the similar JSON() WITH UNIQUE KEYS path) raise their existing
> "cannot use type X" errors instead of crashing:
>
>   ERROR:  cannot use type poc.mystring in IS JSON predicate
>

I have reviewed the patch , i think the error message might be
something like

diff --git a/src/backend/parser/parse_expr.c
b/src/backend/parser/parse_expr.c
index 89d0f348303..59e34f082e4 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -4203,6 +4203,13 @@ transformJsonParseArg(ParseState *pstate, Node
*jsexpr, JsonFormat *format,

     TEXTOID, -1,

     COERCION_IMPLICIT,

     COERCE_IMPLICIT_CAST, -1);
+                       if(expr == NULL)
+                               ereport(ERROR,
+                               (errcode(ERRCODE_CANNOT_COERCE),
+                                errmsg("cannot cast type %s to %s",
+                                               format_type_be(*exprtype),
+                                               format_type_be(TEXTOID)),
+                                parser_errposition(pstate,
exprLocation(raw_expr))));
                        *exprtype = TEXTOID;
                }

-- 
Thanks,
Srinath Reddy Sadipiralla
EDB: https://www.enterprisedb.com/


^ permalink  raw  reply  [nested|flat] 3+ messages in thread


end of thread, other threads:[~2026-05-25 16:36 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-05-22 06:27 BUG #19491: Segmentation fault triggered by IS NULL PG Bug reporting form <[email protected]>
2026-05-25 15:12 ` Ayush Tiwari <[email protected]>
2026-05-25 16:36   ` Srinath Reddy Sadipiralla <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox