changeset 31542:2f08a53e0a23

Fix scanf handling of exceptional values (NA, NaN, Inf) at EOF (bug #63383) * oct-stream.cc (octave_scan): After skipping whitespace, check stream with is.good() before proceeding to read a value. * lo-utils.cc (read_inf_nan_na): Use is.peek() after successfully reading Inf or NaN to possibly set EOF bit. * lo-utils.cc (read_fp_value): Add extra test that value is not NA or NaN before changing it to -value when negative '-' character present. * io.tst: Mark fixed BIST tests with '*' regression marker. Add BIST tests for exceptional '+' character and for +/-NA, +/-NaN.
author Rik <rik@octave.org>
date Fri, 25 Nov 2022 08:30:30 -0800
parents ce5b4a00b022
children 85a073f7c5f9
files libinterp/corefcn/oct-stream.cc liboctave/util/lo-utils.cc test/io.tst
diffstat 3 files changed, 77 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/oct-stream.cc	Thu Nov 24 16:16:52 2022 -0500
+++ b/libinterp/corefcn/oct-stream.cc	Fri Nov 25 08:30:30 2022 -0800
@@ -4442,16 +4442,19 @@
       case 'G':
         {
           is >> std::ws;  // skip through whitespace and advance stream pointer
-          std::streampos pos = is.tellg ();
-
-          ref = read_value<double> (is);
-
-          std::ios::iostate status = is.rdstate ();
-          if (status & std::ios::failbit)
+          if (is.good ())
             {
-              is.clear ();
-              is.seekg (pos);
-              is.setstate (status & ~std::ios_base::eofbit);
+              std::streampos pos = is.tellg ();
+
+              ref = read_value<double> (is);
+
+              std::ios::iostate status = is.rdstate ();
+              if (status & std::ios::failbit)
+                {
+                  is.clear ();
+                  is.seekg (pos);
+                  is.setstate (status & ~std::ios_base::eofbit);
+                }
             }
         }
         break;
--- a/liboctave/util/lo-utils.cc	Thu Nov 24 16:16:52 2022 -0500
+++ b/liboctave/util/lo-utils.cc	Fri Nov 25 08:30:30 2022 -0800
@@ -215,7 +215,10 @@
             {
               char c2 = is.get ();
               if (c2 == 'f' || c2 == 'F')
-                val = std::numeric_limits<T>::infinity ();
+                {
+                  val = std::numeric_limits<T>::infinity ();
+                  is.peek ();  // Potentially set EOF bit
+                }
               else
                 is.setstate (std::ios::failbit);
             }
@@ -231,7 +234,10 @@
             {
               char c2 = is.get ();
               if (c2 == 'n' || c2 == 'N')
-                val = std::numeric_limits<T>::quiet_NaN ();
+                {
+                  val = std::numeric_limits<T>::quiet_NaN ();
+                  is.peek ();  // Potentially set EOF bit
+                }
               else
                 {
                   val = numeric_limits<T>::NA ();
@@ -290,7 +296,7 @@
               is >> val;
             }
 
-          if (neg && ! is.fail ())
+          if (neg && ! math::isnan (val) && ! is.fail ())
             val = -val;
         }
         break;
--- a/test/io.tst	Thu Nov 24 16:16:52 2022 -0500
+++ b/test/io.tst	Fri Nov 25 08:30:30 2022 -0800
@@ -514,7 +514,7 @@
 %! assert (pos, 5);
 
 ## Test NaN at EOF
-%!test <63383>
+%!test <*63383>
 %! [val, count, msg, pos] = sscanf ('2 3 n', '%f');
 %! assert (val, [2; 3]);
 %! assert (count, 2);
@@ -550,7 +550,7 @@
 %! assert (pos, 10);
 
 ## Test Inf at EOF
-%!test <63383>
+%!test <*63383>
 %! [val, count, msg, pos] = sscanf ('2 3 i', '%f');
 %! assert (val, [2; 3]);
 %! assert (count, 2);
@@ -611,6 +611,60 @@
 %! assert (msg, 'sscanf: format failed to match');
 %! assert (pos, 5);
 
+## Test '+' at EOF
+%!test <*63383>
+%! [val, count, msg, pos] = sscanf ('2 3 +', '%d');
+%! assert (val, [2; 3]);
+%! assert (count, 2);
+%! assert (msg, 'sscanf: format failed to match');
+%! assert (pos, 5);
+%! [val, count, msg, pos] = sscanf ('2 3 +', '%f');
+%! assert (val, [2; 3]);
+%! assert (count, 2);
+%! assert (msg, 'sscanf: format failed to match');
+%! assert (pos, 5);
+
+## Test '+' within string
+%!test <63383>
+%! [val, count, msg, pos] = sscanf ('1 2 + 3', '%d');
+%! assert (val, [1; 2]);
+%! assert (count, 2);
+%! assert (msg, 'sscanf: format failed to match');
+%! assert (pos, 5);
+%! [val, count, msg, pos] = sscanf ('1 2 + 3', '%f');
+%! assert (val, [1; 2]);
+%! assert (count, 2);
+%! assert (msg, 'sscanf: format failed to match');
+%! assert (pos, 5);
+
+%## Test +NA, -NA, +NAN, -NAN
+%!test <*63383>
+%! [val, count, msg, pos] = sscanf ('+NA -NA 1 +NAN -NAN', '%f');
+%! assert (val, [NA; NA; 1; NaN; NaN]);
+%! assert (count, 5);
+%! assert (msg, '');
+%! assert (pos, 20);
+%! [val, count, msg, pos] = sscanf ('-NA', '%f');
+%! assert (val, NA);
+%! assert (count, 1);
+%! assert (msg, '');
+%! assert (pos, 4);
+%! [val, count, msg, pos] = sscanf ('+NA', '%f');
+%! assert (val, NA);
+%! assert (count, 1);
+%! assert (msg, '');
+%! assert (pos, 4);
+%! [val, count, msg, pos] = sscanf ('-NaN', '%f');
+%! assert (val, NaN);
+%! assert (count, 1);
+%! assert (msg, '');
+%! assert (pos, 5);
+%! [val, count, msg, pos] = sscanf ('+NaN', '%f');
+%! assert (val, NaN);
+%! assert (count, 1);
+%! assert (msg, '');
+%! assert (pos, 5);
+
 %!test
 %! [a, b, c] = sscanf ("1.2 3 foo", "%f%d%s", "C");
 %! [v1, c1, m1] = sscanf ("1 2 3 4 5 6", "%d");