Fix corner-case uninitialized-variable issues in plpgsql.
If an error was raised during our initial attempt to check whether a successfully-compiled expression is "simple", subsequent calls of exec_stmt_execsql would suppose that stmt->mod_stmt was already computed when it had not been. This could lead to assertion failures in debug builds; in production builds the effect would typically be to act as if INTO STRICT had been specified even when it had not been. Of course that only matters if the subsequent attempt to execute the expression succeeds, so that the problem can only be reached by fixing a failure in some referenced, inline-able SQL function and then retrying the calling plpgsql function in the same session. (There might be even-more-obscure ways to change the expression's behavior without changing the plpgsql function, but that one seems like the only one people would be likely to hit in practice.) The most foolproof way to fix this would be to arrange for exec_prepare_plan to not set expr->plan until we've finished the subsidiary simple-expression check. But it seems hard to do that without creating reference-count leak issues. So settle for documenting the hazard in a comment and fixing exec_stmt_execsql to test separately for whether it's computed stmt->mod_stmt. (That adds a test-and-branch per execution, but hopefully that's negligible in context.) In v11 and up, also fix exec_stmt_call which had a variant of the same issue. Per bug #17113 from Alexander Lakhin. Back-patch to all supported branches. Discussion: https://postgr.es/m/17113-077605ce00e0e7ec@postgresql.org
This commit is contained in:
parent
cf6e5c7ebb
commit
dffec69fee
@ -3384,6 +3384,22 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
|
|||||||
|
|
||||||
/* ----------
|
/* ----------
|
||||||
* Generate a prepared plan
|
* Generate a prepared plan
|
||||||
|
*
|
||||||
|
* CAUTION: it is possible for this function to throw an error after it has
|
||||||
|
* built a SPIPlan and saved it in expr->plan. Therefore, be wary of doing
|
||||||
|
* additional things contingent on expr->plan being NULL. That is, given
|
||||||
|
* code like
|
||||||
|
*
|
||||||
|
* if (query->plan == NULL)
|
||||||
|
* {
|
||||||
|
* // okay to put setup code here
|
||||||
|
* exec_prepare_plan(estate, query, ...);
|
||||||
|
* // NOT okay to put more logic here
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* extra steps at the end are unsafe because they will not be executed when
|
||||||
|
* re-executing the calling statement, if exec_prepare_plan failed the first
|
||||||
|
* time. This is annoyingly error-prone, but the alternatives are worse.
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
@ -3427,15 +3443,15 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
|
|||||||
SPI_keepplan(plan);
|
SPI_keepplan(plan);
|
||||||
expr->plan = plan;
|
expr->plan = plan;
|
||||||
|
|
||||||
/* Check to see if it's a simple expression */
|
|
||||||
exec_simple_check_plan(expr);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mark expression as not using a read-write param. exec_assign_value has
|
* Mark expression as not using a read-write param. exec_assign_value has
|
||||||
* to take steps to override this if appropriate; that seems cleaner than
|
* to take steps to override this if appropriate; that seems cleaner than
|
||||||
* adding parameters to all other callers.
|
* adding parameters to all other callers.
|
||||||
*/
|
*/
|
||||||
expr->rwparam = -1;
|
expr->rwparam = -1;
|
||||||
|
|
||||||
|
/* Check to see if it's a simple expression */
|
||||||
|
exec_simple_check_plan(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -3457,10 +3473,12 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
|
|||||||
* whether the statement is INSERT/UPDATE/DELETE
|
* whether the statement is INSERT/UPDATE/DELETE
|
||||||
*/
|
*/
|
||||||
if (expr->plan == NULL)
|
if (expr->plan == NULL)
|
||||||
|
exec_prepare_plan(estate, expr, 0);
|
||||||
|
|
||||||
|
if (!stmt->mod_stmt_set)
|
||||||
{
|
{
|
||||||
ListCell *l;
|
ListCell *l;
|
||||||
|
|
||||||
exec_prepare_plan(estate, expr, 0);
|
|
||||||
stmt->mod_stmt = false;
|
stmt->mod_stmt = false;
|
||||||
foreach(l, SPI_plan_get_plan_sources(expr->plan))
|
foreach(l, SPI_plan_get_plan_sources(expr->plan))
|
||||||
{
|
{
|
||||||
@ -3481,6 +3499,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stmt->mod_stmt_set = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -4181,6 +4200,14 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
|
|||||||
* if we can pass the target variable as a read-write parameter to the
|
* if we can pass the target variable as a read-write parameter to the
|
||||||
* expression. (This is a bit messy, but it seems cleaner than modifying
|
* expression. (This is a bit messy, but it seems cleaner than modifying
|
||||||
* the API of exec_eval_expr for the purpose.)
|
* the API of exec_eval_expr for the purpose.)
|
||||||
|
*
|
||||||
|
* NOTE: this coding ignores the advice given in exec_prepare_plan's
|
||||||
|
* comments, that one should not do additional setup contingent on
|
||||||
|
* expr->plan being NULL. This means that if we get an error while trying
|
||||||
|
* to check for the expression being simple, we won't check for a
|
||||||
|
* read-write parameter either, so that neither optimization will be
|
||||||
|
* applied in future executions. Nothing will fail though, so we live
|
||||||
|
* with that bit of messiness too.
|
||||||
*/
|
*/
|
||||||
if (expr->plan == NULL)
|
if (expr->plan == NULL)
|
||||||
{
|
{
|
||||||
@ -6544,6 +6571,10 @@ exec_simple_check_node(Node *node)
|
|||||||
* exec_simple_check_plan - Check if a plan is simple enough to
|
* exec_simple_check_plan - Check if a plan is simple enough to
|
||||||
* be evaluated by ExecEvalExpr() instead
|
* be evaluated by ExecEvalExpr() instead
|
||||||
* of SPI.
|
* of SPI.
|
||||||
|
*
|
||||||
|
* Note: the refcount manipulations in this function assume that expr->plan
|
||||||
|
* is a "saved" SPI plan. That's a bit annoying from the caller's standpoint,
|
||||||
|
* but it's otherwise difficult to avoid leaking the plan on failure.
|
||||||
* ----------
|
* ----------
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
|
@ -2934,7 +2934,7 @@ make_execsql_stmt(int firsttoken, int location)
|
|||||||
|
|
||||||
check_sql_expr(expr->query, location, 0);
|
check_sql_expr(expr->query, location, 0);
|
||||||
|
|
||||||
execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
|
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
|
||||||
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
|
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
|
||||||
execsql->lineno = plpgsql_location_to_lineno(location);
|
execsql->lineno = plpgsql_location_to_lineno(location);
|
||||||
execsql->sqlstmt = expr;
|
execsql->sqlstmt = expr;
|
||||||
|
@ -659,9 +659,9 @@ typedef struct
|
|||||||
int lineno;
|
int lineno;
|
||||||
PLpgSQL_expr *sqlstmt;
|
PLpgSQL_expr *sqlstmt;
|
||||||
bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE? */
|
bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE? */
|
||||||
/* note: mod_stmt is set when we plan the query */
|
|
||||||
bool into; /* INTO supplied? */
|
bool into; /* INTO supplied? */
|
||||||
bool strict; /* INTO STRICT flag */
|
bool strict; /* INTO STRICT flag */
|
||||||
|
bool mod_stmt_set; /* is mod_stmt valid yet? */
|
||||||
PLpgSQL_rec *rec; /* INTO target, if record */
|
PLpgSQL_rec *rec; /* INTO target, if record */
|
||||||
PLpgSQL_row *row; /* INTO target, if row */
|
PLpgSQL_row *row; /* INTO target, if row */
|
||||||
} PLpgSQL_stmt_execsql;
|
} PLpgSQL_stmt_execsql;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user