mirror of
https://git.savannah.gnu.org/git/make.git
synced 2025-01-12 08:40:55 +00:00
[SV 66037] Avoid hang/crash from MAKEFLAGS=... on command line
Make enters an infinite loop when some option and MAKEFLAGS=<value> are specified on the command line. For example, $ make -r MAKEFLAGS=hello=world If decode_switches() runs handle_non_switch_argument() from within the getopt() loop, it would recursively call decode_switches() to enter a new getopt() loop, corrupting the state of the outer loop. * src/main.c (decode_switches): Save up non-option arguments and run handle_non_switch_argument() only after we're done with getopt(). * tests/scripts/variables/MAKEFLAGS: Add tests.
This commit is contained in:
parent
49b955a50d
commit
034f862361
2 changed files with 49 additions and 9 deletions
35
src/main.c
35
src/main.c
|
@ -3108,8 +3108,20 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
|
||||||
int bad = 0;
|
int bad = 0;
|
||||||
struct command_switch *cs;
|
struct command_switch *cs;
|
||||||
struct stringlist *sl;
|
struct stringlist *sl;
|
||||||
|
struct stringlist targets;
|
||||||
int c;
|
int c;
|
||||||
unsigned int found_wait = 0;
|
unsigned int found_wait = 0;
|
||||||
|
const char **a;
|
||||||
|
|
||||||
|
/* This is for safety/double-checking. */
|
||||||
|
static int using_getopt = 0;
|
||||||
|
assert (using_getopt == 0);
|
||||||
|
using_getopt = 1;
|
||||||
|
|
||||||
|
/* Get enough space for all the arguments, just in case. */
|
||||||
|
targets.max = argc + 1;
|
||||||
|
targets.list = alloca (targets.max * sizeof (const char **));
|
||||||
|
targets.idx = 0;
|
||||||
|
|
||||||
/* getopt does most of the parsing for us.
|
/* getopt does most of the parsing for us.
|
||||||
First, get its vectors set up. */
|
First, get its vectors set up. */
|
||||||
|
@ -3138,14 +3150,9 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
|
||||||
see all he did wrong. */
|
see all he did wrong. */
|
||||||
bad = 1;
|
bad = 1;
|
||||||
else if (c == 1)
|
else if (c == 1)
|
||||||
{
|
/* An argument not starting with a dash. Defer handling until later,
|
||||||
/* An argument not starting with a dash. */
|
since handle_non_switch_argument() might corrupt getopt(). */
|
||||||
const unsigned int prior_found_wait = found_wait;
|
targets.list[targets.idx++] = coptarg;
|
||||||
found_wait = handle_non_switch_argument (coptarg, origin);
|
|
||||||
if (prior_found_wait && lastgoal)
|
|
||||||
/* If the argument before this was .WAIT, wait here. */
|
|
||||||
lastgoal->wait_here = 1;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
/* An option starting with a dash. */
|
/* An option starting with a dash. */
|
||||||
for (cs = switches; cs->c != '\0'; ++cs)
|
for (cs = switches; cs->c != '\0'; ++cs)
|
||||||
|
@ -3339,9 +3346,19 @@ decode_switches (int argc, const char **argv, enum variable_origin origin)
|
||||||
to be returned in order, this only happens when there is a "--"
|
to be returned in order, this only happens when there is a "--"
|
||||||
argument to prevent later arguments from being options. */
|
argument to prevent later arguments from being options. */
|
||||||
while (optind < argc)
|
while (optind < argc)
|
||||||
|
targets.list[targets.idx++] = argv[optind++];
|
||||||
|
targets.list[targets.idx] = NULL;
|
||||||
|
|
||||||
|
/* Cannot call getopt below this line. */
|
||||||
|
using_getopt = 0;
|
||||||
|
|
||||||
|
/* handle_non_switch_argument() can only be called after getopt is done;
|
||||||
|
if one of the arguments is MAKEFLAGS=<value> then it will recurse here
|
||||||
|
and call getopt() again, corrupting the state if the outer method. */
|
||||||
|
for (a = targets.list; *a; ++a)
|
||||||
{
|
{
|
||||||
const int prior_found_wait = found_wait;
|
const int prior_found_wait = found_wait;
|
||||||
found_wait = handle_non_switch_argument (argv[optind++], origin);
|
found_wait = handle_non_switch_argument (*a, origin);
|
||||||
if (prior_found_wait && lastgoal)
|
if (prior_found_wait && lastgoal)
|
||||||
lastgoal->wait_here = 1;
|
lastgoal->wait_here = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -902,5 +902,28 @@ unlink('hello');
|
||||||
|
|
||||||
rmdir('localtmp');
|
rmdir('localtmp');
|
||||||
|
|
||||||
|
# sv 66037. An infinite loop when MAKEFLAGS is specified on the command line.
|
||||||
|
my @cli= ('-r MAKEFLAGS=-k hello=world',
|
||||||
|
'-r MAKEFLAGS=-k hello=world MAKEFLAGS=-R',
|
||||||
|
'-r MAKEFLAGS="-R -- hello=world MAKEFLAGS=-k"');
|
||||||
|
for my $c (@cli) {
|
||||||
|
run_make_test(q!
|
||||||
|
$(info hello=$(hello))
|
||||||
|
all:;
|
||||||
|
!, $c, "hello=world\n#MAKE#: 'all' is up to date.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
run_make_test(q!
|
||||||
|
$(info hello=$(hello))
|
||||||
|
all:;
|
||||||
|
!, '-r MAKEFLAGS="-R -- hello=world MAKEFLAGS=hello=bye"',
|
||||||
|
"hello=bye\n#MAKE#: 'all' is up to date.\n");
|
||||||
|
|
||||||
|
run_make_test(q!
|
||||||
|
$(info hello=$(hello))
|
||||||
|
all:;
|
||||||
|
!, '-r MAKEFLAGS="-R -- hello=world MAKEFLAGS=-s"',
|
||||||
|
"hello=world\n");
|
||||||
|
|
||||||
# This tells the test driver that the perl test script executed properly.
|
# This tells the test driver that the perl test script executed properly.
|
||||||
1;
|
1;
|
||||||
|
|
Loading…
Reference in a new issue