Blame printf/repl-vsnprintf.c

Packit 5c3484
/* __gmp_replacement_vsnprintf -- for systems which don't have vsnprintf, or
Packit 5c3484
   only have a broken one.
Packit 5c3484
Packit 5c3484
   THE FUNCTIONS IN THIS FILE ARE FOR INTERNAL USE ONLY.  THEY'RE ALMOST
Packit 5c3484
   CERTAIN TO BE SUBJECT TO INCOMPATIBLE CHANGES OR DISAPPEAR COMPLETELY IN
Packit 5c3484
   FUTURE GNU MP RELEASES.
Packit 5c3484
Packit 5c3484
Copyright 2001, 2002 Free Software Foundation, Inc.
Packit 5c3484
Packit 5c3484
This file is part of the GNU MP Library.
Packit 5c3484
Packit 5c3484
The GNU MP Library is free software; you can redistribute it and/or modify
Packit 5c3484
it under the terms of either:
Packit 5c3484
Packit 5c3484
  * the GNU Lesser General Public License as published by the Free
Packit 5c3484
    Software Foundation; either version 3 of the License, or (at your
Packit 5c3484
    option) any later version.
Packit 5c3484
Packit 5c3484
or
Packit 5c3484
Packit 5c3484
  * the GNU General Public License as published by the Free Software
Packit 5c3484
    Foundation; either version 2 of the License, or (at your option) any
Packit 5c3484
    later version.
Packit 5c3484
Packit 5c3484
or both in parallel, as here.
Packit 5c3484
Packit 5c3484
The GNU MP Library is distributed in the hope that it will be useful, but
Packit 5c3484
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
Packit 5c3484
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
Packit 5c3484
for more details.
Packit 5c3484
Packit 5c3484
You should have received copies of the GNU General Public License and the
Packit 5c3484
GNU Lesser General Public License along with the GNU MP Library.  If not,
Packit 5c3484
see https://www.gnu.org/licenses/.  */
Packit 5c3484
Packit 5c3484
#include "config.h"
Packit 5c3484
Packit 5c3484
#if ! HAVE_VSNPRINTF   /* only need this file if we don't have vsnprintf */
Packit 5c3484
Packit 5c3484
Packit 5c3484
#define _GNU_SOURCE    /* for strnlen prototype */
Packit 5c3484
Packit 5c3484
#include <stdarg.h>
Packit 5c3484
#include <ctype.h>     /* for isdigit */
Packit 5c3484
#include <stddef.h>    /* for ptrdiff_t */
Packit 5c3484
#include <string.h>
Packit 5c3484
#include <stdio.h>     /* for NULL */
Packit 5c3484
#include <stdlib.h>
Packit 5c3484
Packit 5c3484
#if HAVE_FLOAT_H
Packit 5c3484
#include <float.h>     /* for DBL_MAX_10_EXP etc */
Packit 5c3484
#endif
Packit 5c3484
Packit 5c3484
#if HAVE_INTTYPES_H
Packit 5c3484
# include <inttypes.h> /* for intmax_t */
Packit 5c3484
#else
Packit 5c3484
# if HAVE_STDINT_H
Packit 5c3484
#  include <stdint.h>
Packit 5c3484
# endif
Packit 5c3484
#endif
Packit 5c3484
Packit 5c3484
#if HAVE_SYS_TYPES_H
Packit 5c3484
#include <sys/types.h> /* for quad_t */
Packit 5c3484
#endif
Packit 5c3484
Packit 5c3484
#include "gmp.h"
Packit 5c3484
#include "gmp-impl.h"
Packit 5c3484
Packit 5c3484
Packit 5c3484
/* Autoconf notes that AIX 4.3 has a broken strnlen, but fortunately it
Packit 5c3484
   doesn't affect us since __gmp_replacement_vsnprintf is not required on
Packit 5c3484
   that system.  */
Packit 5c3484
#if ! HAVE_STRNLEN
Packit 5c3484
static size_t
Packit 5c3484
strnlen (const char *s, size_t n)
Packit 5c3484
{
Packit 5c3484
  size_t  i;
Packit 5c3484
  for (i = 0; i < n; i++)
Packit 5c3484
    if (s[i] == '\0')
Packit 5c3484
      break;
Packit 5c3484
  return i;
Packit 5c3484
}
Packit 5c3484
#endif
Packit 5c3484
Packit 5c3484
Packit 5c3484
/* The approach here is to parse the fmt string, and decide how much space
Packit 5c3484
   it requires, then use vsprintf into a big enough buffer.  The space
Packit 5c3484
   calculated isn't an exact amount, but it's certainly no less than
Packit 5c3484
   required.
Packit 5c3484
Packit 5c3484
   This code was inspired by GNU libiberty/vasprintf.c but we support more
Packit 5c3484
   datatypes, when available.
Packit 5c3484
Packit 5c3484
   mingw32 - doesn't have vsnprintf, it seems.  Because gcc is used a full
Packit 5c3484
       set of types are available, but "long double" is just a plain IEEE
Packit 5c3484
       64-bit "double" and LDBL_MAX_EXP_10 is correspondingly defined, so we
Packit 5c3484
       avoid the big 15-bit exponent estimate.  */
Packit 5c3484
Packit 5c3484
int
Packit 5c3484
__gmp_replacement_vsnprintf (char *buf, size_t buf_size,
Packit 5c3484
			     const char *orig_fmt, va_list orig_ap)
Packit 5c3484
{
Packit 5c3484
  va_list     ap;
Packit 5c3484
  const char  *fmt;
Packit 5c3484
  size_t      total_width, integer_sizeof, floating_sizeof, len;
Packit 5c3484
  char        fchar, type;
Packit 5c3484
  int         width, prec, seen_prec, double_digits, long_double_digits;
Packit 5c3484
  int         *value;
Packit 5c3484
Packit 5c3484
  /* preserve orig_ap for use after size estimation */
Packit 5c3484
  va_copy (ap, orig_ap);
Packit 5c3484
Packit 5c3484
  fmt = orig_fmt;
Packit 5c3484
  total_width = strlen (fmt) + 1;   /* 1 extra for the '\0' */
Packit 5c3484
Packit 5c3484
  integer_sizeof = sizeof (long);
Packit 5c3484
#if HAVE_LONG_LONG
Packit 5c3484
  integer_sizeof = MAX (integer_sizeof, sizeof (long long));
Packit 5c3484
#endif
Packit 5c3484
#if HAVE_QUAD_T
Packit 5c3484
  integer_sizeof = MAX (integer_sizeof, sizeof (quad_t));
Packit 5c3484
#endif
Packit 5c3484
Packit 5c3484
  floating_sizeof = sizeof (double);
Packit 5c3484
#if HAVE_LONG_DOUBLE
Packit 5c3484
  floating_sizeof = MAX (floating_sizeof, sizeof (long double));
Packit 5c3484
#endif
Packit 5c3484
Packit 5c3484
  /* IEEE double or VAX G floats have an 11 bit exponent, so the default is
Packit 5c3484
     a maximum 308 decimal digits.  VAX D floats have only an 8 bit
Packit 5c3484
     exponent, but we don't bother trying to detect that directly.  */
Packit 5c3484
  double_digits = 308;
Packit 5c3484
#ifdef DBL_MAX_10_EXP
Packit 5c3484
  /* but in any case prefer a value the compiler says */
Packit 5c3484
  double_digits = DBL_MAX_10_EXP;
Packit 5c3484
#endif
Packit 5c3484
Packit 5c3484
  /* IEEE 128-bit quad, Intel 80-bit temporary, or VAX H floats all have 15
Packit 5c3484
     bit exponents, so the default is a maximum 4932 decimal digits.  */
Packit 5c3484
  long_double_digits = 4932;
Packit 5c3484
  /* but if double == long double, then go with that size */
Packit 5c3484
#if HAVE_LONG_DOUBLE
Packit 5c3484
  if (sizeof (double) == sizeof (long double))
Packit 5c3484
    long_double_digits = double_digits;
Packit 5c3484
#endif
Packit 5c3484
#ifdef LDBL_MAX_10_EXP
Packit 5c3484
  /* but in any case prefer a value the compiler says */
Packit 5c3484
  long_double_digits = LDBL_MAX_10_EXP;
Packit 5c3484
#endif
Packit 5c3484
Packit 5c3484
  for (;;)
Packit 5c3484
    {
Packit 5c3484
      fmt = strchr (fmt, '%');
Packit 5c3484
      if (fmt == NULL)
Packit 5c3484
	break;
Packit 5c3484
      fmt++;
Packit 5c3484
Packit 5c3484
      type = '\0';
Packit 5c3484
      width = 0;
Packit 5c3484
      prec = 6;
Packit 5c3484
      seen_prec = 0;
Packit 5c3484
      value = &width;
Packit 5c3484
Packit 5c3484
      for (;;)
Packit 5c3484
	{
Packit 5c3484
	  fchar = *fmt++;
Packit 5c3484
	  switch (fchar) {
Packit 5c3484
Packit 5c3484
	  case 'c':
Packit 5c3484
	    /* char, already accounted for by strlen(fmt) */
Packit 5c3484
	    goto next;
Packit 5c3484
Packit 5c3484
	  case 'd':
Packit 5c3484
	  case 'i':
Packit 5c3484
	  case 'o':
Packit 5c3484
	  case 'x':
Packit 5c3484
	  case 'X':
Packit 5c3484
	  case 'u':
Packit 5c3484
	    /* at most 3 digits per byte in hex, dec or octal, plus a sign */
Packit 5c3484
	    total_width += 3 * integer_sizeof + 1;
Packit 5c3484
Packit 5c3484
	    switch (type) {
Packit 5c3484
	    case 'j':
Packit 5c3484
	      /* Let's assume uintmax_t is the same size as intmax_t. */
Packit 5c3484
#if HAVE_INTMAX_T
Packit 5c3484
	      (void) va_arg (ap, intmax_t);
Packit 5c3484
#else
Packit 5c3484
	      ASSERT_FAIL (intmax_t not available);
Packit 5c3484
#endif
Packit 5c3484
	      break;
Packit 5c3484
	    case 'l':
Packit 5c3484
	      (void) va_arg (ap, long);
Packit 5c3484
	      break;
Packit 5c3484
	    case 'L':
Packit 5c3484
#if HAVE_LONG_LONG
Packit 5c3484
	      (void) va_arg (ap, long long);
Packit 5c3484
#else
Packit 5c3484
	      ASSERT_FAIL (long long not available);
Packit 5c3484
#endif
Packit 5c3484
	      break;
Packit 5c3484
	    case 'q':
Packit 5c3484
	      /* quad_t is probably the same as long long, but let's treat
Packit 5c3484
		 it separately just to be sure.  Also let's assume u_quad_t
Packit 5c3484
		 will be the same size as quad_t.  */
Packit 5c3484
#if HAVE_QUAD_T
Packit 5c3484
	      (void) va_arg (ap, quad_t);
Packit 5c3484
#else
Packit 5c3484
	      ASSERT_FAIL (quad_t not available);
Packit 5c3484
#endif
Packit 5c3484
	      break;
Packit 5c3484
	    case 't':
Packit 5c3484
#if HAVE_PTRDIFF_T
Packit 5c3484
	      (void) va_arg (ap, ptrdiff_t);
Packit 5c3484
#else
Packit 5c3484
	      ASSERT_FAIL (ptrdiff_t not available);
Packit 5c3484
#endif
Packit 5c3484
	      break;
Packit 5c3484
	    case 'z':
Packit 5c3484
	      (void) va_arg (ap, size_t);
Packit 5c3484
	      break;
Packit 5c3484
	    default:
Packit 5c3484
	      /* default is an "int", and this includes h=short and hh=char
Packit 5c3484
		 since they're promoted to int in a function call */
Packit 5c3484
	      (void) va_arg (ap, int);
Packit 5c3484
	      break;
Packit 5c3484
	    }
Packit 5c3484
	    goto next;
Packit 5c3484
Packit 5c3484
	  case 'E':
Packit 5c3484
	  case 'e':
Packit 5c3484
	  case 'G':
Packit 5c3484
	  case 'g':
Packit 5c3484
	    /* Requested decimals, sign, point and e, plus an overestimate
Packit 5c3484
	       of exponent digits (the assumption is all the float is
Packit 5c3484
	       exponent!).  */
Packit 5c3484
	    total_width += prec + 3 + floating_sizeof * 3;
Packit 5c3484
	    if (type == 'L')
Packit 5c3484
	      {
Packit 5c3484
#if HAVE_LONG_DOUBLE
Packit 5c3484
		(void) va_arg (ap, long double);
Packit 5c3484
#else
Packit 5c3484
		ASSERT_FAIL (long double not available);
Packit 5c3484
#endif
Packit 5c3484
	      }
Packit 5c3484
	    else
Packit 5c3484
	      (void) va_arg (ap, double);
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  case 'f':
Packit 5c3484
	    /* Requested decimals, sign and point, and a margin for error,
Packit 5c3484
	       then add the maximum digits that can be in the integer part,
Packit 5c3484
	       based on the maximum exponent value. */
Packit 5c3484
	    total_width += prec + 2 + 10;
Packit 5c3484
	    if (type == 'L')
Packit 5c3484
	      {
Packit 5c3484
#if HAVE_LONG_DOUBLE
Packit 5c3484
		(void) va_arg (ap, long double);
Packit 5c3484
		total_width += long_double_digits;
Packit 5c3484
#else
Packit 5c3484
		ASSERT_FAIL (long double not available);
Packit 5c3484
#endif
Packit 5c3484
	      }
Packit 5c3484
	    else
Packit 5c3484
	      {
Packit 5c3484
		(void) va_arg (ap, double);
Packit 5c3484
		total_width += double_digits;
Packit 5c3484
	      }
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  case 'h':  /* short or char */
Packit 5c3484
	  case 'j':  /* intmax_t */
Packit 5c3484
	  case 'L':  /* long long or long double */
Packit 5c3484
	  case 'q':  /* quad_t */
Packit 5c3484
	  case 't':  /* ptrdiff_t */
Packit 5c3484
	  case 'z':  /* size_t */
Packit 5c3484
	  set_type:
Packit 5c3484
	    type = fchar;
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  case 'l':
Packit 5c3484
	    /* long or long long */
Packit 5c3484
	    if (type != 'l')
Packit 5c3484
	      goto set_type;
Packit 5c3484
	    type = 'L';   /* "ll" means "L" */
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  case 'n':
Packit 5c3484
	    /* bytes written, no output as such */
Packit 5c3484
	    (void) va_arg (ap, void *);
Packit 5c3484
	    goto next;
Packit 5c3484
Packit 5c3484
	  case 's':
Packit 5c3484
	    /* If no precision was given, then determine the string length
Packit 5c3484
	       and put it there, to be added to the total under "next".  If
Packit 5c3484
	       a precision was given then that's already the maximum from
Packit 5c3484
	       this field, but see whether the string is shorter than that,
Packit 5c3484
	       in case the limit was very big.  */
Packit 5c3484
	    {
Packit 5c3484
	      const char  *s = va_arg (ap, const char *);
Packit 5c3484
	      prec = (seen_prec ? strnlen (s, prec) : strlen (s));
Packit 5c3484
	    }
Packit 5c3484
	    goto next;
Packit 5c3484
Packit 5c3484
	  case 'p':
Packit 5c3484
	    /* pointer, let's assume at worst it's octal with some padding */
Packit 5c3484
	    (void) va_arg (ap, const void *);
Packit 5c3484
	    total_width += 3 * sizeof (void *) + 16;
Packit 5c3484
	    goto next;
Packit 5c3484
Packit 5c3484
	  case '%':
Packit 5c3484
	    /* literal %, already accounted for by strlen(fmt) */
Packit 5c3484
	    goto next;
Packit 5c3484
Packit 5c3484
	  case '#':
Packit 5c3484
	    /* showbase, at most 2 for "0x" */
Packit 5c3484
	    total_width += 2;
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  case '+':
Packit 5c3484
	  case ' ':
Packit 5c3484
	    /* sign, already accounted for under numerics */
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  case '-':
Packit 5c3484
	    /* left justify, no effect on total width */
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  case '.':
Packit 5c3484
	    seen_prec = 1;
Packit 5c3484
	    value = ≺
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  case '*':
Packit 5c3484
	    {
Packit 5c3484
	      /* negative width means left justify which can be ignored,
Packit 5c3484
		 negative prec would be invalid, just use absolute value */
Packit 5c3484
	      int n = va_arg (ap, int);
Packit 5c3484
	      *value = ABS (n);
Packit 5c3484
	    }
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  case '0': case '1': case '2': case '3': case '4':
Packit 5c3484
	  case '5': case '6': case '7': case '8': case '9':
Packit 5c3484
	    /* process all digits to form a value */
Packit 5c3484
	    {
Packit 5c3484
	      int  n = 0;
Packit 5c3484
	      do {
Packit 5c3484
		n = n * 10 + (fchar-'0');
Packit 5c3484
		fchar = *fmt++;
Packit 5c3484
	      } while (isascii (fchar) && isdigit (fchar));
Packit 5c3484
	      fmt--; /* unget the non-digit */
Packit 5c3484
	      *value = n;
Packit 5c3484
	    }
Packit 5c3484
	    break;
Packit 5c3484
Packit 5c3484
	  default:
Packit 5c3484
	    /* incomplete or invalid % sequence */
Packit 5c3484
	    ASSERT (0);
Packit 5c3484
	    goto next;
Packit 5c3484
	  }
Packit 5c3484
	}
Packit 5c3484
Packit 5c3484
    next:
Packit 5c3484
      total_width += width;
Packit 5c3484
      total_width += prec;
Packit 5c3484
    }
Packit 5c3484
Packit 5c3484
  if (total_width <= buf_size)
Packit 5c3484
    {
Packit 5c3484
      vsprintf (buf, orig_fmt, orig_ap);
Packit 5c3484
      len = strlen (buf);
Packit 5c3484
    }
Packit 5c3484
  else
Packit 5c3484
    {
Packit 5c3484
      char  *s;
Packit 5c3484
Packit 5c3484
      s = __GMP_ALLOCATE_FUNC_TYPE (total_width, char);
Packit 5c3484
      vsprintf (s, orig_fmt, orig_ap);
Packit 5c3484
      len = strlen (s);
Packit 5c3484
      if (buf_size != 0)
Packit 5c3484
	{
Packit 5c3484
	  size_t  copylen = MIN (len, buf_size-1);
Packit 5c3484
	  memcpy (buf, s, copylen);
Packit 5c3484
	  buf[copylen] = '\0';
Packit 5c3484
	}
Packit 5c3484
      (*__gmp_free_func) (s, total_width);
Packit 5c3484
    }
Packit 5c3484
Packit 5c3484
  /* If total_width was somehow wrong then chances are we've already
Packit 5c3484
     clobbered memory, but maybe this check will still work.  */
Packit 5c3484
  ASSERT_ALWAYS (len < total_width);
Packit 5c3484
Packit 5c3484
  return len;
Packit 5c3484
}
Packit 5c3484
Packit 5c3484
#endif /* ! HAVE_VSNPRINTF */