Skip to content

Commit 3fa7b90

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 b8894a3 commit 3fa7b90

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
@@ -522,6 +522,7 @@ helpSQL(const char *topic, unsigned short int pager)
522522
int i;
523523
int j;
524524

525+
/* Find screen width to determine how many columns will fit */
525526
#ifdef TIOCGWINSZ
526527
struct winsize screen_size;
527528

@@ -559,56 +560,63 @@ helpSQL(const char *topic, unsigned short int pager)
559560
else
560561
{
561562
int i,
562-
j,
563-
x = 0;
564-
bool help_found = false;
563+
pass;
565564
FILE *output = NULL;
566565
size_t len,
567-
wordlen;
568-
int nl_count = 0;
566+
wordlen,
567+
j;
568+
int nl_count;
569569

570570
/*
571+
* len is the amount of the input to compare to the help topic names.
571572
* We first try exact match, then first + second words, then first
572573
* word only.
573574
*/
574575
len = strlen(topic);
575576

576-
for (x = 1; x <= 3; x++)
577+
for (pass = 1; pass <= 3; pass++)
577578
{
578-
if (x > 1) /* Nothing on first pass - try the opening
579+
if (pass > 1) /* Nothing on first pass - try the opening
579580
* word(s) */
580581
{
581582
wordlen = j = 1;
582-
while (topic[j] != ' ' && j++ < len)
583+
while (j < len && topic[j++] != ' ')
583584
wordlen++;
584-
if (x == 2)
585+
if (pass == 2 && j < len)
585586
{
586-
j++;
587-
while (topic[j] != ' ' && j++ <= len)
587+
wordlen++;
588+
while (j < len && topic[j++] != ' ')
588589
wordlen++;
589590
}
590-
if (wordlen >= len) /* Don't try again if the same word */
591+
if (wordlen >= len)
591592
{
592-
if (!output)
593-
output = PageOutput(nl_count, pager ? &(pset.popt.topt) : NULL);
594-
break;
593+
/* Failed to shorten input, so try next pass if any */
594+
continue;
595595
}
596596
len = wordlen;
597597
}
598598

599-
/* Count newlines for pager */
599+
/*
600+
* Count newlines for pager. This logic must agree with what the
601+
* following loop will do!
602+
*/
603+
nl_count = 0;
600604
for (i = 0; QL_HELP[i].cmd; i++)
601605
{
602606
if (pg_strncasecmp(topic, QL_HELP[i].cmd, len) == 0 ||
603607
strcmp(topic, "*") == 0)
604608
{
609+
/* magic constant here must match format below! */
605610
nl_count += 5 + QL_HELP[i].nl_count;
606611

607612
/* If we have an exact match, exit. Fixes \h SELECT */
608613
if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0)
609614
break;
610615
}
611616
}
617+
/* If no matches, don't open the output yet */
618+
if (nl_count == 0)
619+
continue;
612620

613621
if (!output)
614622
output = PageOutput(nl_count, pager ? &(pset.popt.topt) : NULL);
@@ -622,24 +630,31 @@ helpSQL(const char *topic, unsigned short int pager)
622630

623631
initPQExpBuffer(&buffer);
624632
QL_HELP[i].syntaxfunc(&buffer);
625-
help_found = true;
633+
/* # of newlines in format must match constant above! */
626634
fprintf(output, _("Command: %s\n"
627635
"Description: %s\n"
628636
"Syntax:\n%s\n\n"),
629637
QL_HELP[i].cmd,
630638
_(QL_HELP[i].help),
631639
buffer.data);
640+
termPQExpBuffer(&buffer);
641+
632642
/* If we have an exact match, exit. Fixes \h SELECT */
633643
if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0)
634644
break;
635645
}
636646
}
637-
if (help_found) /* Don't keep trying if we got a match */
638-
break;
647+
break;
639648
}
640649

641-
if (!help_found)
642-
fprintf(output, _("No help available for \"%s\".\nTry \\h with no arguments to see available help.\n"), topic);
650+
/* If we never found anything, report that */
651+
if (!output)
652+
{
653+
output = PageOutput(2, pager ? &(pset.popt.topt) : NULL);
654+
fprintf(output, _("No help available for \"%s\".\n"
655+
"Try \\h with no arguments to see available help.\n"),
656+
topic);
657+
}
643658

644659
ClosePager(output);
645660
}

0 commit comments

Comments
 (0)