### Eclipse Workspace Patch 1.0 #P jython-trunk Index: src/com/ziclix/python/sql/JDBC20DataHandler.java =================================================================== --- src/com/ziclix/python/sql/JDBC20DataHandler.java (revision 7173) +++ src/com/ziclix/python/sql/JDBC20DataHandler.java (working copy) @@ -24,6 +24,7 @@ import org.python.core.PyFile; import org.python.core.PyObject; import org.python.core.util.StringUtil; +import org.python.util.SafeDoubleParser; /** * Support for JDBC 2.x type mappings, including Arrays, CLOBs and BLOBs. @@ -126,7 +127,7 @@ // in JDBC 2.0, use of a scale is deprecated try { BigDecimal bd = set.getBigDecimal(col); - obj = (bd == null) ? Py.None : Py.newFloat(bd.doubleValue()); + obj = (bd == null) ? Py.None : Py.newFloat(SafeDoubleParser.doubleValue(bd)); } catch (SQLException e) { obj = super.getPyObject(set, col, type); } Index: src/org/python/core/adapter/ClassicPyObjectAdapter.java =================================================================== --- src/org/python/core/adapter/ClassicPyObjectAdapter.java (revision 7173) +++ src/org/python/core/adapter/ClassicPyObjectAdapter.java (working copy) @@ -10,6 +10,7 @@ import org.python.core.PyProxy; import org.python.core.PyType; import org.python.core.PyUnicode; +import org.python.util.SafeDoubleParser; /** * Implements the algorithm originally used in {@link Py#java2py} to adapt objects. @@ -142,7 +143,7 @@ } public PyObject adapt(Object o) { - return new PyFloat(((Number) o).doubleValue()); + return new PyFloat(SafeDoubleParser.doubleValue((Number) o)); } } Index: src/com/ziclix/python/sql/handler/OracleDataHandler.java =================================================================== --- src/com/ziclix/python/sql/handler/OracleDataHandler.java (revision 7173) +++ src/com/ziclix/python/sql/handler/OracleDataHandler.java (working copy) @@ -24,6 +24,7 @@ import org.python.core.Py; import org.python.core.PyInteger; import org.python.core.PyObject; +import org.python.util.SafeDoubleParser; import com.ziclix.python.sql.DataHandler; import com.ziclix.python.sql.FilterDataHandler; @@ -153,7 +154,7 @@ : Py.newDecimal(number); } else { // Floating point binary precision - obj = Py.newFloat(set.getBigDecimal(col).doubleValue()); + obj = Py.newFloat(SafeDoubleParser.doubleValue(set.getBigDecimal(col))); } } else { // Decimal precision. A plain integer when without a scale. Maybe a Index: src/org/python/util/SafeFloatParser.java =================================================================== --- src/org/python/util/SafeFloatParser.java (revision 0) +++ src/org/python/util/SafeFloatParser.java (revision 0) @@ -0,0 +1,60 @@ +package org.python.util; + +import java.math.BigDecimal; + +/** + * A safer way to parse float values + *

+ * Prevents brute force attacks using the famous Java bug. + */ +public final class SafeFloatParser extends SafeDecimalParser { + + /** + * Safe way of parsing a Float value from a String + * + * @param s + * The input String + * @return the Float value + */ + public static Float valueOf(String s) { + Float result = null; + Double decimalValue = decimalValueOf(s); + if (decimalValue != null) { + result = Float.valueOf(decimalValue.floatValue()); + } + return result; + } + + /** + * Safe way of parsing a Float value from a String + * + * @param s + * The input String + * @return the Float value + */ + public static Float parseFloat(String s) { + return valueOf(s); + } + + /** + * Safe way of getting the float value
+ * prevents BigDecimal from calling Float.parseFloat() + * + * @param number + * @return the float value + */ + public static float floatValue(Number number) { + return Float.valueOf((float)decimalValue(number)); + } + + /** + * Safe way of getting the float value
+ * Prevents BigDecimal from calling Float.parseFloat() + * + * @param bigDecimal + * @return the float value + */ + public static float floatValue(BigDecimal bigDecimal) { + return Float.valueOf((float)decimalValue(bigDecimal)); + } +} Index: src/com/ziclix/python/sql/DataHandler.java =================================================================== --- src/com/ziclix/python/sql/DataHandler.java (revision 7173) +++ src/com/ziclix/python/sql/DataHandler.java (working copy) @@ -33,6 +33,7 @@ import org.python.core.PyFile; import org.python.core.PyList; import org.python.core.PyObject; +import org.python.util.SafeDoubleParser; /** * The DataHandler is responsible mapping the JDBC data type to @@ -251,7 +252,7 @@ case Types.NUMERIC: case Types.DECIMAL: BigDecimal bd = set.getBigDecimal(col); - obj = (bd == null) ? Py.None : Py.newFloat(bd.doubleValue()); + obj = (bd == null) ? Py.None : Py.newFloat(SafeDoubleParser.doubleValue(bd)); break; case Types.BIT: @@ -370,7 +371,7 @@ case Types.NUMERIC: case Types.DECIMAL: BigDecimal bd = stmt.getBigDecimal(col); - obj = (bd == null) ? Py.None : Py.newFloat(bd.doubleValue()); + obj = (bd == null) ? Py.None : Py.newFloat(SafeDoubleParser.doubleValue(bd)); break; case Types.BIT: Index: src/org/python/antlr/GrammarActions.java =================================================================== --- src/org/python/antlr/GrammarActions.java (revision 7173) +++ src/org/python/antlr/GrammarActions.java (working copy) @@ -10,6 +10,7 @@ import org.python.core.PyString; import org.python.core.PyUnicode; import org.python.core.codecs; +import org.python.util.SafeDoubleParser; import org.python.antlr.ast.alias; import org.python.antlr.ast.arguments; import org.python.antlr.ast.boolopType; @@ -364,13 +365,13 @@ } Object makeFloat(Token t) { - return Py.newFloat(Double.valueOf(t.getText())); + return Py.newFloat(SafeDoubleParser.valueOf(t.getText())); } Object makeComplex(Token t) { String s = t.getText(); s = s.substring(0, s.length() - 1); - return Py.newImaginary(Double.valueOf(s)); + return Py.newImaginary(SafeDoubleParser.valueOf(s)); } Object makeInt(Token t) { Index: src/com/ziclix/python/sql/Jython22DataHandler.java =================================================================== --- src/com/ziclix/python/sql/Jython22DataHandler.java (revision 7173) +++ src/com/ziclix/python/sql/Jython22DataHandler.java (working copy) @@ -30,6 +30,7 @@ import org.python.core.PyLong; import org.python.core.PyObject; import org.python.core.util.StringUtil; +import org.python.util.SafeDoubleParser; /** * A copy of the DataHandler class as it was before Jython 2.5. By that version, @@ -263,7 +264,7 @@ bd = set.getBigDecimal(col, 10); } - obj = (bd == null) ? Py.None : Py.newFloat(bd.doubleValue()); + obj = (bd == null) ? Py.None : Py.newFloat(SafeDoubleParser.doubleValue(bd)); break; case Types.BIT: @@ -351,7 +352,7 @@ case Types.DECIMAL: BigDecimal bd = stmt.getBigDecimal(col, 10); - obj = (bd == null) ? Py.None : Py.newFloat(bd.doubleValue()); + obj = (bd == null) ? Py.None : Py.newFloat(SafeDoubleParser.doubleValue(bd)); break; case Types.BIT: Index: src/org/python/core/PyString.java =================================================================== --- src/org/python/core/PyString.java (revision 7173) +++ src/org/python/core/PyString.java (working copy) @@ -9,6 +9,7 @@ import org.python.expose.ExposedNew; import org.python.expose.ExposedType; import org.python.expose.MethodType; +import org.python.util.SafeDoubleParser; /** * A builtin python string. @@ -874,7 +875,7 @@ break; } int end = endDouble(getString(),s); - z = Double.valueOf(getString().substring(s, end)).doubleValue(); + z = SafeDoubleParser.valueOf(getString().substring(s, end)).doubleValue(); if (z == Double.POSITIVE_INFINITY) { throw Py.ValueError(String.format("float() out of range: %.150s", getString())); } @@ -1554,7 +1555,7 @@ if (lowSval.endsWith("d") || lowSval.endsWith("f")) { throw new NumberFormatException("format specifiers not allowed"); } - return Double.valueOf(sval).doubleValue(); + return SafeDoubleParser.valueOf(sval).doubleValue(); } catch (NumberFormatException exc) { throw Py.ValueError("invalid literal for __float__: "+getString()); Index: src/org/python/expose/generate/MethodExposer.java =================================================================== --- src/org/python/expose/generate/MethodExposer.java (revision 7173) +++ src/org/python/expose/generate/MethodExposer.java (working copy) @@ -1,6 +1,8 @@ package org.python.expose.generate; import org.objectweb.asm.Type; +import org.python.util.SafeDoubleParser; +import org.python.util.SafeFloatParser; public abstract class MethodExposer extends Exposer { @@ -266,9 +268,9 @@ } else if(arg.equals(BOOLEAN)) { mv.visitLdcInsn(Boolean.valueOf(def) ? 1 : 0); } else if(arg.equals(Type.FLOAT_TYPE)) { - mv.visitLdcInsn(new Float(def)); + mv.visitLdcInsn(SafeFloatParser.valueOf(def)); } else if(arg.equals(Type.DOUBLE_TYPE)) { - mv.visitLdcInsn(new Double(def)); + mv.visitLdcInsn(SafeDoubleParser.valueOf(def)); } } Index: src/org/python/util/SafeDoubleParser.java =================================================================== --- src/org/python/util/SafeDoubleParser.java (revision 0) +++ src/org/python/util/SafeDoubleParser.java (revision 0) @@ -0,0 +1,55 @@ +package org.python.util; + +import java.math.BigDecimal; + +/** + * A safer way to parse double values + *

+ * Prevents brute force attacks using the famous Java bug. + */ +final public class SafeDoubleParser extends SafeDecimalParser { + + /** + * Safe way of parsing a Double value from a String + * + * @param s + * The input String + * @return the Double value + */ + public static Double valueOf(String s) { + return decimalValueOf(s); + } + + /** + * Safe way of parsing a Double value from a String + * + * @param s + * The input String + * @return the Double value + */ + public static Double parseDouble(String s) { + return valueOf(s); + } + + /** + * Safe way of getting the double value
+ * prevents BigDecimal from calling Double.parseDouble() + * + * @param number + * @return the double value + */ + public static double doubleValue(Number number) { + return decimalValue(number); + } + + /** + * Safe way of getting the double value
+ * Prevents BigDecimal from calling Double.parseDouble() + * + * @param bigDecimal + * @return the double value + */ + public static double doubleValue(BigDecimal bigDecimal) { + return decimalValue(bigDecimal); + } +} Index: src/org/python/util/SafeDecimalParser.java =================================================================== --- src/org/python/util/SafeDecimalParser.java (revision 0) +++ src/org/python/util/SafeDecimalParser.java (revision 0) @@ -0,0 +1,151 @@ +package org.python.util; + +import java.math.BigDecimal; + +class SafeDecimalParser { + + /** Constant 2 */ + protected static final BigDecimal TWO = new BigDecimal(2); + + /** Lower allowed value */ + protected static final BigDecimal LOWER = new BigDecimal("2.22507385850720113605e-308"); + + /** Upper allowed value */ + protected static final BigDecimal UPPER = new BigDecimal("2.22507385850720125958e-308"); + + /** The middle of the bad interval - used for rounding bad values */ + protected static final BigDecimal MIDDLE = LOWER.add(UPPER).divide(TWO); + + /** Upper allowed value as Double */ + private static final Double UPPER_DOUBLE = Double.valueOf(UPPER.doubleValue()); + + /** Lower allowed value as Double */ + private static final Double LOWER_DOUBLE = Double.valueOf(LOWER.doubleValue()); + + /** Digit sequence to trigger the slow path */ + private static final String SUSPICIOUS_DIGITS = "22250738585072"; + + /** + * Heuristic test if we should look closer at the value + * + * @param s + * The non-null input String + * @return true if the value is suspicious, false otherwise + */ + final protected static boolean isSuspicious(String s) { + return digits(s).indexOf(SUSPICIOUS_DIGITS) >= 0; + } + + /** + * Safe parsing of a String into a Double + * + * @param s + * The input String, can be null + * @return The Double value + */ + final protected static Double decimalValueOf(String s) { + Double result = null; + if (s != null) { + if (isSuspicious(s)) { + // take the slow path + result = parseSafely(s); + } else { + result = Double.valueOf(s); + } + } + return result; + } + + /** + * Safe way of getting the double value
+ * prevents BigDecimal from calling Double.parseDouble() + * + * @param number + * @return the double value + */ + final protected static double decimalValue(Number number) { + double result = 0; + if (number != null) { + if (number instanceof BigDecimal) { + result = decimalValue((BigDecimal)number); + } else { + result = number.doubleValue(); + } + } + return result; + } + + /** + * Safe way of getting the double value
+ * Prevents BigDecimal from calling Double.parseDouble() + * + * @param bigDecimal + * @return the double value + */ + final protected static double decimalValue(BigDecimal bigDecimal) { + double result = 0.0; + if (bigDecimal != null) { + if (isDangerous(bigDecimal)) { + result = decimalValueOf(bigDecimal.toString()).doubleValue(); + } else { + result = bigDecimal.doubleValue(); + } + } + return result; + } + + /** + * Slow parsing of a suspicious value + *

+ * Rounding takes place if the value is inside the bad interval + * + * @param s + * The non-null input String + * @return the double value + */ + final private static Double parseSafely(String s) { + Double result; + BigDecimal bd = new BigDecimal(s); + if (isDangerous(bd)) { + if (bd.compareTo(MIDDLE) >= 0) { + result = UPPER_DOUBLE; + } else { + result = LOWER_DOUBLE; + } + } else { + result = Double.valueOf(s); + } + return result; + } + + /** + * Extract the digits from a numeric string + * + * @param s + * The non-null String value + * @return A String containing only the digits + */ + final private static String digits(String s) { + char[] ca = s.toCharArray(); + int len = ca.length; + StringBuilder b = new StringBuilder(len); + for (int i = 0; i < len; i++) { + char c = ca[i]; + if (c >= '0' && c <= '9') { + b.append(c); + } + } + return b.toString(); + } + + /** + * Tests if the value is in the dangerous interval + * + * @param bd + * The big decimal value + * @return true if the value is dangerous, false otherwise + */ + final private static boolean isDangerous(BigDecimal bd) { + return bd.compareTo(UPPER) < 0 && bd.compareTo(LOWER) > 0; + } +} Index: tests/java/org/python/util/SafeDecimalParserTest.java =================================================================== --- tests/java/org/python/util/SafeDecimalParserTest.java (revision 0) +++ tests/java/org/python/util/SafeDecimalParserTest.java (revision 0) @@ -0,0 +1,161 @@ +package org.python.util; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import junit.framework.TestCase; + +public class SafeDecimalParserTest extends TestCase { + + /** + * any number with the digit sequence 22250738585072 is suspicious + */ + public void testIsSuspicious() { + assertFalse(SafeDecimalParser.isSuspicious("0")); + assertFalse(SafeDecimalParser.isSuspicious("1.23456")); + assertFalse(SafeDecimalParser.isSuspicious("1.23456e-308")); + assertFalse(SafeDecimalParser.isSuspicious("1.23456e308")); + // + assertTrue(SafeDecimalParser.isSuspicious("222507385850728298829948")); + assertTrue(SafeDecimalParser.isSuspicious("2.22507385850721302094943")); + assertTrue(SafeDecimalParser.isSuspicious("00002.225073858507277728288")); + assertTrue(SafeDecimalParser.isSuspicious("0.0002225073858507248829848")); + // + assertTrue(SafeDecimalParser.isSuspicious("2.22507385850728229400202e-308")); + assertTrue(SafeDecimalParser.isSuspicious("00002.22507385850728299492002e20")); + assertTrue(SafeDecimalParser.isSuspicious("0.00022250738585072100827272e-304")); + } + + public void testDecimalValueOf_bad() { + String s; + s = "2.22507385850720113606e-308"; + assertEqualsLower(SafeDecimalParser.decimalValueOf(s)); + s = "2.22507385850720125957e-308"; + assertEqualsUpper(SafeDecimalParser.decimalValueOf(s)); + s = SafeDecimalParser.MIDDLE.toString(); + assertEqualsUpper(SafeDecimalParser.decimalValueOf(s)); + s = "2.2250738585072012e-308"; + assertRoundedToIntervalBoundary(SafeDecimalParser.decimalValueOf(s)); + s = "2.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeDecimalParser.decimalValueOf(s)); + s = "0000002.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeDecimalParser.decimalValueOf(s)); + s = "22.250738585072012E-309"; + assertRoundedToIntervalBoundary(SafeDecimalParser.decimalValueOf(s)); + } + + public void testDecimalValueOf_good() { + assertGood("0"); + assertGood("1.75"); + assertGood("1.239402e9"); + assertGood("22250738585072"); + assertGood("2.250738585072012E-307"); + assertGood(SafeDecimalParser.LOWER.toString()); + assertGood(SafeDecimalParser.UPPER.toString()); + } + + public void testDecimalValue_Number() { + BigInteger bi = new BigInteger("22250738585072"); + assertEquals(bi.doubleValue(), SafeDecimalParser.decimalValue(bi)); + assertEquals(0.0, SafeDecimalParser.decimalValue((Number)null)); + } + + public void testDecimalValue_BigDecimal() { + BigDecimal bd = new BigDecimal("22250738585072.94289010"); + assertEquals(bd.doubleValue(), SafeDecimalParser.decimalValue(bd)); + assertEqualsUpper(SafeDecimalParser.decimalValue(SafeDecimalParser.MIDDLE)); + assertEqualsUpper(SafeDecimalParser.decimalValue(SafeDecimalParser.UPPER)); + assertEqualsLower(SafeDecimalParser.decimalValue(SafeDecimalParser.LOWER)); + assertEquals(0.0, SafeDecimalParser.decimalValue((BigDecimal)null)); + } + + /** + * make sure lower, upper and middle values are consistent + */ + public void testInterval() { + assertTrue(SafeDecimalParser.LOWER.compareTo(SafeDecimalParser.UPPER) < 0); + assertTrue(SafeDecimalParser.UPPER.compareTo(SafeDecimalParser.LOWER) > 0); + // + assertTrue(SafeDecimalParser.LOWER.compareTo(SafeDecimalParser.MIDDLE) < 0); + assertTrue(SafeDecimalParser.MIDDLE.compareTo(SafeDecimalParser.LOWER) > 0); + // + assertTrue(SafeDecimalParser.MIDDLE.compareTo(SafeDecimalParser.UPPER) < 0); + assertTrue(SafeDecimalParser.UPPER.compareTo(SafeDecimalParser.MIDDLE) > 0); + } + + /** + * this test should not hang + */ + public void testBigDecimalBoundaries() { + @SuppressWarnings("unused") + BigDecimal badd; + BigDecimal good; + // + good = new BigDecimal("2.22507385850720113605e-308"); + good.doubleValue(); + // + badd = new BigDecimal("2.22507385850720113606e-308"); + badd = new BigDecimal("2.22507385850720125957e-308"); + // + good = new BigDecimal("2.22507385850720125958e-308"); + good.doubleValue(); + } + + /** + * make sure Double.valueOf(null) throws a NPE + */ + public void testParseNull_original_Double() { + try { + Double d = Double.valueOf((String)null); + fail("oops - Double.valueOf(null) does not throw a NPE any more: " + d); + } catch (NullPointerException npe) { + // pass + } + try { + Double d = Double.parseDouble((String)null); + fail("oops - Double.parseDouble(null) does not throw a NPE any more: " + d); + } catch (NullPointerException npe) { + // pass + } + } + + /** + * make sure we do not have to take formatting characters into account + */ + public void testParseStrangeChars_original_Double() { + assertNumberFormatException("1,75"); + assertNumberFormatException("1,234.75"); + assertNumberFormatException("1'234.75"); + } + + private void assertNumberFormatException(String s) { + try { + Double.parseDouble(s); + fail("NumberFormatException expected"); + } catch (NumberFormatException nfe) { + // pass + } + } + + private void assertEqualsLower(double d) { + final double lowerDouble = SafeDecimalParser.LOWER.doubleValue(); + assertEquals(lowerDouble, d); + } + + private void assertEqualsUpper(double d) { + final double upperDouble = SafeDecimalParser.UPPER.doubleValue(); + assertEquals(upperDouble, d); + } + + private void assertRoundedToIntervalBoundary(Double d) { + final Double lowerDouble = Double.valueOf(SafeDecimalParser.LOWER.doubleValue()); + final Double upperDouble = Double.valueOf(SafeDecimalParser.UPPER.doubleValue()); + assertTrue(lowerDouble.equals(d) || upperDouble.equals(d)); + } + + private void assertGood(String s) { + Double d1 = SafeDecimalParser.decimalValueOf(s); + Double originalDouble = Double.valueOf(s); + assertEquals(originalDouble, d1); + } +} Index: tests/java/org/python/util/InterpreterTest.java =================================================================== --- tests/java/org/python/util/InterpreterTest.java (revision 7173) +++ tests/java/org/python/util/InterpreterTest.java (working copy) @@ -23,6 +23,16 @@ PyObject pyo = interp.eval("{u'one': u'two'}"); assertEquals(test, pyo); } + + /** + * prevent endless loop due to the famous Java Double.parseDouble() bug + */ + public void testParseDoubleBug() throws Exception { + PythonInterpreter.initialize(System.getProperties(), null, new String[] {}); + PythonInterpreter interp = new PythonInterpreter(); + PyObject result = interp.eval("2.2250738585072012e-308"); + assertNotNull(result); + } public void testMultipleThreads() { final CountDownLatch doneSignal = new CountDownLatch(10); Index: tests/java/org/python/util/SafeDoubleParserTest.java =================================================================== --- tests/java/org/python/util/SafeDoubleParserTest.java (revision 0) +++ tests/java/org/python/util/SafeDoubleParserTest.java (revision 0) @@ -0,0 +1,123 @@ +package org.python.util; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import junit.framework.TestCase; + +public class SafeDoubleParserTest extends TestCase { + + /** + * make sure to push bad values to the valid interval boundaries + */ + public void testValueOf_bad() { + String s; + s = "2.22507385850720113606e-308"; + assertEqualsLower(SafeDoubleParser.valueOf(s)); + s = "2.22507385850720125957e-308"; + assertEqualsUpper(SafeDoubleParser.valueOf(s)); + s = SafeDecimalParser.MIDDLE.toString(); + assertEqualsUpper(SafeDoubleParser.valueOf(s)); + s = "2.2250738585072012e-308"; + assertRoundedToIntervalBoundary(SafeDoubleParser.valueOf(s)); + s = "2.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeDoubleParser.valueOf(s)); + s = "0000002.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeDoubleParser.valueOf(s)); + s = "22.250738585072012E-309"; + assertRoundedToIntervalBoundary(SafeDoubleParser.valueOf(s)); + } + + /** + * make sure to push bad values to the valid interval boundaries + */ + public void testParseDouble_bad() { + String s; + s = "2.22507385850720113606e-308"; + assertEqualsLower(SafeDoubleParser.parseDouble(s)); + s = "2.22507385850720125957e-308"; + assertEqualsUpper(SafeDoubleParser.parseDouble(s)); + s = "2.2250738585072012e-308"; + assertRoundedToIntervalBoundary(SafeDoubleParser.parseDouble(s)); + s = SafeDecimalParser.MIDDLE.toString(); + assertEqualsUpper(SafeDoubleParser.parseDouble(s)); + s = "2.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeDoubleParser.parseDouble(s)); + s = "0000002.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeDoubleParser.parseDouble(s)); + s = "22.250738585072012E-309"; + assertRoundedToIntervalBoundary(SafeDoubleParser.parseDouble(s)); + } + + /** + * harmless values + */ + public void testGood() { + assertGood("0"); + assertGood("1.75"); + assertGood("1.239402e9"); + } + + /** + * harmless but suspicious values + */ + public void testGood_but_suspicious() { + assertGood("22250738585072"); + assertGood("2.250738585072012E-307"); + assertGood(SafeDecimalParser.LOWER.toString()); + assertGood(SafeDecimalParser.UPPER.toString()); + } + + /** + * make sure there is no NPE + */ + public void testValueOf_null() { + assertNull(SafeDoubleParser.valueOf(null)); + } + + /** + * make sure there is no NPE + */ + public void testParseDouble_null() { + assertNull(SafeDoubleParser.parseDouble(null)); + } + + public void testDoubleValue_Number() { + BigInteger bi = new BigInteger("22250738585072"); + assertEquals(bi.doubleValue(), SafeDoubleParser.doubleValue(bi)); + assertEquals(0.0, SafeDoubleParser.doubleValue((Number)null)); + } + + public void testDoubleValue_BigDecimal() { + BigDecimal bd = new BigDecimal("22250738585072.94289010"); + assertEquals(bd.doubleValue(), SafeDoubleParser.doubleValue(bd)); + assertEqualsUpper(SafeDoubleParser.doubleValue(SafeDecimalParser.MIDDLE)); + assertEqualsUpper(SafeDoubleParser.doubleValue(SafeDecimalParser.UPPER)); + assertEqualsLower(SafeDoubleParser.doubleValue(SafeDecimalParser.LOWER)); + assertEquals(0.0, SafeDoubleParser.doubleValue((BigDecimal)null)); + } + + private void assertRoundedToIntervalBoundary(Double d) { + final Double lowerDouble = Double.valueOf(SafeDecimalParser.LOWER.doubleValue()); + final Double upperDouble = Double.valueOf(SafeDecimalParser.UPPER.doubleValue()); + assertTrue(lowerDouble.equals(d) || upperDouble.equals(d)); + } + + private void assertEqualsLower(Double d) { + final Double lowerDouble = Double.valueOf(SafeDecimalParser.LOWER.doubleValue()); + assertEquals(lowerDouble, d); + } + + private void assertEqualsUpper(Double d) { + final Double upperDouble = Double.valueOf(SafeDecimalParser.UPPER.doubleValue()); + assertEquals(upperDouble, d); + } + + private void assertGood(String s) { + Double d1 = SafeDoubleParser.valueOf(s); + Double d2 = SafeDoubleParser.parseDouble(s); + Double originalDouble = Double.valueOf(s); + assertEquals(originalDouble, d1); + assertEquals(originalDouble, d2); + } +} Index: tests/java/org/python/util/SafeFloatParserTest.java =================================================================== --- tests/java/org/python/util/SafeFloatParserTest.java (revision 0) +++ tests/java/org/python/util/SafeFloatParserTest.java (revision 0) @@ -0,0 +1,123 @@ +package org.python.util; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import junit.framework.TestCase; + +public class SafeFloatParserTest extends TestCase { + + public void testFloatValue_Number() { + BigInteger bi = new BigInteger("22250738585072"); + assertEquals(bi.floatValue(), SafeFloatParser.floatValue(bi)); + assertEquals(0f, SafeFloatParser.floatValue((Number)null)); + } + + public void testFloatValue_BigDecimal() { + BigDecimal bd = new BigDecimal("22250738585072.94289010"); + assertEquals(bd.floatValue(), SafeFloatParser.floatValue(bd)); + assertEqualsUpper(SafeFloatParser.floatValue(SafeDecimalParser.MIDDLE)); + assertEqualsUpper(SafeFloatParser.floatValue(SafeDecimalParser.UPPER)); + assertEqualsLower(SafeFloatParser.floatValue(SafeDecimalParser.LOWER)); + assertEquals(0f, SafeFloatParser.floatValue((BigDecimal)null)); + } + + /** + * make sure to push bad values to the valid interval boundaries + */ + public void testValueOf_bad() { + String s; + s = "2.22507385850720113606e-308"; + assertEqualsLower(SafeFloatParser.valueOf(s)); + s = "2.22507385850720125957e-308"; + assertEqualsUpper(SafeFloatParser.valueOf(s)); + s = SafeDecimalParser.MIDDLE.toString(); + assertEqualsUpper(SafeFloatParser.valueOf(s)); + s = "2.2250738585072012e-308"; + assertRoundedToIntervalBoundary(SafeFloatParser.valueOf(s)); + s = "2.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeFloatParser.valueOf(s)); + s = "0000002.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeFloatParser.valueOf(s)); + s = "22.250738585072012E-309"; + assertRoundedToIntervalBoundary(SafeFloatParser.valueOf(s)); + } + + /** + * make sure to push bad values to the valid interval boundaries + */ + public void testParseFloat_bad() { + String s; + s = "2.22507385850720113606e-308"; + assertEqualsLower(SafeFloatParser.parseFloat(s)); + s = "2.22507385850720125957e-308"; + assertEqualsUpper(SafeFloatParser.parseFloat(s)); + s = "2.2250738585072012e-308"; + assertRoundedToIntervalBoundary(SafeFloatParser.parseFloat(s)); + s = SafeDecimalParser.MIDDLE.toString(); + assertEqualsUpper(SafeFloatParser.parseFloat(s)); + s = "2.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeFloatParser.parseFloat(s)); + s = "0000002.2250738585072012E-308"; + assertRoundedToIntervalBoundary(SafeFloatParser.parseFloat(s)); + s = "22.250738585072012E-309"; + assertRoundedToIntervalBoundary(SafeFloatParser.parseFloat(s)); + } + + /** + * harmless values + */ + public void testGood() { + assertGood("0"); + assertGood("1.75"); + assertGood("1.239402e9"); + } + + /** + * harmless but suspicious values + */ + public void testGood_but_suspicious() { + assertGood("22250738585072"); + assertGood("2.250738585072012E-307"); + assertGood(SafeDecimalParser.LOWER.toString()); + assertGood(SafeDecimalParser.UPPER.toString()); + } + + /** + * make sure there is no NPE + */ + public void testValueOf_null() { + assertNull(SafeFloatParser.valueOf(null)); + } + + /** + * make sure there is no NPE + */ + public void testParseFloat_null() { + assertNull(SafeFloatParser.parseFloat(null)); + } + + private void assertRoundedToIntervalBoundary(Float f) { + final Float lowerFloat = Float.valueOf(SafeDecimalParser.LOWER.floatValue()); + final Float upperFloat = Float.valueOf(SafeDecimalParser.UPPER.floatValue()); + assertTrue(lowerFloat.equals(f) || upperFloat.equals(f)); + } + + private void assertEqualsLower(Float f) { + final Float lowerFloat = Float.valueOf(SafeDecimalParser.LOWER.floatValue()); + assertEquals(lowerFloat, f); + } + + private void assertEqualsUpper(Float f) { + final Float upperFloat = Float.valueOf(SafeDecimalParser.UPPER.floatValue()); + assertEquals(upperFloat, f); + } + + private void assertGood(String s) { + Float f1 = SafeFloatParser.valueOf(s); + Float f2 = SafeFloatParser.parseFloat(s); + Float originalFloat = Float.valueOf(s); + assertEquals(originalFloat, f1); + assertEquals(originalFloat, f2); + } +} Index: src/org/python/modules/cPickle.java =================================================================== --- src/org/python/modules/cPickle.java (revision 7173) +++ src/org/python/modules/cPickle.java (working copy) @@ -44,6 +44,7 @@ import org.python.core.exceptions; import org.python.core.imp; import org.python.util.Generic; +import org.python.util.SafeDoubleParser; /** * @@ -1813,7 +1814,7 @@ final private void load_float() { String line = file.readlineNoNl(); - push(new PyFloat(Double.valueOf(line).doubleValue())); + push(new PyFloat(SafeDoubleParser.valueOf(line).doubleValue())); } final private void load_binfloat() {