macro: Fix memory leaks in error cases parsing function arguments.
[pspp] / src / language / lexer / macro.c
index 6b5d62430195b745a7e29e19eca99b3ef4e206ef..a089a3a7c32e1c7538097d0e291d7366d408f46b 100644 (file)
 
 /* An entry in the stack of macros and macro directives being expanded.  The
    stack is maintained as a linked list.  Entries are not dynamically allocated
-   but on the program stack. */
+   but on the program stack.
+
+   The outermost entry, where 'next' is NULL, represents the source location of
+   the call to the macro. */
 struct macro_expansion_stack
   {
-    /* Points to an outer stack entry, or NULL if this is the outermost. */
-    const struct macro_expansion_stack *next;
-
-    /* A macro name or !IF, !DO, etc. */
-    const char *name;
-
-    /* Location of the macro definition, if available. */
-    const char *file_name;
-    int first_line;
-    int last_line;
+    const struct macro_expansion_stack *next; /* Next outer stack entry. */
+    const char *name;                    /* A macro name or !IF, !DO, etc. */
+    const struct msg_location *location; /* Source location if available. */
   };
 
 /* Reports an error during macro expansion.  STACK is the stack for reporting
    the location of the error, MT is the optional token at which the error was
    detected, and FORMAT along with the varargs is the message to report. */
-static void PRINTF_FORMAT (3, 4)
-macro_error (const struct macro_expansion_stack *stack,
-             const struct macro_token *mt,
-             const char *format, ...)
+static void PRINTF_FORMAT (3, 0)
+macro_error_valist (const struct macro_expansion_stack *stack,
+                    const struct macro_token *mt, const char *format,
+                    va_list args)
 {
   struct msg_stack **ms = NULL;
   size_t allocated_ms = 0;
   size_t n_ms = 0;
 
-  for (const struct macro_expansion_stack *p = stack; p; p = p->next)
+  const struct macro_expansion_stack *p;
+  for (p = stack; p && p->next; p = p->next)
     {
       if (n_ms >= allocated_ms)
         ms = x2nrealloc (ms, &allocated_ms, sizeof *ms);
@@ -104,32 +101,37 @@ macro_error (const struct macro_expansion_stack *stack,
 
       ms[n_ms] = xmalloc (sizeof *ms[n_ms]);
       *ms[n_ms] = (struct msg_stack) {
-        .location = {
-          .file_name = xstrdup_if_nonnull (p->file_name),
-          .first_line = p->first_line,
-          .last_line = p->last_line,
-        },
+        .location = msg_location_dup (p->location),
         .description = description,
       };
       n_ms++;
     }
 
-  va_list args;
-  va_start (args, format);
-  char *s = xvasprintf (format, args);
-  va_end (args);
-
   struct msg *m = xmalloc (sizeof *m);
   *m = (struct msg) {
     .category = MSG_C_SYNTAX,
     .severity = MSG_S_ERROR,
     .stack = ms,
     .n_stack = n_ms,
-    .text = s,
+    .location = msg_location_dup (p ? p->location : NULL),
+    .text = xvasprintf (format, args),
   };
   msg_emit (m);
 }
 
+/* Reports an error during macro expansion.  STACK is the stack for reporting
+   the location of the error, MT is the optional token at which the error was
+   detected, and FORMAT along with the varargs is the message to report. */
+static void PRINTF_FORMAT (3, 4)
+macro_error (const struct macro_expansion_stack *stack,
+             const struct macro_token *mt, const char *format, ...)
+{
+  va_list args;
+  va_start (args, format);
+  macro_error_valist (stack, mt, format, args);
+  va_end (args);
+}
+
 void
 macro_token_copy (struct macro_token *dst, const struct macro_token *src)
 {
@@ -225,70 +227,42 @@ macro_tokens_from_string__ (struct macro_tokens *mts, const struct substring src
                             enum segmenter_mode mode,
                             const struct macro_expansion_stack *stack)
 {
-  struct state
-    {
-      struct segmenter segmenter;
-      struct substring body;
-    };
-
-  struct state state = {
-    .segmenter = segmenter_init (mode, true),
-    .body = src,
-  };
-  struct state saved = state;
+  struct segmenter segmenter = segmenter_init (mode, true);
+  struct substring body = src;
 
-  while (state.body.length > 0)
+  while (body.length > 0)
     {
       struct macro_token mt = {
         .token = { .type = T_STOP },
-        .syntax = { .string = state.body.string },
+        .syntax = { .string = body.string },
       };
       struct token *token = &mt.token;
 
-      struct scanner scanner;
-      scanner_init (&scanner, token);
+      enum segment_type type;
+      int seg_len = segmenter_push (&segmenter, body.string,
+                                    body.length, true, &type);
+      assert (seg_len >= 0);
 
-      for (;;)
-        {
-          enum segment_type type;
-          int seg_len = segmenter_push (&state.segmenter, state.body.string,
-                                        state.body.length, true, &type);
-          assert (seg_len >= 0);
-
-          struct substring segment = ss_head (state.body, seg_len);
-          ss_advance (&state.body, seg_len);
-
-          enum scan_result result = scanner_push (&scanner, type, segment, token);
-          if (result == SCAN_SAVE)
-            saved = state;
-          else if (result == SCAN_BACK)
-            {
-              state = saved;
-              break;
-            }
-          else if (result == SCAN_DONE)
-            break;
-        }
+      struct substring segment = ss_head (body, seg_len);
+      enum tokenize_result result = token_from_segment (type, segment, token);
+      ss_advance (&body, seg_len);
 
-      /* We have a token in 'token'. */
-      mt.syntax.length = state.body.string - mt.syntax.string;
-      if (is_scan_type (token->type))
+      switch (result)
         {
-          if (token->type != SCAN_SKIP)
-            {
-              char *s = scan_token_to_error (token);
-              if (stack)
-                {
-                  mt.token.type = T_STRING;
-                  macro_error (stack, &mt, "%s", s);
-                }
-              else
-                msg (SE, "%s", s);
-              free (s);
-            }
+        case TOKENIZE_EMPTY:
+          break;
+
+        case TOKENIZE_TOKEN:
+          mt.syntax.length = body.string - mt.syntax.string;
+          macro_tokens_add (mts, &mt);
+          break;
+
+        case TOKENIZE_ERROR:
+          mt.syntax.length = body.string - mt.syntax.string;
+          macro_error (stack, &mt, "%s", token->string.string);
+          break;
         }
-      else
-        macro_tokens_add (mts, &mt);
+
       token_uninit (token);
     }
 }
@@ -437,31 +411,15 @@ macro_destroy (struct macro *m)
     return;
 
   free (m->name);
-  free (m->file_name);
+  msg_location_destroy (m->location);
   for (size_t i = 0; i < m->n_params; i++)
     {
       struct macro_param *p = &m->params[i];
       free (p->name);
 
       macro_tokens_uninit (&p->def);
-
-      switch (p->arg_type)
-        {
-        case ARG_N_TOKENS:
-          break;
-
-        case ARG_CHAREND:
-          token_uninit (&p->charend);
-          break;
-
-        case ARG_ENCLOSE:
-          token_uninit (&p->enclose[0]);
-          token_uninit (&p->enclose[1]);
-          break;
-
-        case ARG_CMDEND:
-          break;
-        }
+      token_uninit (&p->start);
+      token_uninit (&p->end);
     }
   free (m->params);
   macro_tokens_uninit (&m->body);
@@ -536,13 +494,10 @@ macro_set_add (struct macro_set *set, struct macro *m)
   hmap_insert (&set->macros, &m->hmap_node, hash_macro_name (m->name));
 }
 \f
-/* Macro call parsing.. */
+/* Macro call parsing. */
 
 enum mc_state
   {
-    /* Error state. */
-    MC_ERROR,
-
     /* Accumulating tokens in mc->params toward the end of any type of
        argument. */
     MC_ARG,
@@ -568,12 +523,18 @@ struct macro_call
     const struct macro_set *macros;
     const struct macro *macro;
     struct macro_tokens **args;
+    const struct macro_expansion_stack *stack;
+    const struct macro_expander *me;
 
     enum mc_state state;
     size_t n_tokens;
     const struct macro_param *param; /* Parameter currently being parsed. */
   };
 
+static bool macro_expand_arg (const struct token *,
+                              const struct macro_expander *,
+                              struct macro_tokens *exp);
+
 /* Completes macro expansion by initializing arguments that weren't supplied to
    their defaults. */
 static int
@@ -619,88 +580,142 @@ mc_next_arg (struct macro_call *mc)
     }
 }
 
-static int
-mc_error (struct macro_call *mc)
+static void PRINTF_FORMAT (3, 4)
+mc_error (const struct macro_call *mc, const struct msg_location *loc,
+          const char *format, ...)
 {
-  mc->state = MC_ERROR;
-  return -1;
+  va_list args;
+  va_start (args, format);
+  if (!mc->stack)
+    {
+      const struct macro_expansion_stack stack = { .location = loc };
+      macro_error_valist (&stack, NULL, format, args);
+    }
+  else
+    macro_error_valist (mc->stack, NULL, format, args);
+  va_end (args);
 }
 
 static int
-mc_add_arg (struct macro_call *mc, const struct macro_token *mt)
+mc_add_arg (struct macro_call *mc, const struct macro_token *mt,
+            const struct msg_location *loc)
 {
   const struct macro_param *p = mc->param;
+  struct macro_tokens **argp = &mc->args[p - mc->macro->params];
 
   const struct token *token = &mt->token;
-  if ((token->type == T_ENDCMD || token->type == T_STOP)
-      && p->arg_type != ARG_CMDEND)
+  if (token->type == T_ENDCMD || token->type == T_STOP)
     {
-      msg (SE, _("Unexpected end of command reading argument %s "
-                 "to macro %s."), mc->param->name, mc->macro->name);
+      if (*argp)
+        {
+          switch (p->arg_type)
+            {
+            case ARG_CMDEND:
+              /* This is OK, it's the expected way to end the argument. */
+              break;
+
+            case ARG_N_TOKENS:
+              mc_error (mc, loc,
+                        ngettext (_("Reached end of command expecting %zu "
+                                    "more token in argument %s to macro %s."),
+                                  _("Reached end of command expecting %zu "
+                                    "more tokens in argument %s to macro %s."),
+                                  p->n_tokens - (*argp)->n),
+                        p->n_tokens - (*argp)->n, p->name, mc->macro->name);
+              break;
 
-      return mc_error (mc);
+            case ARG_CHAREND:
+            case ARG_ENCLOSE:
+              {
+                char *end = token_to_string (&p->end);
+                mc_error (mc, loc, _("Reached end of command expecting \"%s\" "
+                                     "in argument %s to macro %s."),
+                          end, p->name, mc->macro->name);
+                free (end);
+              }
+              break;
+            }
+        }
+
+      /* The end of a command ends the current argument, precludes any further
+         arguments, and is not itself part of the argument. */
+      return mc_finished (mc);
     }
 
   mc->n_tokens++;
 
-  struct macro_tokens **argp = &mc->args[p - mc->macro->params];
   if (!*argp)
     *argp = xzalloc (sizeof **argp);
-  struct macro_tokens *arg = *argp;
-  if (p->arg_type == ARG_N_TOKENS)
-    {
-      macro_tokens_add (arg, mt);
-      if (arg->n >= p->n_tokens)
-        return mc_next_arg (mc);
-      return 0;
-    }
-  else if (p->arg_type == ARG_CMDEND)
+
+  bool add_token;               /* Should we add 'mt' to the current arg? */
+  bool next_arg;                /* Should we advance to the next arg? */
+  switch (p->arg_type)
     {
-      if (token->type == T_ENDCMD || token->type == T_STOP)
-        return mc_next_arg (mc);
-      macro_tokens_add (arg, mt);
-      return 0;
+    case ARG_N_TOKENS:
+      next_arg = (*argp)->n + 1 >= p->n_tokens;
+      add_token = true;
+      break;
+
+    case ARG_CHAREND:
+    case ARG_ENCLOSE:
+      next_arg = token_equal (token, &p->end);
+      add_token = !next_arg;
+      break;
+
+    case ARG_CMDEND:
+      next_arg = false;
+      add_token = true;
+      break;
+
+    default:
+      NOT_REACHED ();
     }
-  else
+
+  if (add_token)
     {
-      const struct token *end
-        = p->arg_type == ARG_CHAREND ? &p->charend : &p->enclose[1];
-      if (token_equal (token, end))
-        return mc_next_arg (mc);
-      macro_tokens_add (arg, mt);
-      return 0;
+      if (!macro_expand_arg (&mt->token, mc->me, *argp))
+        macro_tokens_add (*argp, mt);
     }
+  return next_arg ? mc_next_arg (mc) : 0;
 }
 
 static int
 mc_expected (struct macro_call *mc, const struct macro_token *actual,
-             const struct token *expected)
+             const struct msg_location *loc, const struct token *expected)
 {
   const struct substring actual_s = (actual->syntax.length ? actual->syntax
                                      : ss_cstr (_("<end of input>")));
   char *expected_s = token_to_string (expected);
-  msg (SE, _("Found `%.*s' while expecting `%s' reading argument %s "
-             "to macro %s."),
-       (int) actual_s.length, actual_s.string, expected_s,
-       mc->param->name, mc->macro->name);
+  mc_error (mc, loc,
+            _("Found `%.*s' while expecting `%s' reading argument %s "
+              "to macro %s."),
+            (int) actual_s.length, actual_s.string, expected_s,
+            mc->param->name, mc->macro->name);
   free (expected_s);
 
-  return mc_error (mc);
+  return mc_finished (mc);
 }
 
 static int
-mc_enclose (struct macro_call *mc, const struct macro_token *mt)
+mc_enclose (struct macro_call *mc, const struct macro_token *mt,
+            const struct msg_location *loc)
 {
   const struct token *token = &mt->token;
-  mc->n_tokens++;
-
-  if (token_equal (&mc->param->enclose[0], token))
+  const struct macro_param *p = mc->param;
+  if (token_equal (&p->start, token))
     {
+      mc->n_tokens++;
+
+      struct macro_tokens **argp = &mc->args[p - mc->macro->params];
+      if (!*argp)
+        *argp = xzalloc (sizeof **argp);
       mc->state = MC_ARG;
       return 0;
     }
-
-  return mc_expected (mc, mt, &mc->param->enclose[0]);
+  else if (p->positional && (token->type == T_ENDCMD || token->type == T_STOP))
+    return mc_finished (mc);
+  else
+    return mc_expected (mc, mt, loc, &p->start);
 }
 
 static const struct macro_param *
@@ -723,7 +738,8 @@ macro_find_parameter_by_name (const struct macro *m, struct substring name)
 }
 
 static int
-mc_keyword (struct macro_call *mc, const struct macro_token *mt)
+mc_keyword (struct macro_call *mc, const struct macro_token *mt,
+            const struct msg_location *loc)
 {
   const struct token *token = &mt->token;
   if (token->type != T_ID)
@@ -733,16 +749,14 @@ mc_keyword (struct macro_call *mc, const struct macro_token *mt)
                                                               token->string);
   if (p)
     {
-      size_t arg_index = p - mc->macro->params;
-      mc->param = p;
-      if (mc->args[arg_index])
-        {
-          msg (SE,
-               _("Argument %s multiply specified in call to macro %s."),
-               p->name, mc->macro->name);
-          return mc_error (mc);
-        }
+      struct macro_tokens **argp = &mc->args[p - mc->macro->params];
+      if (*argp)
+        mc_error (mc, loc,
+                  _("Argument %s multiply specified in call to macro %s."),
+                  p->name, mc->macro->name);
 
+      *argp = xzalloc (sizeof **argp);
+      mc->param = p;
       mc->n_tokens++;
       mc->state = MC_EQUALS;
       return 0;
@@ -752,31 +766,25 @@ mc_keyword (struct macro_call *mc, const struct macro_token *mt)
 }
 
 static int
-mc_equals (struct macro_call *mc, const struct macro_token *mt)
+mc_equals (struct macro_call *mc, const struct macro_token *mt,
+           const struct msg_location *loc)
 {
-  const struct token *token = &mt->token;
-  mc->n_tokens++;
-
-  if (token->type == T_EQUALS)
+  if (mt->token.type == T_EQUALS)
     {
-      mc->state = MC_ARG;
+      mc->n_tokens++;
+      mc->state = mc->param->arg_type == ARG_ENCLOSE ? MC_ENCLOSE : MC_ARG;
       return 0;
     }
 
-  return mc_expected (mc, mt, &(struct token) { .type = T_EQUALS });
+  return mc_expected (mc, mt, loc, &(struct token) { .type = T_EQUALS });
 }
 
-/* If TOKEN is the first token of a call to a macro in MACROS, create a new
-   macro expander, initializes *MCP to it.  Returns 0 if more tokens are needed
-   and should be added via macro_call_add() or 1 if the caller should next call
-   macro_call_get_expansion().
-
-   If TOKEN is not the first token of a macro call, returns -1 and sets *MCP to
-   NULL. */
-int
-macro_call_create (const struct macro_set *macros,
-                   const struct token *token,
-                   struct macro_call **mcp)
+static int
+macro_call_create__ (const struct macro_set *macros,
+                     const struct macro_expansion_stack *stack,
+                     const struct macro_expander *me,
+                     const struct token *token,
+                     struct macro_call **mcp)
 {
   const struct macro *macro = (token->type == T_ID || token->type == T_MACRO_ID
                                ? macro_set_find (macros, token->string.string)
@@ -798,12 +806,29 @@ macro_call_create (const struct macro_set *macros,
               : MC_ARG),
     .args = macro->n_params ? xcalloc (macro->n_params, sizeof *mc->args) : NULL,
     .param = macro->params,
+    .stack = stack,
+    .me = me,
   };
   *mcp = mc;
 
   return mc->state == MC_FINISHED ? 1 : 0;
 }
 
+/* If TOKEN is the first token of a call to a macro in MACROS, create a new
+   macro expander, initializes *MCP to it.  Returns 0 if more tokens are needed
+   and should be added via macro_call_add() or 1 if the caller should next call
+   macro_call_get_expansion().
+
+   If TOKEN is not the first token of a macro call, returns -1 and sets *MCP to
+   NULL. */
+int
+macro_call_create (const struct macro_set *macros,
+                   const struct token *token,
+                   struct macro_call **mcp)
+{
+  return macro_call_create__ (macros, NULL, NULL, token, mcp);
+}
+
 void
 macro_call_destroy (struct macro_call *mc)
 {
@@ -840,24 +865,22 @@ macro_call_destroy (struct macro_call *mc)
    macro invocation is finished.  The caller should call
    macro_call_get_expansion() to obtain the expansion. */
 int
-macro_call_add (struct macro_call *mc, const struct macro_token *mt)
+macro_call_add (struct macro_call *mc, const struct macro_token *mt,
+                const struct msg_location *loc)
 {
   switch (mc->state)
     {
-    case MC_ERROR:
-      return -1;
-
     case MC_ARG:
-      return mc_add_arg (mc, mt);
+      return mc_add_arg (mc, mt, loc);
 
     case MC_ENCLOSE:
-      return mc_enclose (mc, mt);
+      return mc_enclose (mc, mt, loc);
 
     case MC_KEYWORD:
-      return mc_keyword (mc, mt);
+      return mc_keyword (mc, mt, loc);
 
     case MC_EQUALS:
-      return mc_equals (mc, mt);
+      return mc_equals (mc, mt, loc);
 
     default:
       NOT_REACHED ();
@@ -874,7 +897,7 @@ struct macro_expander
     int nesting_countdown;              /* Remaining nesting levels. */
     const struct macro_expansion_stack *stack; /* Stack for error reporting. */
     bool *expand;                       /* May macro calls be expanded? */
-    struct stringi_map *vars;           /* Variables from !DO and !LET. */
+    struct stringi_map *vars;           /* Variables from !do and !let. */
 
     /* Only nonnull if inside a !DO loop. */
     bool *break_;                       /* Set to true to break out of loop. */
@@ -893,16 +916,6 @@ expand_macro_function (const struct macro_expander *me,
                        const struct macro_token *input, size_t n_input,
                        struct string *output);
 
-/* Returns true if the N tokens within MTS start with !*, false otherwise. */
-static bool
-is_bang_star (const struct macro_token *mts, size_t n)
-{
-  return (n > 1
-          && mts[0].token.type == T_MACRO_ID
-          && ss_equals (mts[0].token.string, ss_cstr ("!"))
-          && mts[1].token.type == T_ASTERISK);
-}
-
 /* Parses one function argument from the N_INPUT tokens in INPUT
    Each argument to a macro function is one of:
 
@@ -937,7 +950,7 @@ parse_function_arg (const struct macro_expander *me,
           return 1;
         }
 
-      if (is_bang_star (input, n_input))
+      if (ss_equals (token->string, ss_cstr ("!*")))
         {
           for (size_t i = 0; i < me->macro->n_params; i++)
             {
@@ -947,7 +960,7 @@ parse_function_arg (const struct macro_expander *me,
                 ds_put_byte (farg, ' ');
               macro_tokens_to_syntax (me->args[i], farg, NULL, NULL);
             }
-          return 2;
+          return 1;
         }
 
       const char *var = stringi_map_find__ (me->vars,
@@ -974,12 +987,7 @@ parse_function_args (const struct macro_expander *me,
                      const char *function,
                      struct string_array *args)
 {
-  if (n < 2 || mts[1].token.type != T_LPAREN)
-    {
-      macro_error (me->stack, n > 1 ? &mts[1] : NULL,
-                   _("`(' expected following %s."), function);
-      return 0;
-    }
+  assert (n >= 2 && mts[1].token.type == T_LPAREN);
 
   for (size_t i = 2; i < n; )
     {
@@ -1016,24 +1024,23 @@ unquote_string (const char *s, enum segmenter_mode segmenter_mode,
   string_lexer_init (&slex, s, strlen (s), segmenter_mode, true);
 
   struct token token1;
-  if (!string_lexer_next (&slex, &token1))
-    return false;
-
-  if (token1.type != T_STRING)
+  if (string_lexer_next (&slex, &token1) != SLR_TOKEN
+      || token1.type != T_STRING)
     {
       token_uninit (&token1);
       return false;
     }
 
   struct token token2;
-  if (string_lexer_next (&slex, &token2))
+  if (string_lexer_next (&slex, &token2) != SLR_END)
     {
       token_uninit (&token1);
       token_uninit (&token2);
       return false;
     }
 
-  ds_put_substring (content, token1.string);
+  if (content)
+    ds_put_substring (content, token1.string);
   token_uninit (&token1);
   return true;
 }
@@ -1080,7 +1087,6 @@ expand_macro_function (const struct macro_expander *me,
       MF_HEAD,
       MF_INDEX,
       MF_LENGTH,
-      MF_NULL,
       MF_QUOTE,
       MF_SUBSTR,
       MF_TAIL,
@@ -1094,7 +1100,6 @@ expand_macro_function (const struct macro_expander *me,
     [MF_HEAD]    = { "!HEAD",    1, 1 },
     [MF_INDEX]   = { "!INDEX",   2, 2 },
     [MF_LENGTH]  = { "!LENGTH",  1, 1 },
-    [MF_NULL]    = { "!NULL",    0, 0 },
     [MF_QUOTE]   = { "!QUOTE",   1, 1 },
     [MF_SUBSTR]  = { "!SUBSTR",  2, 3 },
     [MF_TAIL]    = { "!TAIL",    1, 1 },
@@ -1102,7 +1107,16 @@ expand_macro_function (const struct macro_expander *me,
     [MF_UPCASE]  = { "!UPCASE",  1, 1 },
   };
 
-  /* Is this a macro function? */
+  if (lex_id_match_n (ss_cstr ("!NULL"), input[0].token.string, 4))
+    return 1;
+
+  if (n_input < 2 || input[1].token.type != T_LPAREN)
+    {
+      /* Only consider macro functions when the name is followed by '('. */
+      return 0;
+    }
+
+  /* Is this a macro function name? */
   const struct macro_function *mf;
   for (mf = mfs; ; mf++)
     {
@@ -1117,13 +1131,14 @@ expand_macro_function (const struct macro_expander *me,
     }
 
   enum macro_function_id id = mf - mfs;
-  if (id == MF_NULL)
-    return 1;
 
   struct string_array args = STRING_ARRAY_INITIALIZER;
   size_t n_consumed = parse_function_args (me, input, n_input, mf->name, &args);
   if (!n_consumed)
-    return 0;
+    {
+      string_array_destroy (&args);
+      return 0;
+    }
 
   if (args.n < mf->min_args || args.n > mf->max_args)
     {
@@ -1146,6 +1161,7 @@ expand_macro_function (const struct macro_expander *me,
                      mf->name);
       else
         NOT_REACHED ();
+      string_array_destroy (&args);
       return 0;
     }
 
@@ -1824,6 +1840,7 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
 
           macro_expand (p, do_end - p, &subme, exp);
         }
+      macro_tokens_uninit (&items);
       return do_end - tokens + 1;
     }
   else if (p < end && p->token.type == T_EQUALS)
@@ -1901,7 +1918,7 @@ macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
 }
 
 static void
-macro_expand_arg (const struct macro_expander *me, size_t idx,
+macro_expand_arg__ (const struct macro_expander *me, size_t idx,
                   struct macro_tokens *exp)
 {
   const struct macro_param *param = &me->macro->params[idx];
@@ -1933,6 +1950,44 @@ macro_expand_arg (const struct macro_expander *me, size_t idx,
       macro_tokens_add (exp, &arg->mts[i]);
 }
 
+static bool
+macro_expand_arg (const struct token *token, const struct macro_expander *me,
+                  struct macro_tokens *exp)
+{
+  if (!me || token->type != T_MACRO_ID)
+    return false;
+
+  /* Macro arguments. */
+  if (me->macro)
+    {
+      const struct macro_param *param = macro_find_parameter_by_name (
+        me->macro, token->string);
+      if (param)
+        {
+          macro_expand_arg__ (me, param - me->macro->params, exp);
+          return true;
+        }
+      else if (ss_equals (token->string, ss_cstr ("!*")))
+        {
+          for (size_t j = 0; j < me->macro->n_params; j++)
+            macro_expand_arg__ (me, j, exp);
+          return true;
+        }
+    }
+
+  /* Variables set by !DO or !LET. */
+  const char *var = stringi_map_find__ (me->vars, token->string.string,
+                                        token->string.length);
+  if (var)
+    {
+      macro_tokens_from_string__ (exp, ss_cstr (var),
+                                  me->segmenter_mode, me->stack);
+      return true;
+    }
+
+  return false;
+}
+
 static size_t
 macro_expand__ (const struct macro_token *mts, size_t n,
                 const struct macro_expander *me,
@@ -1944,21 +1999,20 @@ macro_expand__ (const struct macro_token *mts, size_t n,
   if (*me->expand)
     {
       struct macro_call *submc;
-      int n_call = macro_call_create (me->macros, token, &submc);
+      int n_call = macro_call_create__ (me->macros, me->stack, me,
+                                        token, &submc);
       for (size_t j = 1; !n_call; j++)
         {
           const struct macro_token endcmd
             = { .token = { .type = T_ENDCMD } };
-          n_call = macro_call_add (submc, j < n ? &mts[j] : &endcmd);
+          n_call = macro_call_add (submc, j < n ? &mts[j] : &endcmd, NULL);
         }
       if (n_call > 0)
         {
           struct stringi_map vars = STRINGI_MAP_INITIALIZER (vars);
           struct macro_expansion_stack stack = {
             .name = submc->macro->name,
-            .file_name = submc->macro->file_name,
-            .first_line = submc->macro->first_line,
-            .last_line = submc->macro->last_line,
+            .location = submc->macro->location,
             .next = me->stack,
           };
           struct macro_expander subme = {
@@ -1988,33 +2042,9 @@ macro_expand__ (const struct macro_token *mts, size_t n,
       return 1;
     }
 
-  /* Parameters. */
-  if (me->macro)
-    {
-      const struct macro_param *param = macro_find_parameter_by_name (
-        me->macro, token->string);
-      if (param)
-        {
-          macro_expand_arg (me, param - me->macro->params, exp);
-          return 1;
-        }
-      else if (is_bang_star (mts, n))
-        {
-          for (size_t j = 0; j < me->macro->n_params; j++)
-            macro_expand_arg (me, j, exp);
-          return 2;
-        }
-    }
-
-  /* Variables set by !DO or !LET. */
-  const char *var = stringi_map_find__ (me->vars, token->string.string,
-                                        token->string.length);
-  if (var)
-    {
-      macro_tokens_from_string__ (exp, ss_cstr (var),
-                                  me->segmenter_mode, me->stack);
-      return 1;
-    }
+  /* Parameters and macro variables. */
+  if (macro_expand_arg (token, me, exp))
+    return 1;
 
   /* Macro functions. */
   struct string function_output = DS_EMPTY_INITIALIZER;
@@ -2084,17 +2114,20 @@ macro_expand (const struct macro_token *mts, size_t n,
 
 void
 macro_call_expand (struct macro_call *mc, enum segmenter_mode segmenter_mode,
+                   const struct msg_location *call_loc,
                    struct macro_tokens *exp)
 {
   assert (mc->state == MC_FINISHED);
 
   bool expand = true;
   struct stringi_map vars = STRINGI_MAP_INITIALIZER (vars);
-  struct macro_expansion_stack stack = {
+  struct macro_expansion_stack stack0 = {
+    .location = call_loc,
+  };
+  struct macro_expansion_stack stack1 = {
+    .next = &stack0,
     .name = mc->macro->name,
-    .file_name = mc->macro->file_name,
-    .first_line = mc->macro->first_line,
-    .last_line = mc->macro->last_line,
+    .location = mc->macro->location,
   };
   struct macro_expander me = {
     .macros = mc->macros,
@@ -2105,7 +2138,7 @@ macro_call_expand (struct macro_call *mc, enum segmenter_mode segmenter_mode,
     .break_ = NULL,
     .vars = &vars,
     .nesting_countdown = settings_get_mnest (),
-    .stack = &stack,
+    .stack = &stack1,
   };
 
   const struct macro_tokens *body = &mc->macro->body;