Fix psql to not go into infinite recursion when expanding a variable that

refers to itself (directly or indirectly).  Instead, print a message when
recursion is detected, and don't expand the repeated reference.  Per bug
#5448 from Francis Markham.

Back-patch to 8.0.  Although the issue exists in 7.4 as well, it seems
impractical to fix there because of the lack of any state stack that
could be used to track active expansions.
This commit is contained in:
Tom Lane 2010-05-05 22:18:56 +00:00
parent 1ba23f767b
commit 93dc6a1b39
1 changed files with 74 additions and 23 deletions

View File

@ -33,7 +33,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/bin/psql/psqlscan.l,v 1.32 2010/01/29 17:44:12 rhaas Exp $ * $PostgreSQL: pgsql/src/bin/psql/psqlscan.l,v 1.33 2010/05/05 22:18:56 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -59,6 +59,7 @@ typedef struct StackElem
YY_BUFFER_STATE buf; /* flex input control structure */ YY_BUFFER_STATE buf; /* flex input control structure */
char *bufstring; /* data actually being scanned by flex */ char *bufstring; /* data actually being scanned by flex */
char *origstring; /* copy of original data, if needed */ char *origstring; /* copy of original data, if needed */
char *varname; /* name of variable providing data, or NULL */
struct StackElem *next; struct StackElem *next;
} StackElem; } StackElem;
@ -113,7 +114,9 @@ static char *option_quote;
int yylex(void); int yylex(void);
static void push_new_buffer(const char *newstr); static void push_new_buffer(const char *newstr, const char *varname);
static void pop_buffer_stack(PsqlScanState state);
static bool var_is_current_source(PsqlScanState state, const char *varname);
static YY_BUFFER_STATE prepare_buffer(const char *txt, int len, static YY_BUFFER_STATE prepare_buffer(const char *txt, int len,
char **txtcopy); char **txtcopy);
static void emit(const char *txt, int len); static void emit(const char *txt, int len);
@ -688,15 +691,28 @@ other .
:[A-Za-z0-9_]+ { :[A-Za-z0-9_]+ {
/* Possible psql variable substitution */ /* Possible psql variable substitution */
const char *varname = yytext + 1;
const char *value; const char *value;
value = GetVariable(pset.vars, yytext + 1); value = GetVariable(pset.vars, varname);
if (value) if (value)
{ {
/* It is a variable, perform substitution */ /* It is a variable, check for recursion */
push_new_buffer(value); if (var_is_current_source(cur_state, varname))
/* yy_scan_string already made buffer active */ {
/* Recursive expansion --- don't go there */
psql_error("skipping recursive expansion of variable \"%s\"\n",
varname);
/* Instead copy the string as is */
ECHO;
}
else
{
/* OK, perform substitution */
push_new_buffer(value, varname);
/* yy_scan_string already made buffer active */
}
} }
else else
{ {
@ -836,12 +852,7 @@ other .
* We were expanding a variable, so pop the inclusion * We were expanding a variable, so pop the inclusion
* stack and keep lexing * stack and keep lexing
*/ */
cur_state->buffer_stack = stackelem->next; pop_buffer_stack(cur_state);
yy_delete_buffer(stackelem->buf);
free(stackelem->bufstring);
if (stackelem->origstring)
free(stackelem->origstring);
free(stackelem);
stackelem = cur_state->buffer_stack; stackelem = cur_state->buffer_stack;
if (stackelem != NULL) if (stackelem != NULL)
@ -926,6 +937,7 @@ other .
* further examination. This is consistent with the * further examination. This is consistent with the
* pre-8.0 code behavior, if not with the way that * pre-8.0 code behavior, if not with the way that
* variables are handled outside backslash commands. * variables are handled outside backslash commands.
* Note that we needn't guard against recursion here.
*/ */
if (value) if (value)
appendPQExpBufferStr(output_buf, value); appendPQExpBufferStr(output_buf, value);
@ -1315,16 +1327,7 @@ psql_scan_finish(PsqlScanState state)
{ {
/* Drop any incomplete variable expansions. */ /* Drop any incomplete variable expansions. */
while (state->buffer_stack != NULL) while (state->buffer_stack != NULL)
{ pop_buffer_stack(state);
StackElem *stackelem = state->buffer_stack;
state->buffer_stack = stackelem->next;
yy_delete_buffer(stackelem->buf);
free(stackelem->bufstring);
if (stackelem->origstring)
free(stackelem->origstring);
free(stackelem);
}
/* Done with the outer scan buffer, too */ /* Done with the outer scan buffer, too */
if (state->scanbufhandle) if (state->scanbufhandle)
@ -1670,11 +1673,19 @@ psql_scan_slash_command_end(PsqlScanState state)
* NOTE SIDE EFFECT: the new buffer is made the active flex input buffer. * NOTE SIDE EFFECT: the new buffer is made the active flex input buffer.
*/ */
static void static void
push_new_buffer(const char *newstr) push_new_buffer(const char *newstr, const char *varname)
{ {
StackElem *stackelem; StackElem *stackelem;
stackelem = (StackElem *) pg_malloc(sizeof(StackElem)); stackelem = (StackElem *) pg_malloc(sizeof(StackElem));
/*
* In current usage, the passed varname points at the current flex
* input buffer; we must copy it before calling prepare_buffer()
* because that will change the buffer state.
*/
stackelem->varname = varname ? pg_strdup(varname) : NULL;
stackelem->buf = prepare_buffer(newstr, strlen(newstr), stackelem->buf = prepare_buffer(newstr, strlen(newstr),
&stackelem->bufstring); &stackelem->bufstring);
cur_state->curline = stackelem->bufstring; cur_state->curline = stackelem->bufstring;
@ -1692,6 +1703,46 @@ push_new_buffer(const char *newstr)
cur_state->buffer_stack = stackelem; cur_state->buffer_stack = stackelem;
} }
/*
* Pop the topmost buffer stack item (there must be one!)
*
* NB: after this, the flex input state is unspecified; caller must
* switch to an appropriate buffer to continue lexing.
*/
static void
pop_buffer_stack(PsqlScanState state)
{
StackElem *stackelem = state->buffer_stack;
state->buffer_stack = stackelem->next;
yy_delete_buffer(stackelem->buf);
free(stackelem->bufstring);
if (stackelem->origstring)
free(stackelem->origstring);
if (stackelem->varname)
free(stackelem->varname);
free(stackelem);
}
/*
* Check if specified variable name is the source for any string
* currently being scanned
*/
static bool
var_is_current_source(PsqlScanState state, const char *varname)
{
StackElem *stackelem;
for (stackelem = state->buffer_stack;
stackelem != NULL;
stackelem = stackelem->next)
{
if (stackelem->varname && strcmp(stackelem->varname, varname) == 0)
return true;
}
return false;
}
/* /*
* Set up a flex input buffer to scan the given data. We always make a * Set up a flex input buffer to scan the given data. We always make a
* copy of the data. If working in an unsafe encoding, the copy has * copy of the data. If working in an unsafe encoding, the copy has