Fix calculation of space needed for parsed words in tab completion.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 21 Dec 2015 20:08:56 +0000 (15:08 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 21 Dec 2015 20:08:56 +0000 (15:08 -0500)
Yesterday in commit d854118c8, I had a serious brain fade leading me to
underestimate the number of words that the tab-completion logic could
divide a line into.  On input such as "(((((", each character will get
seen as a separate word, which means we do indeed sometimes need more
space for the words than for the original line.  Fix that.

src/bin/psql/tab-complete.c

index 1a7d184af9ffb06c4378a542174ed57c61e0e68e..df678d5ce3629b299a0fe965d56e0d70701a2648 100644 (file)
@@ -3652,41 +3652,39 @@ get_previous_words(int point, char **buffer, int *nwords)
 {
        char      **previous_words;
        char       *buf;
-       int                     buflen;
+       char       *outptr;
        int                     words_found = 0;
        int                     i;
 
        /*
-        * Construct a writable buffer including both preceding and current lines
-        * of the query, up to "point" which is where the currently completable
-        * word begins.  Because our definition of "word" is such that a non-word
-        * character must end each word, we can use this buffer to return the word
-        * data as-is, by placing a '\0' after each word.
+        * If we have anything in tab_completion_query_buf, paste it together with
+        * rl_line_buffer to construct the full query.  Otherwise we can just use
+        * rl_line_buffer as the input string.
         */
-       buflen = point + 1;
-       if (tab_completion_query_buf)
-               buflen += tab_completion_query_buf->len + 1;
-       *buffer = buf = pg_malloc(buflen);
-       i = 0;
-       if (tab_completion_query_buf)
-       {
-               memcpy(buf, tab_completion_query_buf->data,
-                          tab_completion_query_buf->len);
-               i += tab_completion_query_buf->len;
+       if (tab_completion_query_buf && tab_completion_query_buf->len > 0)
+       {
+               i = tab_completion_query_buf->len;
+               buf = pg_malloc(point + i + 2);
+               memcpy(buf, tab_completion_query_buf->data, i);
                buf[i++] = '\n';
+               memcpy(buf + i, rl_line_buffer, point);
+               i += point;
+               buf[i] = '\0';
+               /* Readjust point to reference appropriate offset in buf */
+               point = i;
        }
-       memcpy(buf + i, rl_line_buffer, point);
-       i += point;
-       buf[i] = '\0';
-
-       /* Readjust point to reference appropriate offset in buf */
-       point = i;
+       else
+               buf = rl_line_buffer;
 
        /*
-        * Allocate array of word start points.  There can be at most length/2 + 1
-        * words in the buffer.
+        * Allocate an array of string pointers and a buffer to hold the strings
+        * themselves.  The worst case is that the line contains only
+        * non-whitespace WORD_BREAKS characters, making each one a separate word.
+        * This is usually much more space than we need, but it's cheaper than
+        * doing a separate malloc() for each word.
         */
-       previous_words = (char **) pg_malloc((point / 2 + 1) * sizeof(char *));
+       previous_words = (char **) pg_malloc(point * sizeof(char *));
+       *buffer = outptr = (char *) pg_malloc(point * 2);
 
        /*
         * First we look for a non-word char before the current point.  (This is
@@ -3752,14 +3750,20 @@ get_previous_words(int point, char **buffer, int *nwords)
                }
 
                /* Return the word located at start to end inclusive */
-               previous_words[words_found] = &buf[start];
-               buf[end + 1] = '\0';
-               words_found++;
+               previous_words[words_found++] = outptr;
+               i = end - start + 1;
+               memcpy(outptr, &buf[start], i);
+               outptr += i;
+               *outptr++ = '\0';
 
                /* Continue searching */
                point = start - 1;
        }
 
+       /* Release parsing input workspace, if we made one above */
+       if (buf != rl_line_buffer)
+               free(buf);
+
        *nwords = words_found;
        return previous_words;
 }