[SV 46995] Strip leading/trailing space from variable names

* makeint.h: Change MAP_SPACE to MAP_NEWLINE, and add MAP_PATHSEP
and MAP_SPACE which is now MAP_BLANK|MAP_NEWLINE.  Create
NEW_TOKEN(), END_OF_TOKEN(), ISBLANK(), ISSPACE() macros.
* main.c (initialize_stopchar_map): Set MAP_NEWLINE only for
newline characters.
* Convert all uses of isblank() and isspace() to macros.
* Examine all uses of isblank() (doesn't accept newlines) and
change them wherever possible to ISSPACE() (does accept newlines).
* function.c (func_foreach): Strip leading/trailing space.
* variable.c (parse_variable_definition): Clean up.
* tests/scripts/functions/foreach: Test settings and errors.
* tests/scripts/functions/call: Rewrite to new-style.
* tests/scripts/misc/bs-nl: Add many more tests for newlines.
diff --git a/commands.c b/commands.c
index 6a9df7c..4303b86 100644
--- a/commands.c
+++ b/commands.c
@@ -414,7 +414,7 @@
       unsigned char flags = 0;
       const char *p = lines[idx];
 
-      while (isblank (*p) || *p == '-' || *p == '@' || *p == '+')
+      while (ISBLANK (*p) || *p == '-' || *p == '@' || *p == '+')
         switch (*(p++))
           {
           case '+':
@@ -451,7 +451,7 @@
      the commands are nothing but whitespace.  */
 
   for (p = file->cmds->commands; *p != '\0'; ++p)
-    if (!isspace ((unsigned char)*p) && *p != '-' && *p != '@')
+    if (!ISSPACE (*p) && *p != '-' && *p != '@' && *p != '+')
       break;
   if (*p == '\0')
     {
diff --git a/expand.c b/expand.c
index bb21018..de832b3 100644
--- a/expand.c
+++ b/expand.c
@@ -384,7 +384,7 @@
           break;
 
         default:
-          if (isblank ((unsigned char)p[-1]))
+          if (ISSPACE (p[-1]))
             break;
 
           /* A $ followed by a random char is a variable reference:
diff --git a/function.c b/function.c
index a80f194..2801598 100644
--- a/function.c
+++ b/function.c
@@ -115,8 +115,8 @@
       /* If we're substituting only by fully matched words,
          or only at the ends of words, check that this case qualifies.  */
       if (by_word
-          && ((p > text && !isblank ((unsigned char)p[-1]))
-              || ! STOP_SET (p[slen], MAP_BLANK|MAP_NUL)))
+          && ((p > text && !ISSPACE (p[-1]))
+              || ! STOP_SET (p[slen], MAP_SPACE|MAP_NUL)))
         /* Struck out.  Output the rest of the string that is
            no longer to be replaced.  */
         o = variable_buffer_output (o, subst, slen);
@@ -754,9 +754,9 @@
 char *
 strip_whitespace (const char **begpp, const char **endpp)
 {
-  while (*begpp <= *endpp && isspace ((unsigned char)**begpp))
+  while (*begpp <= *endpp && ISSPACE (**begpp))
     (*begpp) ++;
-  while (*endpp >= *begpp && isspace ((unsigned char)**endpp))
+  while (*endpp >= *begpp && ISSPACE (**endpp))
     (*endpp) --;
   return (char *)*begpp;
 }
@@ -869,8 +869,12 @@
   unsigned int len;
   struct variable *var;
 
+  /* Clean up the variable name by removing whitespace.  */
+  char *vp = next_token (varname);
+  end_of_token (vp)[0] = '\0';
+
   push_new_variable_scope ();
-  var = define_variable (varname, strlen (varname), "", o_automatic, 0);
+  var = define_variable (vp, strlen (vp), "", o_automatic, 0);
 
   /* loop through LIST,  put the value in VAR and expand BODY */
   while ((p = find_next_token (&list_iterator, &len)) != 0)
@@ -1080,10 +1084,9 @@
       int i=0;
       const char *word_start;
 
-      while (isspace ((unsigned char)*p))
-        ++p;
+      NEXT_TOKEN (p);
       word_start = p;
-      for (i=0; *p != '\0' && !isspace ((unsigned char)*p); ++p, ++i)
+      for (i=0; *p != '\0' && !ISSPACE (*p); ++p, ++i)
         {}
       if (!i)
         break;
@@ -1614,8 +1617,7 @@
   extern int dos_command_running, dos_status;
 
   /* Make sure not to bother processing an empty line.  */
-  while (isblank ((unsigned char)*text))
-    ++text;
+  NEXT_TOKEN (text);
   if (*text == '\0')
     return 0;
 
@@ -2001,8 +2003,7 @@
 {
   const char *s = argv[0];
   int result = 0;
-  while (isspace ((unsigned char)*s))
-    s++;
+  NEXT_TOKEN (s);
   result = ! (*s);
   o = variable_buffer_output (o,  result ? "1" : "", result);
   return o;
@@ -2206,7 +2207,7 @@
           mode = "a";
           ++fn;
         }
-      fn = next_token (fn);
+      NEXT_TOKEN (fn);
 
       if (fn[0] == '\0')
         O (fatal, *expanding_var, _("file: missing filename"));
@@ -2231,7 +2232,8 @@
       char *preo = o;
       FILE *fp;
 
-      fn = next_token (++fn);
+      ++fn;
+      NEXT_TOKEN (fn);
       if (fn[0] == '\0')
         O (fatal, *expanding_var, _("file: missing filename"));
 
@@ -2441,7 +2443,8 @@
   /* We found a builtin function.  Find the beginning of its arguments (skip
      whitespace after the name).  */
 
-  beg = next_token (beg + entry_p->len);
+  beg += entry_p->len;
+  NEXT_TOKEN (beg);
 
   /* Find the end of the function invocation, counting nested use of
      whichever kind of parens we use.  Since we're looking, count commas
@@ -2541,7 +2544,6 @@
 {
   static int max_args = 0;
   char *fname;
-  char *cp;
   char *body;
   int flen;
   int i;
@@ -2549,16 +2551,9 @@
   const struct function_table_entry *entry_p;
   struct variable *v;
 
-  /* There is no way to define a variable with a space in the name, so strip
-     leading and trailing whitespace as a favor to the user.  */
-  fname = argv[0];
-  while (isspace ((unsigned char)*fname))
-    ++fname;
-
-  cp = fname + strlen (fname) - 1;
-  while (cp > fname && isspace ((unsigned char)*cp))
-    --cp;
-  cp[1] = '\0';
+  /* Clean up the name of the variable to be invoked.  */
+  fname = next_token (argv[0]);
+  end_of_token (fname)[0] = '\0';
 
   /* Calling nothing is a no-op */
   if (*fname == '\0')
diff --git a/implicit.c b/implicit.c
index aa33b83..ed49bd1 100644
--- a/implicit.c
+++ b/implicit.c
@@ -72,8 +72,7 @@
   char c;
 
   /* Skip any leading whitespace.  */
-  while (isblank ((unsigned char)*p))
-    ++p;
+  NEXT_TOKEN (p);
 
   beg = p;
   c = *(p++);
diff --git a/job.c b/job.c
index 6fa9e8a..512240f 100644
--- a/job.c
+++ b/job.c
@@ -1099,7 +1099,8 @@
         flags |= COMMANDS_RECURSE;
       else if (*p == '-')
         child->noerror = 1;
-      else if (!isblank ((unsigned char)*p))
+      /* Don't skip newlines.  */
+      else if (!ISBLANK (*p))
         break;
       ++p;
     }
@@ -1137,7 +1138,7 @@
     /* Skip any leading whitespace */
     while (*p)
       {
-        if (!isspace(*p))
+        if (!ISSPACE (*p))
           {
             if (*p != '\\')
               break;
@@ -1712,14 +1713,13 @@
                         *out++ = *in++;
                       else
                         {
-                          /* Skip the backslash, newline and
-                             any following whitespace.  */
-                          in = next_token (in + 2);
+                          /* Skip the backslash, newline, and whitespace.  */
+                          in += 2;
+                          NEXT_TOKEN (in);
 
                           /* Discard any preceding whitespace that has
                              already been written to the output.  */
-                          while (out > outref
-                                 && isblank ((unsigned char)out[-1]))
+                          while (out > outref && ISBLANK (out[-1]))
                             --out;
 
                           /* Replace it all with a single space.  */
@@ -2553,8 +2553,8 @@
   if (restp != NULL)
     *restp = NULL;
 
-  /* Make sure not to bother processing an empty line.  */
-  while (isblank ((unsigned char)*line))
+  /* Make sure not to bother processing an empty line but stop at newline.  */
+  while (ISBLANK (*line))
     ++line;
   if (*line == '\0')
     return 0;
@@ -2729,15 +2729,16 @@
                 /* Throw out the backslash and newline.  */
                 ++p;
 
-                /* If there's nothing in this argument yet, skip any
-                   whitespace before the start of the next word.  */
+                /* At the beginning of the argument, skip any whitespace other
+                   than newline before the start of the next word.  */
                 if (ap == new_argv[i])
-                  p = next_token (p + 1) - 1;
+                  while (ISBLANK (p[1]))
+                    ++p;
               }
 #ifdef WINDOWS32
             /* Backslash before whitespace is not special if our shell
                is not Unixy.  */
-            else if (isspace (p[1]) && !unixy_shell)
+            else if (ISSPACE (p[1]) && !unixy_shell)
               {
                 *ap++ = *p;
                 break;
@@ -2764,7 +2765,7 @@
                 else
 #endif
                   if (p[1] != '\\' && p[1] != '\''
-                      && !isspace ((unsigned char)p[1])
+                      && !ISSPACE (p[1])
                       && strchr (sh_chars_sh, p[1]) == 0)
                     /* back up one notch, to copy the backslash */
                     --p;
@@ -2828,8 +2829,9 @@
                   }
               }
 
-            /* Ignore multiple whitespace chars.  */
-            p = next_token (p) - 1;
+            /* Skip whitespace chars, but not newlines.  */
+            while (ISBLANK (p[1]))
+              ++p;
             break;
 
           default:
@@ -2922,8 +2924,7 @@
    */
 
   /* Make sure not to bother processing an empty line.  */
-  while (isspace ((unsigned char)*line))
-    ++line;
+  NEXT_TOKEN (line);
   if (*line == '\0')
     return 0;
 #endif /* WINDOWS32 */
@@ -2983,9 +2984,9 @@
               {
                 int esc = 0;
 
-                /* This is the start of a new recipe line.
-                   Skip whitespace and prefix characters.  */
-                while (isblank (*f) || *f == '-' || *f == '@' || *f == '+')
+                /* This is the start of a new recipe line.  Skip whitespace
+                   and prefix characters but not newlines.  */
+                while (ISBLANK (*f) || *f == '-' || *f == '@' || *f == '+')
                   ++f;
 
                 /* Copy until we get to the next logical recipe line.  */
@@ -3035,21 +3036,21 @@
                [@+-]: they're meaningless in .ONESHELL mode.  */
             while (*f != '\0')
               {
-                /* This is the start of a new recipe line.
-                   Skip whitespace and prefix characters.  */
-                while (isblank (*f) || *f == '-' || *f == '@' || *f == '+')
+                /* This is the start of a new recipe line.  Skip whitespace
+                   and prefix characters but not newlines.  */
+                while (ISBLANK (*f) || *f == '-' || *f == '@' || *f == '+')
                   ++f;
 
                 /* Copy until we get to the next logical recipe line.  */
                 while (*f != '\0')
                   {
-                    /* Remove the escaped newlines in the command, and
-                       the whitespace that follows them.  Windows
-                       shells cannot handle escaped newlines.  */
+                    /* Remove the escaped newlines in the command, and the
+                       blanks that follow them.  Windows shells cannot handle
+                       escaped newlines.  */
                     if (*f == '\\' && f[1] == '\n')
                       {
                         f += 2;
-                        while (isblank (*f))
+                        while (ISBLANK (*f))
                           ++f;
                       }
                     *(t++) = *(f++);
@@ -3161,7 +3162,7 @@
         /* DOS shells don't know about backslash-escaping.  */
         if (unixy_shell && !batch_mode_shell &&
             (*p == '\\' || *p == '\'' || *p == '"'
-             || isspace ((unsigned char)*p)
+             || ISSPACE (*p)
              || strchr (sh_chars, *p) != 0))
           *ap++ = '\\';
 #ifdef __MSDOS__
@@ -3366,13 +3367,11 @@
   cptr = line;
   for (;;)
     {
-      while ((*cptr != 0)
-             && (isspace ((unsigned char)*cptr)))
+      while ((*cptr != 0) && (ISSPACE (*cptr)))
         cptr++;
       if (*cptr == 0)
         break;
-      while ((*cptr != 0)
-             && (!isspace ((unsigned char)*cptr)))
+      while ((*cptr != 0) && (!ISSPACE (*cptr)))
         cptr++;
       argc++;
     }
@@ -3385,15 +3384,13 @@
   argc = 0;
   for (;;)
     {
-      while ((*cptr != 0)
-             && (isspace ((unsigned char)*cptr)))
+      while ((*cptr != 0) && (ISSPACE (*cptr)))
         cptr++;
       if (*cptr == 0)
         break;
       DB (DB_JOBS, ("argv[%d] = [%s]\n", argc, cptr));
       argv[argc++] = cptr;
-      while ((*cptr != 0)
-             && (!isspace ((unsigned char)*cptr)))
+      while ((*cptr != 0) && (!ISSPACE (*cptr)))
         cptr++;
       if (*cptr != 0)
         *cptr++ = 0;
diff --git a/main.c b/main.c
index 22af601..6239702 100644
--- a/main.c
+++ b/main.c
@@ -661,21 +661,22 @@
 
   stopchar_map[(int)'/'] = MAP_DIRSEP;
 #if defined(VMS)
-  stopchar_map[(int)':'] = MAP_COLON | MAP_DIRSEP;
-  stopchar_map[(int)']'] = MAP_DIRSEP;
-  stopchar_map[(int)'>'] = MAP_DIRSEP;
+  stopchar_map[(int)':'] |= MAP_DIRSEP;
+  stopchar_map[(int)']'] |= MAP_DIRSEP;
+  stopchar_map[(int)'>'] |= MAP_DIRSEP;
 #elif defined(HAVE_DOS_PATHS)
-  stopchar_map[(int)'\\'] = MAP_DIRSEP;
+  stopchar_map[(int)'\\'] |= MAP_DIRSEP;
 #endif
 
   for (i = 1; i <= UCHAR_MAX; ++i)
     {
-      if (isblank(i))
-        stopchar_map[i] = MAP_BLANK;
-      if (isspace(i))
-        stopchar_map[i] |= MAP_SPACE;
-      if (isalnum(i))
-        stopchar_map[i] = MAP_USERFUNC;
+      if (isblank (i))
+        stopchar_map[i] |= MAP_BLANK;
+      else if (isspace (i))
+        /* Don't mark blank characters as newline characters.  */
+        stopchar_map[i] |= MAP_NEWLINE;
+      else if (isalnum (i))
+        stopchar_map[i] |= MAP_USERFUNC;
     }
 }
 
@@ -2985,7 +2986,7 @@
   value = variable_expand (varref);
 
   /* Skip whitespace, and check for an empty value.  */
-  value = next_token (value);
+  NEXT_TOKEN (value);
   len = strlen (value);
   if (len == 0)
     return;
@@ -3008,14 +3009,14 @@
     {
       if (*value == '\\' && value[1] != '\0')
         ++value;                /* Skip the backslash.  */
-      else if (isblank ((unsigned char)*value))
+      else if (ISBLANK (*value))
         {
           /* End of the word.  */
           *p++ = '\0';
           argv[++argc] = p;
           do
             ++value;
-          while (isblank ((unsigned char)*value));
+          while (ISBLANK (*value));
           continue;
         }
       *p++ = *value++;
@@ -3046,7 +3047,7 @@
     {
       if (*in == '$')
         *out++ = '$';
-      else if (isblank ((unsigned char)*in) || *in == '\\')
+      else if (ISBLANK (*in) || *in == '\\')
         *out++ = '\\';
       *out++ = *in++;
     }
diff --git a/makeint.h b/makeint.h
index 30bfe60..ff6df00 100644
--- a/makeint.h
+++ b/makeint.h
@@ -95,10 +95,6 @@
 extern int errno;
 #endif
 
-#ifndef isblank
-# define isblank(c)     ((c) == ' ' || (c) == '\t')
-#endif
-
 #ifdef __VMS
 /* In strict ANSI mode, VMS compilers should not be defining the
    VMS macro.  Define it here instead of a bulk edit for the correct code.
@@ -348,21 +344,6 @@
 #define N_(msgid)           gettext_noop (msgid)
 #define S_(msg1,msg2,num)   ngettext (msg1,msg2,num)
 
-/* Handle other OSs.
-   To overcome an issue parsing paths in a DOS/Windows environment when
-   built in a unix based environment, override the PATH_SEPARATOR_CHAR
-   definition unless being built for Cygwin. */
-#if defined(HAVE_DOS_PATHS) && !defined(__CYGWIN__)
-# undef PATH_SEPARATOR_CHAR
-# define PATH_SEPARATOR_CHAR ';'
-#elif !defined(PATH_SEPARATOR_CHAR)
-# if defined (VMS)
-#  define PATH_SEPARATOR_CHAR (vms_comma_separator ? ',' : ':')
-# else
-#  define PATH_SEPARATOR_CHAR ':'
-# endif
-#endif
-
 /* This is needed for getcwd() and chdir(), on some W32 systems.  */
 #if defined(HAVE_DIRECT_H)
 # include <direct.h>
@@ -398,7 +379,7 @@
 # endif
 
 /* Include only the minimal stuff from windows.h.   */
-#define WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
 #endif  /* WINDOWS32 */
 
 #define ANY_SET(_v,_m)  (((_v)&(_m)) != 0)
@@ -406,7 +387,7 @@
 
 #define MAP_NUL         0x0001
 #define MAP_BLANK       0x0002
-#define MAP_SPACE       0x0004
+#define MAP_NEWLINE     0x0004
 #define MAP_COMMENT     0x0008
 #define MAP_SEMI        0x0010
 #define MAP_EQUALS      0x0020
@@ -429,7 +410,40 @@
 # define MAP_VMSCOMMA   0x0000
 #endif
 
-#define STOP_SET(_v,_m) ANY_SET (stopchar_map[(unsigned char)(_v)],(_m))
+#define MAP_SPACE       (MAP_BLANK|MAP_NEWLINE)
+
+/* Handle other OSs.
+   To overcome an issue parsing paths in a DOS/Windows environment when
+   built in a unix based environment, override the PATH_SEPARATOR_CHAR
+   definition unless being built for Cygwin. */
+#if defined(HAVE_DOS_PATHS) && !defined(__CYGWIN__)
+# undef PATH_SEPARATOR_CHAR
+# define PATH_SEPARATOR_CHAR ';'
+# define MAP_PATHSEP    MAP_SEMI
+#elif !defined(PATH_SEPARATOR_CHAR)
+# if defined (VMS)
+#  define PATH_SEPARATOR_CHAR (vms_comma_separator ? ',' : ':')
+#  define MAP_PATHSEP    (vms_comma_separator ? MAP_COMMA : MAP_SEMI)
+# else
+#  define PATH_SEPARATOR_CHAR ':'
+#  define MAP_PATHSEP    MAP_COLON
+# endif
+#elif PATH_SEPARATOR_CHAR == ':'
+# define MAP_PATHSEP     MAP_COLON
+#elif PATH_SEPARATOR_CHAR == ';'
+# define MAP_PATHSEP     MAP_SEMI
+#elif PATH_SEPARATOR_CHAR == ','
+# define MAP_PATHSEP     MAP_COMMA
+#else
+# error "Unknown PATH_SEPARATOR_CHAR"
+#endif
+
+#define STOP_SET(_v,_m) ANY_SET(stopchar_map[(unsigned char)(_v)],(_m))
+
+#define ISBLANK(c)      STOP_SET((c),MAP_BLANK)
+#define ISSPACE(c)      STOP_SET((c),MAP_SPACE)
+#define NEXT_TOKEN(s)   while (ISSPACE (*(s))) ++(s)
+#define END_OF_TOKEN(s) while (! STOP_SET (*(s), MAP_SPACE|MAP_NUL)) ++(s)
 
 #if defined(HAVE_SYS_RESOURCE_H) && defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT)
 # define SET_STACK_SIZE
diff --git a/misc.c b/misc.c
index de8a1b1..f2fa24f 100644
--- a/misc.c
+++ b/misc.c
@@ -91,12 +91,13 @@
         {
           /* Backslash/newline handling:
              In traditional GNU make all trailing whitespace, consecutive
-             backslash/newlines, and any leading whitespace on the next line
-             is reduced to a single space.
+             backslash/newlines, and any leading non-newline whitespace on the
+             next line is reduced to a single space.
              In POSIX, each backslash/newline and is replaced by a space.  */
-          in = next_token (in);
+          while (ISBLANK (*in))
+            ++in;
           if (! posix_pedantic)
-            while (out > line && isblank ((unsigned char)out[-1]))
+            while (out > line && ISBLANK (out[-1]))
               --out;
           *out++ = ' ';
         }
@@ -314,8 +315,7 @@
 char *
 end_of_token (const char *s)
 {
-  while (! STOP_SET (*s, MAP_BLANK|MAP_NUL))
-    ++s;
+  END_OF_TOKEN (s);
   return (char *)s;
 }
 
@@ -324,8 +324,7 @@
 char *
 next_token (const char *s)
 {
-  while (isblank ((unsigned char)*s))
-    ++s;
+  NEXT_TOKEN (s);
   return (char *)s;
 }
 
diff --git a/read.c b/read.c
index bcd6c24..08739a5 100644
--- a/read.c
+++ b/read.c
@@ -511,7 +511,7 @@
   memset (vmod, '\0', sizeof (*vmod));
 
   /* Find the start of the next token.  If there isn't one we're done.  */
-  line = next_token (line);
+  NEXT_TOKEN (line);
   if (*line == '\0')
     return (char *)line;
 
@@ -720,8 +720,7 @@
 
       /* Get rid if starting space (including formfeed, vtab, etc.)  */
       p = collapsed;
-      while (isspace ((unsigned char)*p))
-        ++p;
+      NEXT_TOKEN (p);
 
       /* See if this is a variable assignment.  We need to do this early, to
          allow variables with names like 'ifdef', 'export', 'private', etc.  */
@@ -769,7 +768,7 @@
 
       p2 = end_of_token (p);
       wlen = p2 - p;
-      p2 = next_token (p2);
+      NEXT_TOKEN (p2);
 
       /* If we're in an ignored define, skip this line (but maybe get out).  */
       if (in_ignored_define)
@@ -1242,8 +1241,7 @@
            The rule is that it's only a target, if there are TWO :'s
            OR a space around the :.
         */
-        if (p && !(isspace ((unsigned char)p[1]) || !p[1]
-                   || isspace ((unsigned char)p[-1])))
+        if (p && !(ISSPACE (p[1]) || !p[1] || ISSPACE (p[-1])))
           p = 0;
 #endif
 #ifdef HAVE_DOS_PATHS
@@ -1436,7 +1434,7 @@
   if (*name == '\0')
     O (fatal, &ebuf->floc, _("empty variable name"));
   p = name + strlen (name) - 1;
-  while (p > name && isblank ((unsigned char)*p))
+  while (p > name && ISBLANK (*p))
     --p;
   p[1] = '\0';
 
@@ -1481,7 +1479,7 @@
   if (name[0] == '\0')
     O (fatal, &defstart, _("empty variable name"));
   p = name + strlen (name) - 1;
-  while (p > name && isblank ((unsigned char)*p))
+  while (p > name && ISBLANK (*p))
     --p;
   p[1] = '\0';
 
@@ -1509,13 +1507,13 @@
           len = strlen (p);
 
           /* If this is another 'define', increment the level count.  */
-          if ((len == 6 || (len > 6 && isblank ((unsigned char)p[6])))
+          if ((len == 6 || (len > 6 && ISBLANK (p[6])))
               && strneq (p, "define", 6))
             ++nlevels;
 
           /* If this is an 'endef', decrement the count.  If it's now 0,
              we've found the last one.  */
-          else if ((len == 5 || (len > 5 && isblank ((unsigned char)p[5])))
+          else if ((len == 5 || (len > 5 && ISBLANK (p[5])))
                    && strneq (p, "endef", 5))
             {
               p += 5;
@@ -1591,7 +1589,8 @@
     return -2;
 
   /* Found one: skip past it and any whitespace after it.  */
-  line = next_token (line + len);
+  line += len;
+  NEXT_TOKEN (line);
 
 #define EXTRATEXT() OS (error, flocp, _("extraneous text after '%s' directive"), cmdname)
 #define EXTRACMD()  OS (fatal, flocp, _("extraneous '%s'"), cmdname)
@@ -1712,7 +1711,7 @@
       /* Make sure there's only one variable name to test.  */
       p = end_of_token (var);
       i = p - var;
-      p = next_token (p);
+      NEXT_TOKEN (p);
       if (*p != '\0')
         return -1;
 
@@ -1758,7 +1757,7 @@
         {
           /* Strip blanks after the first string.  */
           char *p = line++;
-          while (isblank ((unsigned char)p[-1]))
+          while (ISBLANK (p[-1]))
             --p;
           *p = '\0';
         }
@@ -1774,7 +1773,7 @@
 
       if (termin != ',')
         /* Find the start of the second string.  */
-        line = next_token (line);
+        NEXT_TOKEN (line);
 
       termin = termin == ',' ? ')' : *line;
       if (termin != ')' && termin != '"' && termin != '\'')
@@ -1809,8 +1808,8 @@
       if (*line == '\0')
         return -1;
 
-      *line = '\0';
-      line = next_token (++line);
+      *(line++) = '\0';
+      NEXT_TOKEN (line);
       if (*line != '\0')
         EXTRATEXT ();
 
@@ -2641,7 +2640,7 @@
   char c;
 
   /* Skip any leading whitespace.  */
-  while (isblank ((unsigned char)*p))
+  while (ISBLANK (*p))
     ++p;
 
   beg = p;
@@ -3076,7 +3075,7 @@
       int i;
 
       /* Skip whitespace; at the end of the string or STOPCHAR we're done.  */
-      p = next_token (p);
+      NEXT_TOKEN (p);
       if (STOP_SET (*p, stopmap))
         break;
 
@@ -3091,8 +3090,7 @@
 #endif
 #ifdef _AMIGA
       if (p && STOP_SET (*p, stopmap & MAP_COLON)
-          && !(isspace ((unsigned char)p[1]) || !p[1]
-               || isspace ((unsigned char)p[-1])))
+          && !(ISSPACE (p[1]) || !p[1] || ISSPACE (p[-1])))
         p = find_char_unquote (p+1, stopmap|MAP_VMSCOMMA|MAP_BLANK);
 #endif
 #ifdef HAVE_DOS_PATHS
@@ -3101,7 +3099,7 @@
        Note that tokens separated by spaces should be treated as separate
        tokens since make doesn't allow path names with spaces */
     if (stopmap | MAP_COLON)
-      while (p != 0 && !isspace ((unsigned char)*p) &&
+      while (p != 0 && !ISSPACE (*p) &&
              (p[1] == '\\' || p[1] == '/') && isalpha ((unsigned char)p[-1]))
         p = find_char_unquote (p + 1, stopmap|MAP_VMSCOMMA|MAP_BLANK);
 #endif
@@ -3196,7 +3194,7 @@
               do
                 {
                   const char *o = e;
-                  e = next_token (e);
+                  NEXT_TOKEN (e);
                   /* Find the end of this word.  We don't want to unquote and
                      we don't care about quoting since we're looking for the
                      last char in the word. */
diff --git a/tests/scripts/functions/call b/tests/scripts/functions/call
index 9db9da7..dc1a623 100644
--- a/tests/scripts/functions/call
+++ b/tests/scripts/functions/call
@@ -4,11 +4,7 @@
 $details = "Try various uses of call and ensure they all give the correct
 results.\n";
 
-open(MAKEFILE, "> $makefile");
-
-# The Contents of the MAKEFILE ...
-
-print MAKEFILE <<'EOMAKE';
+run_make_test(q!
 # Simple, just reverse two things
 #
 reverse = $2 $1
@@ -48,35 +44,22 @@
         echo '$(call my-foreach,a,,,)'; \
         echo '$(call my-if,a,b,c)'; \
         echo '$(call two,bar,baz)'; \
-	echo '$(call tclose,foo)'
+	echo '$(call tclose,foo)';
+!,
+              "", "foo bar\ndefault file file\nb d f\n\n\nb\nbar foo baz\nfoo bar baz blarp quux \n");
 
-
-
-EOMAKE
-
-# These won't work until/unless PR/1527 is resolved.
-#        echo '$(call my-foreach,a,x y z,$(a)$(a))'; \
-#        echo '$(call my-if,,$(warning don't print this),ok)'
+# These won't work because call expands all its arguments first, before
+# passing them on, then marks them as resolved/simple, so they're not
+# expanded again by the function.
 #
-# $answer = "xx yy zz\nok\n";
-
-# END of Contents of MAKEFILE
-
-close(MAKEFILE);
-
-&run_make_with_options($makefile, "", &get_logfile);
-$answer = "foo bar\ndefault file file\nb d f\n\n\nb\nbar foo baz\nfoo bar baz blarp quux \n";
-&compare_output($answer, &get_logfile(1));
-
+#        echo '$(call my-foreach,a,x y z,$$(a)$$(a))'; \
+#        echo '$(call my-if,,$$(info don't print this),$$(info do print this))'
+#
+# $answer = "xx yy zz\ndo print this\n";
 
 # TEST eclipsing of arguments when invoking sub-calls
 
-$makefile2 = &get_tmpfile;
-
-open(MAKEFILE,"> $makefile2");
-
-print MAKEFILE <<'EOF';
-
+run_make_test(q!
 all = $1 $2 $3 $4 $5 $6 $7 $8 $9
 
 level1 = $(call all,$1,$2,$3,$4,$5)
@@ -88,13 +71,8 @@
 	@echo $(call level1,1,2,3,4,5,6,7,8)
 	@echo $(call level2,1,2,3,4,5,6,7,8)
 	@echo $(call level3,1,2,3,4,5,6,7,8)
-EOF
-
-close(MAKEFILE);
-
-&run_make_with_options($makefile2, "", &get_logfile);
-$answer = "1 2 3 4 5 6 7 8 9\n1 2 3 4 5\n1 2 3\n1 2 3\n";
-&compare_output($answer,&get_logfile(1));
+!,
+              "", "1 2 3 4 5 6 7 8 9\n1 2 3 4 5\n1 2 3\n1 2 3\n");
 
 # Ensure that variables are defined in global scope even in a $(call ...)
 
@@ -108,3 +86,7 @@
               '', "\n");
 
 1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/tests/scripts/functions/foreach b/tests/scripts/functions/foreach
index 4d1a11d..451839a 100644
--- a/tests/scripts/functions/foreach
+++ b/tests/scripts/functions/foreach
@@ -53,8 +53,26 @@
               '',
               'FOREACH');
 
+# Allow variable names with trailing space
+run_make_test(q!
+$(foreach \
+  a \
+, b c d \
+, $(info $a))
+all:;@:
+!,
+              "", "b\nc\nd\n");
 
-# TEST 2: Check some error conditions.
+# Allow empty variable names.  We still expand the body.
+
+run_make_test('
+x = $(foreach ,1 2 3,a)
+y := $x
+
+all: ; @echo $y',
+              '', "a a a\n");
+
+# Check some error conditions.
 
 run_make_test('
 x = $(foreach )
@@ -66,12 +84,12 @@
               512);
 
 run_make_test('
-x = $(foreach )
+x = $(foreach x,y)
 y := $x
 
 all: ; @echo $y',
               '',
-              "#MAKEFILE#:2: *** insufficient number of arguments (1) to function 'foreach'.  Stop.",
+              "#MAKEFILE#:2: *** insufficient number of arguments (2) to function 'foreach'.  Stop.",
               512);
 
 1;
diff --git a/tests/scripts/functions/sort b/tests/scripts/functions/sort
index b558910..e6e1343 100644
--- a/tests/scripts/functions/sort
+++ b/tests/scripts/functions/sort
@@ -42,6 +42,10 @@
 
 run_make_test("FOO = a b\tc\rd\fe \f \f \f \f \ff
 all: ; \@echo \$(words \$(sort \$(FOO)))\n",
-              '', "5\n");
+              '', "6\n");
 
 1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/tests/scripts/misc/bs-nl b/tests/scripts/misc/bs-nl
index 4fc3f63..fc323ce 100644
--- a/tests/scripts/misc/bs-nl
+++ b/tests/scripts/misc/bs-nl
@@ -125,5 +125,103 @@
 run_make_with_options($m2, '', get_logfile());
 compare_output("foo bar\n", get_logfile(1));
 
+# Test different types of whitespace, and bsnl inside functions
+
+sub xlate
+{
+    $_ = $_[0];
+    s/\\r/\r/g;
+    s/\\t/\t/g;
+    s/\\f/\f/g;
+    s/\\v/\v/g;
+    s/\\n/\n/g;
+    return $_;
+}
+
+run_make_test(xlate(q!
+$(foreach\r  a \t , b\t  c \r ,$(info    $a  \r  )      )
+all:;@:
+!),
+              '', "b  \r  \nc  \r  \n");
+
+run_make_test(xlate(q!
+all:;@:$(foreach\r  a \t , b\t  c \r ,$(info    $a  \r  )      )
+!),
+              '', "b  \r  \nc  \r  \n");
+
+run_make_test(xlate(q!
+$(foreach \
+\r  a \t\
+ , b\t \
+ c \r ,$(info  \
+  $a  \r  )  \
+    )
+all:;@:
+!),
+              '', "b  \r  \nc  \r  \n");
+
+run_make_test(xlate(q!
+all:;@:$(foreach \
+\r  a \t\
+ , b\t \
+ c \r ,$(info  \
+  $a  \r  )  \
+    )
+!),
+              '', "b  \r  \nc  \r  \n");
+
+run_make_test(xlate(q!
+define FOO
+$(foreach
+\r  a \t
+ , b\t
+ c \r ,$(info
+  $a  \r  )
+    )
+endef
+$(FOO)
+all:;@:
+!),
+              '', "b  \r  \nc  \r  \n");
+
+run_make_test(xlate(q!
+define FOO
+$(foreach
+\r  a \t
+ , b\t
+ c \r ,$(info
+  $a  \r  )
+    )
+endef
+all:;@:$(FOO)
+!),
+              '', "b  \r  \nc  \r  \n");
+
+# Test variables in recipes that expand to multiple lines
+
+run_make_test(q!
+define var
+
+echo foo
+
+
+echo bar
+endef
+all:;$(var)
+!,
+              '', "echo foo\nfoo\necho bar\nbar\n");
+
+run_make_test(q!
+define var
+
+echo foo
+
+@
+
+echo bar
+endef
+all:;$(var)
+!,
+              '', "echo foo\nfoo\necho bar\nbar\n");
 
 1;
diff --git a/variable.c b/variable.c
index ca33a4c..33b7295 100644
--- a/variable.c
+++ b/variable.c
@@ -1431,7 +1431,7 @@
   int wspace = 0;
   const char *e = NULL;
 
-  p = next_token (p);
+  NEXT_TOKEN (p);
   var->name = (char *)p;
   var->length = 0;
 
@@ -1448,7 +1448,7 @@
           /* This begins a variable expansion reference.  Make sure we don't
              treat chars inside the reference as assignment tokens.  */
           char closeparen;
-          int count;
+
           c = *p++;
           if (c == '(')
             closeparen = ')';
@@ -1462,26 +1462,25 @@
 
           /* P now points past the opening paren or brace.
              Count parens or braces until it is matched.  */
-          count = 0;
-          for (; *p != '\0'; ++p)
+          for (unsigned int count = 1; *p != '\0'; ++p)
             {
-              if (*p == c)
-                ++count;
-              else if (*p == closeparen && --count < 0)
+              if (*p == closeparen && --count == 0)
                 {
                   ++p;
                   break;
                 }
+              if (*p == c)
+                ++count;
             }
           continue;
         }
 
       /* If we find whitespace skip it, and remember we found it.  */
-      if (isblank ((unsigned char)c))
+      if (ISBLANK (c))
         {
           wspace = 1;
           e = p - 1;
-          p = next_token (p);
+          NEXT_TOKEN (p);
           c = *p;
           if (c == '\0')
             return NULL;
diff --git a/vpath.c b/vpath.c
index 449779f..2f1dafd 100644
--- a/vpath.c
+++ b/vpath.c
@@ -208,7 +208,7 @@
 #endif
 
   /* Skip over any initial separators and blanks.  */
-  while (*dirpath == PATH_SEPARATOR_CHAR || isblank ((unsigned char)*dirpath))
+  while (STOP_SET (*dirpath, MAP_BLANK|MAP_PATHSEP))
     ++dirpath;
 
   /* Figure out the maximum number of VPATH entries and put it in
@@ -218,7 +218,7 @@
   maxelem = 2;
   p = dirpath;
   while (*p != '\0')
-    if (*p++ == PATH_SEPARATOR_CHAR || isblank ((unsigned char)*p))
+    if (STOP_SET (*p++, MAP_BLANK|MAP_PATHSEP))
       ++maxelem;
 
   vpath = xmalloc (maxelem * sizeof (const char *));
@@ -244,7 +244,7 @@
 #else
              && *p != PATH_SEPARATOR_CHAR
 #endif
-             && !isblank ((unsigned char)*p))
+             && !ISBLANK (*p))
         ++p;
 
       len = p - v;
@@ -266,7 +266,7 @@
         }
 
       /* Skip over separators and blanks between entries.  */
-      while (*p == PATH_SEPARATOR_CHAR || isblank ((unsigned char)*p))
+      while (STOP_SET (*p, MAP_BLANK|MAP_PATHSEP))
         ++p;
     }