pgsql-5448

Version:

8.4.3

Bug Link:

http://archives.postgresql.org/pgsql-bugs/2010-05/msg00027.php

http://postgresql.1045698.n5.nabble.com/BUG-5448-psql-set-does-not-terminate-if-variable-is-referenced-recursively-td2131653.html

Patch Link:

http://archives.postgresql.org/pgsql-committers/2010-05/msg00036.php

Symptom:

psql \set does not terminate if variable is referenced recursively

Failure type:

Hang

Is there any log message?

No

How it is diagnosed:

We reproduced the failure.

How to reproduce:  

postgres=#  \set n 1

postgres=#  \set x (:n+1)

postgres=# select :x;

 ?column?

----------

        2

(1 row)

postgres=# \set n 5

postgres=# select :x; <--it changes  while n changes,since it’s a substitution!!

 ?column?

----------

        6

(1 row)

postgres=# \set n (:x+1)

postgres=# select :x;

                <---hang...

Root Cause:

there’s an infinite recursion when expanding a variable that

refers to itself (directly or indirectly).

pgsql/src/bin/psql/psqlscan.l

typedef struct StackElem
{
   YY_BUFFER_STATE buf;        /* flex input control structure */
   char       *bufstring;      /* data actually being scanned by flex */
   char       *origstring;     /* copy of original data, if needed */

+  char       *varname;        /* name of variable providing data, or NULL */
   struct StackElem *next;
} StackElem;

:[A-Za-z0-9_]+  {

+                           const char *varname = yytext + 1;
                   /* Possible psql variable substitution */
                   const char *value;

                   value = GetVariable(pset.vars, yytext + 1);

                   if (value)
                    {

+                        /* It is a variable, check for recursion */
+                      if (var_is_current_source(cur_state, varname))
+                        {
+                           /* 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
                   {
                       /*
                        * if the variable doesn't exist we'll copy the
                        * string as is
                        */
                       ECHO;
                   }

+ /*
+  * 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;
+  }

Can ErrLog insert a log message:

No