From 4e582d825af9d3463be332a90b9e959980480293 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.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         |  49 ++++++---
 src/pl/plpgsql/src/pl_handler.c      |   2 +
 src/pl/plpgsql/src/plpgsql.h         |  19 ++++
 10 files changed, 298 insertions(+), 14 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 78e4983139..a27700d6cb 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 ecb2e4ccaa..f9d2703162 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 6079de70e0..1b72db6952 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -312,6 +312,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
@@ -814,6 +819,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 */
@@ -945,6 +956,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));
+			}
+
 		;
 
 /*
@@ -17562,6 +17613,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 33a040506b..19fe3b4194 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 350196cd64..e96dce37db 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 ad9aec63cb..3a209cffd3 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 9dc8218292..172f0196ce 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -83,6 +83,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
@@ -362,6 +379,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)
@@ -907,6 +929,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 063ed81f05..9fbcf5477d 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -188,6 +188,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
@@ -969,13 +970,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");
@@ -1252,7 +1253,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));
 
@@ -1282,6 +1283,15 @@ 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,
+												&yylval, &yylloc, yyscanner);
+					}
+				;
+
 stmt_loop		: opt_loop_label K_LOOP loop_body
 					{
 						PLpgSQL_stmt_loop *new;
@@ -1501,7 +1511,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, yyscanner);
 
@@ -1873,6 +1884,8 @@ stmt_raise		: K_RAISE
 							 */
 							if (tok == SCONST)
 							{
+								RawParseMode pmode_expr;
+
 								/* old style message and parameters */
 								new->message = yylval.str;
 								/*
@@ -1885,13 +1898,15 @@ stmt_raise		: K_RAISE
 								if (tok != ',' && tok != ';' && tok != K_USING)
 									yyerror(&yylloc, yyscanner, "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,
 															  &yylval, &yylloc, yyscanner);
@@ -2026,10 +2041,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,
 												  &yylval, &yylloc, yyscanner);
@@ -2069,7 +2087,7 @@ stmt_dynexecute : K_EXECUTE
 								{
 									expr = read_sql_construct(',', ';', K_INTO,
 															  ", or ; or INTO",
-															  RAW_PARSE_PLPGSQL_EXPR,
+															  pmode_expr,
 															  true, true,
 															  NULL, &endtoken,
 															  &yylval, &yylloc, yyscanner);
@@ -2655,7 +2673,7 @@ static PLpgSQL_expr *
 read_sql_expression(int until, const char *expected, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 {
 	return read_sql_construct(until, 0, 0, expected,
-							  RAW_PARSE_PLPGSQL_EXPR,
+							  plpgsql_curr_compile->pmodes->pmode_expr,
 							  true, true, NULL, NULL,
 							  yylvalp, yyllocp, yyscanner);
 }
@@ -2666,7 +2684,7 @@ read_sql_expression2(int until, int until2, const char *expected,
 					 int *endtoken, YYSTYPE *yylvalp, YYLTYPE *yyllocp, yyscan_t yyscanner)
 {
 	return read_sql_construct(until, until2, 0, expected,
-							  RAW_PARSE_PLPGSQL_EXPR,
+							  plpgsql_curr_compile->pmodes->pmode_expr,
 							  true, true, NULL, endtoken,
 							  yylvalp, yyllocp, yyscanner);
 }
@@ -3869,6 +3887,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
 	char	  **argv;
 	StringInfoData ds;
 	bool		any_named = false;
+	RawParseMode pmode_expr;
 
 	tok = yylex(yylvalp, yyllocp, yyscanner);
 	if (cursor->cursor_explicit_argrow < 0)
@@ -3895,6 +3914,8 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
 						cursor->refname),
 				 parser_errposition(*yyllocp)));
 
+	pmode_expr = plpgsql_curr_compile->pmodes->pmode_expr;
+
 	/*
 	 * Read the arguments, one by one.
 	 */
@@ -3965,7 +3986,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
 		 */
 		item = read_sql_construct(',', ')', 0,
 								  ",\" or \")",
-								  RAW_PARSE_PLPGSQL_EXPR,
+								  pmode_expr,
 								  true, true,
 								  NULL, &endtoken,
 								  yylvalp, yyllocp, yyscanner);
@@ -4007,7 +4028,9 @@ read_cursor_args(PLpgSQL_var *cursor, int until, YYSTYPE *yylvalp, YYLTYPE *yyll
 
 	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;
@@ -4181,7 +4204,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 5af38d5773..3ce196de58 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 c3ce4161a3..8a4b9f7636 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.1

