# HG changeset patch # User Mike Miller # Date 1462997542 25200 # Node ID 4418e579cca6f70b69d58c5c79171fb552a2e4dc # Parent f07d6f57921480a55e1cebf05d6fefd5590dcca4 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. diff -r f07d6f579214 -r 4418e579cca6 libinterp/corefcn/oct-stream.cc --- 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; } diff -r f07d6f579214 -r 4418e579cca6 test/io.tst --- 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);