public inbox for [email protected]
help / color / mirror / Atom feedFrom: Ayush Tiwari <[email protected]>
To: [email protected]
To: [email protected]
To: Álvaro Herrera <[email protected]>
Subject: Re: BUG #19491: Segmentation fault triggered by IS NULL
Date: Mon, 25 May 2026 20:42:41 +0530
Message-ID: <CAJTYsWXsDYpYvOs5ZC48ziEkyziy0j7Km023U8u9cLqMd1poug@mail.gmail.com> (raw)
In-Reply-To: <[email protected]>
References: <[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
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected]
Subject: Re: BUG #19491: Segmentation fault triggered by IS NULL
In-Reply-To: <CAJTYsWXsDYpYvOs5ZC48ziEkyziy0j7Km023U8u9cLqMd1poug@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox