summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Blake <ebb9@byu.net>2007-10-25 08:27:28 -0600
committerEric Blake <ebb9@byu.net>2007-12-07 16:09:05 -0700
commit6dcf7d2e3c5deac2d16ee9a29b6a307474603dc7 (patch)
tree962dcef7dded9f6883d5ac15d368f9cd7e1251ed
parent8b5b3b7a74f452fed795c063965966934a68755d (diff)
downloadm4-6dcf7d2e3c5deac2d16ee9a29b6a307474603dc7.tar.gz
Stage 6: convert builtins to push arg at a time
-rw-r--r--NEWS5
-rw-r--r--doc/m4.texinfo57
-rw-r--r--src/builtin.c103
-rw-r--r--src/debug.c70
-rw-r--r--src/input.c80
-rw-r--r--src/m4.c5
-rw-r--r--src/m4.h14
-rw-r--r--src/macro.c60
-rw-r--r--src/output.c1
-rw-r--r--src/symtab.c1
10 files changed, 264 insertions, 132 deletions
diff --git a/NEWS b/NEWS
index 17625719..051a16a5 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,11 @@ Version 1.4.11 - ?? ??? 2007, by ???? (git version 1.4.10a-*)
* Fix regression introduced in 1.4.9b in the `divert' builtin when more
than 512 kibibytes are saved in diversions on platforms like NetBSD where
fopen(name,"a+") seeks to the end of the file.
+* The output of the `maketemp' and `mkstemp' builtins is now quoted if a
+ file was created. This is a minor security fix, because it was possible
+ (although rather unlikely) that an unquoted string could match an
+ existing macro name, such that use of the `mkstemp' output would trigger
+ inadvertent macro expansion and operate on the wrong file name.
* Enhance the `defn' builtin to support concatenation of multiple text
arguments, as required by POSIX. However, at this time, it is not
possible to concatenate a builtin macro with anything else; a warning is
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 803dbf05..93b76963 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -5876,7 +5876,7 @@ builtin macro, @code{mkstemp}, for making a temporary file:
@deffn Builtin mkstemp (@var{template})
@deffnx Builtin maketemp (@var{template})
-Expands to a name of a new, empty file, made from the string
+Expands to the quoted name of a new, empty file, made from the string
@var{template}, which should end with the string @samp{XXXXXX}. The six
@samp{X} characters are then replaced with random characters matching
the regular expression @samp{[a-zA-Z0-9._-]}, in order to make the file
@@ -5888,7 +5888,8 @@ account, and at most only the current user can read and write the file.
The traditional behavior, standardized by @acronym{POSIX}, is that
@code{maketemp} merely replaces the trailing @samp{X} with the process
-id, without creating a file, and without ensuring that the resulting
+id, without creating a file or quoting the expansion, and without
+ensuring that the resulting
string is a unique file name. In part, this means that using the same
@var{template} twice in the same input file will result in the same
expansion. This behavior is a security hole, as it is very easy for
@@ -5912,6 +5913,8 @@ chosen:
@comment ignore
@example
$ @kbd{m4}
+define(`tmp', `oops')
+@result{}
maketemp(`/tmp/fooXXXXXX')
@result{}/tmp/fooa07346
ifdef(`mkstemp', `define(`maketemp', defn(`mkstemp'))',
@@ -5929,26 +5932,35 @@ Unless you use the @option{--traditional} command line option (or
version of @code{maketemp} is secure. This means that using the same
template to multiple calls will generate multiple files. However, we
recommend that you use the new @code{mkstemp} macro, introduced in
-@acronym{GNU} M4 1.4.8, which is secure even in traditional mode.
+@acronym{GNU} M4 1.4.8, which is secure even in traditional mode. Also,
+as of M4 1.4.11, the secure implementation quotes the resulting file
+name, so that you are guaranteed to know what file was created even if
+the random file name happens to match an existing macro. Notice that
+this example is careful to use @code{defn} to avoid unintended expansion
+of @samp{foo}.
@example
$ @kbd{m4}
-syscmd(`rm -f foo??????')sysval
+define(`foo', `errprint(`oops')')
+@result{}
+syscmd(`rm -f foo-??????')sysval
@result{}0
-define(`file1', maketemp(`fooXXXXXX'))dnl
-ifelse(esyscmd(`echo foo??????'), `foo??????', `no file', `created')
+define(`file1', maketemp(`foo-XXXXXX'))dnl
+ifelse(esyscmd(`echo \` foo-?????? \''), ` foo-?????? ',
+ `no file', `created')
@result{}created
-define(`file2', maketemp(`fooXX'))dnl
-define(`file3', mkstemp(`fooXXXXXX'))dnl
-ifelse(len(file1), len(file2), `same length', `different')
+define(`file2', maketemp(`foo-XX'))dnl
+define(`file3', mkstemp(`foo-XXXXXX'))dnl
+ifelse(len(defn(`file1')), len(defn(`file2')),
+ `same length', `different')
@result{}same length
-ifelse(file1, file2, `same', `different file')
+ifelse(defn(`file1'), defn(`file2'), `same', `different file')
@result{}different file
-ifelse(file2, file3, `same', `different file')
+ifelse(defn(`file2'), defn(`file3'), `same', `different file')
@result{}different file
-ifelse(file1, file3, `same', `different file')
+ifelse(defn(`file1'), defn(`file3'), `same', `different file')
@result{}different file
-syscmd(`rm 'file1 file2 file3)
+syscmd(`rm 'defn(`file1') defn(`file2') defn(`file3'))
@result{}
sysval
@result{}0
@@ -5966,6 +5978,25 @@ len(mkstemp(`fooXXXXX'))
syscmd(`rm foo??????')sysval
@result{}0
@end example
+
+@c Likewise, and ensure that traditional mode leaves the result unquoted
+@c without creating a file.
+
+@comment options: -G
+@example
+syscmd(`rm -f foo-*')sysval
+@result{}0
+len(maketemp(`foo-XXXXX'))
+@error{}m4:stdin:2: Warning: maketemp: recommend using mkstemp instead
+@result{}9
+define(`abc', `def')
+@result{}
+maketemp(`foo-abc')
+@result{}foo-def
+@error{}m4:stdin:4: Warning: maketemp: recommend using mkstemp instead
+syscmd(`test -f foo-*')sysval
+@result{}1
+@end example
@end ignore
@node Miscellaneous
diff --git a/src/builtin.c b/src/builtin.c
index d63ed722..9e44e1dd 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -731,37 +731,18 @@ static void
m4_ifdef (struct obstack *obs, int argc, macro_arguments *argv)
{
symbol *s;
- const char *result;
- size_t len = 0;
if (bad_argc (ARG (0), argc, 2, 3))
return;
s = lookup_symbol (ARG (1), SYMBOL_LOOKUP);
-
- if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID)
- {
- result = ARG (2);
- len = arg_len (argv, 2);
- }
- else if (argc >= 4)
- {
- result = ARG (3);
- len = arg_len (argv, 3);
- }
- else
- result = NULL;
-
- if (result != NULL)
- obstack_grow (obs, result, len);
+ push_arg (obs, argv, (s && SYMBOL_TYPE (s) != TOKEN_VOID) ? 2 : 3);
}
static void
m4_ifelse (struct obstack *obs, int argc, macro_arguments *argv)
{
- const char *result;
const char *me;
int index;
- size_t len = 0;
if (argc == 2)
return;
@@ -776,14 +757,13 @@ m4_ifelse (struct obstack *obs, int argc, macro_arguments *argv)
index = 1;
argc--;
- result = NULL;
- while (result == NULL)
- if (arg_equal (argv, index, index + 1))
- {
- result = ARG (index + 2);
- len = arg_len (argv, index + 2);
- }
- else
+ while (true)
+ {
+ if (arg_equal (argv, index, index + 1))
+ {
+ push_arg (obs, argv, index + 2);
+ return;
+ }
switch (argc)
{
case 3:
@@ -791,16 +771,14 @@ m4_ifelse (struct obstack *obs, int argc, macro_arguments *argv)
case 4:
case 5:
- result = ARG (index + 3);
- len = arg_len (argv, index + 3);
- break;
+ push_arg (obs, argv, index + 3);
+ return;
default:
argc -= 3;
index += 3;
}
-
- obstack_grow (obs, result, len);
+ }
}
/*---------------------------------------------------------------------.
@@ -1182,7 +1160,6 @@ m4_eval (struct obstack *obs, int argc, macro_arguments *argv)
obstack_1grow (obs, '0');
while (value-- != 0)
obstack_1grow (obs, '1');
- obstack_1grow (obs, '\0');
return;
}
@@ -1332,8 +1309,7 @@ m4_shift (struct obstack *obs, int argc, macro_arguments *argv)
{
if (bad_argc (ARG (0), argc, 1, -1))
return;
- // TODO push a $@ reference
- dump_args (obs, 2, argv, ",", true);
+ push_args (obs, argv, true, true);
}
/*--------------------------------------------------------------------------.
@@ -1442,40 +1418,42 @@ m4_sinclude (struct obstack *obs, int argc, macro_arguments *argv)
| Use the first argument as a template for a temporary file name. |
`-----------------------------------------------------------------*/
-/* Add trailing 'X' to NAME of length LEN as necessary, then securely
- create the file, and place the new file name on OBS. Report errors
- on behalf of ME. */
+/* Add trailing 'X' to PATTERN of length LEN as necessary, then
+ securely create the file, and place the quoted new file name on
+ OBS. Report errors on behalf of ME. */
static void
-mkstemp_helper (struct obstack *obs, const char *me, const char *name,
+mkstemp_helper (struct obstack *obs, const char *me, const char *pattern,
size_t len)
{
int fd;
int i;
+ char *name;
/* Guarantee that there are six trailing 'X' characters, even if the
- user forgot to supply them. */
- obstack_grow (obs, name, len);
+ user forgot to supply them. Output must be quoted if
+ successful. */
+ obstack_grow (obs, lquote.string, lquote.length);
+ obstack_grow (obs, pattern, len);
for (i = 0; len > 0 && i < 6; i++)
- if (name[--len] != 'X')
+ if (pattern[len - i - 1] != 'X')
break;
- for (; i < 6; i++)
- obstack_1grow (obs, 'X');
- obstack_1grow (obs, '\0');
+ len += 6 - i;
+ obstack_grow0 (obs, "XXXXXX", 6 - i);
+ name = (char *) obstack_base (obs) + lquote.length;
errno = 0;
- fd = mkstemp ((char *) obstack_base (obs));
+ fd = mkstemp (name);
if (fd < 0)
{
- m4_warn (errno, me, _("cannot create tempfile `%s'"), name);
+ m4_warn (errno, me, _("cannot create tempfile `%s'"), pattern);
obstack_free (obs, obstack_finish (obs));
}
else
{
close (fd);
- /* Undo trailing NUL. */
- /* FIXME - should we be quoting this name, on the tiny chance
- that the random name generated matches a user's macro? */
+ /* Remove NUL, then finish quote. */
obstack_blank (obs, -1);
+ obstack_grow (obs, rquote.string, rquote.length);
}
}
@@ -1512,7 +1490,7 @@ m4_maketemp (struct obstack *obs, int argc, macro_arguments *argv)
str = ntoa ((int32_t) getpid (), 10);
len2 = strlen (str);
if (len2 > len - i)
- obstack_grow0 (obs, str + len2 - (len - i), len - i);
+ obstack_grow (obs, str + len2 - (len - i), len - i);
else
{
while (i++ < len - len2)
@@ -1830,7 +1808,7 @@ m4_substr (struct obstack *obs, int argc, macro_arguments *argv)
{
/* builtin(`substr') is blank, but substr(`abc') is abc. */
if (argc == 2)
- obstack_grow (obs, ARG (1), arg_len (argv, 1));
+ push_arg (obs, argv, 1);
return;
}
@@ -1916,7 +1894,7 @@ m4_translit (struct obstack *obs, int argc, macro_arguments *argv)
{
/* builtin(`translit') is blank, but translit(`abc') is abc. */
if (argc == 2)
- obstack_grow (obs, ARG (1), arg_len (argv, 1));
+ push_arg (obs, argv, 1);
return;
}
@@ -2153,7 +2131,7 @@ m4_patsubst (struct obstack *obs, int argc, macro_arguments *argv)
{
/* builtin(`patsubst') is blank, but patsubst(`abc') is abc. */
if (argc == 2)
- obstack_grow (obs, ARG (1), arg_len (argv, 1));
+ push_arg (obs, argv, 1);
return;
}
@@ -2165,7 +2143,7 @@ m4_patsubst (struct obstack *obs, int argc, macro_arguments *argv)
replacement, we need not waste time with it. */
if (!*regexp && !*repl)
{
- obstack_grow (obs, victim, arg_len (argv, 1));
+ push_arg (obs, argv, 1);
return;
}
@@ -2219,9 +2197,12 @@ m4_patsubst (struct obstack *obs, int argc, macro_arguments *argv)
offset = regs->end[0];
if (regs->start[0] == regs->end[0])
- obstack_1grow (obs, victim[offset++]);
+ {
+ if (offset < length)
+ obstack_1grow (obs, victim[offset]);
+ offset++;
+ }
}
- obstack_1grow (obs, '\0');
}
/* Finally, a placeholder builtin. This builtin is not installed by
@@ -2283,8 +2264,7 @@ expand_user_macro (struct obstack *obs, symbol *sym,
for (i = 0; isdigit (to_uchar (*text)); text++)
i = i * 10 + (*text - '0');
}
- if (i < argc)
- obstack_grow (obs, ARG (i), arg_len (argv, i));
+ push_arg (obs, argv, i);
break;
case '#': /* number of arguments */
@@ -2294,8 +2274,7 @@ expand_user_macro (struct obstack *obs, symbol *sym,
case '*': /* all arguments */
case '@': /* ... same, but quoted */
- // TODO push a $@ reference
- dump_args (obs, 1, argv, ",", *text == '@');
+ push_args (obs, argv, false, *text == '@');
text++;
break;
diff --git a/src/debug.c b/src/debug.c
index c4f701d9..2ca7a0d6 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -239,21 +239,21 @@ debug_message_prefix (void)
output from interfering with other debug messages generated by the
various builtins. */
-/*---------------------------------------------------------------------.
-| Tracing output is formatted here, by a simplified printf-to-obstack |
-| function trace_format (). Understands only %S, %s, %d, %l (optional |
-| left quote) and %r (optional right quote). |
-`---------------------------------------------------------------------*/
+/*-------------------------------------------------------------------.
+| Tracing output to the obstack is formatted here, by a simplified |
+| printf-like function trace_format (). Understands only %B (1 arg: |
+| input block), %S (1 arg: length-limited text), %s (1 arg: text), |
+| %d (1 arg: integer), %l (0 args: optional left quote) and %r (0 |
+| args: optional right quote). |
+`-------------------------------------------------------------------*/
static void
trace_format (const char *fmt, ...)
{
va_list args;
char ch;
-
int d;
const char *s;
- int slen;
int maxlen;
va_start (args, fmt);
@@ -266,9 +266,14 @@ trace_format (const char *fmt, ...)
if (ch == '\0')
break;
- maxlen = 0;
+ maxlen = INT_MAX;
switch (*fmt++)
{
+ case 'B':
+ s = "";
+ input_print (&trace, va_arg (args, input_block *));
+ break;
+
case 'S':
maxlen = max_debug_argument_length;
/* fall through */
@@ -295,14 +300,8 @@ trace_format (const char *fmt, ...)
break;
}
- slen = strlen (s);
- if (maxlen == 0 || maxlen > slen)
- obstack_grow (&trace, s, slen);
- else
- {
- obstack_grow (&trace, s, maxlen);
- obstack_grow (&trace, "...", 3);
- }
+ if (obstack_print (&trace, s, SIZE_MAX, &maxlen))
+ break;
}
va_end (args);
@@ -362,10 +361,11 @@ trace_prepre (const char *name, int id)
`-----------------------------------------------------------------------*/
void
-trace_pre (const char *name, int id, int argc, macro_arguments *argv)
+trace_pre (const char *name, int id, macro_arguments *argv)
{
int i;
const builtin *bp;
+ int argc = arg_argc (argv);
trace_header (id);
trace_format ("%s", name);
@@ -417,9 +417,11 @@ trace_pre (const char *name, int id, int argc, macro_arguments *argv)
`-------------------------------------------------------------------*/
void
-trace_post (const char *name, int id, int argc, macro_arguments *argv,
- const char *expanded)
+trace_post (const char *name, int id, macro_arguments *argv,
+ const input_block *expanded)
{
+ int argc = arg_argc (argv);
+
if (debug_level & DEBUG_TRACE_CALL)
{
trace_header (id);
@@ -427,6 +429,34 @@ trace_post (const char *name, int id, int argc, macro_arguments *argv,
}
if (expanded && (debug_level & DEBUG_TRACE_EXPANSION))
- trace_format (" -> %l%S%r", expanded);
+ trace_format (" -> %l%B%r", expanded);
trace_flush ();
}
+
+/* Dump the string STR of length LEN to the obstack OBS. If LEN is
+ SIZE_MAX, use strlen (STR) instead. If MAX_LEN is non-NULL,
+ truncate the dump at MAX_LEN bytes and return true if MAX_LEN was
+ reached; otherwise, return false and update MAX_LEN as
+ appropriate. */
+bool
+obstack_print (struct obstack *obs, const char *str, size_t len, int *max_len)
+{
+ int max = max_len ? *max_len : INT_MAX;
+
+ if (len == SIZE_MAX)
+ len = strlen (str);
+ if (len < max)
+ {
+ obstack_grow (obs, str, len);
+ max -= len;
+ }
+ else
+ {
+ obstack_grow (obs, str, max);
+ obstack_grow (obs, "...", 3);
+ max = 0;
+ }
+ if (max_len)
+ *max_len = max;
+ return max == 0;
+}
diff --git a/src/input.c b/src/input.c
index 551b43d6..4e5d2990 100644
--- a/src/input.c
+++ b/src/input.c
@@ -77,7 +77,7 @@ typedef enum input_type input_type;
/* A block of input to be scanned. */
struct input_block
{
- struct input_block *prev; /* Previous input_block on the input stack. */
+ input_block *prev; /* Previous input_block on the input stack. */
input_type type; /* See enum values. */
const char *file; /* File where this input is from. */
int line; /* Line where this input is from. */
@@ -101,8 +101,6 @@ struct input_block
u;
};
-typedef struct input_block input_block;
-
/* Current input file name. */
const char *current_file;
@@ -208,8 +206,7 @@ push_file (FILE *fp, const char *title, bool close)
if (debug_level & DEBUG_TRACE_INPUT)
DEBUG_MESSAGE1 ("input read from %s", title);
- i = (input_block *) obstack_alloc (current_input,
- sizeof (struct input_block));
+ i = (input_block *) obstack_alloc (current_input, sizeof *i);
i->type = INPUT_FILE;
i->file = (char *) obstack_copy0 (&file_names, title, strlen (title));
i->line = 1;
@@ -242,8 +239,7 @@ push_macro (builtin_func *func)
next = NULL;
}
- i = (input_block *) obstack_alloc (current_input,
- sizeof (struct input_block));
+ i = (input_block *) obstack_alloc (current_input, sizeof *i);
i->type = INPUT_MACRO;
i->file = current_file;
i->line = current_line;
@@ -267,8 +263,7 @@ push_string_init (void)
while (isp && pop_input (false));
/* Reserve the next location on the obstack. */
- next = (input_block *) obstack_alloc (current_input,
- sizeof (struct input_block));
+ next = (input_block *) obstack_alloc (current_input, sizeof *next);
next->type = INPUT_STRING;
next->file = current_file;
next->line = current_line;
@@ -281,30 +276,35 @@ push_string_init (void)
| push_file () or push_macro () has invalidated the previous call to |
| push_string_init (), so we just give up. If the new object is |
| void, we do not push it. The function push_string_finish () |
-| returns a pointer to the finished object. This pointer is only |
-| for temporary use, since reading the next token might release the |
-| memory used for the object. |
+| returns an opaque pointer to the finished object, which can then |
+| be printed with input_print when tracing is enabled. This pointer |
+| is only for temporary use, since reading the next token will |
+| invalidate the object. |
`-------------------------------------------------------------------*/
-const char *
+const input_block *
push_string_finish (void)
{
- const char *ret = NULL;
+ input_block *ret = NULL;
+ size_t len = obstack_object_size (current_input);
if (next == NULL)
- return NULL;
+ {
+ assert (!len);
+ return NULL;
+ }
- if (obstack_object_size (current_input) > 0)
+ if (len)
{
obstack_1grow (current_input, '\0');
next->u.u_s.string = (char *) obstack_finish (current_input);
next->prev = isp;
isp = next;
- ret = isp->u.u_s.string; /* for immediate use only */
input_change = true;
+ ret = isp;
}
else
- obstack_free (current_input, next); /* people might leave garbage on it. */
+ obstack_free (current_input, next);
next = NULL;
return ret;
}
@@ -322,8 +322,7 @@ void
push_wrapup (const char *s)
{
input_block *i;
- i = (input_block *) obstack_alloc (wrapup_stack,
- sizeof (struct input_block));
+ i = (input_block *) obstack_alloc (wrapup_stack, sizeof *i);
i->prev = wsp;
i->type = INPUT_STRING;
i->file = current_file;
@@ -421,7 +420,7 @@ pop_wrapup (void)
}
current_input = wrapup_stack;
- wrapup_stack = (struct obstack *) xmalloc (sizeof (struct obstack));
+ wrapup_stack = (struct obstack *) xmalloc (sizeof *wrapup_stack);
obstack_init (wrapup_stack);
isp = wsp;
@@ -443,6 +442,41 @@ init_macro_token (token_data *td)
TOKEN_DATA_TYPE (td) = TOKEN_FUNC;
TOKEN_DATA_FUNC (td) = isp->u.func;
}
+
+/*--------------------------------------------------------------.
+| Dump a representation of INPUT to the obstack OBS, for use in |
+| tracing. |
+`--------------------------------------------------------------*/
+void
+input_print (struct obstack *obs, const input_block *input)
+{
+ int maxlen = max_debug_argument_length;
+
+ assert (input);
+ switch (input->type)
+ {
+ case INPUT_STRING:
+ obstack_print (obs, input->u.u_s.string, SIZE_MAX, &maxlen);
+ break;
+ case INPUT_FILE:
+ obstack_grow (obs, "<file: ", strlen ("<file: "));
+ obstack_grow (obs, input->file, strlen (input->file));
+ obstack_1grow (obs, '>');
+ break;
+ case INPUT_MACRO:
+ {
+ const builtin *bp = find_builtin_by_addr (input->u.func);
+ assert (bp);
+ obstack_1grow (obs, '<');
+ obstack_grow (obs, bp->name, strlen (bp->name));
+ obstack_1grow (obs, '>');
+ }
+ break;
+ default:
+ assert (!"input_print");
+ abort ();
+ }
+}
/*-----------------------------------------------------------------.
@@ -680,9 +714,9 @@ input_init (void)
current_file = "";
current_line = 0;
- current_input = (struct obstack *) xmalloc (sizeof (struct obstack));
+ current_input = (struct obstack *) xmalloc (sizeof *current_input);
obstack_init (current_input);
- wrapup_stack = (struct obstack *) xmalloc (sizeof (struct obstack));
+ wrapup_stack = (struct obstack *) xmalloc (sizeof *wrapup_stack);
obstack_init (wrapup_stack);
obstack_init (&file_names);
diff --git a/src/m4.c b/src/m4.c
index 0c7f33f0..2cfed194 100644
--- a/src/m4.c
+++ b/src/m4.c
@@ -22,7 +22,6 @@
#include "m4.h"
#include <getopt.h>
-#include <limits.h>
#include <signal.h>
#include <stdarg.h>
@@ -48,7 +47,7 @@ int no_gnu_extensions = 0;
int prefix_all_builtins = 0;
/* Max length of arguments in trace output (-lsize). */
-int max_debug_argument_length = 0;
+int max_debug_argument_length = INT_MAX;
/* Suppress warnings about missing arguments. */
int suppress_warnings = 0;
@@ -553,7 +552,7 @@ main (int argc, char *const *argv, char *const *envp)
case 'l':
max_debug_argument_length = atoi (optarg);
if (max_debug_argument_length <= 0)
- max_debug_argument_length = 0;
+ max_debug_argument_length = INT_MAX;
break;
case 'o':
diff --git a/src/m4.h b/src/m4.h
index d7b6e088..f7b0d37e 100644
--- a/src/m4.h
+++ b/src/m4.h
@@ -28,6 +28,7 @@
#include <assert.h>
#include <ctype.h>
#include <errno.h>
+#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
@@ -96,6 +97,7 @@ typedef struct string STRING;
(OBS)->object_base = (char *) (OBJECT))
/* These must come first. */
+typedef struct input_block input_block;
typedef struct token_data token_data;
typedef struct macro_arguments macro_arguments;
typedef void builtin_func (struct obstack *, int, macro_arguments *);
@@ -245,8 +247,11 @@ bool debug_set_output (const char *, const char *);
void debug_message_prefix (void);
void trace_prepre (const char *, int);
-void trace_pre (const char *, int, int, macro_arguments *);
-void trace_post (const char *, int, int, macro_arguments *, const char *);
+void trace_pre (const char *, int, macro_arguments *);
+void trace_post (const char *, int, macro_arguments *,
+ const input_block *);
+
+bool obstack_print (struct obstack *, const char *, size_t, int *);
/* File: input.c --- lexical definitions. */
@@ -341,9 +346,10 @@ void skip_line (const char *);
void push_file (FILE *, const char *, bool);
void push_macro (builtin_func *);
struct obstack *push_string_init (void);
-const char *push_string_finish (void);
+const input_block *push_string_finish (void);
void push_wrapup (const char *);
bool pop_wrapup (void);
+void input_print (struct obstack *, const input_block *);
/* current input file, and line */
extern const char *current_file;
@@ -447,6 +453,8 @@ size_t arg_len (macro_arguments *, unsigned int);
builtin_func *arg_func (macro_arguments *, unsigned int);
macro_arguments *make_argv_ref (macro_arguments *, const char *, size_t,
bool, bool);
+void push_arg (struct obstack *, macro_arguments *, unsigned int);
+void push_args (struct obstack *, macro_arguments *, bool, bool);
/* File: builtin.c --- builtins. */
diff --git a/src/macro.c b/src/macro.c
index 56a8571d..c4eaaddb 100644
--- a/src/macro.c
+++ b/src/macro.c
@@ -414,9 +414,8 @@ expand_macro (symbol *sym)
unsigned int arg_size; /* Size of arg_stack on entry. */
unsigned int argv_size; /* Size of argv_stack on entry. */
macro_arguments *argv;
- int argc;
struct obstack *expansion;
- const char *expanded;
+ const input_block *expanded;
bool traced;
int my_call_id;
@@ -453,7 +452,6 @@ expand_macro (symbol *sym)
trace_prepre (SYMBOL_NAME (sym), my_call_id);
argv = collect_arguments (sym, &arg_stack);
- argc = argv->argc;
loc_close_file = current_file;
loc_close_line = current_line;
@@ -461,14 +459,14 @@ expand_macro (symbol *sym)
current_line = loc_open_line;
if (traced)
- trace_pre (SYMBOL_NAME (sym), my_call_id, argc, argv);
+ trace_pre (SYMBOL_NAME (sym), my_call_id, argv);
expansion = push_string_init ();
- call_macro (sym, argc, argv, expansion);
+ call_macro (sym, argv->argc, argv, expansion);
expanded = push_string_finish ();
if (traced)
- trace_post (SYMBOL_NAME (sym), my_call_id, argc, argv, expanded);
+ trace_post (SYMBOL_NAME (sym), my_call_id, argv, expanded);
current_file = loc_close_file;
current_line = loc_close_line;
@@ -711,3 +709,53 @@ make_argv_ref (macro_arguments *argv, const char *argv0, size_t argv0_len,
new_argv->quote_age = argv->quote_age;
return new_argv;
}
+
+/* Push argument INDEX from ARGV, which must be a text token, onto the
+ expansion stack OBS for rescanning. */
+void
+push_arg (struct obstack *obs, macro_arguments *argv, unsigned int index)
+{
+ token_data *token;
+
+ if (index == 0)
+ {
+ obstack_grow (obs, argv->argv0, argv->argv0_len);
+ return;
+ }
+ if (index >= argv->argc)
+ return;
+ token = arg_token (argv, index);
+ // TODO handle func tokens?
+ assert (TOKEN_DATA_TYPE (token) == TOKEN_TEXT);
+ // TODO actually push a reference, rather than copying data
+ obstack_grow (obs, TOKEN_DATA_TEXT (token), TOKEN_DATA_LEN (token));
+}
+
+/* Push series of comma-separated arguments from ARGV, which should
+ all be text, onto the expansion stack OBS for rescanning. If SKIP,
+ then don't push the first argument. If QUOTE, the rescan also
+ includes quoting around each arg. */
+void
+push_args (struct obstack *obs, macro_arguments *argv, bool skip, bool quote)
+{
+ token_data *token;
+ unsigned int i;
+ bool comma = false;
+
+ // TODO push reference, rather than copying data
+ for (i = skip ? 2 : 1; i < argv->argc; i++)
+ {
+ token = arg_token (argv, i);
+ if (comma)
+ obstack_1grow (obs, ',');
+ else
+ comma = true;
+ // TODO handle func tokens?
+ assert (TOKEN_DATA_TYPE (token) == TOKEN_TEXT);
+ if (quote)
+ obstack_grow (obs, lquote.string, lquote.length);
+ obstack_grow (obs, TOKEN_DATA_TEXT (token), TOKEN_DATA_LEN (token));
+ if (quote)
+ obstack_grow (obs, rquote.string, rquote.length);
+ }
+}
diff --git a/src/output.c b/src/output.c
index 478d3b28..4c8c9deb 100644
--- a/src/output.c
+++ b/src/output.c
@@ -21,7 +21,6 @@
#include "m4.h"
-#include <limits.h>
#include <sys/stat.h>
#include "gl_avltree_oset.h"
diff --git a/src/symtab.c b/src/symtab.c
index d65d4c5a..e8a027f0 100644
--- a/src/symtab.c
+++ b/src/symtab.c
@@ -32,7 +32,6 @@
will then always be the first found. */
#include "m4.h"
-#include <limits.h>
#ifdef DEBUG_SYM
/* When evaluating hash table performance, this profiling code shows