summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog10
-rw-r--r--stdio-common/Makefile2
-rw-r--r--stdio-common/tst-scanf-round.c51
-rw-r--r--stdio-common/vfscanf.c35
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)))