diff --git a/src/implicit.c b/src/implicit.c index a6fd222c..9aee7db5 100644 --- a/src/implicit.c +++ b/src/implicit.c @@ -230,7 +230,9 @@ pattern_search (struct file *file, int archive, /* Names of possible dependencies are constructed in this buffer. We may replace % by $(*F) for second expansion, increasing the length. */ - char *depname = alloca (namelen + max_pattern_dep_length + 4); + size_t deplen = namelen + max_pattern_dep_length + 4; + char *depname = alloca (deplen); + char *dend = depname + deplen; /* The start and length of the stem of FILENAME for the current rule. */ const char *stem = 0; @@ -597,6 +599,7 @@ pattern_search (struct file *file, int archive, { int add_dir = 0; size_t len; + const char *end; struct dep **dptr; int is_explicit; const char *cp; @@ -605,12 +608,13 @@ pattern_search (struct file *file, int archive, nptr = get_next_word (nptr, &len); if (nptr == 0) continue; + end = nptr + len; /* See if this is a transition to order-only prereqs. */ if (! order_only && len == 1 && nptr[0] == '|') { order_only = 1; - nptr += len; + nptr = end; continue; } @@ -625,7 +629,7 @@ pattern_search (struct file *file, int archive, (since $* and $(*F) are simple variables) there won't be additional re-expansion of the stem. */ - cp = lindex (nptr, nptr + len, '%'); + cp = lindex (nptr, end, '%'); if (cp == 0) { memcpy (depname, nptr, len); @@ -634,28 +638,56 @@ pattern_search (struct file *file, int archive, } else { - size_t i = cp - nptr; + /* Go through all % between NPTR and END. + Copy contents of [NPTR, END) to depname, with the + first % after NPTR and then each first % after white + space replaced with $* or $(*F). depname has enough + room to substitute each % with $(*F). */ char *o = depname; - memcpy (o, nptr, i); - o += i; - if (check_lastslash) - { - add_dir = 1; - memcpy (o, "$(*F)", 5); - o += 5; - } - else - { - memcpy (o, "$*", 2); - o += 2; - } - memcpy (o, cp + 1, len - i - 1); - o[len - i - 1] = '\0'; + is_explicit = 0; + for (;;) + { + size_t i = cp - nptr; + assert (o + i < dend); + memcpy (o, nptr, i); + o += i; + if (check_lastslash) + { + add_dir = 1; + assert (o + 5 < dend); + memcpy (o, "$(*F)", 5); + o += 5; + } + else + { + assert (o + 2 < dend); + memcpy (o, "$*", 2); + o += 2; + } + assert (o < dend); + ++cp; + assert (cp <= end); + nptr = cp; + if (nptr == end) + break; + + /* Skip the rest of this word then find the next %. + No need to worry about order-only, or nested + functions: NPTR went though get_next_word. */ + while (cp < end && ! END_OF_TOKEN (*cp)) + ++cp; + cp = lindex (cp, end, '%'); + if (cp == 0) + break; + } + len = end - nptr; + memcpy (o, nptr, len); + o[len] = '\0'; } /* Set up for the next word. */ - nptr += len; + nptr = end; /* Initialize and set file variables if we haven't already done so. */ diff --git a/src/rule.c b/src/rule.c index 7b12413e..a491b83d 100644 --- a/src/rule.c +++ b/src/rule.c @@ -129,7 +129,18 @@ snap_implicit_rules (void) for (dep = prereqs; dep; dep = dep->next) { - size_t l = strlen (dep_name (dep)); + const char *d = dep_name (dep); + size_t l = strlen (d); + + if (dep->need_2nd_expansion) + /* When pattern_search allocates a buffer, allow 5 bytes per each % to + substitute each % with $(*F) while avoiding realloc. */ + while ((d = strchr (d, '%')) != 0) + { + l += 4; + ++d; + } + if (l > max_pattern_dep_length) max_pattern_dep_length = l; ++pre_deps; diff --git a/tests/scripts/features/patternrules b/tests/scripts/features/patternrules index d33e5a95..c7ded311 100644 --- a/tests/scripts/features/patternrules +++ b/tests/scripts/features/patternrules @@ -469,5 +469,112 @@ run_make_test(q! unlink('1.all', '1.q', '1.r'); +# sv 62206. + +my @dir = ('', 'lib/'); # With and without last slash. +my @secondexpansion = ('', '.SECONDEXPANSION:'); + +# The following combinations are generated with and without second expansion. +# 1. +# all: bye.x +# %.x: ... +# +# 2. +# all: lib/bye.x +# %.x: ... +# +# 3. +# all: lib/bye.x +# lib/%.x: ... +# +# The following combination is not generated, because there is no rule to +# build bye.x, no stem substitution takes place, not of interest of this test. +# 4. +# all: bye.x +# lib/%.x: ... + +for my $se (@secondexpansion) { +for my $d (@dir) { # The directory of the prerequisite of 'all'. +for my $r (@dir) { # The directory of the target in the rule definition. +(!$d && $r) && next; # Combination 4. +my $dollar = $se ? '$' : ''; + +# The prerequisite should only have directory if the prerequisite of 'all' has +# it and if the prequisite pattern in the rule definition does not have it. +# That is combination 2. +my $pdir = $d && !$r ? $d : ''; + +my $prereqs = "${pdir}bye.1"; + +# One func, one %. +run_make_test(" +$se +all: ${d}bye.x +$r%.x: $dollar\$(firstword %.1); \$(info \$@ from \$^) +.PHONY: $prereqs +", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n"); + +$prereqs = "${pdir}bye.1 ${pdir}bye.2"; + +# Multiple funcs, each has one %. +run_make_test(" +$se +all: ${d}bye.x +$r%.x: $dollar\$(firstword %.1) $dollar\$(firstword %.2); \$(info \$@ from \$^) +.PHONY: $prereqs +", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n"); + +$prereqs = "${pdir}bye.1 ${pdir}bye.2 ${pdir}bye.3 ${pdir}bye.4"; + +# Multiple funcs, each has multiple %. +run_make_test(" +$se +all: ${d}bye.x +$r%.x: $dollar\$(wordlist 1, 99, %.1 %.2) $dollar\$(wordlist 1, 99, %.3 %.4); \$(info \$@ from \$^) +.PHONY: $prereqs +", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n"); + +$prereqs = "${pdir}bye.1 ${pdir}bye.2 ${pdir}bye.3 ${pdir}bye.4"; + +# Nested functions. +run_make_test(" +$se +all: ${d}bye.x +$r%.x: $dollar\$(wordlist 1, 99, $dollar\$(wordlist 1, 99, %.1 %.2)) $dollar\$(wordlist 1, 99, $dollar\$(wordlist 1,99, %.3 %.4)); \$(info \$@ from \$^) +.PHONY: $prereqs +", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n"); + +$prereqs = "${pdir}bye1%2% ${pdir}bye ${pdir}3bye4%5 ${pdir}6bye ${pdir}bye7%8 ${pdir}bye9 ${pdir}bye10% ${pdir}11bye12 13"; + +# Multiple funcs, each has multiple words, each word has multiple %, sole %, +# various corner cases. +# Make should substitude the first % and only the first % in each word with the +# stem. +run_make_test(" +$se +all: ${d}bye.x +$r%.x: $dollar\$(wordlist 1, 99, %1%2% % 3%4%5 6%) %7%8 %9 $dollar\$(wordlist 1, 99, %10% 11%12) 13; \$(info \$@ from \$^) +.PHONY: $prereqs +", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n"); + +if ($port_type eq 'UNIX') { +# Test that make does not use some hardcoded array of a finite size on stack. +# Long prerequisite name. This prerequisite name is over 66K long. +my $prefix = 'abcdefgh' x 128 x 33; # 33K long. +my $suffix = 'stuvwxyz' x 128 x 33; # 33K long. +$prereqs = "${pdir}${prefix}bye${suffix}.1 ${pdir}${prefix}bye${suffix}.2"; + +run_make_test(" +$se +all: ${d}bye.x +$r%.x: $dollar\$(wordlist 1, 99, ${prefix}%${suffix}.1 ${prefix}%${suffix}.2); \$(info \$@ from \$^) +.PHONY: $prereqs +", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n"); +} + +} +} +} + # This tells the test driver that the perl test script executed properly. 1;