diff options
author | Eric Blake <ebb9@byu.net> | 2008-01-16 07:28:32 -0700 |
---|---|---|
committer | Eric Blake <ebb9@byu.net> | 2008-01-16 20:16:28 -0700 |
commit | 782e3ac755755787d87a5057a6631329661be3ed (patch) | |
tree | eb8047e605218fe39763a638ace2420477dbd7c0 /modules/format.c | |
parent | e173b6eba5a368103502f281805dd38489475447 (diff) | |
download | m4-782e3ac755755787d87a5057a6631329661be3ed.tar.gz |
Stage 10: avoid extra copying of strings and comments.
* ltdl/m4/gnulib-cache.m4: Import intprops and vasnprintf-posix
modules.
* m4/m4private.h (m4__token_type): Adjust prototype.
* m4/input.c (m4__next_token): Support new parameter.
* m4/macro.c (m4_macro_expand_input, expand_token)
(expand_argument, collect_arguments): Adjust callers.
* modules/m4.c (ntoa): Tighten buffer size.
* m4/output.c (m4_tmpname): Guarantee no buffer overflow.
* modules/format.c (arg_int, arg_long, arg_double): New helper
functions, to detect overflow or unparsed characters.
(ARG_INT, ARG_LONG, ARG_STR, ARG_DOUBLE): Adjust to check for
missing or excess arguments.
(format): Likewise, and also output directly into obstack if there
is room.
* doc/m4.texinfo (History): Update for new year.
(Format): Test for new warnings.
Signed-off-by: Eric Blake <ebb9@byu.net>
Diffstat (limited to 'modules/format.c')
-rw-r--r-- | modules/format.c | 181 |
1 files changed, 137 insertions, 44 deletions
diff --git a/modules/format.c b/modules/format.c index ced39249..fd086c8b 100644 --- a/modules/format.c +++ b/modules/format.c @@ -1,6 +1,6 @@ /* GNU m4 -- A simple macro processor - Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 2001, 2006, 2007 - Free Software Foundation, Inc. + Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 2001, 2006, 2007, + 2008 Free Software Foundation, Inc. This file is part of GNU M4. @@ -20,27 +20,96 @@ /* printf like formatting for m4. */ -#include "xvasprintf.h" +#include "vasnprintf.h" /* Simple varargs substitute. We assume int and unsigned int are the - same size; likewise for long and unsigned long. + same size; likewise for long and unsigned long. We do not yet + handle long double or long long. */ - TODO - warn if we use these because too many % specifiers were used in - relation to number of arguments passed. - TODO - use xstrtoimax, not atoi, to catch overflow, non-numeric - arguments, etc. */ +/* Parse STR as an integer, reporting warnings on behalf of ME. */ +static int +arg_int (struct m4 *context, const char *me, const char *str) +{ + char *endp; + long value; + + /* TODO - also allow parsing `'a' or `"a' which results in the + numeric value of 'a', as in printf(1). */ + if (*str == '\0') + { + m4_warn (context, 0, me, _("empty string treated as 0")); + return 0; + } + errno = 0; + value = strtol (str, &endp, 10); + if (*endp != '\0') + m4_warn (context, 0, me, _("non-numeric argument `%s'"), str); + else if (isspace (to_uchar (*str))) + m4_warn (context, 0, me, _("leading whitespace ignored")); + else if (errno == ERANGE || (int) value != value) + m4_warn (context, 0, me, _("numeric overflow detected")); + return value; +} -#define ARG_INT(i, argc, argv) \ - ((argc <= i++) ? 0 : atoi (M4ARG (i - 1))) +/* Parse STR as a long, reporting warnings on behalf of ME. */ +static long +arg_long (struct m4 *context, const char *me, const char *str) +{ + char *endp; + long value; -#define ARG_LONG(i, argc, argv) \ - ((argc <= i++) ? 0L : atol (M4ARG (i - 1))) + /* TODO - also allow parsing `'a' or `"a' which results in the + numeric value of 'a', as in printf(1). */ + if (*str == '\0') + { + m4_warn (context, 0, me, _("empty string treated as 0")); + return 0L; + } + errno = 0; + value = strtol (str, &endp, 10); + if (*endp != '\0') + m4_warn (context, 0, me, _("non-numeric argument `%s'"), str); + else if (isspace (to_uchar (*str))) + m4_warn (context, 0, me, _("leading whitespace ignored")); + else if (errno == ERANGE) + m4_warn (context, 0, me, _("numeric overflow detected")); + return value; +} -#define ARG_STR(i, argc, argv) \ - ((argc <= i++) ? "" : M4ARG (i - 1)) +/* Parse STR as a double, reporting warnings on behalf of ME. */ +static double +arg_double (struct m4 *context, const char *me, const char *str) +{ + char *endp; + double value; -#define ARG_DOUBLE(i, argc, argv) \ - ((argc <= i++) ? 0.0 : atof (M4ARG (i - 1))) + if (*str == '\0') + { + m4_warn (context, 0, me, _("empty string treated as 0")); + return 0.0; + } + errno = 0; + value = strtod (str, &endp); + if (*endp != '\0') + m4_warn (context, 0, me, _("non-numeric argument `%s'"), str); + else if (isspace (to_uchar (*str))) + m4_warn (context, 0, me, _("leading whitespace ignored")); + else if (errno == ERANGE) + m4_warn (context, 0, me, _("numeric overflow detected")); + return value; +} + +#define ARG_INT(i, argc, argv) \ + ((argc <= ++i) ? 0 : arg_int (context, me, M4ARG (i))) + +#define ARG_LONG(i, argc, argv) \ + ((argc <= ++i) ? 0L : arg_long (context, me, M4ARG (i))) + +#define ARG_STR(i, argc, argv) \ + ((argc <= ++i) ? "" : M4ARG (i)) + +#define ARG_DOUBLE(i, argc, argv) \ + ((argc <= ++i) ? 0.0 : arg_double (context, me, M4ARG (i))) /* The main formatting function. Output is placed on the obstack OBS, @@ -52,30 +121,31 @@ static void format (m4 *context, m4_obstack *obs, int argc, m4_macro_args *argv) { - const char *name = M4ARG (0); /* Macro name. */ + const char *me = M4ARG (0); /* Macro name. */ const char *f; /* Format control string. */ const char *fmt; /* Position within f. */ char fstart[] = "%'+- 0#*.*hhd"; /* Current format spec. */ char *p; /* Position within fstart. */ unsigned char c; /* A simple character. */ - int index = 1; /* Index within argc used so far. */ + int index = 0; /* Index within argc used so far. */ + bool valid_format = true; /* True if entire format string ok. */ /* Flags. */ - char flags; /* flags to use in fstart */ + char flags; /* Flags to use in fstart. */ enum { - THOUSANDS = 0x01, /* ' */ - PLUS = 0x02, /* + */ - MINUS = 0x04, /* - */ - SPACE = 0x08, /* */ - ZERO = 0x10, /* 0 */ - ALT = 0x20, /* # */ - DONE = 0x40 /* no more flags */ + THOUSANDS = 0x01, /* '\''. */ + PLUS = 0x02, /* '+'. */ + MINUS = 0x04, /* '-'. */ + SPACE = 0x08, /* ' '. */ + ZERO = 0x10, /* '0'. */ + ALT = 0x20, /* '#'. */ + DONE = 0x40 /* No more flags. */ }; /* Precision specifiers. */ - int width; /* minimum field width */ - int prec; /* precision */ - char lflag; /* long flag */ + int width; /* Minimum field width. */ + int prec; /* Precision. */ + char lflag; /* Long flag. */ /* Specifiers we are willing to accept. ok['x'] implies %x is ok. Various modifiers reduce the set, in order to avoid undefined @@ -83,17 +153,23 @@ format (m4 *context, m4_obstack *obs, int argc, m4_macro_args *argv) char ok[128]; /* Buffer and stuff. */ - char *str; /* malloc'd buffer of formatted text */ + char *base; /* Current position in obs. */ + size_t len; /* Length of formatted text. */ + char *str; /* Malloc'd buffer of formatted text. */ enum {CHAR, INT, LONG, DOUBLE, STR} datatype; f = fmt = ARG_STR (index, argc, argv); memset (ok, 0, sizeof ok); - for (;;) + while (true) { while ((c = *fmt++) != '%') { if (c == '\0') - return; + { + if (valid_format) + m4_bad_argc (context, argc, me, index, index, true); + return; + } obstack_1grow (obs, c); } @@ -229,7 +305,8 @@ format (m4 *context, m4_obstack *obs, int argc, m4_macro_args *argv) c = *fmt++; if (c > sizeof ok || !ok[c]) { - m4_warn (context, 0, name, _("unrecognized specifier in `%s'"), f); + m4_warn (context, 0, me, _("unrecognized specifier in `%s'"), f); + valid_format = false; if (c == '\0') fmt--; continue; @@ -272,40 +349,56 @@ format (m4 *context, m4_obstack *obs, int argc, m4_macro_args *argv) } *p++ = c; *p = '\0'; + base = obstack_next_free (obs); + len = obstack_room (obs); switch (datatype) { case CHAR: - str = xasprintf (fstart, width, ARG_INT (index, argc, argv)); + str = asnprintf (base, &len, fstart, width, + ARG_INT (index, argc, argv)); break; case INT: - str = xasprintf (fstart, width, prec, ARG_INT (index, argc, argv)); + str = asnprintf (base, &len, fstart, width, prec, + ARG_INT (index, argc, argv)); break; case LONG: - str = xasprintf (fstart, width, prec, ARG_LONG (index, argc, argv)); + str = asnprintf (base, &len, fstart, width, prec, + ARG_LONG (index, argc, argv)); break; case DOUBLE: - str = xasprintf (fstart, width, prec, + str = asnprintf (base, &len, fstart, width, prec, ARG_DOUBLE (index, argc, argv)); break; case STR: - str = xasprintf (fstart, width, prec, ARG_STR (index, argc, argv)); + str = asnprintf (base, &len, fstart, width, prec, + ARG_STR (index, argc, argv)); break; default: abort (); } - /* NULL was returned on failure, such as invalid format string. For - now, just silently ignore that bad specifier. */ if (str == NULL) - continue; - - obstack_grow (obs, str, strlen (str)); - free (str); + /* NULL is unexpected (EILSEQ and EINVAL are not possible + based on our construction of fstart, leaving only ENOMEM, + which should always be fatal). */ + m4_error (context, EXIT_FAILURE, errno, me, + _("unable to format output for `%s'"), f); + else if (str == base) + /* The output was already computed in place, but we need to + account for its size. */ + obstack_blank_fast (obs, len); + else + { + /* The output exceeded available obstack space, copy the + allocated string. */ + obstack_grow (obs, str, len); + free (str); + } } } |