Skip to content

Commit 2c2e134

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 57a7d6f commit 2c2e134

File tree

1 file changed

+36
-21
lines changed

1 file changed

+36
-21
lines changed

src/bin/psql/help.c

+36-21
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ helpSQL(const char *topic, unsigned short int pager)
443443
int i;
444444
int j;
445445

446+
/* Find screen width to determine how many columns will fit */
446447
#ifdef TIOCGWINSZ
447448
struct winsize screen_size;
448449

@@ -480,56 +481,63 @@ helpSQL(const char *topic, unsigned short int pager)
480481
else
481482
{
482483
int i,
483-
j,
484-
x = 0;
485-
bool help_found = false;
484+
pass;
486485
FILE *output = NULL;
487486
size_t len,
488-
wordlen;
489-
int nl_count = 0;
487+
wordlen,
488+
j;
489+
int nl_count;
490490

491491
/*
492+
* len is the amount of the input to compare to the help topic names.
492493
* We first try exact match, then first + second words, then first
493494
* word only.
494495
*/
495496
len = strlen(topic);
496497

497-
for (x = 1; x <= 3; x++)
498+
for (pass = 1; pass <= 3; pass++)
498499
{
499-
if (x > 1) /* Nothing on first pass - try the opening
500+
if (pass > 1) /* Nothing on first pass - try the opening
500501
* word(s) */
501502
{
502503
wordlen = j = 1;
503-
while (topic[j] != ' ' && j++ < len)
504+
while (j < len && topic[j++] != ' ')
504505
wordlen++;
505-
if (x == 2)
506+
if (pass == 2 && j < len)
506507
{
507-
j++;
508-
while (topic[j] != ' ' && j++ <= len)
508+
wordlen++;
509+
while (j < len && topic[j++] != ' ')
509510
wordlen++;
510511
}
511-
if (wordlen >= len) /* Don't try again if the same word */
512+
if (wordlen >= len)
512513
{
513-
if (!output)
514-
output = PageOutput(nl_count, pager ? &(pset.popt.topt) : NULL);
515-
break;
514+
/* Failed to shorten input, so try next pass if any */
515+
continue;
516516
}
517517
len = wordlen;
518518
}
519519

520-
/* Count newlines for pager */
520+
/*
521+
* Count newlines for pager. This logic must agree with what the
522+
* following loop will do!
523+
*/
524+
nl_count = 0;
521525
for (i = 0; QL_HELP[i].cmd; i++)
522526
{
523527
if (pg_strncasecmp(topic, QL_HELP[i].cmd, len) == 0 ||
524528
strcmp(topic, "*") == 0)
525529
{
530+
/* magic constant here must match format below! */
526531
nl_count += 5 + QL_HELP[i].nl_count;
527532

528533
/* If we have an exact match, exit. Fixes \h SELECT */
529534
if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0)
530535
break;
531536
}
532537
}
538+
/* If no matches, don't open the output yet */
539+
if (nl_count == 0)
540+
continue;
533541

534542
if (!output)
535543
output = PageOutput(nl_count, pager ? &(pset.popt.topt) : NULL);
@@ -543,24 +551,31 @@ helpSQL(const char *topic, unsigned short int pager)
543551

544552
initPQExpBuffer(&buffer);
545553
QL_HELP[i].syntaxfunc(&buffer);
546-
help_found = true;
554+
/* # of newlines in format must match constant above! */
547555
fprintf(output, _("Command: %s\n"
548556
"Description: %s\n"
549557
"Syntax:\n%s\n\n"),
550558
QL_HELP[i].cmd,
551559
_(QL_HELP[i].help),
552560
buffer.data);
561+
termPQExpBuffer(&buffer);
562+
553563
/* If we have an exact match, exit. Fixes \h SELECT */
554564
if (pg_strcasecmp(topic, QL_HELP[i].cmd) == 0)
555565
break;
556566
}
557567
}
558-
if (help_found) /* Don't keep trying if we got a match */
559-
break;
568+
break;
560569
}
561570

562-
if (!help_found)
563-
fprintf(output, _("No help available for \"%s\".\nTry \\h with no arguments to see available help.\n"), topic);
571+
/* If we never found anything, report that */
572+
if (!output)
573+
{
574+
output = PageOutput(2, pager ? &(pset.popt.topt) : NULL);
575+
fprintf(output, _("No help available for \"%s\".\n"
576+
"Try \\h with no arguments to see available help.\n"),
577+
topic);
578+
}
564579

565580
ClosePager(output);
566581
}

0 commit comments

Comments
 (0)