From f5a4370aea3580f5f7f59a77e41fde62f2be12d8 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 21 Dec 2015 15:08:56 -0500 Subject: [PATCH] Fix calculation of space needed for parsed words in tab completion. 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 | 58 ++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 1a7d184af9..df678d5ce3 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -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) + if (tab_completion_query_buf && tab_completion_query_buf->len > 0) { - memcpy(buf, tab_completion_query_buf->data, - tab_completion_query_buf->len); - i += tab_completion_query_buf->len; + 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; }