Skip to content

Commit 93dc6a1

Browse files
committed
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.
1 parent 1ba23f7 commit 93dc6a1

File tree

1 file changed

+74
-23
lines changed

1 file changed

+74
-23
lines changed

src/bin/psql/psqlscan.l

Lines changed: 74 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* Portions Copyright (c) 1994, Regents of the University of California
3434
*
3535
* IDENTIFICATION
36-
* $PostgreSQL: pgsql/src/bin/psql/psqlscan.l,v 1.32 2010/01/29 17:44:12 rhaas Exp $
36+
* $PostgreSQL: pgsql/src/bin/psql/psqlscan.l,v 1.33 2010/05/05 22:18:56 tgl Exp $
3737
*
3838
*-------------------------------------------------------------------------
3939
*/
@@ -59,6 +59,7 @@ typedef struct StackElem
5959
YY_BUFFER_STATE buf; /* flex input control structure */
6060
char *bufstring; /* data actually being scanned by flex */
6161
char *origstring; /* copy of original data, if needed */
62+
char *varname; /* name of variable providing data, or NULL */
6263
struct StackElem *next;
6364
} StackElem;
6465

@@ -113,7 +114,9 @@ static char *option_quote;
113114

114115
int yylex(void);
115116

116-
static void push_new_buffer(const char *newstr);
117+
static void push_new_buffer(const char *newstr, const char *varname);
118+
static void pop_buffer_stack(PsqlScanState state);
119+
static bool var_is_current_source(PsqlScanState state, const char *varname);
117120
static YY_BUFFER_STATE prepare_buffer(const char *txt, int len,
118121
char **txtcopy);
119122
static void emit(const char *txt, int len);
@@ -688,15 +691,28 @@ other .
688691

689692
:[A-Za-z0-9_]+ {
690693
/* Possible psql variable substitution */
694+
const char *varname = yytext + 1;
691695
const char *value;
692696

693-
value = GetVariable(pset.vars, yytext + 1);
697+
value = GetVariable(pset.vars, varname);
694698

695699
if (value)
696700
{
697-
/* It is a variable, perform substitution */
698-
push_new_buffer(value);
699-
/* yy_scan_string already made buffer active */
701+
/* It is a variable, check for recursion */
702+
if (var_is_current_source(cur_state, varname))
703+
{
704+
/* Recursive expansion --- don't go there */
705+
psql_error("skipping recursive expansion of variable \"%s\"\n",
706+
varname);
707+
/* Instead copy the string as is */
708+
ECHO;
709+
}
710+
else
711+
{
712+
/* OK, perform substitution */
713+
push_new_buffer(value, varname);
714+
/* yy_scan_string already made buffer active */
715+
}
700716
}
701717
else
702718
{
@@ -836,12 +852,7 @@ other .
836852
* We were expanding a variable, so pop the inclusion
837853
* stack and keep lexing
838854
*/
839-
cur_state->buffer_stack = stackelem->next;
840-
yy_delete_buffer(stackelem->buf);
841-
free(stackelem->bufstring);
842-
if (stackelem->origstring)
843-
free(stackelem->origstring);
844-
free(stackelem);
855+
pop_buffer_stack(cur_state);
845856

846857
stackelem = cur_state->buffer_stack;
847858
if (stackelem != NULL)
@@ -926,6 +937,7 @@ other .
926937
* further examination. This is consistent with the
927938
* pre-8.0 code behavior, if not with the way that
928939
* variables are handled outside backslash commands.
940+
* Note that we needn't guard against recursion here.
929941
*/
930942
if (value)
931943
appendPQExpBufferStr(output_buf, value);
@@ -1315,16 +1327,7 @@ psql_scan_finish(PsqlScanState state)
13151327
{
13161328
/* Drop any incomplete variable expansions. */
13171329
while (state->buffer_stack != NULL)
1318-
{
1319-
StackElem *stackelem = state->buffer_stack;
1320-
1321-
state->buffer_stack = stackelem->next;
1322-
yy_delete_buffer(stackelem->buf);
1323-
free(stackelem->bufstring);
1324-
if (stackelem->origstring)
1325-
free(stackelem->origstring);
1326-
free(stackelem);
1327-
}
1330+
pop_buffer_stack(state);
13281331

13291332
/* Done with the outer scan buffer, too */
13301333
if (state->scanbufhandle)
@@ -1670,11 +1673,19 @@ psql_scan_slash_command_end(PsqlScanState state)
16701673
* NOTE SIDE EFFECT: the new buffer is made the active flex input buffer.
16711674
*/
16721675
static void
1673-
push_new_buffer(const char *newstr)
1676+
push_new_buffer(const char *newstr, const char *varname)
16741677
{
16751678
StackElem *stackelem;
16761679

16771680
stackelem = (StackElem *) pg_malloc(sizeof(StackElem));
1681+
1682+
/*
1683+
* In current usage, the passed varname points at the current flex
1684+
* input buffer; we must copy it before calling prepare_buffer()
1685+
* because that will change the buffer state.
1686+
*/
1687+
stackelem->varname = varname ? pg_strdup(varname) : NULL;
1688+
16781689
stackelem->buf = prepare_buffer(newstr, strlen(newstr),
16791690
&stackelem->bufstring);
16801691
cur_state->curline = stackelem->bufstring;
@@ -1692,6 +1703,46 @@ push_new_buffer(const char *newstr)
16921703
cur_state->buffer_stack = stackelem;
16931704
}
16941705

1706+
/*
1707+
* Pop the topmost buffer stack item (there must be one!)
1708+
*
1709+
* NB: after this, the flex input state is unspecified; caller must
1710+
* switch to an appropriate buffer to continue lexing.
1711+
*/
1712+
static void
1713+
pop_buffer_stack(PsqlScanState state)
1714+
{
1715+
StackElem *stackelem = state->buffer_stack;
1716+
1717+
state->buffer_stack = stackelem->next;
1718+
yy_delete_buffer(stackelem->buf);
1719+
free(stackelem->bufstring);
1720+
if (stackelem->origstring)
1721+
free(stackelem->origstring);
1722+
if (stackelem->varname)
1723+
free(stackelem->varname);
1724+
free(stackelem);
1725+
}
1726+
1727+
/*
1728+
* Check if specified variable name is the source for any string
1729+
* currently being scanned
1730+
*/
1731+
static bool
1732+
var_is_current_source(PsqlScanState state, const char *varname)
1733+
{
1734+
StackElem *stackelem;
1735+
1736+
for (stackelem = state->buffer_stack;
1737+
stackelem != NULL;
1738+
stackelem = stackelem->next)
1739+
{
1740+
if (stackelem->varname && strcmp(stackelem->varname, varname) == 0)
1741+
return true;
1742+
}
1743+
return false;
1744+
}
1745+
16951746
/*
16961747
* Set up a flex input buffer to scan the given data. We always make a
16971748
* copy of the data. If working in an unsafe encoding, the copy has

0 commit comments

Comments
 (0)