]> git.ipfire.org Git - thirdparty/make.git/commitdiff
[SV 62278] Only expand the first pattern during secondary expansion
authorDmitry Goncharov <dgoncharov@users.sf.net>
Sun, 24 Apr 2022 00:21:31 +0000 (20:21 -0400)
committerPaul Smith <psmith@gnu.org>
Sun, 24 Apr 2022 14:39:32 +0000 (10:39 -0400)
During normal pattern rule expansion only the first pattern (%) is
expanded; however during secondary expansion all patterns were
expanded.  Modify secondary expansion to match the behavior of normal
expansion.

Implementation tweaked by Paul Smith <psmith@gnu.org>

* src/file.c (expand_deps): Don't use subst_expand() which replaces
all % with $*: instead replace only the first one, by hand.
Fix a memory leak where the dep structure was not always freed.
* tests/scripts/features/statipattrules: Use .RECIPEPREFIX not TAB.
Add a series of tests verifying that static pattern rules with and
without secondary expansion both return the same results.

src/file.c
tests/scripts/features/statipattrules

index 2afad87267b5447b3a83b9b8d257e4eda997134b..5aba3b6dc7de8742c7663aae8ba100f9363b8d2e 100644 (file)
@@ -572,6 +572,7 @@ expand_deps (struct file *f)
   struct dep *d;
   struct dep **dp;
   const char *file_stem = f->stem;
+  const char *fstem;
   int initialized = 0;
 
   f->updating = 0;
@@ -584,7 +585,6 @@ expand_deps (struct file *f)
     {
       char *p;
       struct dep *new, *next;
-      char *name = (char *)d->name;
 
       if (! d->name || ! d->need_2nd_expansion)
         {
@@ -594,15 +594,47 @@ expand_deps (struct file *f)
           continue;
         }
 
-      /* If it's from a static pattern rule, convert the patterns into
-         "$*" so they'll expand properly.  */
+      /* If it's from a static pattern rule, convert the initial pattern in
+         each word to "$*" so they'll expand properly.  */
       if (d->staticpattern)
         {
-          char *o = subst_expand (variable_buffer, name, "%", "$*", 1, 2, 0);
-          *o = '\0';
-          free (name);
-          d->name = name = xstrdup (variable_buffer);
-          d->staticpattern = 0;
+          const char *cs = d->name;
+          size_t nperc = 0;
+
+          /* Count the number of % in the string.  */
+          while ((cs = strchr (cs, '%')) != NULL)
+            {
+              ++nperc;
+              ++cs;
+            }
+
+          if (nperc)
+            {
+              /* Allocate enough space to replace all % with $*.  */
+              size_t slen = strlen (d->name) + nperc + 1;
+              const char *pcs = d->name;
+              char *name = xmalloc (slen);
+              char *s = name;
+
+              /* Substitute the first % in each word.  */
+              cs = strchr (pcs, '%');
+
+              while (cs)
+                {
+                  memcpy (s, pcs, cs - pcs);
+                  s += cs - pcs;
+                  *(s++) = '$';
+                  *(s++) = '*';
+                  pcs = ++cs;
+
+                  /* Find the first % after the next whitespace.  */
+                  cs = strchr (end_of_token (cs), '%');
+                }
+              strcpy (s, pcs);
+
+              free ((char*)d->name);
+              d->name = name;
+            }
         }
 
       /* We're going to do second expansion so initialize file variables for
@@ -621,14 +653,14 @@ expand_deps (struct file *f)
 
       p = variable_expand_for_file (d->name, f);
 
+      /* Free the un-expanded name.  */
+      free ((char*)d->name);
+
       if (d->stem != 0)
         f->stem = file_stem;
 
-      /* At this point we don't need the name anymore: free it.  */
-      free (name);
-
       /* Parse the prerequisites and enter them into the file database.  */
-      new = enter_prereqs (split_prereqs (p), d->stem);
+      new = split_prereqs (p);
 
       /* If there were no prereqs here (blank!) then throw this one out.  */
       if (new == 0)
@@ -640,10 +672,21 @@ expand_deps (struct file *f)
         }
 
       /* Add newly parsed prerequisites.  */
+      fstem = d->stem;
       next = d->next;
+      free_dep (d);
       *dp = new;
-      for (dp = &new->next, d = new->next; d != 0; dp = &d->next, d = d->next)
-        ;
+      for (dp = &new, d = new; d != 0; dp = &d->next, d = d->next)
+        {
+          d->file = lookup_file (d->name);
+          if (d->file == 0)
+            d->file = enter_file (d->name);
+          d->name = 0;
+          d->stem = fstem;
+          if (!fstem)
+            /* This file is explicitly mentioned as a prereq.  */
+            d->file->is_explicit = 1;
+        }
       *dp = next;
       d = *dp;
     }
index 378fedc5fda1c5fde64f3ec8e013cbf87adfb149..7a6a8bcb73789668a8ad166f028437251203921d 100644 (file)
@@ -72,13 +72,15 @@ foo.baz: ;@:
 #          perprerequisite's stem (Savannah bug #16053).
 #
 run_make_test('
+.RECIPEPREFIX := >
+
 all.foo.bar: %.foo.bar: %.one
 
 all.foo.bar: %.bar: %.two
 
 all.foo.bar:
-       @echo $*
-       @echo $^
+>      @echo $*
+>      @echo $^
 
 .DEFAULT:;@:
 ',
@@ -92,6 +94,7 @@ all.one all.foo.two');
 #          (Savannah bug #16053).
 #
 run_make_test('
+.RECIPEPREFIX := >
 .SECONDEXPANSION:
 
 all.foo.bar: %.foo.bar: %.one $$*-one
@@ -99,8 +102,8 @@ all.foo.bar: %.foo.bar: %.one $$*-one
 all.foo.bar: %.bar: %.two $$*-two
 
 all.foo.bar:
-       @echo $*
-       @echo $^
+>      @echo $*
+>      @echo $^
 
 .DEFAULT:;@:
 ',
@@ -138,4 +141,161 @@ unrelated: hello.x
 
 unlink('hello.z');
 
+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
+# bye.x: %.x: ...
+#
+# 2.
+# all: lib/bye.x
+# lib/bye.x: %.x: ...
+#
+# 3.
+# all: lib/bye.x
+# 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
+# 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 prerequisite 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 : '';
+
+
+# One func, one %.
+my $prereqs = "${pdir}bye.1";
+run_make_test("
+$se
+.PHONY: $prereqs
+all: ${d}bye.x
+${d}bye.x: $r%.x: $dollar\$(firstword %.1); \$(info \$@ from \$^)
+", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n");
+
+
+# Multiple funcs, each has one %.
+$prereqs = "${pdir}bye.1 ${pdir}bye.2";
+run_make_test("
+$se
+.PHONY: $prereqs
+all: ${d}bye.x
+${d}bye.x: $r%.x: $dollar\$(firstword %.1) $dollar\$(firstword %.2); \$(info \$@ from \$^)
+", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n");
+
+
+# Multiple funcs, each has multiple %.
+$prereqs = "${pdir}bye.1 ${pdir}bye.2 ${pdir}bye.3 ${pdir}bye.4";
+run_make_test("
+$se
+.PHONY: $prereqs
+all: ${d}bye.x
+${d}bye.x: $r%.x: $dollar\$(wordlist 1, 99, %.1 %.2) $dollar\$(wordlist 1, 99, %.3 %.4); \$(info \$@ from \$^)
+", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n");
+
+
+# Multiple funcs, each has multiple %, each prerequisite has multiple %.
+$prereqs = "${pdir}bye_%_%.1 ${pdir}bye_%_%.2 ${pdir}bye_%_%.3 ${pdir}bye_%_%.4";
+run_make_test("
+$se
+.PHONY: $prereqs
+all: ${d}bye.x
+${d}bye.x: $r%.x: $dollar\$(wordlist 1, 99, %_%_%.1 %_%_%.2) $dollar\$(wordlist 1, 99, %_%_%.3 %_%_%.4); \$(info \$@ from \$^)
+", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n");
+
+
+# Nested functions.
+$prereqs = "${pdir}bye.1 ${pdir}bye.2 ${pdir}bye.3 ${pdir}bye.4";
+run_make_test("
+$se
+.PHONY: $prereqs
+all: ${d}bye.x
+${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 \$^)
+", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n");
+
+
+# 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.
+$prereqs = "${pdir}bye1%2% ${pdir}bye 3${pdir}bye4%5 6${pdir}bye ${pdir}bye7%8 ${pdir}bye9 ${pdir}bye10% 11${pdir}bye12 13";
+run_make_test("
+$se
+.PHONY: $prereqs
+all: ${d}bye.x
+${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 \$^)
+", '', "${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 = "${prefix}${pdir}bye${suffix}.1 ${prefix}${pdir}bye${suffix}.2";
+run_make_test("
+$se
+.PHONY: $prereqs
+all: ${d}bye.x
+${d}bye.x: $r%.x: $dollar\$(wordlist 1, 99, ${prefix}%${suffix}.1 ${prefix}%${suffix}.2); \$(info \$@ from \$^)
+", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n");
+}
+
+
+# Empty stem.
+$prereqs = "${pdir}.1";
+run_make_test("
+$se
+.PHONY: $prereqs
+all: ${d}bye.x
+${d}bye.x: $r%bye.x: $dollar\$(firstword %.1); \$(info \$@ from \$^)
+", '', "${d}bye.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n");
+
+
+# A word expands to an empty prerequisite.
+run_make_test("
+$se
+all: ${d}bye.x
+${d}bye.x: $r%.x: $dollar\$(%); \$(info \$@ from \$^)
+", '', "${d}bye.x from \n#MAKE#: Nothing to be done for 'all'.\n");
+
+}
+}
+}
+
+# Escaped %.
+# The following combinations are generated without second expansion.
+# 1.
+# all: the%weird\\_hello_pattern\\.x
+# the\\%weird\\_hello_pattern\\.x: the\\%weird\\_%_pattern\\.x: ...
+#
+# 2.
+# all: lib/the%weird\\_hello_pattern\\.x
+# lib/the\\%weird\\_hello_pattern\\.x: lib/the\\%weird\\_%_pattern\\.x: ...
+#
+# Other combinations or second expansion are not tested, because escaped % is
+# not implemented for those.
+
+for my $d (@dir) {
+my $prereqs = "${d}the%weird\\\\_hello_pattern%\\\\.1 ${d}the%weird\\\\_hello_pattern%\\\\.2";
+run_make_test("
+$se
+.PHONY: $prereqs
+all: ${d}the%weird\\\\_hello_pattern\\\\.x
+${d}the\\%weird\\\\_hello_pattern\\\\.x: ${d}the\\%weird\\\\_%_pattern\\\\.x: $dollar\$(wordlist 1, 99, ${d}the\\%weird\\\\_%_pattern%\\\\.1 ${d}the\\%weird\\\\_%_pattern%\\\\.2); \$(info \$@ from \$^)
+", '', "${d}the%weird\\\\_hello_pattern\\\\.x from $prereqs\n#MAKE#: Nothing to be done for 'all'.\n");
+}
+
 1;