changeset 21687:4418e579cca6

scanf: return maximum/minimum value on integer overflow (bug #47759) * oct-stream.cc (octave_scan_1): Avoid overflow state propagating as a read error. Assign converted value to output argument only when no error occurs. * io.tst: Add %!tests.
author Mike Miller <mtmiller@octave.org>
date Wed, 11 May 2016 13:12:22 -0700
parents f07d6f579214
children 68ab693d16e4
files libinterp/corefcn/oct-stream.cc test/io.tst
diffstat 2 files changed, 37 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/oct-stream.cc	Wed May 11 12:02:22 2016 -0700
+++ b/libinterp/corefcn/oct-stream.cc	Wed May 11 13:12:22 2016 -0700
@@ -4158,16 +4158,16 @@
 std::istream&
 octave_scan_1 (std::istream& is, const scanf_format_elt& fmt, T* valptr)
 {
-  T& ref = *valptr;
+  T value = T ();
 
   switch (fmt.type)
     {
     case 'o':
-      is >> std::oct >> ref >> std::dec;
+      is >> std::oct >> value >> std::dec;
       break;
 
     case 'x':
-      is >> std::hex >> ref >> std::dec;
+      is >> std::hex >> value >> std::dec;
       break;
 
     case 'i':
@@ -4188,42 +4188,55 @@
                   {
                     is.ignore ();
                     if (std::isxdigit (is.peek ()))
-                      is >> std::hex >> ref >> std::dec;
+                      is >> std::hex >> value >> std::dec;
                     else
-                      ref = 0;
+                      value = 0;
                   }
                 else
                   {
                     if (c2 == '0' || c2 == '1' || c2 == '2'
                         || c2 == '3' || c2 == '4' || c2 == '5'
                         || c2 == '6' || c2 == '7')
-                      is >> std::oct >> ref >> std::dec;
+                      is >> std::oct >> value >> std::dec;
                     else if (c2 == '8' || c2 == '9')
                     {
                       // FIXME: Would like to set error state on octave stream.
                       // See bug #46493.  But only std::istream is input to fcn
                       // error ("internal failure to match octal format");
-                      ref = 0;
+                      value = 0;
                     }
                     else
-                      ref = 0;
+                      value = 0;
                   }
               }
             else
               {
                 is.putback (c1);
 
-                is >> ref;
+                is >> value;
               }
           }
       }
       break;
 
     default:
-      is >> ref;
+      is >> value;
       break;
     }
 
+  // If conversion produces an integer that overflows, failbit is set but
+  // value is non-zero. We want to treat this case as success, so clear
+  // failbit from the stream state to keep going.
+  // FIXME: Maybe set error state on octave stream as above? Matlab does
+  // *not* indicate an error message on overflow.
+  if ((is.rdstate () & std::ios::failbit) && value != T ())
+    is.clear (is.rdstate () & ~std::ios::failbit);
+
+  // Only copy the converted value if the stream is in a state where we
+  // want to continue reading.
+  if (! (is.rdstate () & std::ios::failbit))
+    *valptr = value;
+
   return is;
 }
 
--- a/test/io.tst	Wed May 11 12:02:22 2016 -0700
+++ b/test/io.tst	Wed May 11 13:12:22 2016 -0700
@@ -298,6 +298,20 @@
 %!assert (sscanf ('7777777777777777', '%lo'), 281474976710655)
 %!assert (sscanf ('ffffffffffff', '%lx'), 281474976710655)
 
+## bug #47759
+%!assert (sscanf ('999999999999999', '%d'), double (intmax ("int32")))
+%!assert (sscanf ('999999999999999', '%i'), double (intmax ("int32")))
+%!assert (sscanf ('999999999999999', '%u'), double (intmax ("uint32")))
+%!assert (sscanf ('777777777777777', '%o'), double (intmax ("uint32")))
+%!assert (sscanf ('fffffffffffffff', '%x'), double (intmax ("uint32")))
+## FIXME: scanf should return int64/uint64 if all conversions are %l[dioux].
+## Until then cast to a double (and lose precision) for comparison.
+%!assert (sscanf ('9999999999999999999999', '%ld'), double (intmax ("int64")))
+%!assert (sscanf ('9999999999999999999999', '%li'), double (intmax ("int64")))
+%!assert (sscanf ('9999999999999999999999', '%lu'), double (intmax ("uint64")))
+%!assert (sscanf ('7777777777777777777777', '%lo'), double (intmax ("uint64")))
+%!assert (sscanf ('ffffffffffffffffffffff', '%lx'), double (intmax ("uint64")))
+
 %!test
 %! [val, count, msg, pos] = sscanf ("3I2", "%f");
 %! assert (val, 3);