Skip to content

Instantly share code, notes, and snippets.

Created February 7, 2011 21:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/815270 to your computer and use it in GitHub Desktop.
Save anonymous/815270 to your computer and use it in GitHub Desktop.
diff --git a/src/org/jruby/RubyBigDecimal.java b/src/org/jruby/RubyBigDecimal.java
index e72fc30..bbd9f79 100644
--- a/src/org/jruby/RubyBigDecimal.java
+++ b/src/org/jruby/RubyBigDecimal.java
@@ -46,6 +46,7 @@ import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.ConvertDouble;
/**
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
@@ -339,8 +340,6 @@ public class RubyBigDecimal extends RubyNumeric {
}
private final static Pattern INFINITY_PATTERN = Pattern.compile("^([+-])?Infinity$");
- private final static Pattern NUMBER_PATTERN
- = Pattern.compile("^([+-]?\\d*\\.?\\d*([eE][+-]?)?\\d*).*");
@JRubyMethod(name = "new", required = 1, optional = 1, meta = true)
public static RubyBigDecimal newInstance(IRubyObject recv, IRubyObject[] args) {
@@ -364,15 +363,7 @@ public class RubyBigDecimal extends RubyNumeric {
}
return newInfinity(runtime, sign);
}
-
- // Clean-up string representation so that it could be understood
- // by Java's BigDecimal. Not terribly efficient for now.
- // 1. MRI allows d and D as exponent separators
- strValue = strValue.replaceFirst("[dD]", "E");
- // 2. MRI allows underscores anywhere
- strValue = strValue.replaceAll("_", "");
- // 3. MRI ignores the trailing junk
- strValue = NUMBER_PATTERN.matcher(strValue).replaceFirst("$1");
+ strValue = ConvertDouble.normalizeDoubleString(strValue);
try {
decimal = new BigDecimal(strValue);
diff --git a/src/org/jruby/lexer/yacc/RubyYaccLexer.java b/src/org/jruby/lexer/yacc/RubyYaccLexer.java
index f3b12cd..093af2b 100755
--- a/src/org/jruby/lexer/yacc/RubyYaccLexer.java
+++ b/src/org/jruby/lexer/yacc/RubyYaccLexer.java
@@ -59,6 +59,7 @@ import org.jruby.lexer.yacc.SyntaxException.PID;
import org.jruby.parser.ParserSupport;
import org.jruby.parser.Tokens;
import org.jruby.util.ByteList;
+import org.jruby.util.ConvertDouble;
import org.jruby.util.StringSupport;
@@ -129,7 +130,7 @@ public class RubyYaccLexer {
private int getFloatToken(String number) {
double d;
try {
- d = Double.parseDouble(number);
+ d = ConvertDouble.parseDouble(number);
} catch (NumberFormatException e) {
warnings.warn(ID.FLOAT_OUT_OF_RANGE, getPosition(), "Float " + number + " out of range.", number);
diff --git a/src/org/jruby/util/ConvertDouble.java b/src/org/jruby/util/ConvertDouble.java
index 67aead3..0cd4954 100644
--- a/src/org/jruby/util/ConvertDouble.java
+++ b/src/org/jruby/util/ConvertDouble.java
@@ -27,7 +27,17 @@
***** END LICENSE BLOCK *****/
package org.jruby.util;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+import org.jruby.RubyBigDecimal;
+
public class ConvertDouble {
+ private final static Pattern NUMBER_PATTERN
+ = Pattern.compile("^([+-]?\\d*\\.?\\d*([eE][+-]?)?\\d*).*");
+
/**
* Converts supplied ByteList into a double. strict-mode will not like
* extra text non-numeric text or multiple sequention underscores.
@@ -45,6 +55,64 @@ public class ConvertDouble {
return new DoubleConverter().parse(bytes, strict, true);
}
+ private static final BigDecimal MIN_NORMAL_DOUBLE = new BigDecimal(Double.longBitsToDouble(0x0010000000000000L));
+
+ /**
+ * Parse the given String into a double value.
+ *
+ * Note that this includes workarounds for the JDK DOS exploint involving numbers
+ * near 2.2250738585072012e-308 that cause default Double.parseDouble to enter
+ * an infinite loop.
+ *
+ * @param value The string to parse
+ * @return the parsed double value
+ */
+ public static double parseDouble(String value) {
+ // normalize to a clean form
+ String normalString = normalizeDoubleString(value);
+
+ // split mantissa and exponent into separate pieces
+ int offset = normalString.indexOf('E');
+ BigDecimal base;
+ int exponent;
+ if (offset == -1) {
+ base = new BigDecimal(normalString);
+ exponent = 0;
+ } else {
+ base = new BigDecimal(normalString.substring(0, offset));
+ exponent = Integer.parseInt(normalString.charAt(offset + 1) == '+' ?
+ normalString.substring(offset + 2) :
+ normalString.substring(offset + 1));
+ }
+
+ // scale final value inside BigDecimal using mantissa and exponent
+ BigDecimal finalValue = base.scaleByPowerOfTen(exponent);
+
+ // if final value is > 0 but < MIN_NORMAL, just return MIN_NORMAL
+ if (finalValue.abs().equals(finalValue) && finalValue.compareTo(MIN_NORMAL_DOUBLE) <= 0 && finalValue.compareTo(BigDecimal.ZERO) > 0) {
+ // We special case values in this range, since it is now a widely-known exploit.
+ // Defaulting to Java's minimum "normal" double value.
+ // See JRUBY-5441.
+ return Double.MIN_NORMAL;
+ }
+
+ // if not in our magic range, just parse normally
+ return Double.parseDouble(value);
+ }
+
+ public static String normalizeDoubleString(String strValue) {
+ // Clean-up string representation so that it could be understood
+ // by Java's BigDecimal. Not terribly efficient for now.
+ // 1. MRI allows d and D as exponent separators
+ strValue = strValue.replaceFirst("[edD]", "E");
+ // 2. MRI allows underscores anywhere and + before digits
+ strValue = strValue.replaceAll("_", "");
+ // 3. MRI ignores the trailing junk
+ strValue = NUMBER_PATTERN.matcher(strValue).replaceFirst("$1");
+ // 4. trim any leading or trailing whitespace
+ return strValue.trim();
+ }
+
public static class DoubleConverter {
private byte[] bytes;
private int index;
@@ -127,7 +195,7 @@ public class ConvertDouble {
strictError(); // We know it is not whitespace at this point
}
- return Double.parseDouble(new String(chars));
+ return parseDouble(new String(chars));
}
private void strictError() {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment