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;