biultin/rev-parse: fix memory leaks in `--parseopt` mode

We have a bunch of memory leaks in git-rev-parse(1)'s `--parseopt` mode.
Refactor the code to use `struct strvec`s to make it easier for us to
track the lifecycle of those leaking variables and then free them.

While at it, remove the unneeded static lifetime for some of the
variables.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
Patrick Steinhardt 2024-06-11 11:19:36 +02:00 committed by Junio C Hamano
parent 11ee9a75e7
commit afb0653d23
3 changed files with 32 additions and 23 deletions

View File

@ -423,12 +423,12 @@ static char *findspace(const char *s)


static int cmd_parseopt(int argc, const char **argv, const char *prefix) static int cmd_parseopt(int argc, const char **argv, const char *prefix)
{ {
static int keep_dashdash = 0, stop_at_non_option = 0; int keep_dashdash = 0, stop_at_non_option = 0;
static char const * const parseopt_usage[] = { char const * const parseopt_usage[] = {
N_("git rev-parse --parseopt [<options>] -- [<args>...]"), N_("git rev-parse --parseopt [<options>] -- [<args>...]"),
NULL NULL
}; };
static struct option parseopt_opts[] = { struct option parseopt_opts[] = {
OPT_BOOL(0, "keep-dashdash", &keep_dashdash, OPT_BOOL(0, "keep-dashdash", &keep_dashdash,
N_("keep the `--` passed as an arg")), N_("keep the `--` passed as an arg")),
OPT_BOOL(0, "stop-at-non-option", &stop_at_non_option, OPT_BOOL(0, "stop-at-non-option", &stop_at_non_option,
@ -438,12 +438,11 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
N_("output in stuck long form")), N_("output in stuck long form")),
OPT_END(), OPT_END(),
}; };
static const char * const flag_chars = "*=?!";

struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT; struct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;
const char **usage = NULL; struct strvec longnames = STRVEC_INIT;
struct strvec usage = STRVEC_INIT;
struct option *opts = NULL; struct option *opts = NULL;
int onb = 0, osz = 0, unb = 0, usz = 0; size_t opts_nr = 0, opts_alloc = 0;


strbuf_addstr(&parsed, "set --"); strbuf_addstr(&parsed, "set --");
argc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage, argc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage,
@ -453,16 +452,16 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)


/* get the usage up to the first line with a -- on it */ /* get the usage up to the first line with a -- on it */
for (;;) { for (;;) {
strbuf_reset(&sb);
if (strbuf_getline(&sb, stdin) == EOF) if (strbuf_getline(&sb, stdin) == EOF)
die(_("premature end of input")); die(_("premature end of input"));
ALLOC_GROW(usage, unb + 1, usz);
if (!strcmp("--", sb.buf)) { if (!strcmp("--", sb.buf)) {
if (unb < 1) if (!usage.nr)
die(_("no usage string given before the `--' separator")); die(_("no usage string given before the `--' separator"));
usage[unb] = NULL;
break; break;
} }
usage[unb++] = strbuf_detach(&sb, NULL);
strvec_push(&usage, sb.buf);
} }


/* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */ /* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */
@ -474,10 +473,10 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
if (!sb.len) if (!sb.len)
continue; continue;


ALLOC_GROW(opts, onb + 1, osz); ALLOC_GROW(opts, opts_nr + 1, opts_alloc);
memset(opts + onb, 0, sizeof(opts[onb])); memset(opts + opts_nr, 0, sizeof(*opts));


o = &opts[onb++]; o = &opts[opts_nr++];
help = findspace(sb.buf); help = findspace(sb.buf);
if (!help || sb.buf == help) { if (!help || sb.buf == help) {
o->type = OPTION_GROUP; o->type = OPTION_GROUP;
@ -494,20 +493,22 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
o->callback = &parseopt_dump; o->callback = &parseopt_dump;


/* name(s) */ /* name(s) */
s = strpbrk(sb.buf, flag_chars); s = strpbrk(sb.buf, "*=?!");
if (!s) if (!s)
s = help; s = help;


if (s == sb.buf) if (s == sb.buf)
die(_("missing opt-spec before option flags")); die(_("missing opt-spec before option flags"));


if (s - sb.buf == 1) /* short option only */ if (s - sb.buf == 1) { /* short option only */
o->short_name = *sb.buf; o->short_name = *sb.buf;
else if (sb.buf[1] != ',') /* long option only */ } else if (sb.buf[1] != ',') { /* long option only */
o->long_name = xmemdupz(sb.buf, s - sb.buf); o->long_name = strvec_pushf(&longnames, "%.*s",
else { (int)(s - sb.buf), sb.buf);
} else {
o->short_name = *sb.buf; o->short_name = *sb.buf;
o->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2); o->long_name = strvec_pushf(&longnames, "%.*s",
(int)(s - sb.buf - 2), sb.buf + 2);
} }


/* flags */ /* flags */
@ -537,9 +538,9 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
strbuf_release(&sb); strbuf_release(&sb);


/* put an OPT_END() */ /* put an OPT_END() */
ALLOC_GROW(opts, onb + 1, osz); ALLOC_GROW(opts, opts_nr + 1, opts_alloc);
memset(opts + onb, 0, sizeof(opts[onb])); memset(opts + opts_nr, 0, sizeof(*opts));
argc = parse_options(argc, argv, prefix, opts, usage, argc = parse_options(argc, argv, prefix, opts, usage.v,
(keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0) | (keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0) |
(stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) | (stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) |
PARSE_OPT_SHELL_EVAL); PARSE_OPT_SHELL_EVAL);
@ -547,7 +548,13 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
strbuf_addstr(&parsed, " --"); strbuf_addstr(&parsed, " --");
sq_quote_argv(&parsed, argv); sq_quote_argv(&parsed, argv);
puts(parsed.buf); puts(parsed.buf);

strbuf_release(&parsed); strbuf_release(&parsed);
strbuf_release(&sb);
strvec_clear(&longnames);
strvec_clear(&usage);
free((char *) opts->help);
free(opts);
return 0; return 0;
} }



View File

@ -5,6 +5,7 @@ test_description='Test workflows involving pull request.'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME


TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh . ./test-lib.sh


if ! test_have_prereq PERL if ! test_have_prereq PERL

View File

@ -2,6 +2,7 @@


test_description='Test automatic use of a pager.' test_description='Test automatic use of a pager.'


TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh . ./test-lib.sh
. "$TEST_DIRECTORY"/lib-pager.sh . "$TEST_DIRECTORY"/lib-pager.sh
. "$TEST_DIRECTORY"/lib-terminal.sh . "$TEST_DIRECTORY"/lib-terminal.sh