Skip to content

Commit 172189d

Browse files
author
Commitfest Bot
committed
[CF 5044] v20250731 - new plpgsql.extra_errors check - strict_expr_check
This branch was automatically generated by a robot using patches from an email thread registered at: https://commitfest.postgresql.org/patch/5044 The branch will be overwritten each time a new patch version is posted to the thread, and also periodically to check for bitrot caused by changes on the master branch. Patch(es): https://www.postgresql.org/message-id/CAFj8pRAzvqs3T-j43j3ewqMN2R2CdOu3qyYOu5Fd5fh+KoQAcg@mail.gmail.com Author(s): Pavel Stehule
2 parents 03fbb08 + 951ad35 commit 172189d

File tree

7 files changed

+172
-18
lines changed

7 files changed

+172
-18
lines changed

doc/src/sgml/plpgsql.sgml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5388,6 +5388,20 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
53885388
</para>
53895389
</listitem>
53905390
</varlistentry>
5391+
5392+
<varlistentry id="plpgsql-extra-checks-strict-expr-check">
5393+
<term><varname>strict_expr_check</varname></term>
5394+
<listitem>
5395+
<para>
5396+
Enabling this check will cause <application>PL/pgSQL</application> to
5397+
check if a <application>PL/pgSQL</application> expression is just an
5398+
expression without any SQL clauses like <literal>FROM</literal>,
5399+
<literal>ORDER BY</literal>. This undocumented form of expressions
5400+
is allowed for compatibility reasons, but in some special cases
5401+
it doesn't allow to detect broken code.
5402+
</para>
5403+
</listitem>
5404+
</varlistentry>
53915405
</variablelist>
53925406

53935407
The following example shows the effect of <varname>plpgsql.extra_warnings</varname>

src/pl/plpgsql/src/pl_comp.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,13 @@ plpgsql_compile_inline(char *proc_source)
800800
function->extra_warnings = 0;
801801
function->extra_errors = 0;
802802

803+
/*
804+
* Although function->extra_errors is disabled, we want to
805+
* do strict_expr_check inside annoymous block too.
806+
*/
807+
if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
808+
function->extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
809+
803810
function->nstatements = 0;
804811
function->requires_procedure_resowner = false;
805812
function->has_exception_block = false;

src/pl/plpgsql/src/pl_gram.y

Lines changed: 120 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "catalog/namespace.h"
1919
#include "catalog/pg_proc.h"
2020
#include "catalog/pg_type.h"
21+
#include "nodes/nodeFuncs.h"
2122
#include "parser/parser.h"
2223
#include "parser/parse_type.h"
2324
#include "parser/scanner.h"
@@ -71,6 +72,7 @@ static PLpgSQL_expr *read_sql_construct(int until,
7172
const char *expected,
7273
RawParseMode parsemode,
7374
bool isexpression,
75+
bool allowlist,
7476
bool valid_sql,
7577
int *startloc,
7678
int *endtoken,
@@ -106,7 +108,7 @@ static PLpgSQL_row *make_scalar_list1(char *initial_name,
106108
PLpgSQL_datum *initial_datum,
107109
int lineno, int location, yyscan_t yyscanner);
108110
static void check_sql_expr(const char *stmt,
109-
RawParseMode parseMode, int location, yyscan_t yyscanner);
111+
RawParseMode parseMode, bool allowlist, int location, yyscan_t yyscanner);
110112
static void plpgsql_sql_error_callback(void *arg);
111113
static PLpgSQL_type *parse_datatype(const char *string, int location, yyscan_t yyscanner);
112114
static void check_labels(const char *start_label,
@@ -117,6 +119,7 @@ static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor, int until,
117119
YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
118120
static List *read_raise_options(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner);
119121
static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
122+
static bool is_strict_expr(List *parsetree, int *errpos, bool allowlist);
120123

121124
%}
122125

@@ -193,6 +196,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
193196
%type <expr> expr_until_semi
194197
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
195198
%type <expr> opt_exitcond
199+
%type <expr> expressions_until_then
196200

197201
%type <var> cursor_variable
198202
%type <datum> decl_cursor_arg
@@ -914,7 +918,7 @@ stmt_perform : K_PERFORM
914918
*/
915919
new->expr = read_sql_construct(';', 0, 0, ";",
916920
RAW_PARSE_DEFAULT,
917-
false, false,
921+
false, false, false,
918922
&startloc, NULL,
919923
&yylval, &yylloc, yyscanner);
920924
/* overwrite "perform" ... */
@@ -924,7 +928,7 @@ stmt_perform : K_PERFORM
924928
strlen(new->expr->query));
925929
/* offset syntax error position to account for that */
926930
check_sql_expr(new->expr->query, new->expr->parseMode,
927-
startloc + 1, yyscanner);
931+
false, startloc + 1, yyscanner);
928932

929933
$$ = (PLpgSQL_stmt *) new;
930934
}
@@ -1001,7 +1005,7 @@ stmt_assign : T_DATUM
10011005
plpgsql_push_back_token(T_DATUM, &yylval, &yylloc, yyscanner);
10021006
new->expr = read_sql_construct(';', 0, 0, ";",
10031007
pmode,
1004-
false, true,
1008+
false, false, true,
10051009
NULL, NULL,
10061010
&yylval, &yylloc, yyscanner);
10071011
mark_expr_as_assignment_source(new->expr, $1.datum);
@@ -1262,7 +1266,7 @@ case_when_list : case_when_list case_when
12621266
}
12631267
;
12641268

1265-
case_when : K_WHEN expr_until_then proc_sect
1269+
case_when : K_WHEN expressions_until_then proc_sect
12661270
{
12671271
PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
12681272

@@ -1292,6 +1296,15 @@ opt_case_else :
12921296
}
12931297
;
12941298

1299+
expressions_until_then :
1300+
{
1301+
$$ = read_sql_construct(K_THEN, 0, 0, "THEN",
1302+
RAW_PARSE_PLPGSQL_EXPR, /* expr_list */
1303+
true, true, true, NULL, NULL,
1304+
&yylval, &yylloc, yyscanner);
1305+
}
1306+
;
1307+
12951308
stmt_loop : opt_loop_label K_LOOP loop_body
12961309
{
12971310
PLpgSQL_stmt_loop *new;
@@ -1496,6 +1509,7 @@ for_control : for_variable K_IN
14961509
RAW_PARSE_DEFAULT,
14971510
true,
14981511
false,
1512+
false,
14991513
&expr1loc,
15001514
&tok,
15011515
&yylval, &yylloc, yyscanner);
@@ -1514,7 +1528,7 @@ for_control : for_variable K_IN
15141528
*/
15151529
expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
15161530
check_sql_expr(expr1->query, expr1->parseMode,
1517-
expr1loc, yyscanner);
1531+
false, expr1loc, yyscanner);
15181532

15191533
/* Read and check the second one */
15201534
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1571,7 +1585,7 @@ for_control : for_variable K_IN
15711585

15721586
/* Check syntax as a regular query */
15731587
check_sql_expr(expr1->query, expr1->parseMode,
1574-
expr1loc, yyscanner);
1588+
false, expr1loc, yyscanner);
15751589

15761590
new = palloc0(sizeof(PLpgSQL_stmt_fors));
15771591
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1903,7 +1917,7 @@ stmt_raise : K_RAISE
19031917
expr = read_sql_construct(',', ';', K_USING,
19041918
", or ; or USING",
19051919
RAW_PARSE_PLPGSQL_EXPR,
1906-
true, true,
1920+
true, false, true,
19071921
NULL, &tok,
19081922
&yylval, &yylloc, yyscanner);
19091923
new->params = lappend(new->params, expr);
@@ -2041,7 +2055,7 @@ stmt_dynexecute : K_EXECUTE
20412055
expr = read_sql_construct(K_INTO, K_USING, ';',
20422056
"INTO or USING or ;",
20432057
RAW_PARSE_PLPGSQL_EXPR,
2044-
true, true,
2058+
true, false, true,
20452059
NULL, &endtoken,
20462060
&yylval, &yylloc, yyscanner);
20472061

@@ -2081,7 +2095,7 @@ stmt_dynexecute : K_EXECUTE
20812095
expr = read_sql_construct(',', ';', K_INTO,
20822096
", or ; or INTO",
20832097
RAW_PARSE_PLPGSQL_EXPR,
2084-
true, true,
2098+
true, false, true,
20852099
NULL, &endtoken,
20862100
&yylval, &yylloc, yyscanner);
20872101
new->params = lappend(new->params, expr);
@@ -2717,7 +2731,7 @@ read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *
27172731
{
27182732
return read_sql_construct(until, 0, 0, expected,
27192733
RAW_PARSE_PLPGSQL_EXPR,
2720-
true, true, NULL, NULL,
2734+
true, false, true, NULL, NULL,
27212735
yylvalp, yyllocp, yyscanner);
27222736
}
27232737

@@ -2728,7 +2742,7 @@ read_sql_expression2(int until, int until2, const char *expected,
27282742
{
27292743
return read_sql_construct(until, until2, 0, expected,
27302744
RAW_PARSE_PLPGSQL_EXPR,
2731-
true, true, NULL, endtoken,
2745+
true, false, true, NULL, endtoken,
27322746
yylvalp, yyllocp, yyscanner);
27332747
}
27342748

@@ -2738,7 +2752,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
27382752
{
27392753
return read_sql_construct(';', 0, 0, ";",
27402754
RAW_PARSE_DEFAULT,
2741-
false, true, NULL, NULL,
2755+
false, false, true, NULL, NULL,
27422756
yylvalp, yyllocp, yyscanner);
27432757
}
27442758

@@ -2751,6 +2765,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
27512765
* expected: text to use in complaining that terminator was not found
27522766
* parsemode: raw_parser() mode to use
27532767
* isexpression: whether to say we're reading an "expression" or a "statement"
2768+
* allowlist: the result can be list of expressions
27542769
* valid_sql: whether to check the syntax of the expr
27552770
* startloc: if not NULL, location of first token is stored at *startloc
27562771
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2763,6 +2778,7 @@ read_sql_construct(int until,
27632778
const char *expected,
27642779
RawParseMode parsemode,
27652780
bool isexpression,
2781+
bool allowlist,
27662782
bool valid_sql,
27672783
int *startloc,
27682784
int *endtoken,
@@ -2858,7 +2874,7 @@ read_sql_construct(int until,
28582874
pfree(ds.data);
28592875

28602876
if (valid_sql)
2861-
check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);
2877+
check_sql_expr(expr->query, expr->parseMode, allowlist, startlocation, yyscanner);
28622878

28632879
return expr;
28642880
}
@@ -3179,7 +3195,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
31793195
expr = make_plpgsql_expr(ds.data, RAW_PARSE_DEFAULT);
31803196
pfree(ds.data);
31813197

3182-
check_sql_expr(expr->query, expr->parseMode, location, yyscanner);
3198+
check_sql_expr(expr->query, expr->parseMode, false, location, yyscanner);
31833199

31843200
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
31853201
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3780,11 +3796,15 @@ make_scalar_list1(char *initial_name,
37803796
* If no error cursor is provided, we'll just point at "location".
37813797
*/
37823798
static void
3783-
check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t yyscanner)
3799+
check_sql_expr(const char *stmt,
3800+
RawParseMode parseMode, bool allowlist,
3801+
int location, yyscan_t yyscanner)
37843802
{
37853803
sql_error_callback_arg cbarg;
37863804
ErrorContextCallback syntax_errcontext;
37873805
MemoryContext oldCxt;
3806+
List *parsetree;
3807+
int errpos;
37883808

37893809
if (!plpgsql_check_syntax)
37903810
return;
@@ -3798,11 +3818,25 @@ check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t
37983818
error_context_stack = &syntax_errcontext;
37993819

38003820
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
3801-
(void) raw_parser(stmt, parseMode);
3821+
parsetree = raw_parser(stmt, parseMode);
38023822
MemoryContextSwitchTo(oldCxt);
38033823

38043824
/* Restore former ereport callback */
38053825
error_context_stack = syntax_errcontext.previous;
3826+
3827+
if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_STRICTEXPRCHECK ||
3828+
plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
3829+
{
3830+
/* do this check only for expressions */
3831+
if (parseMode == RAW_PARSE_DEFAULT)
3832+
return;
3833+
3834+
if (!is_strict_expr(parsetree, &errpos, allowlist))
3835+
ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK ? ERROR : WARNING,
3836+
(errcode(ERRCODE_SYNTAX_ERROR),
3837+
errmsg("syntax of expression is not strict"),
3838+
parser_errposition(errpos != -1 ? location + errpos : location)));
3839+
}
38063840
}
38073841

38083842
static void
@@ -3836,6 +3870,74 @@ plpgsql_sql_error_callback(void *arg)
38363870
errposition(0);
38373871
}
38383872

3873+
/*
3874+
* Returns true, when the only targetList is in parsetree. Cursors
3875+
* can require list of expressions or list of named expressions.
3876+
*/
3877+
static bool
3878+
is_strict_expr(List *parsetree, int *errpos, bool allowlist)
3879+
{
3880+
RawStmt *rawstmt;
3881+
SelectStmt *select;
3882+
int targets = 0;
3883+
ListCell *lc;
3884+
3885+
/* Top should be RawStmt */
3886+
rawstmt = castNode(RawStmt, linitial(parsetree));
3887+
3888+
if (IsA(rawstmt->stmt, SelectStmt))
3889+
{
3890+
select = (SelectStmt *) rawstmt->stmt;
3891+
}
3892+
else if (IsA(rawstmt->stmt, PLAssignStmt))
3893+
{
3894+
select = castNode(SelectStmt, ((PLAssignStmt *) rawstmt->stmt)->val);
3895+
}
3896+
else
3897+
elog(ERROR, "unexpected node type");
3898+
3899+
if (!select->targetList)
3900+
{
3901+
*errpos = -1;
3902+
return false;
3903+
}
3904+
else
3905+
*errpos = exprLocation((Node *) select->targetList);
3906+
3907+
if (select->distinctClause ||
3908+
select->fromClause ||
3909+
select->whereClause ||
3910+
select->groupClause ||
3911+
select->groupDistinct ||
3912+
select->havingClause ||
3913+
select->windowClause ||
3914+
select->sortClause ||
3915+
select->limitOffset ||
3916+
select->limitCount ||
3917+
select->limitOption ||
3918+
select->lockingClause)
3919+
return false;
3920+
3921+
foreach(lc, select->targetList)
3922+
{
3923+
ResTarget *rt = castNode(ResTarget, lfirst(lc));
3924+
3925+
if (targets++ >= 1 && !allowlist)
3926+
{
3927+
*errpos = exprLocation((Node *) rt);
3928+
return false;
3929+
}
3930+
3931+
if (rt->name)
3932+
{
3933+
*errpos = exprLocation((Node *) rt);
3934+
return false;
3935+
}
3936+
}
3937+
3938+
return true;
3939+
}
3940+
38393941
/*
38403942
* Parse a SQL datatype name and produce a PLpgSQL_type structure.
38413943
*
@@ -4025,7 +4127,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
40254127
item = read_sql_construct(',', ')', 0,
40264128
",\" or \")",
40274129
RAW_PARSE_PLPGSQL_EXPR,
4028-
true, true,
4130+
true, false, true,
40294131
NULL, &endtoken,
40304132
yylvalp, yyllocp, yyscanner);
40314133

src/pl/plpgsql/src/pl_handler.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
9797
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
9898
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
9999
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
100+
else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
101+
extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
100102
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
101103
{
102104
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);

src/pl/plpgsql/src/plpgsql.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,7 @@ extern bool plpgsql_check_asserts;
11951195
#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
11961196
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
11971197
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
1198+
#define PLPGSQL_XCHECK_STRICTEXPRCHECK (1 << 4)
11981199
#define PLPGSQL_XCHECK_ALL ((int) ~0)
11991200

12001201
extern int plpgsql_extra_warnings;

0 commit comments

Comments
 (0)