commit 1b7f824056d551c89dae5ef78e40415ca77ba62d
Author: Jean-Baptiste Poittevin <jb.poittevin@gmail.com>
Date:   mar. 19 juil. 2022 19:32:36

    [SV 60165] static pattern rule with multiple targets is equivalent to multiple pattern rules

diff --git a/src/read.c b/src/read.c
index 97e71bac..0053364c 100644
--- a/src/read.c
+++ b/src/read.c
@@ -2057,45 +2057,80 @@ record_files (struct nameseq *filenames, int are_also_makes,
     {
       struct nameseq *nextf;
       const char **targets, **target_pats;
-      unsigned short c;
 
       if (pattern != 0)
         O (fatal, flocp, _("mixed implicit and static pattern rules"));
 
-      /* Count the targets to create an array of target names.
-         We already have the first one.  */
-      nextf = filenames->next;
-      free_ns (filenames);
-      filenames = nextf;
+      if (are_also_makes) {
+          /* A shared pattern rule "&:" with multiple targets is treated as a
+             single rule. In consequence, the cmds can only run once. */
 
-      for (c = 1; nextf; ++c, nextf = nextf->next)
-        ;
-      targets = xmalloc (c * sizeof (const char *));
-      target_pats = xmalloc (c * sizeof (const char *));
+          unsigned short c = 1;
+
+          /* Count the targets to create an array of target names.
+             We already have the first one.  */
+          nextf = filenames->next;
+          free_ns (filenames);
+          filenames = nextf;
+
+          for (c = 1; nextf; ++c, nextf = nextf->next)
+            ;
+
+          targets = xmalloc(c * sizeof (const char *));
+          target_pats = xmalloc(c * sizeof (const char *));
+
+          targets[0] = name;
+          target_pats[0] = implicit_percent;
 
-      targets[0] = name;
-      target_pats[0] = implicit_percent;
+          c = 1;
+          while (filenames)
+            {
+                name = filenames->name;
+                implicit_percent = find_percent_cached (&name);
+
+                if (implicit_percent == 0)
+                    O (fatal, flocp, _("mixed implicit and normal rules"));
+
+                targets[c] = name;
+                target_pats[c] = implicit_percent;
+                ++c;
+
+                nextf = filenames->next;
+                free_ns (filenames);
+                filenames = nextf;
+            }
+
+          create_pattern_rule (targets, target_pats, c,
+                               two_colon, deps, cmds, 1);
 
-      c = 1;
+          return;
+      }
+
+      /* A simple static pattern rule ":" with multiple targets is equivalent
+         to multiple pattern rules with a single target with the same deps
+         and cmds. In consequence, the cmds can be run multiple time. */
       while (filenames)
         {
+          targets = xmalloc(sizeof (const char *));
+          target_pats = xmalloc(sizeof (const char *));
+
           name = filenames->name;
           implicit_percent = find_percent_cached (&name);
 
           if (implicit_percent == 0)
             O (fatal, flocp, _("mixed implicit and normal rules"));
 
-          targets[c] = name;
-          target_pats[c] = implicit_percent;
-          ++c;
+          *targets = name;
+          *target_pats = implicit_percent;
+
+          create_pattern_rule (targets, target_pats, 1,
+                               two_colon, deps, cmds, 1);
 
           nextf = filenames->next;
           free_ns (filenames);
           filenames = nextf;
         }
 
-      create_pattern_rule (targets, target_pats, c, two_colon, deps, cmds, 1);
-
       return;
     }
 
diff --git a/tests/scripts/features/patternrules b/tests/scripts/features/patternrules
index c7ded311..edabda34 100644
--- a/tests/scripts/features/patternrules
+++ b/tests/scripts/features/patternrules
@@ -112,7 +112,7 @@ run_make_test('
 .PHONY: all
 all: foo.c foo.o
 
-%.h %.c: %.in ; touch $*.h ; touch $*.c
+%.h %.c &: %.in ; touch $*.h ; touch $*.c
 
 %.o: %.c %.h ; echo $+ >$@
 
@@ -136,7 +136,7 @@ p1.% p2.%: %.orig ; @echo $@
 
 .PHONY: foo.orig
 ',
-              '', "foo.s1\np1.foo\n");
+              '', "foo.s1\nfoo.s2\np1.foo\np2.foo\n");
 
 # TEST 6: Make sure that non-target files are still eligible to be created
 # as part of implicit rule chaining.  Savannah bug #17752.
@@ -163,7 +163,7 @@ final: x ; @echo $@
 x: x.t1 x.t2 ; @echo $@
 x.t2: dep
 dep: ; @echo $@
-%.t1 %.t2: ; @echo $*.t1 ; echo $*.t2
+%.t1 %.t2 &: ; @echo $*.t1 ; echo $*.t2
 !,
               '', "dep\nx.t1\nx.t2\nx\nfinal\n");
 
@@ -200,35 +200,37 @@ all: foo.x foo-mt.x
 
 touch('a.c');
 
-# a.lnk isn't listed as removed, because it's not actually created
+# TEST #10: a.lnk isn't listed as removed, because it's not actually created
 run_make_test(q!
 all: a.elf a.dbg
 
-%.elf %.lnk: %.c ; : $*.elf $*.lnk
+%.elf %.lnk &: %.c ; : $*.elf $*.lnk
 
-%.elf %.dbg: %.lnk ; : $*.elf $*.dbg
+%.elf %.dbg &: %.lnk ; : $*.elf $*.dbg
 !,
     '-j2', ": a.elf a.lnk\n: a.elf a.dbg\n");
 
-# SV 60435 : a.lnk is removed, because it is intermediate.
+# TEST #11: a.lnk is removed, because it is intermediate.
+# See SV 60435.
 run_make_test(q!
 all: a.elf a.dbg
 
-%.elf %.lnk: %.c ; touch $*.elf $*.lnk
+%.elf %.lnk &: %.c ; touch $*.elf $*.lnk
 
-%.elf %.dbg: %.lnk ; touch $*.elf $*.dbg
+%.elf %.dbg &: %.lnk ; touch $*.elf $*.dbg
 !,
               '-j2', "touch a.elf a.lnk\ntouch a.elf a.dbg\nrm a.lnk\n");
 
 unlink('a.elf', 'a.dbg');
 
-# SV 60435 : a.lnk is not intermediate, because it is explicitly mentioned.
+# TEST #12: a.lnk is not intermediate, because it is explicitly mentioned.
+# See SV 60435.
 run_make_test(q!
 all: a.elf a.dbg
 
-%.elf %.lnk: %.c ; touch $*.elf $*.lnk
+%.elf %.lnk &: %.c ; touch $*.elf $*.lnk
 
-%.elf %.dbg: %.lnk ; touch $*.elf $*.dbg
+%.elf %.dbg &: %.lnk ; touch $*.elf $*.dbg
 
 install: a.lnk
 .PHONY: install
@@ -343,74 +345,78 @@ unlink('hello.x', 'test.x', 'hello.tsk');
 # files.
 # sv 60435.
 
-# subtest 1.
-# a.1 and a.2 are intermediate and should be removed.
+# Test 21: a.1 and a.2 are intermediate and should be removed.
 
 run_make_test(q!
 a.4:
 %.4: %.1 %.15 ; cat $^ >$@
-%.1 %.15: ; touch $*.1 $*.15
+%.1 %.15: ; touch $@
 !,
-              '', "touch a.1 a.15\ncat a.1 a.15 >a.4\nrm a.15 a.1");
+              '', "touch a.1\ntouch a.15\ncat a.1 a.15 >a.4\nrm a.15 a.1");
 
-unlink('a.4');
+unlink('a.1', 'a.4', 'a.15');
 
+# Test 22:
 # subtest 2.
 # a.1 and a.2 are intermediate and should be removed.
 # a.3 is explicit and should not be removed.
 run_make_test(q!
 a.4:
 %.4: %.1 %.15 a.3 ; cat $^ >$@
-%.1 %.15: ; touch $*.1 $*.15
+%.1 %.15: ; touch $@
 %.3: ; touch $@
 !,
-              '', "touch a.3\ntouch a.1 a.15\ncat a.1 a.15 a.3 >a.4\nrm a.15 a.1");
+              '', "touch a.3\ntouch a.1\ntouch a.15\ncat a.1 a.15 a.3 >a.4\nrm a.15 a.1");
 
-unlink('a.3', 'a.4');
+unlink('a.1', 'a.3', 'a.4', 'a.15');
 
+# Test 23:
 # subtest 3.
 # a.1 and a.2 are intermediate and should be removed.
 # a.3 is explicit and should not be removed.
 run_make_test(q!
 a.4:
 %.4: %.1 %.15 a.3 ; cat $^ >$@
-%.1 %.15 %.3: ; touch $*.1 $*.15 $*.3
+%.1 %.15 %.3 &: ; touch $*.1 $*.15 $*.3
 !,
               '', "touch a.1 a.15 a.3\ncat a.1 a.15 a.3 >a.4\nrm a.15 a.1");
 
-unlink('a.3', 'a.4');
+unlink('a.3', 'a.4', 'a.15');
 
+# Test 24:
 # subtest 4.
 # a.1 and a.2 are intermediate and should be removed.
 # a.3 is explicit and should not be removed.
 run_make_test(q!
 a.4:
 %.4: %.1 %.15 a.3 ; cat $^ >$@
-%.3 %.1 %.15: ; touch $*.1 $*.15 $*.3
+%.3 %.1 %.15 &: ; touch $*.1 $*.15 $*.3
 !,
               '', "touch a.1 a.15 a.3\ncat a.1 a.15 a.3 >a.4\nrm a.15 a.1");
 
-unlink('a.3', 'a.4');
+unlink('a.3', 'a.4', 'a.15');
 
+# Test 25:
 # subtest 5.
 # a.1 and a.2 are intermediate and should be removed.
 # a.3 is explicit and should not be removed.
 run_make_test(q!
 a.4:
 %.4: a.3 %.1 %.15 ; cat $^ >$@
-%.1 %.15 %.3: ; touch $*.1 $*.15 $*.3
+%.1 %.15 %.3 &: ; touch $*.1 $*.15 $*.3
 !,
               '', "touch a.1 a.15 a.3\ncat a.3 a.1 a.15 >a.4\nrm a.15 a.1");
 
-unlink('a.3', 'a.4');
+unlink('a.3', 'a.4', 'a.15');
 
+# Test 26:
 # subtest 6.
 # a.2 is intermediate and should be removed.
 # a.1 is mentioned explicitly on an unrelated rule and should not be removed.
 run_make_test(q!
 a.3:
 %.3: %.1 %.2 ; cat $^ >$@
-%.1 %.2: ; touch $*.1 $*.2
+%.1 %.2 &: ; touch $*.1 $*.2
 install: a.1
 .PHONY: install
 !,
@@ -420,6 +426,7 @@ unlink('a.1', 'a.3');
 
 # Test removal of intermediate files.
 
+# Test 27
 # subtest 1.
 # hello.x is removed, because it is intermediate.
 run_make_test(q!
