[Jython-checkins] jython: Prevent String.atof() from accepting hex notation.

jeff.allen jython-checkins at python.org
Wed Dec 31 02:41:07 CET 2014


https://hg.python.org/jython/rev/f125a1eeacbc
changeset:   7478:f125a1eeacbc
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Wed Dec 17 08:27:24 2014 +0000
summary:
  Prevent String.atof() from accepting hex notation.

Fixes a test failure in test_float: float("0x3.p-1") to raise ValueError.

files:
  Lib/test/test_float.py            |    5 +-
  src/org/python/core/PyString.java |  106 ++++++++++-------
  2 files changed, 66 insertions(+), 45 deletions(-)


diff --git a/Lib/test/test_float.py b/Lib/test/test_float.py
--- a/Lib/test/test_float.py
+++ b/Lib/test/test_float.py
@@ -35,9 +35,8 @@
         self.assertEqual(float("  3.14  "), 3.14)
         self.assertRaises(ValueError, float, "  0x3.1  ")
 
-        #FIXME: not raising ValueError on Jython:
-        #self.assertRaises(ValueError, float, "  -0x3.p-1  ")
-        #self.assertRaises(ValueError, float, "  +0x3.p-1  ")
+        self.assertRaises(ValueError, float, "  -0x3.p-1  ")
+        self.assertRaises(ValueError, float, "  +0x3.p-1  ")
 
         self.assertRaises(ValueError, float, "++3.14")
         self.assertRaises(ValueError, float, "+-3.14")
diff --git a/src/org/python/core/PyString.java b/src/org/python/core/PyString.java
--- a/src/org/python/core/PyString.java
+++ b/src/org/python/core/PyString.java
@@ -4,6 +4,8 @@
 import java.lang.ref.Reference;
 import java.lang.ref.SoftReference;
 import java.math.BigInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import org.python.core.buffer.BaseBuffer;
 import org.python.core.buffer.SimpleStringBuffer;
@@ -2710,55 +2712,75 @@
         }
     }
 
+    /**
+     * Convert this PyString to a floating-point value according to Python rules.
+     *
+     * @return the value
+     */
     public double atof() {
-        StringBuilder s = null;
-        int n = getString().length();
-        for (int i = 0; i < n; i++) {
-            char ch = getString().charAt(i);
-            if (ch == '\u0000') {
-                throw Py.ValueError("null byte in argument for float()");
+        String bogus = null;
+        double x = 0.0;
+        Matcher m = atofPattern.matcher(getString());
+
+        if (m.matches()) {
+            // Might be a valid float
+            try {
+                if (m.group(3) == null) {
+                    // No numeric part was found: it's something like "-Inf" or "hOrsE"
+                    x = atofSpecials(m.group(1));
+                } else {
+                    // A numeric part was present, try to convert the whole
+                    x = Double.parseDouble(m.group(1));
+                }
+            } catch (NumberFormatException e) {
+                bogus = m.group(1);
             }
-            if (Character.isDigit(ch)) {
-                if (s == null) {
-                    s = new StringBuilder(getString());
-                }
-                int val = Character.digit(ch, 10);
-                s.setCharAt(i, Character.forDigit(val, 10));
-            }
+        } else {
+            // This doesn't match the pattern for a float value
+            bogus = getString().trim();
         }
-        String sval = getString();
-        if (s != null) {
-            sval = s.toString();
+
+        // At this point, bogus will have been set to the trimmed string if there was a problem.
+        if (bogus == null) {
+            return x;
+        } else {
+            String fmt = "could not convert string to float: %s";
+            throw Py.ValueError(String.format(fmt, bogus));
         }
-        try {
-            // Double.valueOf allows format specifier ("d" or "f") at the end
-            String lowSval = sval.toLowerCase();
-            if (lowSval.equals("nan")) {
+
+    }
+
+    /**
+     * Regular expression that includes all valid a Python float() arguments, in which group 1
+     * captures the whole, stripped of white space, and group 3 will be present only if the form is
+     * numeric. Invalid non numerics are accepted ("+hOrsE" as "-inf").
+     */
+    private static Pattern atofPattern = Pattern
+            .compile("\\s*([+-]?(((\\d+(\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?)|\\p{Alpha}+))\\s*");
+
+    /**
+     * Conversion for non-numeric floats, accepting signed or unsigned "inf" and "nan", in any case.
+     *
+     * @param s to convert
+     * @return non-numeric result (if valid)
+     * @throws NumberFormatException if not a valid non-numeric indicator
+     */
+    private static double atofSpecials(String s) throws NumberFormatException {
+        switch (s.toLowerCase()) {
+            case "nan":
+            case "+nan":
+            case "-nan":
                 return Double.NaN;
-            } else if (lowSval.equals("+nan")) {
-                return Double.NaN;
-            } else if (lowSval.equals("-nan")) {
-                return Double.NaN;
-            } else if (lowSval.equals("inf")) {
+            case "inf":
+            case "+inf":
+            case "infinity":
+            case "+infinity":
                 return Double.POSITIVE_INFINITY;
-            } else if (lowSval.equals("+inf")) {
-                return Double.POSITIVE_INFINITY;
-            } else if (lowSval.equals("-inf")) {
+            case "-inf":
+            case "-infinity":
                 return Double.NEGATIVE_INFINITY;
-            } else if (lowSval.equals("infinity")) {
-                return Double.POSITIVE_INFINITY;
-            } else if (lowSval.equals("+infinity")) {
-                return Double.POSITIVE_INFINITY;
-            } else if (lowSval.equals("-infinity")) {
-                return Double.NEGATIVE_INFINITY;
-            }
-
-            if (lowSval.endsWith("d") || lowSval.endsWith("f")) {
-                throw new NumberFormatException("format specifiers not allowed");
-            }
-            return Double.valueOf(sval).doubleValue();
-        } catch (NumberFormatException exc) {
-            throw Py.ValueError("invalid literal for __float__: " + getString());
+            default:
+                throw new NumberFormatException();
         }
     }
 

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list