1
0
mirror of git://git.sv.gnu.org/coreutils.git synced 2026-04-10 06:03:31 +02:00

printf cleanup, to avoid undefined behavior, to add support for

formats that Bash supports, and to support wide integers like
Bash does.

(UNSPECIFIED): Remove.  All uses now replaced by
booleans, so that we don't reserve any values for precision or
width (like Bash).
(STRTOX): Use prototype, not K&R-style definition.
(vstrtoimax): Renamed from xstrtol (to avoid confusion with xstrtol
in ../lib), with type change to intmax_t.
All uses changed.
(vstrtoumax): Renamed from xstrtoul, with type change to uintmax_t.
All uses changed.
(vstrtod): Renamed from xstrtod.  All uses changed.
(print_direc): Use boolean arg instead of special value to indicate
a missing precision or width.  LENGTH no longer includes
length modifiers or conversion character.  New arg CONVERSION
now specifies conversion character.
Use intmax_t-width formatting for integers (like Bash).
Add support for C99 %a, %A, %F (like Bash).
Add support for field width with %c (POSIX requires this).
Add a FIXME for lack of support for field width and precision
for %b.
Add support for '\'', '0' flags.
Check for invalid combinations of flags, field width, precision,
and conversion, to prevent use of undefined behavior.
Allow multiple length modifiers, for formats like "%lld" (like Bash).
Add support for C99 'j', 't', 'z' length modifiers (like Bash).
In error message, output entire invalid conversion specification,
instead of merely outputting % followed by the conversion char.
This commit is contained in:
Jim Meyering
2004-07-08 14:01:49 +00:00
parent 2aa1f2d16b
commit 62e1d5259d

View File

@@ -67,9 +67,6 @@
(c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : (c) - '0')
#define octtobin(c) ((c) - '0')
/* A value for field_width or precision that indicates it was not specified. */
#define UNSPECIFIED INT_MIN
/* The value to return to the calling program. */
static int exit_status;
@@ -162,8 +159,7 @@ verify (const char *s, const char *end)
#define STRTOX(TYPE, FUNC_NAME, LIB_FUNC_EXPR) \
static TYPE \
FUNC_NAME (s) \
const char *s; \
FUNC_NAME (char const *s) \
{ \
char *end; \
TYPE val; \
@@ -187,9 +183,9 @@ FUNC_NAME (s) \
return val; \
} \
STRTOX (unsigned long int, xstrtoul, (strtoul (s, &end, 0)))
STRTOX (long int, xstrtol, (strtol (s, &end, 0)))
STRTOX (double, xstrtod, (c_strtod (s, &end)))
STRTOX (intmax_t, vstrtoimax, (strtoimax (s, &end, 0)))
STRTOX (uintmax_t, vstrtoumax, (strtoumax (s, &end, 0)))
STRTOX (double, vstrtod, (c_strtod (s, &end)))
/* Output a single-character \ escape. */
@@ -317,97 +313,138 @@ print_esc_string (const char *str)
putchar (*str);
}
/* Output a % directive. START is the start of the directive,
LENGTH is its length, and ARGUMENT is its argument.
If FIELD_WIDTH or PRECISION is UNSPECIFIED, they are args for
'*' values in those fields. */
/* Evaluate a printf conversion specification. START is the start of
the directive, LENGTH is its length, and CONVERSION specifies the
type of conversion. LENGTH does not include any length modifier or
the conversion specifier itself. FIELD_WIDTH and PRECISION are the
field width and precision for '*' values, if HAVE_FIELD_WIDTH and
HAVE_PRECISION are true, respectively. ARGUMENT is the argument to
be formatted. */
static void
print_direc (const char *start, size_t length, int field_width,
int precision, const char *argument)
print_direc (const char *start, size_t length, char conversion,
bool have_field_width, int field_width,
bool have_precision, int precision,
char const *argument)
{
char *p; /* Null-terminated copy of % directive. */
p = xmalloc ((unsigned) (length + 1));
strncpy (p, start, length);
p[length] = 0;
/* Create a null-terminated copy of the % directive, with an
intmax_t-wide length modifier substituted for any existing
integer length modifier. */
{
char *q;
size_t length_modifier_len;
switch (p[length - 1])
switch (conversion)
{
case 'd': case 'i': case 'o': case 'u': case 'x': case 'X':
length_modifier_len = sizeof PRIdMAX - 2;
break;
default:
length_modifier_len = 0;
break;
}
p = xmalloc (length + length_modifier_len + 2);
q = mempcpy (p, start, length);
q = mempcpy (q, PRIdMAX, length_modifier_len);
*q++ = conversion;
*q = '\0';
}
switch (conversion)
{
case 'd':
case 'i':
if (field_width == UNSPECIFIED)
{
if (precision == UNSPECIFIED)
printf (p, xstrtol (argument));
else
printf (p, precision, xstrtol (argument));
}
else
{
if (precision == UNSPECIFIED)
printf (p, field_width, xstrtol (argument));
else
printf (p, field_width, precision, xstrtol (argument));
}
{
intmax_t arg = vstrtoimax (argument);
if (!have_field_width)
{
if (!have_precision)
printf (p, arg);
else
printf (p, precision, arg);
}
else
{
if (!have_precision)
printf (p, field_width, arg);
else
printf (p, field_width, precision, arg);
}
}
break;
case 'o':
case 'u':
case 'x':
case 'X':
if (field_width == UNSPECIFIED)
{
if (precision == UNSPECIFIED)
printf (p, xstrtoul (argument));
else
printf (p, precision, xstrtoul (argument));
}
else
{
if (precision == UNSPECIFIED)
printf (p, field_width, xstrtoul (argument));
else
printf (p, field_width, precision, xstrtoul (argument));
}
{
uintmax_t arg = vstrtoumax (argument);
if (!have_field_width)
{
if (!have_precision)
printf (p, arg);
else
printf (p, precision, arg);
}
else
{
if (!have_precision)
printf (p, field_width, arg);
else
printf (p, field_width, precision, arg);
}
}
break;
case 'f':
case 'a':
case 'A':
case 'e':
case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
if (field_width == UNSPECIFIED)
{
if (precision == UNSPECIFIED)
printf (p, xstrtod (argument));
else
printf (p, precision, xstrtod (argument));
}
else
{
if (precision == UNSPECIFIED)
printf (p, field_width, xstrtod (argument));
else
printf (p, field_width, precision, xstrtod (argument));
}
{
double arg = vstrtod (argument);
if (!have_field_width)
{
if (!have_precision)
printf (p, arg);
else
printf (p, precision, arg);
}
else
{
if (!have_precision)
printf (p, field_width, arg);
else
printf (p, field_width, precision, arg);
}
}
break;
case 'c':
printf (p, *argument);
if (!have_field_width)
printf (p, *argument);
else
printf (p, field_width, *argument);
break;
case 's':
if (field_width == UNSPECIFIED)
if (!have_field_width)
{
if (precision == UNSPECIFIED)
if (!have_precision)
printf (p, argument);
else
printf (p, precision, argument);
}
else
{
if (precision == UNSPECIFIED)
if (!have_precision)
printf (p, field_width, argument);
else
printf (p, field_width, precision, argument);
@@ -429,8 +466,11 @@ print_formatted (const char *format, int argc, char **argv)
const char *f; /* Pointer into `format'. */
const char *direc_start; /* Start of % directive. */
size_t direc_length; /* Length of % directive. */
int field_width; /* Arg to first '*', or UNSPECIFIED if none. */
int precision; /* Arg to second '*', or UNSPECIFIED if none. */
bool have_field_width; /* True if FIELD_WIDTH is valid. */
int field_width = 0; /* Arg to first '*'. */
bool have_precision; /* True if PRECISION is valid. */
int precision = 0; /* Arg to second '*'. */
char ok[UCHAR_MAX + 1]; /* ok['x'] is true if %x is allowed. */
for (f = format; *f; ++f)
{
@@ -439,7 +479,7 @@ print_formatted (const char *format, int argc, char **argv)
case '%':
direc_start = f++;
direc_length = 1;
field_width = precision = UNSPECIFIED;
have_field_width = have_precision = false;
if (*f == '%')
{
putchar ('%');
@@ -447,6 +487,8 @@ print_formatted (const char *format, int argc, char **argv)
}
if (*f == 'b')
{
/* FIXME: Field width and precision are not supported
for %b, even though POSIX requires it. */
if (argc > 0)
{
print_esc_string (*argv);
@@ -455,19 +497,42 @@ print_formatted (const char *format, int argc, char **argv)
}
break;
}
while (*f == ' ' || *f == '#' || *f == '+' || *f == '-')
{
++f;
++direc_length;
}
memset (ok, 0, sizeof ok);
ok['a'] = ok['A'] = ok['c'] = ok['d'] = ok['e'] = ok['E'] =
ok['f'] = ok['F'] = ok['g'] = ok['G'] = ok['i'] = ok['o'] =
ok['s'] = ok['u'] = ok['x'] = ok['X'] = 1;
for (;; f++, direc_length++)
switch (*f)
{
case '\'':
ok['a'] = ok['A'] = ok['c'] = ok['e'] = ok['E'] =
ok['o'] = ok['s'] = ok['x'] = ok['X'] = 0;
break;
case '-': case '+': case ' ':
break;
case '#':
ok['c'] = ok['d'] = ok['i'] = ok['s'] = ok['u'] = 0;
break;
case '0':
ok['c'] = ok['s'] = 0;
break;
default:
goto no_more_flag_characters;
}
no_more_flag_characters:;
if (*f == '*')
{
++f;
++direc_length;
if (argc > 0)
{
field_width = xstrtoul (*argv);
if (field_width == UNSPECIFIED)
intmax_t width = vstrtoimax (*argv);
if (INT_MIN <= width && width <= INT_MAX)
field_width = width;
else
error (EXIT_FAILURE, 0, _("invalid field width: %s"),
*argv);
++argv;
@@ -475,6 +540,7 @@ print_formatted (const char *format, int argc, char **argv)
}
else
field_width = 0;
have_field_width = true;
}
else
while (ISDIGIT (*f))
@@ -486,21 +552,32 @@ print_formatted (const char *format, int argc, char **argv)
{
++f;
++direc_length;
ok['c'] = 0;
if (*f == '*')
{
++f;
++direc_length;
if (argc > 0)
{
precision = xstrtoul (*argv);
if (precision == UNSPECIFIED)
intmax_t prec = vstrtoimax (*argv);
if (prec < 0)
{
/* A negative precision is taken as if the
precision were omitted, so -1 is safe
here even if prec < INT_MIN. */
precision = -1;
}
else if (INT_MAX < prec)
error (EXIT_FAILURE, 0, _("invalid precision: %s"),
*argv);
else
precision = prec;
++argv;
--argc;
}
else
precision = 0;
have_precision = true;
}
else
while (ISDIGIT (*f))
@@ -509,24 +586,23 @@ print_formatted (const char *format, int argc, char **argv)
++direc_length;
}
}
if (*f == 'l' || *f == 'L' || *f == 'h')
{
++f;
++direc_length;
}
if (! (*f && strchr ("diouxXfeEgGcs", *f)))
error (EXIT_FAILURE, 0, _("%%%c: invalid directive"), *f);
++direc_length;
if (argc > 0)
{
print_direc (direc_start, direc_length, field_width,
precision, *argv);
++argv;
--argc;
}
else
print_direc (direc_start, direc_length, field_width,
precision, "");
while (*f == 'l' || *f == 'L' || *f == 'h'
|| *f == 'j' || *f == 't' || *f == 'z')
++f;
{
unsigned char conversion = *f;
if (! ok[conversion])
error (EXIT_FAILURE, 0,
_("%.*s: invalid conversion specification"),
(int) (f + 1 - direc_start), direc_start);
}
print_direc (direc_start, direc_length, *f,
have_field_width, field_width,
have_precision, precision,
(argc <= 0 ? "" : (argc--, *argv++)));
break;
case '\\':