Skip to content

Commit 131825c

Browse files
committed
Code review for psql's helpSQL() function.
The loops to identify word boundaries could access past the end of the input string. Likely that would never result in an actual crash, but it makes valgrind unhappy. The logic to try different numbers of words didn't work when the input has two words but we only have a match to the first, eg "\h with select". (We must "continue" the pass loop, not "break".) The logic to compute nl_count was bizarrely managed, and in at least two code paths could end up calling PageOutput with nl_count = 0, resulting in failing to paginate output that should have been fed to the pager. Also, in v12 and up, the nl_count calculation hadn't been updated to account for the addition of a URL. The PQExpBuffer holding the command syntax details wasn't freed, resulting in a session-lifespan memory leak. While here, improve some comments, choose a more descriptive name for a variable, fix inconsistent datatype choice for another variable. Per bug #16837 from Alexander Lakhin. This code is very old, so back-patch to all supported branches. Kyotaro Horiguchi and Tom Lane Discussion: https://postgr.es/m/16837-479bcd56040c71b3@postgresql.org
1 parent 41309f7 commit 131825c

File tree

1 file changed

+36
-21
lines changed

1 file changed

+36
-21
lines changed

src/bin/psql/help.c

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ helpSQL(const char *topic, unsigned short int pager)
423423
int i;
424424
int j;
425425

426+
/* Find screen width to determine how many columns will fit */
426427
#ifdef TIOCGWINSZ
427428
struct winsize screen_size;
428429

@@ -460,56 +461,63 @@ helpSQL(const char *topic, unsigned short int pager)
460461
else
461462
{
462463
int i,
463-
j,
464-
x = 0;
465-
bool help_found = false;
464+
pass;
466465
FILE *output = NULL;
467466
size_t len,
468-
wordlen;
469-
int nl_count = 0;
467+
wordlen,
468+
j;
469+
int nl_count;
470470

471471
/*
472+
* len is the amount of the input to compare to the help topic names.
472473
* We first try exact match, then first + second words, then first
473474
* word only.
474475
*/
475476
len = strlen(topic);
476477

477-
for (x = 1; x <= 3; x++)
478+
for (pass = 1; pass <= 3; pass++)
478479
{
479-
if (x > 1) /* Nothing on first pass - try the opening
480+
if (pass > 1) /* Nothing on first pass - try the opening
480481
* word(s) */
481482
{
482483
wordlen = j = 1;
483-
while (topic[j] != ' ' && j++ < len)
484+
while (j < len && topic[j++] != ' ')
484485
wordlen++;
485-
if (x == 2)
486+
if (pass == 2 && j < len)
486487
{
487-
j++;
488-
while (topic[j] != ' ' && j++ <= len)
488+
wordlen++;
489+
while (j < len && topic[j++] != ' ')
489490
wordlen++;
490491
}
491-
if (wordlen >= len) /* Don't try again if the same word */
492+
if (wordlen >= len)
492493
{
493-
if (!output)
494-
output = PageOutput(nl_count, pager ? &(pset.popt.topt) : NULL);
495-
break;
494+
/* Failed to shorten input, so try next pass if any */
495+
continue;
496496
}
497497
len = wordlen;
498498
}
499499

500-
/* Count newlines for pager */
500+
/*
501+
* Count newlines for pager. This logic must agree with what the
502+
* following loop will do!
503+
*/
504+
nl_count = 0;
501505
for (i = 0; QL_HELP[i].cmd; i++)
502506
{
503507
if (pg_strncasecmp(topic, QL_HELP[i].cmd, len) == 0 ||
504508
strcmp(topic, "*") == 0)
505509
{
510+
/* magic constant here must match format below! */
506511
nl_count += 5 + QL_HELP[i].nl_count;
507512

508513
/* If we have an exact match, exit. Fixes \h SELECT */
509514
if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0)
510515
break;
511516
}
512517
}
518+
/* If no matches, don't open the output yet */
519+
if (nl_count == 0)
520+
continue;
513521

514522
if (!output)
515523
output = PageOutput(nl_count, pager ? &(pset.popt.topt) : NULL);
@@ -523,24 +531,31 @@ helpSQL(const char *topic, unsigned short int pager)
523531

524532
initPQExpBuffer(&buffer);
525533
QL_HELP[i].syntaxfunc(&buffer);
526-
help_found = true;
534+
/* # of newlines in format must match constant above! */
527535
fprintf(output, _("Command: %s\n"
528536
"Description: %s\n"
529537
"Syntax:\n%s\n\n"),
530538
QL_HELP[i].cmd,
531539
_(QL_HELP[i].help),
532540
buffer.data);
541+
termPQExpBuffer(&buffer);
542+
533543
/* If we have an exact match, exit. Fixes \h SELECT */
534544
if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0)
535545
break;
536546
}
537547
}
538-
if (help_found) /* Don't keep trying if we got a match */
539-
break;
548+
break;
540549
}
541550

542-
if (!help_found)
543-
fprintf(output, _("No help available for \"%s\".\nTry \\h with no arguments to see available help.\n"), topic);
551+
/* If we never found anything, report that */
552+
if (!output)
553+
{
554+
output = PageOutput(2, pager ? &(pset.popt.topt) : NULL);
555+
fprintf(output, _("No help available for \"%s\".\n"
556+
"Try \\h with no arguments to see available help.\n"),
557+
topic);
558+
}
544559

545560
ClosePager(output);
546561
}

0 commit comments

Comments
 (0)