Skip to content

Commit ccd10a9

Browse files
committed
Tighten enforcement of variable CONSTANT markings in plpgsql.
I noticed that plpgsql would allow assignment of a new value to a variable even when that variable is marked CONSTANT, if the variable is used as an output parameter in CALL or is a refcursor variable that OPEN assigns a new value to. Fix these oversights. In the CALL case, the check has to be done at runtime because we cannot know at parse time which parameters are OUT parameters. For OPEN, it seems best to likewise enforce at runtime because then we needn't throw error if the variable has a nonnull value (since OPEN will only try to overwrite a null value). Although this is surely a bug fix, no back-patch: it seems unlikely that anyone would thank us for breaking formerly-working code in minor releases. Discussion: https://postgr.es/m/214453.1651182729@sss.pgh.pa.us
1 parent a79153b commit ccd10a9

File tree

5 files changed

+134
-4
lines changed

5 files changed

+134
-4
lines changed

src/pl/plpgsql/src/expected/plpgsql_call.out

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@ CONTEXT: PL/pgSQL function inline_code_block line 6 at CALL
122122
DO
123123
LANGUAGE plpgsql
124124
$$
125+
DECLARE
126+
x constant int := 3;
127+
y int := 4;
128+
BEGIN
129+
CALL test_proc6(2, x, y); -- error because x is constant
130+
END;
131+
$$;
132+
ERROR: variable "x" is declared CONSTANT
133+
CONTEXT: PL/pgSQL function inline_code_block line 6 at CALL
134+
DO
135+
LANGUAGE plpgsql
136+
$$
125137
DECLARE
126138
x int := 3;
127139
y int := 4;

src/pl/plpgsql/src/pl_exec.c

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
331331
static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
332332
static void exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan);
333333
static void exec_check_rw_parameter(PLpgSQL_expr *expr);
334+
static void exec_check_assignable(PLpgSQL_execstate *estate, int dno);
334335
static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
335336
PLpgSQL_expr *expr,
336337
Datum *result,
@@ -2342,9 +2343,13 @@ make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
23422343
if (IsA(n, Param))
23432344
{
23442345
Param *param = (Param *) n;
2346+
int dno;
23452347

23462348
/* paramid is offset by 1 (see make_datum_param()) */
2347-
row->varnos[nfields++] = param->paramid - 1;
2349+
dno = param->paramid - 1;
2350+
/* must check assignability now, because grammar can't */
2351+
exec_check_assignable(estate, dno);
2352+
row->varnos[nfields++] = dno;
23482353
}
23492354
else
23502355
{
@@ -2926,10 +2931,14 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
29262931
SPI_result_code_string(SPI_result));
29272932

29282933
/*
2929-
* If cursor variable was NULL, store the generated portal name in it
2934+
* If cursor variable was NULL, store the generated portal name in it,
2935+
* after verifying it's okay to assign to.
29302936
*/
29312937
if (curname == NULL)
2938+
{
2939+
exec_check_assignable(estate, stmt->curvar);
29322940
assign_text_var(estate, curvar, portal->name);
2941+
}
29332942

29342943
/*
29352944
* Clean up before entering exec_for_query
@@ -4688,13 +4697,18 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
46884697
stmt->cursor_options);
46894698

46904699
/*
4691-
* If cursor variable was NULL, store the generated portal name in it.
4700+
* If cursor variable was NULL, store the generated portal name in it,
4701+
* after verifying it's okay to assign to.
4702+
*
46924703
* Note: exec_dynquery_with_params already reset the stmt_mcontext, so
46934704
* curname is a dangling pointer here; but testing it for nullness is
46944705
* OK.
46954706
*/
46964707
if (curname == NULL)
4708+
{
4709+
exec_check_assignable(estate, stmt->curvar);
46974710
assign_text_var(estate, curvar, portal->name);
4711+
}
46984712

46994713
return PLPGSQL_RC_OK;
47004714
}
@@ -4763,10 +4777,14 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
47634777
SPI_result_code_string(SPI_result));
47644778

47654779
/*
4766-
* If cursor variable was NULL, store the generated portal name in it
4780+
* If cursor variable was NULL, store the generated portal name in it,
4781+
* after verifying it's okay to assign to.
47674782
*/
47684783
if (curname == NULL)
4784+
{
4785+
exec_check_assignable(estate, stmt->curvar);
47694786
assign_text_var(estate, curvar, portal->name);
4787+
}
47704788

47714789
/* If we had any transient data, clean it up */
47724790
exec_eval_cleanup(estate);
@@ -8242,6 +8260,43 @@ exec_check_rw_parameter(PLpgSQL_expr *expr)
82428260
}
82438261
}
82448262

8263+
/*
8264+
* exec_check_assignable --- is it OK to assign to the indicated datum?
8265+
*
8266+
* This should match pl_gram.y's check_assignable().
8267+
*/
8268+
static void
8269+
exec_check_assignable(PLpgSQL_execstate *estate, int dno)
8270+
{
8271+
PLpgSQL_datum *datum;
8272+
8273+
Assert(dno >= 0 && dno < estate->ndatums);
8274+
datum = estate->datums[dno];
8275+
switch (datum->dtype)
8276+
{
8277+
case PLPGSQL_DTYPE_VAR:
8278+
case PLPGSQL_DTYPE_PROMISE:
8279+
case PLPGSQL_DTYPE_REC:
8280+
if (((PLpgSQL_variable *) datum)->isconst)
8281+
ereport(ERROR,
8282+
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
8283+
errmsg("variable \"%s\" is declared CONSTANT",
8284+
((PLpgSQL_variable *) datum)->refname)));
8285+
break;
8286+
case PLPGSQL_DTYPE_ROW:
8287+
/* always assignable; member vars were checked at compile time */
8288+
break;
8289+
case PLPGSQL_DTYPE_RECFIELD:
8290+
/* assignable if parent record is */
8291+
exec_check_assignable(estate,
8292+
((PLpgSQL_recfield *) datum)->recparentno);
8293+
break;
8294+
default:
8295+
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
8296+
break;
8297+
}
8298+
}
8299+
82458300
/* ----------
82468301
* exec_set_found Set the global found variable to true/false
82478302
* ----------

src/pl/plpgsql/src/sql/plpgsql_call.sql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ END;
112112
$$;
113113

114114

115+
DO
116+
LANGUAGE plpgsql
117+
$$
118+
DECLARE
119+
x constant int := 3;
120+
y int := 4;
121+
BEGIN
122+
CALL test_proc6(2, x, y); -- error because x is constant
123+
END;
124+
$$;
125+
126+
115127
DO
116128
LANGUAGE plpgsql
117129
$$

src/test/regress/expected/plpgsql.out

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2256,6 +2256,33 @@ select refcursor_test2(20000, 20000) as "Should be false",
22562256
f | t
22572257
(1 row)
22582258

2259+
-- should fail
2260+
create function constant_refcursor() returns refcursor as $$
2261+
declare
2262+
rc constant refcursor;
2263+
begin
2264+
open rc for select a from rc_test;
2265+
return rc;
2266+
end
2267+
$$ language plpgsql;
2268+
select constant_refcursor();
2269+
ERROR: variable "rc" is declared CONSTANT
2270+
CONTEXT: PL/pgSQL function constant_refcursor() line 5 at OPEN
2271+
-- but it's okay like this
2272+
create or replace function constant_refcursor() returns refcursor as $$
2273+
declare
2274+
rc constant refcursor := 'my_cursor_name';
2275+
begin
2276+
open rc for select a from rc_test;
2277+
return rc;
2278+
end
2279+
$$ language plpgsql;
2280+
select constant_refcursor();
2281+
constant_refcursor
2282+
--------------------
2283+
my_cursor_name
2284+
(1 row)
2285+
22592286
--
22602287
-- tests for cursors with named parameter arguments
22612288
--

src/test/regress/sql/plpgsql.sql

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,6 +1933,30 @@ $$ language plpgsql;
19331933
select refcursor_test2(20000, 20000) as "Should be false",
19341934
refcursor_test2(20, 20) as "Should be true";
19351935

1936+
-- should fail
1937+
create function constant_refcursor() returns refcursor as $$
1938+
declare
1939+
rc constant refcursor;
1940+
begin
1941+
open rc for select a from rc_test;
1942+
return rc;
1943+
end
1944+
$$ language plpgsql;
1945+
1946+
select constant_refcursor();
1947+
1948+
-- but it's okay like this
1949+
create or replace function constant_refcursor() returns refcursor as $$
1950+
declare
1951+
rc constant refcursor := 'my_cursor_name';
1952+
begin
1953+
open rc for select a from rc_test;
1954+
return rc;
1955+
end
1956+
$$ language plpgsql;
1957+
1958+
select constant_refcursor();
1959+
19361960
--
19371961
-- tests for cursors with named parameter arguments
19381962
--

0 commit comments

Comments
 (0)