Detect mismatched CONTINUE and EXIT statements at plpgsql compile time.

With a bit of tweaking of the compile namestack data structure, we can
verify at compile time whether a CONTINUE or EXIT is legal.  This is
surely better than leaving it to runtime, both because earlier is better
and because we can issue a proper error pointer.  Also, we can get rid
of the ad-hoc old way of detecting the problem, which only took care of
CONTINUE not EXIT.

Jim Nasby, adjusted a bit by me
This commit is contained in:
Tom Lane 2015-08-21 20:17:19 -04:00
parent 072710dff3
commit fcdfce6820
7 changed files with 226 additions and 85 deletions

View File

@ -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();

View File

@ -437,19 +437,9 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
{
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")));
ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
errmsg("control reached end of function without RETURN")));
}
/*
@ -791,19 +781,9 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
{
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")));
ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
errmsg("control reached end of trigger procedure without RETURN")));
}
estate.err_stmt = NULL;
@ -919,19 +899,9 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
{
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")));
ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
errmsg("control reached end of trigger procedure without RETURN")));
}
estate.err_stmt = NULL;

View File

@ -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, (int) label_type, label);
}
@ -206,6 +206,25 @@ plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur, const char *name)
}
/* ----------
* plpgsql_ns_find_nearest_loop Find innermost loop label in namespace chain
* ----------
*/
PLpgSQL_nsitem *
plpgsql_ns_find_nearest_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; /* no loop found */
}
/*
* Statement type as a string, for use in error messages etc.
*/

View File

@ -186,7 +186,8 @@ 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
%type <str> option_value
%type <list> proc_sect stmt_elsifs stmt_else
%type <loop_body> loop_body
@ -535,7 +536,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 +1219,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 +1236,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 +1254,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 +1283,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 +1604,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 +1667,42 @@ stmt_exit : exit_type opt_label opt_exitcond
new->label = $2;
new->cond = $3;
if ($2)
{
/* We have a label, so verify it exists */
PLpgSQL_nsitem *label;
label = plpgsql_ns_lookup_label(plpgsql_ns_top(), $2);
if (label == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("label \"%s\" does not exist",
$2),
parser_errposition(@2)));
/* CONTINUE only allows loop labels */
if (label->itemno != PLPGSQL_LABEL_LOOP && !$1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("block label \"%s\" cannot be used in CONTINUE",
$2),
parser_errposition(@2)));
}
else
{
/*
* No label, so make sure there is some loop (an
* unlabelled EXIT does not match a block, so this
* is the same test for both EXIT and CONTINUE)
*/
if (plpgsql_ns_find_nearest_loop(plpgsql_ns_top()) == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
/* translator: %s is EXIT or CONTINUE */
errmsg("%s cannot be used outside a loop",
plpgsql_stmt_typename((PLpgSQL_stmt *) new)),
parser_errposition(@1)));
}
$$ = (PLpgSQL_stmt *)new;
}
;
@ -2290,12 +2327,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_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_LABEL_LOOP);
$$ = $2;
}
;
@ -2306,8 +2355,7 @@ opt_label :
}
| any_identifier
{
if (plpgsql_ns_lookup_label(plpgsql_ns_top(), $1) == NULL)
yyerror("label does not exist");
/* label validity will be checked by outer production */
$$ = $1;
}
;

View File

@ -46,6 +46,17 @@ enum
PLPGSQL_NSTYPE_REC
};
/* ----------
* A PLPGSQL_NSTYPE_LABEL stack entry must be one of these types
* ----------
*/
enum PLpgSQL_label_types
{
PLPGSQL_LABEL_BLOCK, /* DECLARE/BEGIN block */
PLPGSQL_LABEL_LOOP, /* looping construct */
PLPGSQL_LABEL_OTHER /* anything else */
};
/* ----------
* Datum array node types
* ----------
@ -331,6 +342,8 @@ typedef struct PLpgSQL_nsitem
{ /* Item in the compilers namespace tree */
int itemtype;
int itemno;
/* For labels, itemno is a value of enum PLpgSQL_label_types. */
/* For other itemtypes, itemno is the associated PLpgSQL_datum's dno. */
struct PLpgSQL_nsitem *prev;
char name[FLEXIBLE_ARRAY_MEMBER]; /* nul-terminated string */
} PLpgSQL_nsitem;
@ -997,7 +1010,8 @@ 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 +1020,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_nearest_loop(PLpgSQL_nsitem *ns_cur);
/* ----------
* Other functions in pl_funcs.c

View File

@ -2830,21 +2830,58 @@ NOTICE: 10
(1 row)
-- CONTINUE is only legal inside a loop
create function continue_test2() returns void as $$
drop function continue_test1();
drop table conttesttbl;
-- should fail: CONTINUE is only legal inside a loop
create function continue_error1() returns void as $$
begin
begin
continue;
end;
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
create function continue_test3() returns void as $$
LINE 4: continue;
^
-- should fail: EXIT is only legal inside a loop
create function exit_error1() returns void as $$
begin
begin
exit;
end;
end;
$$ language plpgsql;
ERROR: EXIT cannot be used outside a loop
LINE 4: exit;
^
-- should fail: no such label
create function continue_error2() returns void as $$
begin
begin
loop
continue no_such_label;
end loop;
end;
end;
$$ language plpgsql;
ERROR: label "no_such_label" does not exist
LINE 5: continue no_such_label;
^
-- should fail: no such label
create function exit_error2() returns void as $$
begin
begin
loop
exit no_such_label;
end loop;
end;
end;
$$ language plpgsql;
ERROR: label "no_such_label" does not exist
LINE 5: exit no_such_label;
^
-- should fail: CONTINUE can't reference the label of a named block
create function continue_error3() returns void as $$
begin
<<begin_block1>>
begin
@ -2854,14 +2891,28 @@ 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 "begin_block1" cannot be used in CONTINUE
LINE 6: continue begin_block1;
^
-- On the other hand, EXIT *can* reference the label of a named block
create function exit_block1() returns void as $$
begin
<<begin_block1>>
begin
loop
exit begin_block1;
raise exception 'should not get here';
end loop;
end;
end;
$$ language plpgsql;
select exit_block1();
exit_block1
-------------
(1 row)
drop function exit_block1();
-- verbose end block and end loop
create function end_label1() returns void as $$
<<blbl>>
@ -2891,7 +2942,7 @@ begin
end loop flbl1;
end;
$$ language plpgsql;
ERROR: label does not exist at or near "flbl1"
ERROR: end label "flbl1" specified for unlabelled block
LINE 5: end loop flbl1;
^
-- should fail: end label does not match start label

View File

@ -2361,21 +2361,51 @@ end; $$ language plpgsql;
select continue_test1();
-- CONTINUE is only legal inside a loop
create function continue_test2() returns void as $$
drop function continue_test1();
drop table conttesttbl;
-- should fail: CONTINUE is only legal inside a loop
create function continue_error1() returns void as $$
begin
begin
continue;
end;
return;
end;
$$ language plpgsql;
-- should fail
select continue_test2();
-- should fail: EXIT is only legal inside a loop
create function exit_error1() returns void as $$
begin
begin
exit;
end;
end;
$$ language plpgsql;
-- CONTINUE can't reference the label of a named block
create function continue_test3() returns void as $$
-- should fail: no such label
create function continue_error2() returns void as $$
begin
begin
loop
continue no_such_label;
end loop;
end;
end;
$$ language plpgsql;
-- should fail: no such label
create function exit_error2() returns void as $$
begin
begin
loop
exit no_such_label;
end loop;
end;
end;
$$ language plpgsql;
-- should fail: CONTINUE can't reference the label of a named block
create function continue_error3() returns void as $$
begin
<<begin_block1>>
begin
@ -2386,13 +2416,21 @@ begin
end;
$$ language plpgsql;
-- should fail
select continue_test3();
-- On the other hand, EXIT *can* reference the label of a named block
create function exit_block1() returns void as $$
begin
<<begin_block1>>
begin
loop
exit begin_block1;
raise exception 'should not get here';
end loop;
end;
end;
$$ language plpgsql;
drop function continue_test1();
drop function continue_test2();
drop function continue_test3();
drop table conttesttbl;
select exit_block1();
drop function exit_block1();
-- verbose end block and end loop
create function end_label1() returns void as $$