Skip to content

Commit 64bdb6e

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 366d302 commit 64bdb6e

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
@@ -534,6 +534,7 @@ helpSQL(const char *topic, unsigned short int pager)
534534
int i;
535535
int j;
536536

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

@@ -571,56 +572,63 @@ helpSQL(const char *topic, unsigned short int pager)
571572
else
572573
{
573574
int i,
574-
j,
575-
x = 0;
576-
bool help_found = false;
575+
pass;
577576
FILE *output = NULL;
578577
size_t len,
579-
wordlen;
580-
int nl_count = 0;
578+
wordlen,
579+
j;
580+
int nl_count;
581581

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

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

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

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

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

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

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

663678
ClosePager(output);
664679
}

0 commit comments

Comments
 (0)