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 for_variable %type for_control -%type any_identifier opt_block_label opt_label option_value +%type any_identifier opt_block_label opt_loop_label opt_label option_value %type proc_sect stmt_elsifs stmt_else %type 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 <> @@ -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 + 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 $$ <> 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 <> @@ -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 + loop + exit begin_block1; + end loop; + end; +end; +$$ language plpgsql; -- verbose end block and end loop create function end_label1() returns void as $$