Skip to content

Commit f76a850

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 7b4c660 commit f76a850

File tree

1 file changed

+37
-22
lines changed

1 file changed

+37
-22
lines changed

src/bin/psql/help.c

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ helpSQL(const char *topic, unsigned short int pager)
535535
int i;
536536
int j;
537537

538+
/* Find screen width to determine how many columns will fit */
538539
#ifdef TIOCGWINSZ
539540
struct winsize screen_size;
540541

@@ -572,56 +573,63 @@ helpSQL(const char *topic, unsigned short int pager)
572573
else
573574
{
574575
int i,
575-
j,
576-
x = 0;
577-
bool help_found = false;
576+
pass;
578577
FILE *output = NULL;
579578
size_t len,
580-
wordlen;
581-
int nl_count = 0;
579+
wordlen,
580+
j;
581+
int nl_count;
582582

583583
/*
584+
* len is the amount of the input to compare to the help topic names.
584585
* We first try exact match, then first + second words, then first
585586
* word only.
586587
*/
587588
len = strlen(topic);
588589

589-
for (x = 1; x <= 3; x++)
590+
for (pass = 1; pass <= 3; pass++)
590591
{
591-
if (x > 1) /* Nothing on first pass - try the opening
592+
if (pass > 1) /* Nothing on first pass - try the opening
592593
* word(s) */
593594
{
594595
wordlen = j = 1;
595-
while (topic[j] != ' ' && j++ < len)
596+
while (j < len && topic[j++] != ' ')
596597
wordlen++;
597-
if (x == 2)
598+
if (pass == 2 && j < len)
598599
{
599-
j++;
600-
while (topic[j] != ' ' && j++ <= len)
600+
wordlen++;
601+
while (j < len && topic[j++] != ' ')
601602
wordlen++;
602603
}
603-
if (wordlen >= len) /* Don't try again if the same word */
604+
if (wordlen >= len)
604605
{
605-
if (!output)
606-
output = PageOutput(nl_count, pager ? &(pset.popt.topt) : NULL);
607-
break;
606+
/* Failed to shorten input, so try next pass if any */
607+
continue;
608608
}
609609
len = wordlen;
610610
}
611611

612-
/* Count newlines for pager */
612+
/*
613+
* Count newlines for pager. This logic must agree with what the
614+
* following loop will do!
615+
*/
616+
nl_count = 0;
613617
for (i = 0; QL_HELP[i].cmd; i++)
614618
{
615619
if (pg_strncasecmp(topic, QL_HELP[i].cmd, len) == 0 ||
616620
strcmp(topic, "*") == 0)
617621
{
618-
nl_count += 5 + QL_HELP[i].nl_count;
622+
/* magic constant here must match format below! */
623+
nl_count += 7 + QL_HELP[i].nl_count;
619624

620625
/* If we have an exact match, exit. Fixes \h SELECT */
621626
if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0)
622627
break;
623628
}
624629
}
630+
/* If no matches, don't open the output yet */
631+
if (nl_count == 0)
632+
continue;
625633

626634
if (!output)
627635
output = PageOutput(nl_count, pager ? &(pset.popt.topt) : NULL);
@@ -636,10 +644,10 @@ helpSQL(const char *topic, unsigned short int pager)
636644

637645
initPQExpBuffer(&buffer);
638646
QL_HELP[i].syntaxfunc(&buffer);
639-
help_found = true;
640647
url = psprintf("https://www.postgresql.org/docs/%s/%s.html",
641648
strstr(PG_VERSION, "devel") ? "devel" : PG_MAJORVERSION,
642649
QL_HELP[i].docbook_id);
650+
/* # of newlines in format must match constant above! */
643651
fprintf(output, _("Command: %s\n"
644652
"Description: %s\n"
645653
"Syntax:\n%s\n\n"
@@ -649,17 +657,24 @@ helpSQL(const char *topic, unsigned short int pager)
649657
buffer.data,
650658
url);
651659
free(url);
660+
termPQExpBuffer(&buffer);
661+
652662
/* If we have an exact match, exit. Fixes \h SELECT */
653663
if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0)
654664
break;
655665
}
656666
}
657-
if (help_found) /* Don't keep trying if we got a match */
658-
break;
667+
break;
659668
}
660669

661-
if (!help_found)
662-
fprintf(output, _("No help available for \"%s\".\nTry \\h with no arguments to see available help.\n"), topic);
670+
/* If we never found anything, report that */
671+
if (!output)
672+
{
673+
output = PageOutput(2, pager ? &(pset.popt.topt) : NULL);
674+
fprintf(output, _("No help available for \"%s\".\n"
675+
"Try \\h with no arguments to see available help.\n"),
676+
topic);
677+
}
663678

664679
ClosePager(output);
665680
}

0 commit comments

Comments
 (0)