macro: Fix memory leaks in error cases parsing function arguments.
[pspp] / src / language / lexer / macro.c
index 85706ce3a4286b8561f06e80bad5852d2270f1a3..a089a3a7c32e1c7538097d0e291d7366d408f46b 100644 (file)
@@ -418,24 +418,8 @@ macro_destroy (struct macro *m)
       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);
@@ -510,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,
@@ -543,12 +524,17 @@ struct macro_call
     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
@@ -615,48 +601,82 @@ 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)
     {
-      mc_error (mc, loc,
-                _("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;
 
-      mc->state = MC_ERROR;
-      return -1;
+            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;
+
+            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
@@ -673,8 +693,7 @@ mc_expected (struct macro_call *mc, const struct macro_token *actual,
             mc->param->name, mc->macro->name);
   free (expected_s);
 
-  mc->state = MC_ERROR;
-  return -1;
+  return mc_finished (mc);
 }
 
 static int
@@ -682,15 +701,21 @@ 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, loc, &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 *
@@ -724,17 +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])
-        {
-          mc_error (mc, loc,
-                    _("Argument %s multiply specified in call to macro %s."),
-                    p->name, mc->macro->name);
-          mc->state = MC_ERROR;
-          return -1;
-        }
+      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;
@@ -747,12 +769,10 @@ static int
 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;
     }
 
@@ -762,6 +782,7 @@ mc_equals (struct macro_call *mc, const struct macro_token *mt,
 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)
 {
@@ -786,6 +807,7 @@ macro_call_create__ (const struct macro_set *macros,
     .args = macro->n_params ? xcalloc (macro->n_params, sizeof *mc->args) : NULL,
     .param = macro->params,
     .stack = stack,
+    .me = me,
   };
   *mcp = mc;
 
@@ -804,7 +826,7 @@ macro_call_create (const struct macro_set *macros,
                    const struct token *token,
                    struct macro_call **mcp)
 {
-  return macro_call_create__ (macros, NULL, token, mcp);
+  return macro_call_create__ (macros, NULL, NULL, token, mcp);
 }
 
 void
@@ -848,9 +870,6 @@ macro_call_add (struct macro_call *mc, const struct macro_token *mt,
 {
   switch (mc->state)
     {
-    case MC_ERROR:
-      return -1;
-
     case MC_ARG:
       return mc_add_arg (mc, mt, loc);
 
@@ -897,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:
 
@@ -941,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++)
             {
@@ -951,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,
@@ -978,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; )
     {
@@ -1035,7 +1039,8 @@ unquote_string (const char *s, enum segmenter_mode segmenter_mode,
       return false;
     }
 
-  ds_put_substring (content, token1.string);
+  if (content)
+    ds_put_substring (content, token1.string);
   token_uninit (&token1);
   return true;
 }
@@ -1082,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,
@@ -1096,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 },
@@ -1104,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++)
     {
@@ -1119,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)
     {
@@ -1148,6 +1161,7 @@ expand_macro_function (const struct macro_expander *me,
                      mf->name);
       else
         NOT_REACHED ();
+      string_array_destroy (&args);
       return 0;
     }
 
@@ -1826,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)
@@ -1903,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];
@@ -1935,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,
@@ -1946,7 +1999,8 @@ 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, me->stack, 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
@@ -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;