On 8/17/15 4:46 PM, Tom Lane wrote:
Jim Nasby <jim.na...@bluetreble.com> writes:
On 8/17/15 9:48 AM, Tom Lane wrote:
I'm inclined to think that if we wanted to make this better, the way to
improve it would be to detect the error*at compile time*, and get rid of
this hack in plpgsql_exec_function altogether.

So split PLPGSQL_NSTYPE_LABEL into PLPGSQL_NSTYPE_BLOCK_LABEL and
PLPGSQL_NSTYPE_LOOP_LABEL, and split opt_block_label and opt_label the
same way?

I think using two NSTYPE codes would probably be a pain because there are
numerous places that don't care about the distinction; it'd be better to
have a secondary attribute distinguishing these cases.  (It looks like you
could perhaps reuse the "itemno" field for the purpose, since that seems
to be going unused in LABEL items.)

You likely do need to split opt_block_label into two productions, since
that will be the easiest way to pass forward the knowledge of whether
it's being called from a loop or non-loop construct.

Here's a patch that does that. This also made it possible to check for CONTINUE/EXIT being used outside a loop during parsing, so I changed that as well and removed those checks from pl_exec.c. I refactored the 3 places that were doing the check into exec_stmt_block(), renaming the original function exec_stmt_block_rc for the one place that still needs the return code.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 05268e3..1ae4bb7 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -371,7 +371,7 @@ do_compile(FunctionCallInfo fcinfo,
         * variables (such as FOUND), and is named after the function itself.
         */
        plpgsql_ns_init();
-       plpgsql_ns_push(NameStr(procStruct->proname));
+       plpgsql_ns_push(NameStr(procStruct->proname), PLPGSQL_LABEL_BLOCK);
        plpgsql_DumpExecTree = false;
        plpgsql_start_datums();
 
@@ -851,7 +851,7 @@ plpgsql_compile_inline(char *proc_source)
        function->extra_errors = 0;
 
        plpgsql_ns_init();
-       plpgsql_ns_push(func_name);
+       plpgsql_ns_push(func_name, PLPGSQL_LABEL_BLOCK);
        plpgsql_DumpExecTree = false;
        plpgsql_start_datums();
 
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index fb93336..c953e3c 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -131,7 +131,9 @@ static HTAB *shared_cast_hash = NULL;
 static void plpgsql_exec_error_callback(void *arg);
 static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
 
-static int exec_stmt_block(PLpgSQL_execstate *estate,
+static void exec_stmt_block(PLpgSQL_execstate *estate,
+                               PLpgSQL_stmt_block *block);
+static int exec_stmt_block_rc(PLpgSQL_execstate *estate,
                                PLpgSQL_stmt_block *block);
 static int exec_stmts(PLpgSQL_execstate *estate,
                   List *stmts);
@@ -303,7 +305,6 @@ plpgsql_exec_function(PLpgSQL_function *func, 
FunctionCallInfo fcinfo,
        PLpgSQL_execstate estate;
        ErrorContextCallback plerrcontext;
        int                     i;
-       int                     rc;
 
        /*
         * Setup the execution state
@@ -432,25 +433,7 @@ plpgsql_exec_function(PLpgSQL_function *func, 
FunctionCallInfo fcinfo,
         */
        estate.err_text = NULL;
        estate.err_stmt = (PLpgSQL_stmt *) (func->action);
-       rc = exec_stmt_block(&estate, func->action);
-       if (rc != PLPGSQL_RC_RETURN)
-       {
-               estate.err_stmt = NULL;
-               estate.err_text = NULL;
-
-               /*
-                * Provide a more helpful message if a CONTINUE has been used 
outside
-                * the context it can work in.
-                */
-               if (rc == PLPGSQL_RC_CONTINUE)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                        errmsg("CONTINUE cannot be used 
outside a loop")));
-               else
-                       ereport(ERROR,
-                          
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
-                               errmsg("control reached end of function without 
RETURN")));
-       }
+       exec_stmt_block(&estate, func->action);
 
        /*
         * We got a return value - process it
@@ -598,7 +581,6 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        PLpgSQL_execstate estate;
        ErrorContextCallback plerrcontext;
        int                     i;
-       int                     rc;
        PLpgSQL_var *var;
        PLpgSQL_rec *rec_new,
                           *rec_old;
@@ -786,25 +768,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
         */
        estate.err_text = NULL;
        estate.err_stmt = (PLpgSQL_stmt *) (func->action);
-       rc = exec_stmt_block(&estate, func->action);
-       if (rc != PLPGSQL_RC_RETURN)
-       {
-               estate.err_stmt = NULL;
-               estate.err_text = NULL;
-
-               /*
-                * Provide a more helpful message if a CONTINUE has been used 
outside
-                * the context it can work in.
-                */
-               if (rc == PLPGSQL_RC_CONTINUE)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                        errmsg("CONTINUE cannot be used 
outside a loop")));
-               else
-                       ereport(ERROR,
-                          
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
-                               errmsg("control reached end of trigger 
procedure without RETURN")));
-       }
+       exec_stmt_block(&estate, func->action);
 
        estate.err_stmt = NULL;
        estate.err_text = gettext_noop("during function exit");
@@ -871,7 +835,6 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, 
EventTriggerData *trigdata)
        PLpgSQL_execstate estate;
        ErrorContextCallback plerrcontext;
        int                     i;
-       int                     rc;
        PLpgSQL_var *var;
 
        /*
@@ -914,25 +877,7 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, 
EventTriggerData *trigdata)
         */
        estate.err_text = NULL;
        estate.err_stmt = (PLpgSQL_stmt *) (func->action);
-       rc = exec_stmt_block(&estate, func->action);
-       if (rc != PLPGSQL_RC_RETURN)
-       {
-               estate.err_stmt = NULL;
-               estate.err_text = NULL;
-
-               /*
-                * Provide a more helpful message if a CONTINUE has been used 
outside
-                * the context it can work in.
-                */
-               if (rc == PLPGSQL_RC_CONTINUE)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
-                                        errmsg("CONTINUE cannot be used 
outside a loop")));
-               else
-                       ereport(ERROR,
-                          
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
-                               errmsg("control reached end of trigger 
procedure without RETURN")));
-       }
+       exec_stmt_block(&estate, func->action);
 
        estate.err_stmt = NULL;
        estate.err_text = gettext_noop("during function exit");
@@ -1108,11 +1053,11 @@ exception_matches_conditions(ErrorData *edata, 
PLpgSQL_condition *cond)
 
 
 /* ----------
- * exec_stmt_block                     Execute a block of statements
+ * exec_stmt_block_rc                  Execute a block of statements 
  * ----------
  */
 static int
-exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
+exec_stmt_block_rc(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 {
        volatile int rc = -1;
        int                     i;
@@ -1463,7 +1408,7 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
        switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
        {
                case PLPGSQL_STMT_BLOCK:
-                       rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) 
stmt);
+                       rc = exec_stmt_block_rc(estate, (PLpgSQL_stmt_block *) 
stmt);
                        break;
 
                case PLPGSQL_STMT_ASSIGN:
@@ -7201,6 +7146,27 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate,
        return portal;
 }
 
+/* ----------
+ * exec_stmt_block                             Execute block of statements and 
sanity-check return code
+ * ----------
+ */
+static void exec_stmt_block(PLpgSQL_execstate *estate,
+                               PLpgSQL_stmt_block *block)
+{
+       int rc = exec_stmt_block_rc(estate, block);
+
+       if (rc != PLPGSQL_RC_RETURN)
+       {
+               estate->err_stmt = NULL;
+               estate->err_text = NULL;
+
+        Assert(rc == PLPGSQL_RC_OK);
+        ereport(ERROR,
+           (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+            errmsg("control reached end of function without RETURN")));
+       }
+}
+
 /*
  * Return a formatted string with information about an expression's parameters,
  * or NULL if the expression does not take any parameters.
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index 900ba3c..9269b51 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -51,11 +51,11 @@ plpgsql_ns_init(void)
  * ----------
  */
 void
-plpgsql_ns_push(const char *label)
+plpgsql_ns_push(const char *label, enum PLpgSQL_label_types label_type)
 {
        if (label == NULL)
                label = "";
-       plpgsql_ns_additem(PLPGSQL_NSTYPE_LABEL, 0, label);
+       plpgsql_ns_additem(PLPGSQL_NSTYPE_LABEL, label_type, label);
 }
 
 
@@ -206,6 +206,25 @@ plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur, const char 
*name)
 }
 
 
+/* ----------
+ * plpgsql_ns_find_loop                        Lookup a loop label in the 
given namespace chain
+ * ----------
+ */
+PLpgSQL_nsitem *
+plpgsql_ns_find_loop(PLpgSQL_nsitem *ns_cur)
+{
+       while (ns_cur != NULL)
+       {
+               if (ns_cur->itemtype == PLPGSQL_NSTYPE_LABEL &&
+                       ns_cur->itemno == PLPGSQL_LABEL_LOOP)
+                       return ns_cur;
+               ns_cur = ns_cur->prev;
+       }
+
+       return NULL;                            /* label not found */
+}
+
+
 /*
  * Statement type as a string, for use in error messages etc.
  */
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 0097890..54fc207 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -186,7 +186,7 @@ static      void                    
check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %type <forvariable>    for_variable
 %type <stmt>   for_control
 
-%type <str>            any_identifier opt_block_label opt_label option_value
+%type <str>            any_identifier opt_block_label opt_loop_label opt_label 
option_value
 
 %type <list>   proc_sect stmt_elsifs stmt_else
 %type <loop_body>      loop_body
@@ -535,7 +535,7 @@ decl_statement      : decl_varname decl_const decl_datatype 
decl_collate decl_notnull
                                                                                
   $4->itemno, $1.name);
                                        }
                                | decl_varname opt_scrollable K_CURSOR
-                                       { plpgsql_ns_push($1.name); }
+                                       { plpgsql_ns_push($1.name, 
PLPGSQL_LABEL_OTHER); }
                                  decl_cursor_args decl_is_for decl_cursor_query
                                        {
                                                PLpgSQL_var *new;
@@ -1218,7 +1218,7 @@ opt_case_else     :
                                        }
                                ;
 
-stmt_loop              : opt_block_label K_LOOP loop_body
+stmt_loop              : opt_loop_label K_LOOP loop_body
                                        {
                                                PLpgSQL_stmt_loop *new;
 
@@ -1235,7 +1235,7 @@ stmt_loop         : opt_block_label K_LOOP loop_body
                                        }
                                ;
 
-stmt_while             : opt_block_label K_WHILE expr_until_loop loop_body
+stmt_while             : opt_loop_label K_WHILE expr_until_loop loop_body
                                        {
                                                PLpgSQL_stmt_while *new;
 
@@ -1253,7 +1253,7 @@ stmt_while                : opt_block_label K_WHILE 
expr_until_loop loop_body
                                        }
                                ;
 
-stmt_for               : opt_block_label K_FOR for_control loop_body
+stmt_for               : opt_loop_label K_FOR for_control loop_body
                                        {
                                                /* This runs after we've 
scanned the loop body */
                                                if ($3->cmd_type == 
PLPGSQL_STMT_FORI)
@@ -1282,7 +1282,7 @@ stmt_for          : opt_block_label K_FOR for_control 
loop_body
                                                }
 
                                                check_labels($1, $4.end_label, 
$4.end_label_location);
-                                               /* close namespace started in 
opt_block_label */
+                                               /* close namespace started in 
opt_loop_label */
                                                plpgsql_ns_pop();
                                        }
                                ;
@@ -1603,7 +1603,7 @@ for_variable      : T_DATUM
                                        }
                                ;
 
-stmt_foreach_a : opt_block_label K_FOREACH for_variable foreach_slice K_IN 
K_ARRAY expr_until_loop loop_body
+stmt_foreach_a : opt_loop_label K_FOREACH for_variable foreach_slice K_IN 
K_ARRAY expr_until_loop loop_body
                                        {
                                                PLpgSQL_stmt_foreach_a *new;
 
@@ -1666,6 +1666,28 @@ stmt_exit                : exit_type opt_label 
opt_exitcond
                                                new->label        = $2;
                                                new->cond         = $3;
 
+                                               /* If we have a label make sure 
it's a loop label */
+                                               if ($2)
+                                               {
+                                                       PLpgSQL_nsitem *label;
+                                                       label = 
plpgsql_ns_lookup_label(plpgsql_ns_top(), $2);
+
+                                                       if (label->itemno != 
PLPGSQL_LABEL_LOOP)
+                                                               ereport(ERROR,
+                                                                               
(errcode(ERRCODE_SYNTAX_ERROR),
+                                                                               
 errmsg("block label used in %s",
+                                                                               
                 plpgsql_stmt_typename((PLpgSQL_stmt *)new)),
+                                                                               
 parser_errposition(@2)));
+
+                                               }
+                        else
+                            if (! plpgsql_ns_find_loop(plpgsql_ns_top()))
+                                                               ereport(ERROR,
+                                                                               
(errcode(ERRCODE_SYNTAX_ERROR),
+                                                                               
 errmsg("%s cannot be used outside a loop",
+                                                                               
                 plpgsql_stmt_typename((PLpgSQL_stmt *)new)),
+                                                                               
 parser_errposition(@1)));
+
                                                $$ = (PLpgSQL_stmt *)new;
                                        }
                                ;
@@ -2290,12 +2312,24 @@ expr_until_loop :
 
 opt_block_label        :
                                        {
-                                               plpgsql_ns_push(NULL);
+                                               plpgsql_ns_push(NULL, 
PLPGSQL_LABEL_BLOCK);
+                                               $$ = NULL;
+                                       }
+                               | LESS_LESS any_identifier GREATER_GREATER
+                                       {
+                                               plpgsql_ns_push($2, 
PLPGSQL_LABEL_BLOCK);
+                                               $$ = $2;
+                                       }
+                               ;
+
+opt_loop_label :
+                                       {
+                                               plpgsql_ns_push(NULL, 
PLPGSQL_LABEL_LOOP);
                                                $$ = NULL;
                                        }
                                | LESS_LESS any_identifier GREATER_GREATER
                                        {
-                                               plpgsql_ns_push($2);
+                                               plpgsql_ns_push($2, 
PLPGSQL_LABEL_LOOP);
                                                $$ = $2;
                                        }
                                ;
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index fd5679c..5d2ee99 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -47,6 +47,17 @@ enum
 };
 
 /* ----------
+ * Types of labels for PLPGSQL_NSTYPE_LABEL
+ * ----------
+ */
+enum PLpgSQL_label_types
+{
+       PLPGSQL_LABEL_OTHER,
+       PLPGSQL_LABEL_BLOCK,
+       PLPGSQL_LABEL_LOOP
+};
+
+/* ----------
  * Datum array node types
  * ----------
  */
@@ -330,7 +341,7 @@ typedef struct
 typedef struct PLpgSQL_nsitem
 {                                                              /* Item in the 
compilers namespace tree */
        int                     itemtype;
-       int                     itemno;
+       int                     itemno;                 /* For labels this is 
PLpgSQL_label_types */
        struct PLpgSQL_nsitem *prev;
        char            name[FLEXIBLE_ARRAY_MEMBER];    /* nul-terminated 
string */
 } PLpgSQL_nsitem;
@@ -997,7 +1008,7 @@ extern void exec_get_datum_type_info(PLpgSQL_execstate 
*estate,
  * ----------
  */
 extern void plpgsql_ns_init(void);
-extern void plpgsql_ns_push(const char *label);
+extern void plpgsql_ns_push(const char *label, enum PLpgSQL_label_types 
label_type);
 extern void plpgsql_ns_pop(void);
 extern PLpgSQL_nsitem *plpgsql_ns_top(void);
 extern void plpgsql_ns_additem(int itemtype, int itemno, const char *name);
@@ -1006,6 +1017,7 @@ extern PLpgSQL_nsitem *plpgsql_ns_lookup(PLpgSQL_nsitem 
*ns_cur, bool localmode,
                                  const char *name3, int *names_used);
 extern PLpgSQL_nsitem *plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur,
                                                const char *name);
+extern PLpgSQL_nsitem *plpgsql_ns_find_loop(PLpgSQL_nsitem *ns_cur);
 
 /* ----------
  * Other functions in pl_funcs.c
diff --git a/src/test/regress/expected/plpgsql.out 
b/src/test/regress/expected/plpgsql.out
index 452ef9d..d9e0f2d 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -2830,7 +2830,9 @@ NOTICE:  10
  
 (1 row)
 
--- CONTINUE is only legal inside a loop
+drop function continue_test1();
+drop table conttesttbl;
+-- should fail: CONTINUE is only legal inside a loop
 create function continue_test2() returns void as $$
 begin
     begin
@@ -2839,11 +2841,22 @@ begin
     return;
 end;
 $$ language plpgsql;
--- should fail
-select continue_test2();
 ERROR:  CONTINUE cannot be used outside a loop
-CONTEXT:  PL/pgSQL function continue_test2()
--- CONTINUE can't reference the label of a named block
+LINE 4:         continue;
+                ^
+-- should fail: EXIT is only legal inside a loop
+create function exit_test2() returns void as $$
+begin
+    begin
+        exit;
+    end;
+    return;
+end;
+$$ language plpgsql;
+ERROR:  EXIT cannot be used outside a loop
+LINE 4:         exit;
+                ^
+-- should fail: CONTINUE can't reference the label of a named block
 create function continue_test3() returns void as $$
 begin
     <<begin_block1>>
@@ -2854,14 +2867,23 @@ begin
     end;
 end;
 $$ language plpgsql;
--- should fail
-select continue_test3();
-ERROR:  CONTINUE cannot be used outside a loop
-CONTEXT:  PL/pgSQL function continue_test3()
-drop function continue_test1();
-drop function continue_test2();
-drop function continue_test3();
-drop table conttesttbl;
+ERROR:  block label used in CONTINUE
+LINE 6:             continue begin_block1;
+                             ^
+-- should fail: EXIT can't reference the label of a named block
+create function exit_test3() returns void as $$
+begin
+    <<begin_block1>>
+    begin
+        loop
+            exit begin_block1;
+        end loop;
+    end;
+end;
+$$ language plpgsql;
+ERROR:  block label used in EXIT
+LINE 6:             exit begin_block1;
+                         ^
 -- verbose end block and end loop
 create function end_label1() returns void as $$
 <<blbl>>
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index b46439e..4435eb7 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2360,8 +2360,10 @@ begin
 end; $$ language plpgsql;
 
 select continue_test1();
+drop function continue_test1();
+drop table conttesttbl;
 
--- CONTINUE is only legal inside a loop
+-- should fail: CONTINUE is only legal inside a loop
 create function continue_test2() returns void as $$
 begin
     begin
@@ -2371,10 +2373,17 @@ begin
 end;
 $$ language plpgsql;
 
--- should fail
-select continue_test2();
+-- should fail: EXIT is only legal inside a loop
+create function exit_test2() returns void as $$
+begin
+    begin
+        exit;
+    end;
+    return;
+end;
+$$ language plpgsql;
 
--- CONTINUE can't reference the label of a named block
+-- should fail: CONTINUE can't reference the label of a named block
 create function continue_test3() returns void as $$
 begin
     <<begin_block1>>
@@ -2386,13 +2395,17 @@ begin
 end;
 $$ language plpgsql;
 
--- should fail
-select continue_test3();
-
-drop function continue_test1();
-drop function continue_test2();
-drop function continue_test3();
-drop table conttesttbl;
+-- should fail: EXIT can't reference the label of a named block
+create function exit_test3() returns void as $$
+begin
+    <<begin_block1>>
+    begin
+        loop
+            exit begin_block1;
+        end loop;
+    end;
+end;
+$$ language plpgsql;
 
 -- verbose end block and end loop
 create function end_label1() returns void as $$
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to