Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 3d21683

Browse files
author
Commitfest Bot
committed
[CF 5044] v20250227 - 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/CAFj8pRBAbKe8A50MEeMg2bTJcwhBfS01bCKFaiC_z__YWsVM+Q@mail.gmail.com Author(s): Pavel Stehule
2 parents 5987553 + 354dba4 commit 3d21683

File tree

7 files changed

+177
-18
lines changed

7 files changed

+177
-18
lines changed

doc/src/sgml/plpgsql.sgml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5388,6 +5388,25 @@ 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+
5404+
<para>
5405+
This check is allowed only when <varname>plpgsql.extra_errors</varname>
5406+
is set to <literal>"strict_expr_check"</literal>.
5407+
</para>
5408+
</listitem>
5409+
</varlistentry>
53915410
</variablelist>
53925411

53935412
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
@@ -786,6 +786,13 @@ plpgsql_compile_inline(char *proc_source)
786786
function->extra_warnings = 0;
787787
function->extra_errors = 0;
788788

789+
/*
790+
* Although function->extra_errors is disabled, we want to
791+
* do strict_expr_check inside annoymous block too.
792+
*/
793+
if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
794+
function->extra_errors = PLPGSQL_XCHECK_STRICTEXPRCHECK;
795+
789796
function->nstatements = 0;
790797
function->requires_procedure_resowner = false;
791798
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;
@@ -1495,6 +1508,7 @@ for_control : for_variable K_IN
14951508
RAW_PARSE_DEFAULT,
14961509
true,
14971510
false,
1511+
false,
14981512
&expr1loc,
14991513
&tok,
15001514
&yylval, &yylloc, yyscanner);
@@ -1513,7 +1527,7 @@ for_control : for_variable K_IN
15131527
*/
15141528
expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
15151529
check_sql_expr(expr1->query, expr1->parseMode,
1516-
expr1loc, yyscanner);
1530+
false, expr1loc, yyscanner);
15171531

15181532
/* Read and check the second one */
15191533
expr2 = read_sql_expression2(K_LOOP, K_BY,
@@ -1570,7 +1584,7 @@ for_control : for_variable K_IN
15701584

15711585
/* Check syntax as a regular query */
15721586
check_sql_expr(expr1->query, expr1->parseMode,
1573-
expr1loc, yyscanner);
1587+
false, expr1loc, yyscanner);
15741588

15751589
new = palloc0(sizeof(PLpgSQL_stmt_fors));
15761590
new->cmd_type = PLPGSQL_STMT_FORS;
@@ -1902,7 +1916,7 @@ stmt_raise : K_RAISE
19021916
expr = read_sql_construct(',', ';', K_USING,
19031917
", or ; or USING",
19041918
RAW_PARSE_PLPGSQL_EXPR,
1905-
true, true,
1919+
true, false, true,
19061920
NULL, &tok,
19071921
&yylval, &yylloc, yyscanner);
19081922
new->params = lappend(new->params, expr);
@@ -2040,7 +2054,7 @@ stmt_dynexecute : K_EXECUTE
20402054
expr = read_sql_construct(K_INTO, K_USING, ';',
20412055
"INTO or USING or ;",
20422056
RAW_PARSE_PLPGSQL_EXPR,
2043-
true, true,
2057+
true, false, true,
20442058
NULL, &endtoken,
20452059
&yylval, &yylloc, yyscanner);
20462060

@@ -2080,7 +2094,7 @@ stmt_dynexecute : K_EXECUTE
20802094
expr = read_sql_construct(',', ';', K_INTO,
20812095
", or ; or INTO",
20822096
RAW_PARSE_PLPGSQL_EXPR,
2083-
true, true,
2097+
true, false, true,
20842098
NULL, &endtoken,
20852099
&yylval, &yylloc, yyscanner);
20862100
new->params = lappend(new->params, expr);
@@ -2713,7 +2727,7 @@ read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *
27132727
{
27142728
return read_sql_construct(until, 0, 0, expected,
27152729
RAW_PARSE_PLPGSQL_EXPR,
2716-
true, true, NULL, NULL,
2730+
true, false, true, NULL, NULL,
27172731
yylvalp, yyllocp, yyscanner);
27182732
}
27192733

@@ -2724,7 +2738,7 @@ read_sql_expression2(int until, int until2, const char *expected,
27242738
{
27252739
return read_sql_construct(until, until2, 0, expected,
27262740
RAW_PARSE_PLPGSQL_EXPR,
2727-
true, true, NULL, endtoken,
2741+
true, false, true, NULL, endtoken,
27282742
yylvalp, yyllocp, yyscanner);
27292743
}
27302744

@@ -2734,7 +2748,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
27342748
{
27352749
return read_sql_construct(';', 0, 0, ";",
27362750
RAW_PARSE_DEFAULT,
2737-
false, true, NULL, NULL,
2751+
false, false, true, NULL, NULL,
27382752
yylvalp, yyllocp, yyscanner);
27392753
}
27402754

@@ -2747,6 +2761,7 @@ read_sql_stmt(YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
27472761
* expected: text to use in complaining that terminator was not found
27482762
* parsemode: raw_parser() mode to use
27492763
* isexpression: whether to say we're reading an "expression" or a "statement"
2764+
* allowlist: the result can be list of expressions
27502765
* valid_sql: whether to check the syntax of the expr
27512766
* startloc: if not NULL, location of first token is stored at *startloc
27522767
* endtoken: if not NULL, ending token is stored at *endtoken
@@ -2759,6 +2774,7 @@ read_sql_construct(int until,
27592774
const char *expected,
27602775
RawParseMode parsemode,
27612776
bool isexpression,
2777+
bool allowlist,
27622778
bool valid_sql,
27632779
int *startloc,
27642780
int *endtoken,
@@ -2854,7 +2870,7 @@ read_sql_construct(int until,
28542870
pfree(ds.data);
28552871

28562872
if (valid_sql)
2857-
check_sql_expr(expr->query, expr->parseMode, startlocation, yyscanner);
2873+
check_sql_expr(expr->query, expr->parseMode, allowlist, startlocation, yyscanner);
28582874

28592875
return expr;
28602876
}
@@ -3175,7 +3191,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *word, YYSTYPE *yylvalp,
31753191
expr = make_plpgsql_expr(ds.data, RAW_PARSE_DEFAULT);
31763192
pfree(ds.data);
31773193

3178-
check_sql_expr(expr->query, expr->parseMode, location, yyscanner);
3194+
check_sql_expr(expr->query, expr->parseMode, false, location, yyscanner);
31793195

31803196
execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
31813197
execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
@@ -3775,11 +3791,15 @@ make_scalar_list1(char *initial_name,
37753791
* If no error cursor is provided, we'll just point at "location".
37763792
*/
37773793
static void
3778-
check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t yyscanner)
3794+
check_sql_expr(const char *stmt,
3795+
RawParseMode parseMode, bool allowlist,
3796+
int location, yyscan_t yyscanner)
37793797
{
37803798
sql_error_callback_arg cbarg;
37813799
ErrorContextCallback syntax_errcontext;
37823800
MemoryContext oldCxt;
3801+
List *parsetree;
3802+
int errpos;
37833803

37843804
if (!plpgsql_check_syntax)
37853805
return;
@@ -3793,11 +3813,25 @@ check_sql_expr(const char *stmt, RawParseMode parseMode, int location, yyscan_t
37933813
error_context_stack = &syntax_errcontext;
37943814

37953815
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
3796-
(void) raw_parser(stmt, parseMode);
3816+
parsetree = raw_parser(stmt, parseMode);
37973817
MemoryContextSwitchTo(oldCxt);
37983818

37993819
/* Restore former ereport callback */
38003820
error_context_stack = syntax_errcontext.previous;
3821+
3822+
if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_STRICTEXPRCHECK ||
3823+
plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
3824+
{
3825+
/* do this check only for expressions */
3826+
if (parseMode == RAW_PARSE_DEFAULT)
3827+
return;
3828+
3829+
if (!is_strict_expr(parsetree, &errpos, allowlist))
3830+
ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK ? ERROR : WARNING,
3831+
(errcode(ERRCODE_SYNTAX_ERROR),
3832+
errmsg("syntax of expression is not strict"),
3833+
parser_errposition(errpos != -1 ? location + errpos : location)));
3834+
}
38013835
}
38023836

38033837
static void
@@ -3831,6 +3865,74 @@ plpgsql_sql_error_callback(void *arg)
38313865
errposition(0);
38323866
}
38333867

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

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)