Skip to content

Commit 4984784

Browse files
committed
Fix crash in json{b}_populate_recordset() and json{b}_to_recordset().
As of commit 37a795a, populate_recordset_worker() tried to pass back (as rsi.setDesc) a tupdesc that it also had cached in its fn_extra. But the core executor would free the passed-back tupdesc, risking a crash if the function were called again in the same query. The safest and least invasive way to fix that is to make an extra tupdesc copy to pass back. While at it, I failed to resist the temptation to get rid of unnecessary get_fn_expr_argtype() calls here and in populate_record_worker(). Per report from Dmitry Dolgov; thanks to Michael Paquier and Andrew Gierth for investigation and discussion. Discussion: https://postgr.es/m/CA+q6zcWzN9ztCfR47ZwgTr1KLnuO6BAY6FurxXhovP4hxr+yOQ@mail.gmail.com
1 parent 93ad00c commit 4984784

File tree

5 files changed

+59
-23
lines changed

5 files changed

+59
-23
lines changed

src/backend/utils/adt/jsonfuncs.c

+35-23
Original file line numberDiff line numberDiff line change
@@ -421,9 +421,9 @@ static void sn_scalar(void *state, char *token, JsonTokenType tokentype);
421421

422422
/* worker functions for populate_record, to_record, populate_recordset and to_recordset */
423423
static Datum populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
424-
bool have_record_arg);
424+
bool is_json, bool have_record_arg);
425425
static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
426-
bool have_record_arg);
426+
bool is_json, bool have_record_arg);
427427

428428
/* helper functions for populate_record[set] */
429429
static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p,
@@ -2296,25 +2296,29 @@ elements_scalar(void *state, char *token, JsonTokenType tokentype)
22962296
Datum
22972297
jsonb_populate_record(PG_FUNCTION_ARGS)
22982298
{
2299-
return populate_record_worker(fcinfo, "jsonb_populate_record", true);
2299+
return populate_record_worker(fcinfo, "jsonb_populate_record",
2300+
false, true);
23002301
}
23012302

23022303
Datum
23032304
jsonb_to_record(PG_FUNCTION_ARGS)
23042305
{
2305-
return populate_record_worker(fcinfo, "jsonb_to_record", false);
2306+
return populate_record_worker(fcinfo, "jsonb_to_record",
2307+
false, false);
23062308
}
23072309

23082310
Datum
23092311
json_populate_record(PG_FUNCTION_ARGS)
23102312
{
2311-
return populate_record_worker(fcinfo, "json_populate_record", true);
2313+
return populate_record_worker(fcinfo, "json_populate_record",
2314+
true, true);
23122315
}
23132316

23142317
Datum
23152318
json_to_record(PG_FUNCTION_ARGS)
23162319
{
2317-
return populate_record_worker(fcinfo, "json_to_record", false);
2320+
return populate_record_worker(fcinfo, "json_to_record",
2321+
true, false);
23182322
}
23192323

23202324
/* helper function for diagnostics */
@@ -3203,21 +3207,22 @@ populate_record(TupleDesc tupdesc,
32033207
return res->t_data;
32043208
}
32053209

3210+
/*
3211+
* common worker for json{b}_populate_record() and json{b}_to_record()
3212+
* is_json and have_record_arg identify the specific function
3213+
*/
32063214
static Datum
32073215
populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
3208-
bool have_record_arg)
3216+
bool is_json, bool have_record_arg)
32093217
{
32103218
int json_arg_num = have_record_arg ? 1 : 0;
3211-
Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
32123219
JsValue jsv = {0};
32133220
HeapTupleHeader rec;
32143221
Datum rettuple;
32153222
JsonbValue jbv;
32163223
MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt;
32173224
PopulateRecordCache *cache = fcinfo->flinfo->fn_extra;
32183225

3219-
Assert(jtype == JSONOID || jtype == JSONBOID);
3220-
32213226
/*
32223227
* If first time through, identify input/result record type. Note that
32233228
* this stanza looks only at fcinfo context, which can't change during the
@@ -3303,9 +3308,9 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
33033308
PG_RETURN_NULL();
33043309
}
33053310

3306-
jsv.is_json = jtype == JSONOID;
3311+
jsv.is_json = is_json;
33073312

3308-
if (jsv.is_json)
3313+
if (is_json)
33093314
{
33103315
text *json = PG_GETARG_TEXT_PP(json_arg_num);
33113316

@@ -3489,25 +3494,29 @@ hash_scalar(void *state, char *token, JsonTokenType tokentype)
34893494
Datum
34903495
jsonb_populate_recordset(PG_FUNCTION_ARGS)
34913496
{
3492-
return populate_recordset_worker(fcinfo, "jsonb_populate_recordset", true);
3497+
return populate_recordset_worker(fcinfo, "jsonb_populate_recordset",
3498+
false, true);
34933499
}
34943500

34953501
Datum
34963502
jsonb_to_recordset(PG_FUNCTION_ARGS)
34973503
{
3498-
return populate_recordset_worker(fcinfo, "jsonb_to_recordset", false);
3504+
return populate_recordset_worker(fcinfo, "jsonb_to_recordset",
3505+
false, false);
34993506
}
35003507

35013508
Datum
35023509
json_populate_recordset(PG_FUNCTION_ARGS)
35033510
{
3504-
return populate_recordset_worker(fcinfo, "json_populate_recordset", true);
3511+
return populate_recordset_worker(fcinfo, "json_populate_recordset",
3512+
true, true);
35053513
}
35063514

35073515
Datum
35083516
json_to_recordset(PG_FUNCTION_ARGS)
35093517
{
3510-
return populate_recordset_worker(fcinfo, "json_to_recordset", false);
3518+
return populate_recordset_worker(fcinfo, "json_to_recordset",
3519+
true, false);
35113520
}
35123521

35133522
static void
@@ -3544,14 +3553,14 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
35443553
}
35453554

35463555
/*
3547-
* common worker for json_populate_recordset() and json_to_recordset()
3556+
* common worker for json{b}_populate_recordset() and json{b}_to_recordset()
3557+
* is_json and have_record_arg identify the specific function
35483558
*/
35493559
static Datum
35503560
populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
3551-
bool have_record_arg)
3561+
bool is_json, bool have_record_arg)
35523562
{
35533563
int json_arg_num = have_record_arg ? 1 : 0;
3554-
Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
35553564
ReturnSetInfo *rsi;
35563565
MemoryContext old_cxt;
35573566
HeapTupleHeader rec;
@@ -3662,7 +3671,7 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
36623671
state->cache = cache;
36633672
state->rec = rec;
36643673

3665-
if (jtype == JSONOID)
3674+
if (is_json)
36663675
{
36673676
text *json = PG_GETARG_TEXT_PP(json_arg_num);
36683677
JsonLexContext *lex;
@@ -3693,8 +3702,6 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
36933702
bool skipNested = false;
36943703
JsonbIteratorToken r;
36953704

3696-
Assert(jtype == JSONBOID);
3697-
36983705
if (JB_ROOT_IS_SCALAR(jb) || !JB_ROOT_IS_ARRAY(jb))
36993706
ereport(ERROR,
37003707
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -3726,8 +3733,13 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
37263733
}
37273734
}
37283735

3736+
/*
3737+
* Note: we must copy the cached tupdesc because the executor will free
3738+
* the passed-back setDesc, but we want to hang onto the cache in case
3739+
* we're called again in the same query.
3740+
*/
37293741
rsi->setResult = state->tuple_store;
3730-
rsi->setDesc = cache->c.io.composite.tupdesc;
3742+
rsi->setDesc = CreateTupleDescCopy(cache->c.io.composite.tupdesc);
37313743

37323744
PG_RETURN_NULL();
37333745
}

src/test/regress/expected/json.out

+10
Original file line numberDiff line numberDiff line change
@@ -1841,6 +1841,16 @@ SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
18411841
(0,1)
18421842
(1 row)
18431843

1844+
SELECT i, json_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]')
1845+
FROM (VALUES (1),(2)) v(i);
1846+
i | json_populate_recordset
1847+
---+-------------------------
1848+
1 | (42,50)
1849+
1 | (1,43)
1850+
2 | (42,50)
1851+
2 | (2,43)
1852+
(4 rows)
1853+
18441854
-- composite domain
18451855
SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');
18461856
json_populate_recordset

src/test/regress/expected/jsonb.out

+10
Original file line numberDiff line numberDiff line change
@@ -2523,6 +2523,16 @@ SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
25232523
(0,1)
25242524
(1 row)
25252525

2526+
SELECT i, jsonb_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]')
2527+
FROM (VALUES (1),(2)) v(i);
2528+
i | jsonb_populate_recordset
2529+
---+--------------------------
2530+
1 | (42,50)
2531+
1 | (1,43)
2532+
2 | (42,50)
2533+
2 | (2,43)
2534+
(4 rows)
2535+
25262536
-- composite domain
25272537
SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]');
25282538
jsonb_populate_recordset

src/test/regress/sql/json.sql

+2
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,8 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
547547
-- anonymous record type
548548
SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
549549
SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
550+
SELECT i, json_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]')
551+
FROM (VALUES (1),(2)) v(i);
550552

551553
-- composite domain
552554
SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');

src/test/regress/sql/jsonb.sql

+2
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,8 @@ SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200
663663
-- anonymous record type
664664
SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
665665
SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
666+
SELECT i, jsonb_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]')
667+
FROM (VALUES (1),(2)) v(i);
666668

667669
-- composite domain
668670
SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]');

0 commit comments

Comments
 (0)