From 4496bde2064941e348ea2eed42ea73ac17f40dd8 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Wed, 12 Jun 2024 21:34:05 +0200
Subject: [PATCH 1/3] use strict rules for parsing PL/pgSQL expressions

Originally the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed
to allow old undocumented syntax

    var := col FROM tab;

The reason for support of this "strange" syntax was technical. The PLpgSQL parser
cannot use SQL parser accurately (it was really primitive), and people found
this undocumented syntax. Lattery, when it was possible to do exact parsing, from
compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax.

Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept
really broken code like

    DO $$
    DECLARE
      l_cnt int;
    BEGIN
      l_cnt := 1
      DELETE FROM foo3 WHERE id=1;
    END; $$;

proposed patch introduce new extra error check strict_expr_check, that solve
this issue.
---
 doc/src/sgml/plpgsql.sgml            |  18 ++++
 src/backend/executor/spi.c           |   6 ++
 src/backend/parser/gram.y            | 149 +++++++++++++++++++++++++++
 src/backend/parser/parser.c          |   6 ++
 src/include/parser/parser.h          |  22 ++++
 src/interfaces/ecpg/preproc/parse.pl |  10 +-
 src/pl/plpgsql/src/pl_comp.c         |  31 ++++++
 src/pl/plpgsql/src/pl_gram.y         |  48 ++++++---
 src/pl/plpgsql/src/pl_handler.c      |   2 +
 src/pl/plpgsql/src/plpgsql.h         |  19 ++++
 10 files changed, 297 insertions(+), 14 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 78e4983139b..a27700d6cb3 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -5386,6 +5386,24 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry id="plpgsql-extra-checks-strict-expr-check">
+      <term><varname>strict_expr_check</varname></term>
+      <listitem>
+       <para>
+        Enabling this check will cause <application>PL/pgSQL</application> to
+        check if a <application>PL/pgSQL</application> expression is just an
+        expression without any SQL clauses like <literal>FROM</literal>,
+        <literal>ORDER BY</literal>. This undocumented form of expressions
+        is allowed for compatibility reasons, but in some special cases
+        it doesn't to allow to detect broken code.
+       </para>
+
+       <para>
+        This check is allowed only <varname>plpgsql.extra_errors</varname>.
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
 
     The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 2fb2e73604e..97443e93d44 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2984,11 +2984,17 @@ _SPI_error_callback(void *arg)
 		switch (carg->mode)
 		{
 			case RAW_PARSE_PLPGSQL_EXPR:
+			case RAW_PARSE_PLPGSQL_STRICT_EXPR:
+			case RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST:
+			case RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST:
 				errcontext("PL/pgSQL expression \"%s\"", query);
 				break;
 			case RAW_PARSE_PLPGSQL_ASSIGN1:
 			case RAW_PARSE_PLPGSQL_ASSIGN2:
 			case RAW_PARSE_PLPGSQL_ASSIGN3:
+			case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1:
+			case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2:
+			case RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3:
 				errcontext("PL/pgSQL assignment \"%s\"", query);
 				break;
 			default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index baca4059d2e..247ed336fc3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
 				PLpgSQL_Expr PLAssignStmt
+				PLpgSQLStrictExpr PLpgSQLStrictExprs PLpgSQLStrictNamedExprs
+				PLAssignStmtStrictExpr
+
+%type <target>	plpgsql_strict_expr plpgsql_strict_named_expr
+%type <list>	plpgsql_strict_expr_list plpgsql_strict_named_expr_list
 
 %type <str>			opt_single_name
 %type <list>		opt_qualified_name
@@ -812,6 +817,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %token		MODE_PLPGSQL_ASSIGN1
 %token		MODE_PLPGSQL_ASSIGN2
 %token		MODE_PLPGSQL_ASSIGN3
+%token		MODE_PLPGSQL_STRICT_EXPR
+%token		MODE_PLPGSQL_STRICT_EXPR_LIST
+%token		MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST
+%token		MODE_PLPGSQL_STRICT_EXPR_ASSIGN1
+%token		MODE_PLPGSQL_STRICT_EXPR_ASSIGN2
+%token		MODE_PLPGSQL_STRICT_EXPR_ASSIGN3
 
 
 /* Precedence: lowest to highest */
@@ -943,6 +954,46 @@ parse_toplevel:
 				pg_yyget_extra(yyscanner)->parsetree =
 					list_make1(makeRawStmt((Node *) n, @2));
 			}
+			| MODE_PLPGSQL_STRICT_EXPR PLpgSQLStrictExpr
+			{
+				pg_yyget_extra(yyscanner)->parsetree =
+					list_make1(makeRawStmt($2, 0));
+			}
+			| MODE_PLPGSQL_STRICT_EXPR_LIST PLpgSQLStrictExprs
+			{
+				pg_yyget_extra(yyscanner)->parsetree =
+					list_make1(makeRawStmt($2, 0));
+			}
+			| MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST PLpgSQLStrictNamedExprs
+			{
+					pg_yyget_extra(yyscanner)->parsetree =
+					list_make1(makeRawStmt($2, 0));
+			}
+			| MODE_PLPGSQL_STRICT_EXPR_ASSIGN1 PLAssignStmtStrictExpr
+			{
+				PLAssignStmt *n = (PLAssignStmt *) $2;
+
+				n->nnames = 1;
+				pg_yyget_extra(yyscanner)->parsetree =
+					list_make1(makeRawStmt((Node *) n, 0));
+			}
+			| MODE_PLPGSQL_STRICT_EXPR_ASSIGN2 PLAssignStmtStrictExpr
+			{
+				PLAssignStmt *n = (PLAssignStmt *) $2;
+
+				n->nnames = 2;
+				pg_yyget_extra(yyscanner)->parsetree =
+					list_make1(makeRawStmt((Node *) n, 0));
+			}
+			| MODE_PLPGSQL_STRICT_EXPR_ASSIGN3 PLAssignStmtStrictExpr
+			{
+				PLAssignStmt *n = (PLAssignStmt *) $2;
+
+				n->nnames = 3;
+				pg_yyget_extra(yyscanner)->parsetree =
+					list_make1(makeRawStmt((Node *) n, 0));
+			}
+
 		;
 
 /*
@@ -17488,6 +17539,104 @@ plassign_equals: COLON_EQUALS
 			| '='
 		;
 
+/*
+ * In "strict" mode plpgsql expressions are just an a_expr. From compatibility
+ * reasons (with default mode) it returns SelectStmt still.
+ */
+PLpgSQLStrictExpr: a_expr
+				{
+					SelectStmt *n = makeNode(SelectStmt);
+					ResTarget *rt = makeNode(ResTarget);
+
+					rt->name = NULL;
+					rt->indirection = NIL;
+					rt->val = (Node *) $1;
+					rt->location = @1;
+
+					n->targetList = list_make1((Node *) rt);
+					$$ = (Node *) n;
+				}
+		;
+
+PLAssignStmtStrictExpr: plassign_target opt_indirection plassign_equals PLpgSQLStrictExpr
+				{
+					PLAssignStmt *n = makeNode(PLAssignStmt);
+
+					n->name = $1;
+					n->indirection = check_indirection($2, yyscanner);
+					/* nnames will be filled by calling production */
+					n->val = (SelectStmt *) $4;
+					n->location = @1;
+					$$ = (Node *) n;
+				}
+		;
+
+/*
+ * Used for unnamed plpgsql cursor's argument and plpgsql case in
+ * "strict" mode.
+ */
+PLpgSQLStrictExprs: plpgsql_strict_expr_list
+				{
+					SelectStmt *n = makeNode(SelectStmt);
+
+					n->targetList = $1;
+					$$ = (Node *) n;
+				}
+		;
+
+plpgsql_strict_expr_list:
+			plpgsql_strict_expr
+				{
+					$$ = list_make1($1);
+				}
+			| plpgsql_strict_expr_list ',' plpgsql_strict_expr
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
+plpgsql_strict_expr: a_expr
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = NULL;
+					$$->indirection = NIL;
+					$$->val = (Node *) $1;
+					$$->location = @1;
+				}
+		;
+
+/*
+ * Used for named cursor's arguments in "strict" mode
+ */
+PLpgSQLStrictNamedExprs: plpgsql_strict_named_expr_list
+				{
+					SelectStmt *n = makeNode(SelectStmt);
+
+					n->targetList = $1;
+					$$ = (Node *) n;
+				}
+		;
+
+plpgsql_strict_named_expr_list:
+			plpgsql_strict_named_expr
+				{
+					$$ = list_make1($1);
+				}
+			| plpgsql_strict_named_expr_list ',' plpgsql_strict_named_expr
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
+plpgsql_strict_named_expr: a_expr AS ColLabel
+				{
+					$$ = makeNode(ResTarget);
+					$$->name = $3;
+					$$->indirection = NIL;
+					$$->val = (Node *) $1;
+					$$->location = @1;
+				}
+		;
 
 /*
  * Name classification hierarchy.
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 118488c3f30..9d2dde67939 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -62,6 +62,12 @@ raw_parser(const char *str, RawParseMode mode)
 			[RAW_PARSE_PLPGSQL_ASSIGN1] = MODE_PLPGSQL_ASSIGN1,
 			[RAW_PARSE_PLPGSQL_ASSIGN2] = MODE_PLPGSQL_ASSIGN2,
 			[RAW_PARSE_PLPGSQL_ASSIGN3] = MODE_PLPGSQL_ASSIGN3,
+			[RAW_PARSE_PLPGSQL_STRICT_EXPR] = MODE_PLPGSQL_STRICT_EXPR,
+			[RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST] = MODE_PLPGSQL_STRICT_EXPR_LIST,
+			[RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST] = MODE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+			[RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+			[RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+			[RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3] = MODE_PLPGSQL_STRICT_EXPR_ASSIGN3,
 		};
 
 		yyextra.have_lookahead = true;
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index be184ec5066..8e41907bd4d 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -33,6 +33,22 @@
  * RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement,
  * and return a one-element List containing a RawStmt node.  "n"
  * gives the number of dotted names comprising the target ColumnRef.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR: parse a PL/pgSQL expression, and
+ * return a one-element List containing a RwaStmt node. The result is
+ * compatible with RAW_PARSE_PLPGSQL_EXPR, but parser allows only
+ * a_expr (instead almost all complete query).
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST: parse a comma separated list
+ * of PL/pgSQL expressions (only a_expr are allowed). It is used by
+ * PLpGSQL CASE and OPEN commands.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST: parse a comma separated
+ * list of a_expr node with labels. It is used for evaluation of
+ * named arguments of PLpgSQL OPEN (cursor) statement.
+ *
+ * RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGNn: parse a PL/pgSQL assignment
+ * statement, but only a_expr are allowed).
  */
 typedef enum
 {
@@ -42,6 +58,12 @@ typedef enum
 	RAW_PARSE_PLPGSQL_ASSIGN1,
 	RAW_PARSE_PLPGSQL_ASSIGN2,
 	RAW_PARSE_PLPGSQL_ASSIGN3,
+	RAW_PARSE_PLPGSQL_STRICT_EXPR,
+	RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+	RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+	RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+	RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+	RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3,
 } RawParseMode;
 
 /* Values for the backslash_quote GUC */
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 86943ae2537..9b6ad467ac6 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -70,7 +70,15 @@ my %replace_types = (
 	'PLpgSQL_Expr' => 'ignore',
 	'PLAssignStmt' => 'ignore',
 	'plassign_target' => 'ignore',
-	'plassign_equals' => 'ignore',);
+	'plassign_equals' => 'ignore',
+	'plpgsql_strict_expr' => 'ignore',
+	'plpgsql_strict_named_expr' => 'ignore',
+	'plpgsql_strict_expr_list' => 'ignore',
+	'plpgsql_strict_named_expr_list' => 'ignore',
+	'PLpgSQLStrictExpr' => 'ignore',
+	'PLpgSQLStrictExprs' => 'ignore',
+	'PLpgSQLStrictNamedExprs' => 'ignore',
+	'PLAssignStmtStrictExpr' => 'ignore',);
 
 my %replace_types_used;
 
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index f1bce708d62..8f9d3ed64d2 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -84,6 +84,23 @@ static const ExceptionLabelMap exception_label_map[] = {
 	{NULL, 0}
 };
 
+static const PLpgSQL_parse_modes default_parse_modes = {
+	RAW_PARSE_PLPGSQL_EXPR,
+	RAW_PARSE_PLPGSQL_EXPR,
+	RAW_PARSE_PLPGSQL_EXPR,
+	RAW_PARSE_PLPGSQL_ASSIGN1,
+	RAW_PARSE_PLPGSQL_ASSIGN2,
+	RAW_PARSE_PLPGSQL_ASSIGN3
+};
+
+static const PLpgSQL_parse_modes strict_expr_parse_modes = {
+	RAW_PARSE_PLPGSQL_STRICT_EXPR,
+	RAW_PARSE_PLPGSQL_STRICT_EXPR_LIST,
+	RAW_PARSE_PLPGSQL_STRICT_NAMED_EXPR_LIST,
+	RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN1,
+	RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN2,
+	RAW_PARSE_PLPGSQL_STRICT_EXPR_ASSIGN3
+};
 
 /* ----------
  * static prototypes
@@ -355,6 +372,11 @@ do_compile(FunctionCallInfo fcinfo,
 	function->extra_warnings = forValidator ? plpgsql_extra_warnings : 0;
 	function->extra_errors = forValidator ? plpgsql_extra_errors : 0;
 
+	if (function->extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+		function->pmodes = &strict_expr_parse_modes;
+	else
+		function->pmodes = &default_parse_modes;
+
 	if (is_dml_trigger)
 		function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
 	else if (is_event_trigger)
@@ -898,6 +920,15 @@ plpgsql_compile_inline(char *proc_source)
 	function->extra_warnings = 0;
 	function->extra_errors = 0;
 
+	/*
+	 * Although function->extra_errors is disabled, we want to
+	 * do strict_expr_check inside annoymous block too.
+	 */
+	if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTEXPRCHECK)
+		function->pmodes = &strict_expr_parse_modes;
+	else
+		function->pmodes = &default_parse_modes;
+
 	function->nstatements = 0;
 	function->requires_procedure_resowner = false;
 
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 8182ce28aa1..d3bf9ab6a5f 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -183,6 +183,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %type <expr>	expr_until_semi
 %type <expr>	expr_until_then expr_until_loop opt_expr_until_when
 %type <expr>	opt_exitcond
+%type <expr>	expressions_until_then
 
 %type <var>		cursor_variable
 %type <datum>	decl_cursor_arg
@@ -962,13 +963,13 @@ stmt_assign		: T_DATUM
 						switch ($1.ident ? 1 : list_length($1.idents))
 						{
 							case 1:
-								pmode = RAW_PARSE_PLPGSQL_ASSIGN1;
+								pmode = plpgsql_curr_compile->pmodes->pmode_assign1;
 								break;
 							case 2:
-								pmode = RAW_PARSE_PLPGSQL_ASSIGN2;
+								pmode = plpgsql_curr_compile->pmodes->pmode_assign2;
 								break;
 							case 3:
-								pmode = RAW_PARSE_PLPGSQL_ASSIGN3;
+								pmode = plpgsql_curr_compile->pmodes->pmode_assign3;
 								break;
 							default:
 								elog(ERROR, "unexpected number of names");
@@ -1244,7 +1245,7 @@ case_when_list	: case_when_list case_when
 					}
 				;
 
-case_when		: K_WHEN expr_until_then proc_sect
+case_when		: K_WHEN expressions_until_then proc_sect
 					{
 						PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
 
@@ -1274,6 +1275,14 @@ opt_case_else	:
 					}
 				;
 
+expressions_until_then :
+					{
+						$$ = read_sql_construct(K_THEN, 0, 0, "THEN",
+												plpgsql_curr_compile->pmodes->pmode_expr_list,
+												true, true, NULL, NULL);
+					}
+				;
+
 stmt_loop		: opt_loop_label K_LOOP loop_body
 					{
 						PLpgSQL_stmt_loop *new;
@@ -1493,7 +1502,8 @@ for_control		: for_variable K_IN
 								 * Relabel first expression as an expression;
 								 * then we can check its syntax.
 								 */
-								expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+								expr1->parseMode = plpgsql_curr_compile->pmodes->pmode_expr;
+
 								check_sql_expr(expr1->query, expr1->parseMode,
 											   expr1loc);
 
@@ -1863,6 +1873,8 @@ stmt_raise		: K_RAISE
 							 */
 							if (tok == SCONST)
 							{
+								RawParseMode pmode_expr;
+
 								/* old style message and parameters */
 								new->message = yylval.str;
 								/*
@@ -1875,13 +1887,15 @@ stmt_raise		: K_RAISE
 								if (tok != ',' && tok != ';' && tok != K_USING)
 									yyerror("syntax error");
 
+								pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
 								while (tok == ',')
 								{
 									PLpgSQL_expr *expr;
 
 									expr = read_sql_construct(',', ';', K_USING,
 															  ", or ; or USING",
-															  RAW_PARSE_PLPGSQL_EXPR,
+															  pmode_expr,
 															  true, true,
 															  NULL, &tok);
 									new->params = lappend(new->params, expr);
@@ -2015,10 +2029,13 @@ stmt_dynexecute : K_EXECUTE
 						PLpgSQL_stmt_dynexecute *new;
 						PLpgSQL_expr *expr;
 						int			endtoken;
+						RawParseMode pmode_expr;
+
+						pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
 
 						expr = read_sql_construct(K_INTO, K_USING, ';',
 												  "INTO or USING or ;",
-												  RAW_PARSE_PLPGSQL_EXPR,
+												  pmode_expr,
 												  true, true,
 												  NULL, &endtoken);
 
@@ -2057,7 +2074,7 @@ stmt_dynexecute : K_EXECUTE
 								{
 									expr = read_sql_construct(',', ';', K_INTO,
 															  ", or ; or INTO",
-															  RAW_PARSE_PLPGSQL_EXPR,
+															  pmode_expr,
 															  true, true,
 															  NULL, &endtoken);
 									new->params = lappend(new->params, expr);
@@ -2642,7 +2659,7 @@ static PLpgSQL_expr *
 read_sql_expression(int until, const char *expected)
 {
 	return read_sql_construct(until, 0, 0, expected,
-							  RAW_PARSE_PLPGSQL_EXPR,
+							  plpgsql_curr_compile->pmodes->pmode_expr,
 							  true, true, NULL, NULL);
 }
 
@@ -2652,7 +2669,7 @@ read_sql_expression2(int until, int until2, const char *expected,
 					 int *endtoken)
 {
 	return read_sql_construct(until, until2, 0, expected,
-							  RAW_PARSE_PLPGSQL_EXPR,
+							  plpgsql_curr_compile->pmodes->pmode_expr,
 							  true, true, NULL, endtoken);
 }
 
@@ -3845,6 +3862,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
 	char	  **argv;
 	StringInfoData ds;
 	bool		any_named = false;
+	RawParseMode pmode_expr;
 
 	tok = yylex();
 	if (cursor->cursor_explicit_argrow < 0)
@@ -3871,6 +3889,8 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
 						cursor->refname),
 				 parser_errposition(yylloc)));
 
+	pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
 	/*
 	 * Read the arguments, one by one.
 	 */
@@ -3941,7 +3961,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
 		 */
 		item = read_sql_construct(',', ')', 0,
 								  ",\" or \")",
-								  RAW_PARSE_PLPGSQL_EXPR,
+								  pmode_expr,
 								  true, true,
 								  NULL, &endtoken);
 
@@ -3982,7 +4002,9 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
 
 	expr = palloc0(sizeof(PLpgSQL_expr));
 	expr->query = pstrdup(ds.data);
-	expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+	expr->parseMode = any_named ?
+						  plpgsql_curr_compile->pmodes->pmode_named_expr_list
+						  : plpgsql_curr_compile->pmodes->pmode_expr_list;
 	expr->plan = NULL;
 	expr->paramnos = NULL;
 	expr->target_param = -1;
@@ -4156,7 +4178,7 @@ make_case(int location, PLpgSQL_expr *t_expr,
 			StringInfoData ds;
 
 			/* We expect to have expressions not statements */
-			Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR);
+			Assert(expr->parseMode == plpgsql_curr_compile->pmodes->pmode_expr_list);
 
 			/* Do the string hacking */
 			initStringInfo(&ds);
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index adfbbc8a7b7..5cb779a85b6 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -94,6 +94,8 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
 				extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
 			else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
 				extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+			else if (pg_strcasecmp(tok, "strict_expr_check") == 0)
+				extrachecks |= PLPGSQL_XCHECK_STRICTEXPRCHECK;
 			else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
 			{
 				GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 50c3b28472b..b2fb67e8514 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -960,6 +960,21 @@ typedef enum PLpgSQL_trigtype
 	PLPGSQL_NOT_TRIGGER,
 } PLpgSQL_trigtype;
 
+/*
+ * Raw parse modes, that should be used for expressions,
+ * assignment, expression's list. When extra_errors.strict_expr_check
+ * is active, then only a_expr parsing is allowed.
+ */
+typedef struct PLpgSQL_parse_modes
+{
+	RawParseMode pmode_expr;
+	RawParseMode pmode_expr_list;
+	RawParseMode pmode_named_expr_list;
+	RawParseMode pmode_assign1;
+	RawParseMode pmode_assign2;
+	RawParseMode pmode_assign3;
+} PLpgSQL_parse_modes;
+
 /*
  * Complete compiled function
  */
@@ -1010,6 +1025,9 @@ typedef struct PLpgSQL_function
 	unsigned int nstatements;	/* counter for assigning stmtids */
 	bool		requires_procedure_resowner;	/* contains CALL or DO? */
 
+	/* Raw parse modes configuration */
+	const PLpgSQL_parse_modes *pmodes;
+
 	/* these fields change when the function is used */
 	struct PLpgSQL_execstate *cur_estate;
 	unsigned long use_count;
@@ -1204,6 +1222,7 @@ extern bool plpgsql_check_asserts;
 #define PLPGSQL_XCHECK_SHADOWVAR				(1 << 1)
 #define PLPGSQL_XCHECK_TOOMANYROWS				(1 << 2)
 #define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT	(1 << 3)
+#define PLPGSQL_XCHECK_STRICTEXPRCHECK			(1 << 4)
 #define PLPGSQL_XCHECK_ALL						((int) ~0)
 
 extern int	plpgsql_extra_warnings;
-- 
2.47.0

