diff options
-rw-r--r-- | ChangeLog | 10 | ||||
-rw-r--r-- | stdio-common/Makefile | 2 | ||||
-rw-r--r-- | stdio-common/tst-scanf-round.c | 51 | ||||
-rw-r--r-- | stdio-common/vfscanf.c | 35 |
4 files changed, 81 insertions, 17 deletions
diff --git a/ChangeLog b/ChangeLog index e83db88929..90b65fd3a6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2018-06-19 Joseph Myers <joseph@codesourcery.com> + + [BZ #23280] + * stdio-common/vfscanf.c (_IO_vfscanf_internal): Pass sign of + floating-point number to strtod functions rather than possibly + negating result of those functions. + * stdio-common/tst-scanf-round.c: New file. + * stdio-common/Makefile (tests): Add tst-scanf-round. + ($(objpfx)tst-scanf-round): Depend on $(libm). + 2018-06-18 Samuel Thibault <samuel.thibault@ens-lyon.org> * sysdeps/mach/hurd/localplt.data: Move to... diff --git a/stdio-common/Makefile b/stdio-common/Makefile index 9dfc115313..45038372ff 100644 --- a/stdio-common/Makefile +++ b/stdio-common/Makefile @@ -61,6 +61,7 @@ tests := tstscanf test_rdwr test-popen tstgetln test-fseek \ tst-printf-bz18872 tst-vfprintf-width-prec tst-fmemopen4 \ tst-vfprintf-user-type \ tst-vfprintf-mbs-prec \ + tst-scanf-round \ test-srcs = tst-unbputc tst-printf @@ -158,3 +159,4 @@ $(objpfx)tst-setvbuf1-cmp.out: tst-setvbuf1.expect $(objpfx)tst-setvbuf1.out $(evaluate-test) $(objpfx)tst-printf-round: $(libm) +$(objpfx)tst-scanf-round: $(libm) diff --git a/stdio-common/tst-scanf-round.c b/stdio-common/tst-scanf-round.c new file mode 100644 index 0000000000..a2fb620abf --- /dev/null +++ b/stdio-common/tst-scanf-round.c @@ -0,0 +1,51 @@ +/* Test for correct rounding of negative floating-point numbers by scanf + (bug 23280). + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <fenv.h> +#include <stdio.h> +#include <stdlib.h> +#include <support/check.h> + +static int +do_test (void) +{ +#ifdef FE_DOWNWARD + if (fesetround (FE_DOWNWARD) == 0) + { + double a = strtod ("-0.1", NULL); + double b = 0; + int r = sscanf ("-0.1", "%lf", &b); + TEST_VERIFY (r == 1); + TEST_VERIFY (a == b); + } +#endif +#ifdef FE_UPWARD + if (fesetround (FE_UPWARD) == 0) + { + double a = strtod ("-0.1", NULL); + double b = 0; + int r = sscanf ("-0.1", "%lf", &b); + TEST_VERIFY (r == 1); + TEST_VERIFY (a == b); + } +#endif + return 0; +} + +#include <support/test-driver.c> diff --git a/stdio-common/vfscanf.c b/stdio-common/vfscanf.c index 3263268c7e..1ce836a324 100644 --- a/stdio-common/vfscanf.c +++ b/stdio-common/vfscanf.c @@ -292,7 +292,7 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, /* Errno of last failed inchar call. */ int inchar_errno = 0; /* Status for reading F-P nums. */ - char got_digit, got_dot, got_e, negative; + char got_digit, got_dot, got_e, got_sign; /* If a [...] is a [^...]. */ CHAR_T not_in; #define exp_char not_in @@ -1914,20 +1914,19 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, if (__glibc_unlikely (c == EOF)) input_error (); - got_digit = got_dot = got_e = 0; + got_digit = got_dot = got_e = got_sign = 0; /* Check for a sign. */ if (c == L_('-') || c == L_('+')) { - negative = c == L_('-'); + got_sign = 1; + char_buffer_add (&charbuf, c); if (__glibc_unlikely (width == 0 || inchar () == EOF)) /* EOF is only an input error before we read any chars. */ conv_error (); if (width > 0) --width; } - else - negative = 0; /* Take care for the special arguments "nan" and "inf". */ if (TOLOWER (c) == L_('n')) @@ -2176,7 +2175,7 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, digits with ASCII letters. */ && !(flags & HEXA_FLOAT) /* Minimum requirement. */ - && (char_buffer_size (&charbuf) == 0 || got_dot) + && (char_buffer_size (&charbuf) == got_sign || got_dot) && (map = __wctrans ("to_inpunct")) != NULL) { /* Reget the first character. */ @@ -2195,8 +2194,8 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, for localized FP numbers, then we may have localized digits. Note, we test GOT_DOT above. */ #ifdef COMPILE_WSCANF - if (char_buffer_size (&charbuf) == 0 - || (char_buffer_size (&charbuf) == 1 + if (char_buffer_size (&charbuf) == got_sign + || (char_buffer_size (&charbuf) == got_sign + 1 && wcdigits[11] == decimal)) #else char mbdigits[12][MB_LEN_MAX + 1]; @@ -2204,13 +2203,13 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, mbstate_t state; memset (&state, '\0', sizeof (state)); - bool match_so_far = char_buffer_size (&charbuf) == 0; + bool match_so_far = char_buffer_size (&charbuf) == got_sign; size_t mblen = __wcrtomb (mbdigits[11], wcdigits[11], &state); if (mblen != (size_t) -1) { mbdigits[11][mblen] = '\0'; match_so_far |= - (char_buffer_size (&charbuf) == strlen (decimal) + (char_buffer_size (&charbuf) == strlen (decimal) + got_sign && strcmp (decimal, mbdigits[11]) == 0); } else @@ -2220,7 +2219,8 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, from a file. */ if (decimal_len <= MB_LEN_MAX) { - match_so_far |= char_buffer_size (&charbuf) == decimal_len; + match_so_far |= (char_buffer_size (&charbuf) + == decimal_len + got_sign); memcpy (mbdigits[11], decimal, decimal_len + 1); } else @@ -2284,7 +2284,7 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, if (got_e && charbuf.current[-1] == exp_char && (c == L_('-') || c == L_('+'))) char_buffer_add (&charbuf, c); - else if (char_buffer_size (&charbuf) > 0 && !got_e + else if (char_buffer_size (&charbuf) > got_sign && !got_e && (CHAR_T) TOLOWER (c) == exp_char) { char_buffer_add (&charbuf, exp_char); @@ -2408,9 +2408,10 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, /* Have we read any character? If we try to read a number in hexadecimal notation and we have read only the `0x' prefix this is an error. */ - if (__glibc_unlikely (char_buffer_size (&charbuf) == 0 + if (__glibc_unlikely (char_buffer_size (&charbuf) == got_sign || ((flags & HEXA_FLOAT) - && char_buffer_size (&charbuf) == 2))) + && (char_buffer_size (&charbuf) + == 2 + got_sign)))) conv_error (); scan_float: @@ -2427,21 +2428,21 @@ _IO_vfscanf_internal (FILE *s, const char *format, va_list argptr, long double d = __strtold_internal (char_buffer_start (&charbuf), &tw, flags & GROUP); if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf)) - *ARG (long double *) = negative ? -d : d; + *ARG (long double *) = d; } else if (flags & (LONG | LONGDBL)) { double d = __strtod_internal (char_buffer_start (&charbuf), &tw, flags & GROUP); if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf)) - *ARG (double *) = negative ? -d : d; + *ARG (double *) = d; } else { float d = __strtof_internal (char_buffer_start (&charbuf), &tw, flags & GROUP); if (!(flags & SUPPRESS) && tw != char_buffer_start (&charbuf)) - *ARG (float *) = negative ? -d : d; + *ARG (float *) = d; } if (__glibc_unlikely (tw == char_buffer_start (&charbuf))) |