-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
8331485: Odd Results when Parsing Scientific Notation with Large Expo…
…nent 8331680: NumberFormat is missing some bad exponent strict parse cases Reviewed-by: naoto
- Loading branch information
Justin Lu
committed
May 30, 2024
1 parent
79f4998
commit ffb0867
Showing
4 changed files
with
267 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
test/jdk/java/text/Format/DecimalFormat/LargeExponentsTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
/* | ||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. | ||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
* | ||
* This code is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License version 2 only, as | ||
* published by the Free Software Foundation. | ||
* | ||
* This code is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
* version 2 for more details (a copy is included in the LICENSE file that | ||
* accompanied this code). | ||
* | ||
* You should have received a copy of the GNU General Public License version | ||
* 2 along with this work; if not, write to the Free Software Foundation, | ||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
* | ||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
* or visit www.oracle.com if you need additional information or have any | ||
* questions. | ||
*/ | ||
|
||
/* | ||
* @test | ||
* @bug 8331485 | ||
* @summary Ensure correctness when parsing large (+/-) exponent values that | ||
* exceed Integer.MAX_VALUE and Long.MAX_VALUE. | ||
* @run junit/othervm --add-opens java.base/java.text=ALL-UNNAMED LargeExponentsTest | ||
*/ | ||
|
||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import java.lang.reflect.InvocationTargetException; | ||
import java.lang.reflect.Method; | ||
import java.text.DecimalFormat; | ||
import java.text.NumberFormat; | ||
import java.text.ParseException; | ||
import java.text.ParsePosition; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Locale; | ||
import java.util.stream.Stream; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
// We prevent odd results when parsing large exponent values by ensuring | ||
// that we properly handle overflow in the implementation of DigitList | ||
public class LargeExponentsTest { | ||
|
||
// Exponent symbol is 'E' | ||
private static final NumberFormat FMT = NumberFormat.getInstance(Locale.US); | ||
|
||
// Check that the parsed value and parse position index are both equal to the expected values. | ||
// We are mainly checking that an exponent > Integer.MAX_VALUE no longer | ||
// parses to 0 and that an exponent > Long.MAX_VALUE no longer parses to the mantissa. | ||
@ParameterizedTest | ||
@MethodSource({"largeExponentValues", "smallExponentValues", "bugReportValues", "edgeCases"}) | ||
public void overflowTest(String parseString, Double expectedValue) throws ParseException { | ||
checkParse(parseString, expectedValue); | ||
checkParseWithPP(parseString, expectedValue); | ||
} | ||
|
||
// A separate white-box test to avoid the memory consumption of testing cases | ||
// when the String is near Integer.MAX_LENGTH | ||
@ParameterizedTest | ||
@MethodSource | ||
public void largeDecimalAtExponentTest(int expected, int decimalAt, long expVal) | ||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { | ||
DecimalFormat df = new DecimalFormat(); | ||
Method m = df.getClass().getDeclaredMethod( | ||
"shiftDecimalAt", int.class, long.class); | ||
m.setAccessible(true); | ||
assertEquals(expected, m.invoke(df, decimalAt, expVal)); | ||
} | ||
|
||
// Cases where we can test behavior when the String is near Integer.MAX_LENGTH | ||
private static Stream<Arguments> largeDecimalAtExponentTest() { | ||
return Stream.of( | ||
// Equivalent to testing Arguments.of("0."+"0".repeat(Integer.MAX_VALUE-20)+"1"+"E2147483650", 1.0E22) | ||
// This is an absurdly long decimal string with length close to Integer.MAX_VALUE | ||
// where the decimal position correctly negates the exponent value, even if it exceeds Integer.MAX_VALUE | ||
Arguments.of(23, -(Integer.MAX_VALUE-20), 3L+Integer.MAX_VALUE), | ||
Arguments.of(-23, Integer.MAX_VALUE-20, -(3L+Integer.MAX_VALUE)), | ||
Arguments.of(Integer.MIN_VALUE, -(Integer.MAX_VALUE-20), -(3L+Integer.MAX_VALUE)), | ||
Arguments.of(Integer.MAX_VALUE, Integer.MAX_VALUE-20, 3L+Integer.MAX_VALUE), | ||
Arguments.of(Integer.MAX_VALUE, -(Integer.MAX_VALUE-20), Long.MAX_VALUE), | ||
Arguments.of(Integer.MIN_VALUE, Integer.MAX_VALUE-20, Long.MIN_VALUE) | ||
); | ||
} | ||
|
||
// Checks the parse(String, ParsePosition) method | ||
public void checkParse(String parseString, Double expectedValue) { | ||
ParsePosition pp = new ParsePosition(0); | ||
Number actualValue = FMT.parse(parseString, pp); | ||
assertEquals(expectedValue, (double)actualValue); | ||
assertEquals(parseString.length(), pp.getIndex()); | ||
} | ||
|
||
// Checks the parse(String) method | ||
public void checkParseWithPP(String parseString, Double expectedValue) | ||
throws ParseException { | ||
Number actualValue = FMT.parse(parseString); | ||
assertEquals(expectedValue, (double)actualValue); | ||
} | ||
|
||
// Generate large enough exponents that should all be parsed as infinity | ||
// when positive. This includes exponents that exceed Long.MAX_VALUE | ||
private static List<Arguments> largeExponentValues() { | ||
return createExponentValues(false); | ||
} | ||
|
||
// Same as previous provider but for negative exponent values, so expecting | ||
// a parsed value of 0. | ||
private static List<Arguments> smallExponentValues() { | ||
return createExponentValues(true); | ||
} | ||
|
||
// Programmatically generate some large parse values that are expected | ||
// to be parsed as infinity or 0 | ||
private static List<Arguments> createExponentValues(boolean negative) { | ||
List<Arguments> args = new ArrayList<>(); | ||
// Start with a base value that should be parsed as infinity | ||
String baseValue = "12234.123E1100"; | ||
// Continuously add to the String until we trigger the overflow condition | ||
for (int i = 0; i < 100; i++) { | ||
StringBuilder bldr = new StringBuilder(); | ||
// Add to exponent | ||
bldr.append(baseValue).append("1".repeat(i)); | ||
// Add to mantissa | ||
bldr.insert(0, "1".repeat(i)); | ||
args.add(Arguments.of( | ||
// Prepend "-" to exponent if negative | ||
negative ? bldr.insert(bldr.indexOf("E")+1, "-").toString() : bldr.toString(), | ||
// Expect 0 if negative, else infinity | ||
negative ? 0.0 : Double.POSITIVE_INFINITY)); | ||
} | ||
return args; | ||
} | ||
|
||
// The provided values are all from the JBS issue | ||
// These contain exponents that exceed Integer.MAX_VALUE, but not Long.MAX_VALUE | ||
private static Stream<Arguments> bugReportValues() { | ||
return Stream.of( | ||
Arguments.of("0.123E1", 1.23), | ||
Arguments.of("0.123E309", 1.23E308), | ||
Arguments.of("0.123E310", Double.POSITIVE_INFINITY), | ||
Arguments.of("0.123E2147483647", Double.POSITIVE_INFINITY), | ||
Arguments.of("0.123E2147483648", Double.POSITIVE_INFINITY), | ||
Arguments.of("0.0123E2147483648", Double.POSITIVE_INFINITY), | ||
Arguments.of("0.0123E2147483649", Double.POSITIVE_INFINITY), | ||
Arguments.of("1.23E2147483646", Double.POSITIVE_INFINITY), | ||
Arguments.of("1.23E2147483647", Double.POSITIVE_INFINITY), | ||
Arguments.of("0.123E4294967296", Double.POSITIVE_INFINITY), | ||
Arguments.of("0.123E-322", 9.9E-324), | ||
Arguments.of("0.123E-323", 0.0), | ||
Arguments.of("0.123E-2147483647", 0.0), | ||
Arguments.of("0.123E-2147483648", 0.0), | ||
Arguments.of("0.123E-2147483649", 0.0) | ||
); | ||
} | ||
|
||
// Some other edge case values to ensure parse correctness | ||
private static Stream<Arguments> edgeCases() { | ||
return Stream.of( | ||
// Exponent itself does not cause underflow, but decimalAt adjustment | ||
// based off mantissa should. decimalAt(-1) + exponent(Integer.MIN_VALUE) = underflow | ||
Arguments.of("0.0123E-2147483648", 0.0), | ||
// 0 exponent | ||
Arguments.of("1.23E0", 1.23), | ||
// Leading zeroes | ||
Arguments.of("1.23E0000123", 1.23E123), | ||
// Leading zeroes - Past Long.MAX_VALUE length | ||
Arguments.of("1.23E00000000000000000000000000000000000000000123", 1.23E123), | ||
// Trailing zeroes | ||
Arguments.of("1.23E100", 1.23E100), | ||
// Long.MAX_VALUE length | ||
Arguments.of("1.23E1234567891234567800", Double.POSITIVE_INFINITY), | ||
// Long.MAX_VALUE with trailing zeroes | ||
Arguments.of("1.23E9223372036854775807000", Double.POSITIVE_INFINITY), | ||
// Long.MIN_VALUE | ||
Arguments.of("1.23E-9223372036854775808", 0.0), | ||
// Exponent value smaller than Long.MIN_VALUE | ||
Arguments.of("1.23E-9223372036854775809", 0.0), | ||
// Exponent value equal to Long.MAX_VALUE | ||
Arguments.of("1.23E9223372036854775807", Double.POSITIVE_INFINITY), | ||
// Exponent value larger than Long.MAX_VALUE | ||
Arguments.of("1.23E9223372036854775808", Double.POSITIVE_INFINITY) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ffb0867
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review
Issues