Skip to content

Commit 1f6a7eb

Browse files
committed
Repair unsafe use of shared typecast-lookup table in plpgsql DO blocks.
DO blocks use private simple_eval_estates to avoid intra-transaction memory leakage, cf commit c7b849a. I had forgotten about that while writing commit 0fc94a5, but it means that expression execution trees created within a DO block disappear immediately on exiting the DO block, and hence can't safely be linked into plpgsql's session-wide cast hash table. To fix, give a DO block a private cast hash table to go with its private simple_eval_estate. This is less efficient than one could wish, since DO blocks can no longer share any cast lookup work with other plpgsql execution, but it shouldn't be too bad; in any case it's no worse than what happened in DO blocks before commit 0fc94a5. Per bug #13571 from Feike Steenbergen. Preliminary analysis by Oleksandr Shulgin.
1 parent 6942663 commit 1f6a7eb

File tree

4 files changed

+71
-28
lines changed

4 files changed

+71
-28
lines changed

src/pl/plpgsql/src/pl_exec.c

+51-28
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
9898
*
9999
* The evaluation state trees (cast_exprstate) are managed in the same way as
100100
* simple expressions (i.e., we assume cast expressions are always simple).
101+
*
102+
* As with simple expressions, DO blocks don't use the shared hash table but
103+
* must have their own. This isn't ideal, but we don't want to deal with
104+
* multiple simple_eval_estates within a DO block.
101105
*/
102106
typedef struct /* lookup key for cast info */
103107
{
@@ -118,8 +122,8 @@ typedef struct /* cast_hash table entry */
118122
LocalTransactionId cast_lxid;
119123
} plpgsql_CastHashEntry;
120124

121-
static MemoryContext cast_hash_context = NULL;
122-
static HTAB *cast_hash = NULL;
125+
static MemoryContext shared_cast_context = NULL;
126+
static HTAB *shared_cast_hash = NULL;
123127

124128
/************************************************************
125129
* Local function forward declarations
@@ -283,7 +287,9 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
283287
* difference that this code is aware of is that for a DO block, we want
284288
* to use a private simple_eval_estate, which is created and passed in by
285289
* the caller. For regular functions, pass NULL, which implies using
286-
* shared_simple_eval_estate.
290+
* shared_simple_eval_estate. (When using a private simple_eval_estate,
291+
* we must also use a private cast hashtable, but that's taken care of
292+
* within plpgsql_estate_setup.)
287293
* ----------
288294
*/
289295
Datum
@@ -3286,6 +3292,8 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
32863292
ReturnSetInfo *rsi,
32873293
EState *simple_eval_estate)
32883294
{
3295+
HASHCTL ctl;
3296+
32893297
/* this link will be restored at exit from plpgsql_call_handler */
32903298
func->cur_estate = estate;
32913299

@@ -3333,11 +3341,44 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
33333341
estate->paramLI->parserSetupArg = NULL; /* filled during use */
33343342
estate->paramLI->numParams = estate->ndatums;
33353343

3336-
/* set up for use of appropriate simple-expression EState */
3344+
/* set up for use of appropriate simple-expression EState and cast hash */
33373345
if (simple_eval_estate)
3346+
{
33383347
estate->simple_eval_estate = simple_eval_estate;
3348+
/* Private cast hash just lives in function's main context */
3349+
memset(&ctl, 0, sizeof(ctl));
3350+
ctl.keysize = sizeof(plpgsql_CastHashKey);
3351+
ctl.entrysize = sizeof(plpgsql_CastHashEntry);
3352+
ctl.hcxt = CurrentMemoryContext;
3353+
estate->cast_hash = hash_create("PLpgSQL private cast cache",
3354+
16, /* start small and extend */
3355+
&ctl,
3356+
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
3357+
estate->cast_hash_context = CurrentMemoryContext;
3358+
}
33393359
else
3360+
{
33403361
estate->simple_eval_estate = shared_simple_eval_estate;
3362+
/* Create the session-wide cast-info hash table if we didn't already */
3363+
if (shared_cast_hash == NULL)
3364+
{
3365+
shared_cast_context = AllocSetContextCreate(TopMemoryContext,
3366+
"PLpgSQL cast info",
3367+
ALLOCSET_DEFAULT_MINSIZE,
3368+
ALLOCSET_DEFAULT_INITSIZE,
3369+
ALLOCSET_DEFAULT_MAXSIZE);
3370+
memset(&ctl, 0, sizeof(ctl));
3371+
ctl.keysize = sizeof(plpgsql_CastHashKey);
3372+
ctl.entrysize = sizeof(plpgsql_CastHashEntry);
3373+
ctl.hcxt = shared_cast_context;
3374+
shared_cast_hash = hash_create("PLpgSQL cast cache",
3375+
16, /* start small and extend */
3376+
&ctl,
3377+
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
3378+
}
3379+
estate->cast_hash = shared_cast_hash;
3380+
estate->cast_hash_context = shared_cast_context;
3381+
}
33413382

33423383
estate->eval_tuptable = NULL;
33433384
estate->eval_processed = 0;
@@ -6014,32 +6055,12 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
60146055
LocalTransactionId curlxid;
60156056
MemoryContext oldcontext;
60166057

6017-
/* Create the session-wide cast-info hash table if we didn't already */
6018-
if (cast_hash == NULL)
6019-
{
6020-
HASHCTL ctl;
6021-
6022-
cast_hash_context = AllocSetContextCreate(TopMemoryContext,
6023-
"PLpgSQL cast info",
6024-
ALLOCSET_DEFAULT_MINSIZE,
6025-
ALLOCSET_DEFAULT_INITSIZE,
6026-
ALLOCSET_DEFAULT_MAXSIZE);
6027-
memset(&ctl, 0, sizeof(ctl));
6028-
ctl.keysize = sizeof(plpgsql_CastHashKey);
6029-
ctl.entrysize = sizeof(plpgsql_CastHashEntry);
6030-
ctl.hcxt = cast_hash_context;
6031-
cast_hash = hash_create("PLpgSQL cast cache",
6032-
16, /* start small and extend */
6033-
&ctl,
6034-
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
6035-
}
6036-
60376058
/* Look for existing entry */
60386059
cast_key.srctype = srctype;
60396060
cast_key.dsttype = dsttype;
60406061
cast_key.srctypmod = srctypmod;
60416062
cast_key.dsttypmod = dsttypmod;
6042-
cast_entry = (plpgsql_CastHashEntry *) hash_search(cast_hash,
6063+
cast_entry = (plpgsql_CastHashEntry *) hash_search(estate->cast_hash,
60436064
(void *) &cast_key,
60446065
HASH_FIND, NULL);
60456066

@@ -6124,15 +6145,15 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
61246145
cast_expr = (Node *) expression_planner((Expr *) cast_expr);
61256146

61266147
/* Now copy the tree into cast_hash_context */
6127-
MemoryContextSwitchTo(cast_hash_context);
6148+
MemoryContextSwitchTo(estate->cast_hash_context);
61286149

61296150
cast_expr = copyObject(cast_expr);
61306151
}
61316152

61326153
MemoryContextSwitchTo(oldcontext);
61336154

61346155
/* Now we can fill in a hashtable entry. */
6135-
cast_entry = (plpgsql_CastHashEntry *) hash_search(cast_hash,
6156+
cast_entry = (plpgsql_CastHashEntry *) hash_search(estate->cast_hash,
61366157
(void *) &cast_key,
61376158
HASH_ENTER, &found);
61386159
Assert(!found); /* wasn't there a moment ago */
@@ -6154,7 +6175,9 @@ get_cast_hashentry(PLpgSQL_execstate *estate,
61546175
* executions. (We will leak some memory intra-transaction if that
61556176
* happens a lot, but we don't expect it to.) It's okay to update the
61566177
* hash table with the new tree because all plpgsql functions within a
6157-
* given transaction share the same simple_eval_estate.
6178+
* given transaction share the same simple_eval_estate. (Well, regular
6179+
* functions do; DO blocks have private simple_eval_estates, and private
6180+
* cast hash tables to go with them.)
61586181
*/
61596182
curlxid = MyProc->lxid;
61606183
if (cast_entry->cast_lxid != curlxid || cast_entry->cast_in_use)

src/pl/plpgsql/src/plpgsql.h

+4
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,10 @@ typedef struct PLpgSQL_execstate
796796
/* EState to use for "simple" expression evaluation */
797797
EState *simple_eval_estate;
798798

799+
/* Lookup table to use for executing type casts */
800+
HTAB *cast_hash;
801+
MemoryContext cast_hash_context;
802+
799803
/* temporary state for results from evaluation of query or expr */
800804
SPITupleTable *eval_tuptable;
801805
uint32 eval_processed;

src/test/regress/expected/plpgsql.out

+7
Original file line numberDiff line numberDiff line change
@@ -4764,6 +4764,13 @@ commit;
47644764
drop function cast_invoker(integer);
47654765
drop function sql_to_date(integer) cascade;
47664766
NOTICE: drop cascades to cast from integer to date
4767+
-- Test handling of cast cache inside DO blocks
4768+
-- (to check the original crash case, this must be a cast not previously
4769+
-- used in this session)
4770+
begin;
4771+
do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$;
4772+
do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$;
4773+
end;
47674774
-- Test for consistent reporting of error context
47684775
create function fail() returns int language plpgsql as $$
47694776
begin

src/test/regress/sql/plpgsql.sql

+9
Original file line numberDiff line numberDiff line change
@@ -3836,6 +3836,15 @@ commit;
38363836
drop function cast_invoker(integer);
38373837
drop function sql_to_date(integer) cascade;
38383838

3839+
-- Test handling of cast cache inside DO blocks
3840+
-- (to check the original crash case, this must be a cast not previously
3841+
-- used in this session)
3842+
3843+
begin;
3844+
do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$;
3845+
do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$;
3846+
end;
3847+
38393848
-- Test for consistent reporting of error context
38403849

38413850
create function fail() returns int language plpgsql as $$

0 commit comments

Comments
 (0)