From 510e5ce801d957338be764a794f1463b662a7edc Mon Sep 17 00:00:00 2001
From: Dmitry Goncharov <dgoncharov@users.sf.net>
Date: Mon, 15 Mar 2021 02:10:49 -0400
Subject: [PATCH] [SV 60188] Explicit prereqs cannot be intermediate files

If a prereq of a pattern is an explicit target, it should not be
considered an intermediate file.

(Minor tweaks by Paul Smith <psmith@gnu.org>)

* src/dep.h (struct nameseq): Add is_explicit flag.
* src/implicit.c (struct patdeps): Ditto.
(pattern_search): Set the is_explicit flag appropriately for each
prerequisite, based on whether it contained a pattern or not.
Update the help output to note implicit vs. explicit prereqs.
* tests/scripts/features/double_colon: Add tests.
* tests/scripts/features/grouped_targets: Ditto.
* tests/scripts/features/patternrules: Ditto.
* tests/scripts/features/se_implicit: Ditto.
* tests/scripts/features/statipattrules: Ditto.
---
 src/dep.h                              |  8 ++-
 src/implicit.c                         | 17 ++++-
 tests/scripts/features/double_colon    | 15 +++++
 tests/scripts/features/grouped_targets | 26 ++++++++
 tests/scripts/features/patternrules    | 89 +++++++++++++++++++++++++-
 tests/scripts/features/se_implicit     | 63 ++++++++++++++++++
 tests/scripts/features/statipattrules  | 21 ++++++
 7 files changed, 233 insertions(+), 6 deletions(-)

diff --git a/src/dep.h b/src/dep.h
index 76718f00..a86e40fa 100644
--- a/src/dep.h
+++ b/src/dep.h
@@ -38,7 +38,10 @@ struct nameseq
 
 /* Structure representing one dependency of a file.
    Each struct file's 'deps' points to a chain of these, through 'next'.
-   'stem' is the stem for this dep line of static pattern rule or NULL.  */
+   'stem' is the stem for this dep line of static pattern rule or NULL.
+   explicit is set when implicit rule search is performed and the prerequisite
+   does not contain %. When explicit is set the file is not intermediate.  */
+
 
 #define DEP(_t)                                 \
     NAMESEQ (_t);                               \
@@ -49,7 +52,8 @@ struct nameseq
     unsigned int ignore_mtime : 1;              \
     unsigned int staticpattern : 1;             \
     unsigned int need_2nd_expansion : 1;        \
-    unsigned int ignore_automatic_vars : 1
+    unsigned int ignore_automatic_vars : 1;     \
+    unsigned int is_explicit : 1;
 
 struct dep
   {
diff --git a/src/implicit.c b/src/implicit.c
index b281a177..5bbd6b79 100644
--- a/src/implicit.c
+++ b/src/implicit.c
@@ -153,6 +153,7 @@ struct patdeps
     struct file *file;
     unsigned int ignore_mtime : 1;
     unsigned int ignore_automatic_vars : 1;
+    unsigned int is_explicit : 1;
   };
 
 /* This structure stores information about pattern rules that we need
@@ -540,6 +541,7 @@ pattern_search (struct file *file, int archive,
               /* If we don't need a second expansion, just replace the %.  */
               if (! dep->need_2nd_expansion)
                 {
+                  int is_explicit = 1;
                   p = strchr (nptr, '%');
                   if (p == 0)
                     strcpy (depname, nptr);
@@ -556,6 +558,7 @@ pattern_search (struct file *file, int archive,
                       memcpy (o, stem, stemlen);
                       o += stemlen;
                       strcpy (o, p + 1);
+                      is_explicit = 0;
                     }
 
                   /* Parse the expanded string.  It might have wildcards.  */
@@ -566,6 +569,7 @@ pattern_search (struct file *file, int archive,
                       ++deps_found;
                       d->ignore_mtime = dep->ignore_mtime;
                       d->ignore_automatic_vars = dep->ignore_automatic_vars;
+                      d->is_explicit = is_explicit;
                     }
 
                   /* We've used up this dep, so next time get a new one.  */
@@ -586,6 +590,7 @@ pattern_search (struct file *file, int archive,
                   int add_dir = 0;
                   size_t len;
                   struct dep **dptr;
+                  int is_explicit;
 
                   nptr = get_next_word (nptr, &len);
                   if (nptr == 0)
@@ -615,6 +620,7 @@ pattern_search (struct file *file, int archive,
                     {
                       memcpy (depname, nptr, len);
                       depname[len] = '\0';
+                      is_explicit = 1;
                     }
                   else
                     {
@@ -635,6 +641,7 @@ pattern_search (struct file *file, int archive,
                         }
                       memcpy (o, p + 1, len - i - 1);
                       o[len - i - 1] = '\0';
+                      is_explicit = 0;
                     }
 
                   /* Set up for the next word.  */
@@ -674,6 +681,7 @@ pattern_search (struct file *file, int archive,
                           ++deps_found;
                           if (order_only)
                             d->ignore_mtime = 1;
+                          d->is_explicit = is_explicit;
                           dptr = &d->next;
                         }
 
@@ -726,6 +734,7 @@ pattern_search (struct file *file, int archive,
                   memset (pat, '\0', sizeof (struct patdeps));
                   pat->ignore_mtime = d->ignore_mtime;
                   pat->ignore_automatic_vars = d->ignore_automatic_vars;
+                  pat->is_explicit = d->is_explicit;
 
                   DBS (DB_IMPLICIT,
                        (is_rule
@@ -777,13 +786,14 @@ pattern_search (struct file *file, int archive,
                   }
 
                   /* We could not find the file in any place we should look.
-                     Try to make this dependency as an intermediate file, but
+                     Look for an implicit rule to make this dependency, but
                      only on the second pass.  */
 
                   if (intermed_ok)
                     {
                       DBS (DB_IMPLICIT,
-                           (_("Looking for a rule with intermediate file '%s'.\n"),
+                           (_("Looking for a rule with %s file '%s'.\n"),
+                            d->is_explicit ? "explicit" : "intermediate",
                             d->name));
 
                       if (int_file == 0)
@@ -899,7 +909,7 @@ pattern_search (struct file *file, int archive,
           f->pat_searched = imf->pat_searched;
           f->also_make = imf->also_make;
           f->is_target = 1;
-          f->intermediate = 1;
+          f->intermediate = !pat->is_explicit;
           f->tried_implicit = 1;
 
           imf = lookup_file (pat->pattern);
@@ -916,6 +926,7 @@ pattern_search (struct file *file, int archive,
 
       dep = alloc_dep ();
       dep->ignore_mtime = pat->ignore_mtime;
+      dep->is_explicit = pat->is_explicit;
       dep->ignore_automatic_vars = pat->ignore_automatic_vars;
       s = strcache_add (pat->name);
       if (recursions)
diff --git a/tests/scripts/features/double_colon b/tests/scripts/features/double_colon
index 58f126f6..cd51ea4f 100644
--- a/tests/scripts/features/double_colon
+++ b/tests/scripts/features/double_colon
@@ -212,6 +212,21 @@ FORCE:
 
 unlink('joe-is-forced');
 
+# sv 60188.
+# Even though test.x is explicitly mentioned, terminal pattern rules still
+# apply only if the prerequisite exists.
+touch('hello.z');
+
+run_make_test(q!
+all: hello.z
+%.z:: test.x ; touch $@
+%.x: ;
+!, '', "#MAKE#: Nothing to be done for 'all'.\n");
+
+unlink('hello.z');
+
+
+
 # This tells the test driver that the perl test script executed properly.
 1;
 
diff --git a/tests/scripts/features/grouped_targets b/tests/scripts/features/grouped_targets
index 615e9627..131b8c0b 100644
--- a/tests/scripts/features/grouped_targets
+++ b/tests/scripts/features/grouped_targets
@@ -129,5 +129,31 @@ f g h&:: ; @echo Z
 '',
 "Z");
 
+# sv 60188.
+# Test that a file explicitly mentioned by the user and made by an implicit
+# rule is not considered intermediate.
+
+touch('hello.z');
+touch('hello.q');
+
+# subtest 1
+# hello.x is not explicitly mentioned and thus is an intermediate file.
+run_make_test(q!
+all: hello.z
+%.z %.q: %.x ; touch $*.z $*.q
+%.x: ;
+!, '', "#MAKE#: Nothing to be done for 'all'.\n");
+
+# subtest 2
+# test.x is explicitly mentioned and thus is not an intermediate file.
+run_make_test(q!
+all: hello.z
+%.z %.q: %.x test.x ; @echo $*.z $*.q
+%.x: ;
+!, '', "hello.z hello.q\n");
+
+unlink('hello.z');
+unlink('hello.q');
+
 # This tells the test driver that the perl test script executed properly.
 1;
diff --git a/tests/scripts/features/patternrules b/tests/scripts/features/patternrules
index 6510c387..ded8e0db 100644
--- a/tests/scripts/features/patternrules
+++ b/tests/scripts/features/patternrules
@@ -18,7 +18,7 @@ $dir =~ s,.*/([^/]+)$,../$1,;
 run_make_test(q!
 .PHONY: all
 
-all: case.1 case.2 case.3
+all: case.1 case.2 case.3 case.4
 
 # We can't have this, due to "Implicit Rule Search Algorithm" step 5c
 #xxx: void
@@ -43,6 +43,13 @@ all: case.1 case.2 case.3
 	@exit 0
 
 3.implicit-phony:
+
+# 4 - explicitly mentioned file made by an implicit rule
+%.4: void
+	@exit 1
+%.4: test.x
+	@exit 0
+%.x: ;
 !, '', '');
 
 # TEST #1: make sure files that are built via implicit rules are marked
@@ -244,6 +251,86 @@ run_make_test(q!
 
 unlink('some file.xx', 'some file.yy');
 
+
+# sv 60188.
+# Test that a file explicitly mentioned by the user and made by an implicit
+# rule is not considered intermediate.
+
+touch('hello.z');
+unlink('hello.x');
+unlink('test.x');
+
+# subtest 1
+# hello.x is not explicitly mentioned and thus is an intermediate file.
+run_make_test(q!
+all: hello.z
+%.z: %.x
+	touch $@
+%.x: ;
+!, '', "#MAKE#: Nothing to be done for 'all'.\n");
+
+# subtest 2
+# test.x is explicitly mentioned and thus is not an intermediate file.
+run_make_test(q!
+all: hello.z
+%.z: %.x test.x
+	touch $@
+%.x: ;
+!, '', "touch hello.z");
+
+unlink('hello.z');
+
+# sv 60188.
+# Test that a file explicitly mentioned by the user and made by an implicit
+# rule is not considered intermediate, even when the builtin rules are used.
+
+touch('hello.x');
+touch('test.x');
+touch('hello.tsk');
+
+# subtest 1
+# hello.o is not explicitly mentioned and thus is an intermediate file.
+run_make_test(q!
+all: hello.tsk
+%.tsk: %.z ; @echo $@
+%.z : %.x ; @echo $@
+!, '', "#MAKE#: Nothing to be done for 'all'.\n");
+
+# subtest 2
+# test.z is explicitly mentioned and thus is not an intermediate file.
+# test.z is built first because until it's built we don't know if we
+# need to rebuild the intermediate hello.z
+run_make_test(q!
+all: hello.tsk
+%.tsk: %.z test.z ; @echo $@
+%.z : %.x ; @echo $@
+!, '', "test.z\nhello.z\nhello.tsk\n");
+
+# subtest 3
+# hello.o is not explicitly mentioned and thus is an intermediate file.
+run_make_test(q!
+all: hello.tsk
+dep:=%.o
+%.tsk: $(dep) ; @echo $@
+!, '', "#MAKE#: Nothing to be done for 'all'.\n");
+
+# subtest 4
+# Even when test.z is constructed from 2 variables it is still explicitly
+# mentioned and thus is not an intermediate file.
+# test.z is built first because until it's built we don't know if we
+# need to rebuild the intermediate hello.z
+run_make_test(q!
+all: hello.tsk
+name:=test
+suf:=.z
+%.tsk: %.z $(name)$(suf) ; @echo $@
+%.z: %.x ; @echo $@
+!, '', "test.z\nhello.z\nhello.tsk\n");
+
+unlink('hello.x');
+unlink('test.x');
+
+
 # This tells the test driver that the perl test script executed properly.
 1;
 
diff --git a/tests/scripts/features/se_implicit b/tests/scripts/features/se_implicit
index 866d1fb0..07ce8023 100644
--- a/tests/scripts/features/se_implicit
+++ b/tests/scripts/features/se_implicit
@@ -262,5 +262,68 @@ run_make_test(q!
 !,
               'q/ux', "q/u\nq/u\n");
 
+
+
+# sv 60188.
+# Test that a file explicitly mentioned by the user and made by an implicit
+# rule is not considered intermediate.
+
+touch('hello.z');
+
+# subtest 1.
+# hello.x is derived from the stem and thus is an intermediate file.
+run_make_test(q!
+.SECONDEXPANSION:
+dep:=.x
+all: hello.z
+%.z: %$$(dep) ; @echo $@
+%.x: ;
+!, '', "#MAKE#: Nothing to be done for 'all'.\n");
+
+
+# subtest 2.
+# test.x is explicitly mentioned and thus is not an intermediate file.
+run_make_test(q!
+.SECONDEXPANSION:
+dep:=test.x
+all: hello.z
+%.z: %.x $$(dep) ; @echo $@
+%.x: ;
+!, '', "hello.z\n");
+
+unlink('hello.z');
+
+
+# sv 60188.
+# Test that a file explicitly mentioned by the user and made by an implicit
+# rule is not considered intermediate, even when the builtin rules are used.
+
+touch('hello.x');
+touch('hello.tsk');
+
+# subtest 1.
+# hello.z is explicitly mentioned and thus is not an intermediate file.
+run_make_test(q!
+.SECONDEXPANSION:
+dep:=hello.z
+all: hello.tsk
+%.tsk: $$(dep) ; @echo $@
+%.z : %.x ; @echo $@
+!, '', "hello.z\nhello.tsk");
+
+# subtest 2.
+# hello.z is derived from the stem and thus is an intermediate file.
+run_make_test(q!
+.SECONDEXPANSION:
+dep:=.z
+all: hello.tsk
+%.tsk: %$$(dep) ; @echo $@
+%.z : %.x ; @echo $@
+!, '', "#MAKE#: Nothing to be done for 'all'.\n");
+
+unlink('hello.x');
+unlink('hello.tsk');
+
+
 # This tells the test driver that the perl test script executed properly.
 1;
diff --git a/tests/scripts/features/statipattrules b/tests/scripts/features/statipattrules
index 3f363def..096521ec 100644
--- a/tests/scripts/features/statipattrules
+++ b/tests/scripts/features/statipattrules
@@ -108,4 +108,25 @@ all.foo.bar:
 'all.foo
 all.one all-one all.foo.two all.foo-two');
 
+# Test #8:
+# sv 60188.
+# Static pattern rules are considered explicit rules: no prerequisite of
+# a static pattern rule can ever be considered intermediate.
+
+touch('hello.z');
+
+# subtest 1
+run_make_test(q!
+hello.z: %.z: %.x ; @echo $@
+%.x: ;
+!, '', "hello.z\n");
+
+# subtest 2
+run_make_test(q!
+hello.z: %.z: test.x ; @echo $@
+%.x: ;
+!, '', "hello.z\n");
+
+unlink('hello.z');
+
 1;