ICU-5698 Set maximum limit for digits used for creating BigInteger/BigDecimal in DecimalFormat to prevent the parse method to trigger OutOfMemoryException. Also added test case for some extreme cases.

X-SVN-Rev: 23216
This commit is contained in:
Yoshito Umaoka 2008-01-12 07:45:14 +00:00
parent b33fe49157
commit 849c401c9b
3 changed files with 162 additions and 17 deletions

View File

@ -1,7 +1,7 @@
//##header J2SE15
/*
*******************************************************************************
* Copyright (C) 2001-2007, International Business Machines Corporation and *
* Copyright (C) 2001-2008, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -231,4 +231,54 @@ public class NumberFormatRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
}
}
}
/*
* Test case for ticket#5698 - parsing extremely large/small values
*/
public void TestT5698() {
final String[] data = {
"12345679E66666666666666666",
"-12345679E66666666666666666",
".1E2147483648", // exponent > max int
".1E2147483647", // exponent == max int
".1E-2147483648", // exponent == min int
".1E-2147483649", // exponent < min int
"1.23E350", // value > max double
"1.23E300", // value < max double
"-1.23E350", // value < min double
"-1.23E300", // value > min double
"4.9E-324", // value = smallest non-zero double
"1.0E-325", // 0 < value < smallest non-zero positive double0
"-1.0E-325", // 0 > value > largest non-zero negative double
};
final double[] expected = {
Double.POSITIVE_INFINITY,
Double.NEGATIVE_INFINITY,
Double.POSITIVE_INFINITY,
Double.POSITIVE_INFINITY,
0.0,
0.0,
Double.POSITIVE_INFINITY,
1.23e300d,
Double.NEGATIVE_INFINITY,
-1.23e300d,
4.9e-324d,
0.0,
-0.0,
};
NumberFormat nfmt = NumberFormat.getInstance();
for (int i = 0; i < data.length; i++) {
try {
Number n = nfmt.parse(data[i]);
if (expected[i] != n.doubleValue()) {
errln("Failed: Parsed result for " + data[i] + ": "
+ n.doubleValue() + " / expected: " + expected[i]);
}
} catch (ParseException pe) {
errln("Failed: ParseException is thrown for " + data[i]);
}
}
}
}

View File

@ -1,7 +1,7 @@
//##header J2SE15
/*
*******************************************************************************
* Copyright (C) 1996-2007, International Business Machines Corporation and *
* Copyright (C) 1996-2008, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -1651,9 +1651,14 @@ public class DecimalFormat extends NumberFormat {
: Double.NEGATIVE_INFINITY);
}
// Handle underflow
else if (status[STATUS_UNDERFLOW]) {
n = status[STATUS_POSITIVE] ? new Double("0.0") : new Double("-0.0");
}
// Handle -0.0
else if (!status[STATUS_POSITIVE] && digitList.isZero()) {
n = new Double(-0.0);
n = new Double("-0.0");
}
else {
@ -1689,7 +1694,6 @@ public class DecimalFormat extends NumberFormat {
(Number) new Long(big.longValue()) : (Number) big;
}
}
// Handle non-integral values or the case where parseBigDecimal is set
else {
BigDecimal big = digitList.getBigDecimalICU(status[STATUS_POSITIVE]);
@ -1708,7 +1712,8 @@ public class DecimalFormat extends NumberFormat {
private static final int STATUS_INFINITE = 0;
private static final int STATUS_POSITIVE = 1;
private static final int STATUS_LENGTH = 2;
private static final int STATUS_UNDERFLOW = 2;
private static final int STATUS_LENGTH = 3;
private static final UnicodeSet dotEquivalents =(UnicodeSet) new UnicodeSet(
"[.\u2024\u3002\uFE12\uFE52\uFF0E\uFF61]").freeze();
private static final UnicodeSet commaEquivalents = (UnicodeSet) new UnicodeSet(
@ -1728,6 +1733,14 @@ public class DecimalFormat extends NumberFormat {
private static final UnicodeSet strictDefaultGroupingSeparators = (UnicodeSet) new UnicodeSet(
strictDotEquivalents).addAll(strictCommaEquivalents).addAll(strictOtherGroupingSeparators).freeze();
// When parsing a number with big exponential value, it requires to transform
// the value into a string representation to construct BigInteger instance.
// We want to set the maximum size because it can easily trigger OutOfMemoryException.
// PARSE_MAX_EXPONENT is currently set to 1000, which is much bigger than
// MAX_VALUE of Double (
// See the problem reported by ticket#5698
private static final int PARSE_MAX_EXPONENT = 1000;
/**
* <strong><font face=helvetica color=red>CHANGED</font></strong>
* Parse the given text into a number. The text is parsed beginning at
@ -1806,7 +1819,7 @@ public class DecimalFormat extends NumberFormat {
boolean sawDecimal = false;
boolean sawExponent = false;
boolean sawDigit = false;
int exponent = 0; // Set to the exponent value, if any
long exponent = 0; // Set to the exponent value, if any
int digit = 0;
// strict parsing
@ -2012,10 +2025,22 @@ public class DecimalFormat extends NumberFormat {
}
}
exponentDigits.decimalAt = exponentDigits.count;
exponent = (int) exponentDigits.getLong();
if (negExp) {
exponent = -exponent;
// Quick overflow check for exponential part.
// Actual limit check will be done later in this code.
if (exponentDigits.count > 10 /* maximum decimal digits for int */) {
if (negExp) {
// set underflow flag
status[STATUS_UNDERFLOW] = true;
} else {
// set infinite flag
status[STATUS_INFINITE] = true;
}
} else {
exponentDigits.decimalAt = exponentDigits.count;
exponent = exponentDigits.getLong();
if (negExp) {
exponent = -exponent;
}
}
position = pos; // Advance past the exponent
sawExponent = true;
@ -2048,7 +2073,14 @@ public class DecimalFormat extends NumberFormat {
if (!sawDecimal) digits.decimalAt = digitCount; // Not digits.count!
// Adjust for exponent, if any
digits.decimalAt += exponent;
exponent += digits.decimalAt;
if (exponent < -PARSE_MAX_EXPONENT) {
status[STATUS_UNDERFLOW] = true;
} else if (exponent > PARSE_MAX_EXPONENT) {
status[STATUS_INFINITE] = true;
} else {
digits.decimalAt = (int)exponent;
}
// If none of the text string was recognized. For example, parse
// "x" with pattern "#0.00" (return index and error index both 0)

View File

@ -1,7 +1,7 @@
//##header J2SE15
/*
*******************************************************************************
* Copyright (C) 1996-2005, International Business Machines Corporation and *
* Copyright (C) 1996-2008, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -227,7 +227,7 @@ final class DigitList {
}
return stringRep.toString();
}
//#if defined(FOUNDATION10) || defined(J2SE13)
//#else
/**
@ -238,8 +238,41 @@ final class DigitList {
* @return the value of this object as a <code>BigDecimal</code>
*/
public java.math.BigDecimal getBigDecimal(boolean isPositive) {
if (isZero()) return java.math.BigDecimal.valueOf(0);
return new java.math.BigDecimal(getStringRep(isPositive));
if (isZero()) {
return java.math.BigDecimal.valueOf(0);
}
// if exponential notion is negative,
// we prefer to use BigDecimal constructor with scale,
// because it works better when extremely small value
// is used. See #5698.
long scale = (long)count - (long)decimalAt;
if (scale > 0) {
int numDigits = count;
if (scale > (long)Integer.MAX_VALUE) {
// try to reduce the scale
long numShift = scale - (long)Integer.MAX_VALUE;
if (numShift < count) {
numDigits -= numShift;
} else {
// fallback to 0
return new java.math.BigDecimal(0);
}
}
StringBuffer significantDigits = new StringBuffer(numDigits + 1);
if (!isPositive) {
significantDigits.append('-');
}
for (int i = 0; i < numDigits; i++) {
significantDigits.append((char)digits[i]);
}
BigInteger unscaledVal = new BigInteger(significantDigits.toString());
return new java.math.BigDecimal(unscaledVal, (int)scale);
} else {
// We should be able to use a negative scale value for a positive exponential
// value on JDK1.5. But it is not supported by older JDK. So, for now,
// we always use BigDecimal constructor which takes String.
return new java.math.BigDecimal(getStringRep(isPositive));
}
}
//#endif
@ -251,8 +284,38 @@ final class DigitList {
* @return the value of this object as a <code>BigDecimal</code>
*/
public com.ibm.icu.math.BigDecimal getBigDecimalICU(boolean isPositive) {
if (isZero()) return com.ibm.icu.math.BigDecimal.valueOf(0);
return new com.ibm.icu.math.BigDecimal(getStringRep(isPositive));
if (isZero()) {
return com.ibm.icu.math.BigDecimal.valueOf(0);
}
// if exponential notion is negative,
// we prefer to use BigDecimal constructor with scale,
// because it works better when extremely small value
// is used. See #5698.
long scale = (long)count - (long)decimalAt;
if (scale > 0) {
int numDigits = count;
if (scale > (long)Integer.MAX_VALUE) {
// try to reduce the scale
long numShift = scale - (long)Integer.MAX_VALUE;
if (numShift < count) {
numDigits -= numShift;
} else {
// fallback to 0
return new com.ibm.icu.math.BigDecimal(0);
}
}
StringBuffer significantDigits = new StringBuffer(numDigits + 1);
if (!isPositive) {
significantDigits.append('-');
}
for (int i = 0; i < numDigits; i++) {
significantDigits.append((char)digits[i]);
}
BigInteger unscaledVal = new BigInteger(significantDigits.toString());
return new com.ibm.icu.math.BigDecimal(unscaledVal, (int)scale);
} else {
return new com.ibm.icu.math.BigDecimal(getStringRep(isPositive));
}
}
/**