Skip to content

Commit d02d4a6

Browse files
committed
Avoid premature free of pass-by-reference CALL arguments.
Prematurely freeing the EState used to evaluate CALL arguments led, in some cases, to passing dangling pointers to the procedure. This was masked in trivial cases because the argument pointers would point to Const nodes in the original expression tree, and in some other cases because the result value would end up in the standalone ExprContext rather than in memory belonging to the EState --- but that wasn't exactly high quality programming either, because the standalone ExprContext was never explicitly freed, breaking assorted API contracts. In addition, using a separate EState for each argument was just silly. So let's use just one EState, and one ExprContext, and make the latter belong to the former rather than be standalone, and clean up the EState (and hence the ExprContext) post-call. While at it, improve the function's commentary a bit. Discussion: https://postgr.es/m/29173.1518282748@sss.pgh.pa.us
1 parent 65b1d76 commit d02d4a6

File tree

3 files changed

+33
-11
lines changed

3 files changed

+33
-11
lines changed

src/backend/commands/functioncmds.c

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,6 +2204,12 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
22042204
* transaction commands based on that information, e.g., call
22052205
* SPI_connect_ext(SPI_OPT_NONATOMIC). The language should also pass on the
22062206
* atomic flag to any nested invocations to CALL.
2207+
*
2208+
* The expression data structures and execution context that we create
2209+
* within this function are children of the portalContext of the Portal
2210+
* that the CALL utility statement runs in. Therefore, any pass-by-ref
2211+
* values that we're passing to the procedure will survive transaction
2212+
* commits that might occur inside the procedure.
22072213
*/
22082214
void
22092215
ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic)
@@ -2218,8 +2224,11 @@ ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic)
22182224
FmgrInfo flinfo;
22192225
FunctionCallInfoData fcinfo;
22202226
CallContext *callcontext;
2227+
EState *estate;
2228+
ExprContext *econtext;
22212229
HeapTuple tp;
22222230

2231+
/* We need to do parse analysis on the procedure call and its arguments */
22232232
targs = NIL;
22242233
foreach(lc, stmt->funccall->args)
22252234
{
@@ -2241,7 +2250,6 @@ ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic)
22412250
aclresult = pg_proc_aclcheck(fexpr->funcid, GetUserId(), ACL_EXECUTE);
22422251
if (aclresult != ACLCHECK_OK)
22432252
aclcheck_error(aclresult, OBJECT_PROCEDURE, get_func_name(fexpr->funcid));
2244-
InvokeFunctionExecuteHook(fexpr->funcid);
22452253

22462254
nargs = list_length(fexpr->args);
22472255

@@ -2254,6 +2262,7 @@ ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic)
22542262
FUNC_MAX_ARGS,
22552263
FUNC_MAX_ARGS)));
22562264

2265+
/* Prep the context object we'll pass to the procedure */
22572266
callcontext = makeNode(CallContext);
22582267
callcontext->atomic = atomic;
22592268

@@ -2270,23 +2279,28 @@ ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic)
22702279
callcontext->atomic = true;
22712280
ReleaseSysCache(tp);
22722281

2282+
/* Initialize function call structure */
2283+
InvokeFunctionExecuteHook(fexpr->funcid);
22732284
fmgr_info(fexpr->funcid, &flinfo);
22742285
InitFunctionCallInfoData(fcinfo, &flinfo, nargs, fexpr->inputcollid, (Node *) callcontext, NULL);
22752286

2287+
/*
2288+
* Evaluate procedure arguments inside a suitable execution context. Note
2289+
* we can't free this context till the procedure returns.
2290+
*/
2291+
estate = CreateExecutorState();
2292+
econtext = CreateExprContext(estate);
2293+
22762294
i = 0;
22772295
foreach(lc, fexpr->args)
22782296
{
2279-
EState *estate;
22802297
ExprState *exprstate;
2281-
ExprContext *econtext;
22822298
Datum val;
22832299
bool isnull;
22842300

2285-
estate = CreateExecutorState();
22862301
exprstate = ExecPrepareExpr(lfirst(lc), estate);
2287-
econtext = CreateStandaloneExprContext();
2302+
22882303
val = ExecEvalExprSwitchContext(exprstate, econtext, &isnull);
2289-
FreeExecutorState(estate);
22902304

22912305
fcinfo.arg[i] = val;
22922306
fcinfo.argnull[i] = isnull;
@@ -2295,4 +2309,6 @@ ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic)
22952309
}
22962310

22972311
FunctionCallInvoke(&fcinfo);
2312+
2313+
FreeExecutorState(estate);
22982314
}

src/test/regress/expected/create_procedure.out

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,22 @@ LINE 1: SELECT ptest1('x');
2121
^
2222
HINT: To call a procedure, use CALL.
2323
CALL ptest1('a'); -- ok
24+
CALL ptest1('xy' || 'zzy'); -- ok, constant-folded arg
25+
CALL ptest1(substring(random()::text, 1, 1)); -- ok, volatile arg
2426
\df ptest1
2527
List of functions
2628
Schema | Name | Result data type | Argument data types | Type
2729
--------+--------+------------------+---------------------+------
2830
public | ptest1 | | x text | proc
2931
(1 row)
3032

31-
SELECT * FROM cp_test ORDER BY a;
32-
a | b
33-
---+---
33+
SELECT * FROM cp_test ORDER BY b COLLATE "C";
34+
a | b
35+
---+-------
36+
1 | 0
3437
1 | a
35-
(1 row)
38+
1 | xyzzy
39+
(3 rows)
3640

3741
CREATE PROCEDURE ptest2()
3842
LANGUAGE SQL

src/test/regress/sql/create_procedure.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ $$;
1313

1414
SELECT ptest1('x'); -- error
1515
CALL ptest1('a'); -- ok
16+
CALL ptest1('xy' || 'zzy'); -- ok, constant-folded arg
17+
CALL ptest1(substring(random()::text, 1, 1)); -- ok, volatile arg
1618

1719
\df ptest1
1820

19-
SELECT * FROM cp_test ORDER BY a;
21+
SELECT * FROM cp_test ORDER BY b COLLATE "C";
2022

2123

2224
CREATE PROCEDURE ptest2()

0 commit comments

Comments
 (0)