LargeNumberReadTest.java
package tools.jackson.core.unittest.constraints;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.core.StreamReadConstraints;
import tools.jackson.core.exc.StreamConstraintsException;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.util.JsonRecyclerPools;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/**
* Set of basic unit tests for verifying that "too big" number constraints
* are caught by various JSON parser backends.
*/
@SuppressWarnings("resource")
class LargeNumberReadTest
extends tools.jackson.core.unittest.JacksonCoreTestBase
{
private final static int STRICT_MAX_NUM_LEN = 1_000;
private final static int OVERSIZED_INT_LEN = 100_000;
private final JsonFactory JSON_F = newStreamFactory();
/*
/**********************************************************************
/* Tests, BigDecimal (core#677)
/**********************************************************************
*/
@Test
void bigBigDecimalsBytesFailByDefault() throws Exception
{
_testBigBigDecimals(MODE_INPUT_STREAM, true);
_testBigBigDecimals(MODE_INPUT_STREAM_THROTTLED, true);
}
@Test
void bigBigDecimalsBytes() throws Exception
{
try {
_testBigBigDecimals(MODE_INPUT_STREAM, false);
fail("Should not pass");
} catch (StreamConstraintsException e) {
verifyException(e, "Invalid numeric value ", "exceeds the maximum");
}
try {
_testBigBigDecimals(MODE_INPUT_STREAM_THROTTLED, false);
fail("Should not pass");
} catch (StreamConstraintsException jpe) {
verifyException(jpe, "Invalid numeric value ", "exceeds the maximum");
}
}
@Test
void bigBigDecimalsCharsFailByDefault() throws Exception
{
try {
_testBigBigDecimals(MODE_READER, false);
fail("Should not pass");
} catch (StreamConstraintsException jpe) {
verifyException(jpe, "Invalid numeric value ", "exceeds the maximum");
}
}
@Test
void bigBigDecimalsChars() throws Exception
{
_testBigBigDecimals(MODE_READER, true);
}
@Test
void bigBigDecimalsDataInputFailByDefault() throws Exception
{
try {
_testBigBigDecimals(MODE_DATA_INPUT, false);
fail("Should not pass");
} catch (StreamConstraintsException jpe) {
verifyException(jpe, "Invalid numeric value ", "exceeds the maximum allowed");
}
}
@Test
void bigBigDecimalsDataInput() throws Exception
{
_testBigBigDecimals(MODE_DATA_INPUT, true);
}
@Test
void tooLongDecimalIntegerFailsBeforeFullBuffering() throws Exception
{
assertAll(
() -> _testTooLongDecimalIntegerFailsBeforeFullBuffering("InputStream", MODE_INPUT_STREAM),
() -> _testTooLongDecimalIntegerFailsBeforeFullBuffering("Reader", MODE_READER),
() -> _testTooLongDecimalIntegerFailsBeforeFullBuffering("DataInput", MODE_DATA_INPUT)
);
}
@Test
void decimalIntegerAtMaxLengthStillPasses() throws Exception
{
assertAll(
() -> _testDecimalIntegerAtMaxLengthStillPasses(MODE_INPUT_STREAM, false),
() -> _testDecimalIntegerAtMaxLengthStillPasses(MODE_INPUT_STREAM, true),
() -> _testDecimalIntegerAtMaxLengthStillPasses(MODE_READER, false),
() -> _testDecimalIntegerAtMaxLengthStillPasses(MODE_READER, true),
() -> _testDecimalIntegerAtMaxLengthStillPasses(MODE_DATA_INPUT, false),
() -> _testDecimalIntegerAtMaxLengthStillPasses(MODE_DATA_INPUT, true)
);
}
@Test
void tooLongDecimalFloatFailsBeforeFullBuffering() throws Exception
{
assertAll(
() -> _testTooLongDecimalFPFailsBeforeFullBuffering("InputStream/fraction", MODE_INPUT_STREAM, false),
() -> _testTooLongDecimalFPFailsBeforeFullBuffering("InputStream/fraction (throttled)", MODE_INPUT_STREAM_THROTTLED, false),
() -> _testTooLongDecimalFPFailsBeforeFullBuffering("Reader/fraction", MODE_READER, false),
() -> _testTooLongDecimalFPFailsBeforeFullBuffering("Reader/fraction (throttled)", MODE_READER_THROTTLED, false),
() -> _testTooLongDecimalFPFailsBeforeFullBuffering("DataInput/fraction", MODE_DATA_INPUT, false),
() -> _testTooLongDecimalFPFailsBeforeFullBuffering("InputStream/exponent", MODE_INPUT_STREAM, true),
() -> _testTooLongDecimalFPFailsBeforeFullBuffering("Reader/exponent", MODE_READER, true),
() -> _testTooLongDecimalFPFailsBeforeFullBuffering("DataInput/exponent", MODE_DATA_INPUT, true)
);
}
@Test
void decimalFloatAtMaxLengthStillPasses() throws Exception
{
assertAll(
() -> _testDecimalFloatAtMaxLengthStillPasses(MODE_INPUT_STREAM, false),
() -> _testDecimalFloatAtMaxLengthStillPasses(MODE_INPUT_STREAM, true),
() -> _testDecimalFloatAtMaxLengthStillPasses(MODE_INPUT_STREAM_THROTTLED, false),
() -> _testDecimalFloatAtMaxLengthStillPasses(MODE_READER, false),
() -> _testDecimalFloatAtMaxLengthStillPasses(MODE_READER, true),
() -> _testDecimalFloatAtMaxLengthStillPasses(MODE_READER_THROTTLED, false),
() -> _testDecimalFloatAtMaxLengthStillPasses(MODE_DATA_INPUT, false),
() -> _testDecimalFloatAtMaxLengthStillPasses(MODE_DATA_INPUT, true)
);
}
private void _testBigBigDecimals(final int mode, final boolean enableUnlimitedNumberLen) throws Exception
{
final String BASE_FRACTION =
"01610253934481930774151441507943554511027782188707463024288149352877602369090537"
+"80583522838238149455840874862907649203136651528841378405339370751798532555965157588"
+"51877960056849468879933122908090021571162427934915567330612627267701300492535817858"
+"36107216979078343419634586362681098115326893982589327952357032253344676618872460059"
+"52652865429180458503533715200184512956356092484787210672008123556320998027133021328"
+"04777044107393832707173313768807959788098545050700242134577863569636367439867566923"
+"33479277494056927358573496400831024501058434838492057410330673302052539013639792877"
+"76670882022964335417061758860066263335250076803973514053909274208258510365484745192"
+"39425298649420795296781692303253055152441850691276044546565109657012938963181532017"
+"97420631515930595954388119123373317973532146157980827838377034575940814574561703270"
+"54949003909864767732479812702835339599792873405133989441135669998398892907338968744"
+"39682249327621463735375868408190435590094166575473967368412983975580104741004390308"
+"45302302121462601506802738854576700366634229106405188353120298347642313881766673834"
+"60332729485083952142460470270121052469394888775064758246516888122459628160867190501"
+"92476878886543996441778751825677213412487177484703116405390741627076678284295993334"
+"23142914551517616580884277651528729927553693274406612634848943914370188078452131231"
+"17351787166509190240927234853143290940647041705485514683182501795615082930770566118"
+"77488417962195965319219352314664764649802231780262169742484818333055713291103286608"
+"64318433253572997833038335632174050981747563310524775762280529871176578487487324067"
+"90242862159403953039896125568657481354509805409457993946220531587293505986329150608"
+"18702520420240989908678141379300904169936776618861221839938283876222332124814830207"
+"073816864076428273177778788053613345444299361357958409716099682468768353446625063";
for (String asText : new String[] {
"50."+BASE_FRACTION,
"-37."+BASE_FRACTION,
"0.00"+BASE_FRACTION,
"-0.012"+BASE_FRACTION,
"9999998."+BASE_FRACTION,
"-8888392."+BASE_FRACTION,
}) {
final String DOC = "[ "+asText+" ]";
JsonFactory jsonFactory = JSON_F;
if (enableUnlimitedNumberLen) {
jsonFactory = JsonFactory.builder()
.streamReadConstraints(StreamReadConstraints.builder().maxNumberLength(Integer.MAX_VALUE).build())
.build();
}
try (JsonParser p = createParser(jsonFactory, mode, DOC)) {
assertToken(JsonToken.START_ARRAY, p.nextToken());
assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
final BigDecimal exp = new BigDecimal(asText);
assertEquals(exp, p.getDecimalValue());
}
}
}
private void _testTooLongDecimalIntegerFailsBeforeFullBuffering(String modeDesc, final int mode)
throws Exception
{
RecordingStreamReadConstraints constraints =
new RecordingStreamReadConstraints(STRICT_MAX_NUM_LEN);
try (JsonParser p = createParserForLongDecimalInteger(mode, constraints)) {
StreamConstraintsException e = assertThrows(StreamConstraintsException.class,
() -> p.nextToken());
verifyException(e, "Number value length (");
verifyException(e, "exceeds the maximum allowed");
}
int failingIntegerLength = constraints.failingIntegerLength();
assertTrue(failingIntegerLength > 0,
modeDesc + " did not record failing integer length validation");
assertTrue(failingIntegerLength < (OVERSIZED_INT_LEN / 10),
modeDesc + " integer length validation occurred too late: "
+ failingIntegerLength + " digits for "
+ OVERSIZED_INT_LEN + "-digit input");
}
private void _testDecimalIntegerAtMaxLengthStillPasses(final int mode, boolean negative)
throws Exception
{
final String digits = repeatDigit('7', STRICT_MAX_NUM_LEN);
final String value = negative ? ("-" + digits) : digits;
JsonFactory f = JsonFactory.builder()
.recyclerPool(JsonRecyclerPools.nonRecyclingPool())
.streamReadConstraints(StreamReadConstraints.builder()
.maxNumberLength(STRICT_MAX_NUM_LEN)
.build())
.build();
try (JsonParser p = createParser(f, mode, value + " ")) {
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
assertEquals(value, p.getString());
assertNull(p.nextToken());
}
}
// [core#1686]: oversized fraction/exponent must be caught while buffering, not after
private void _testTooLongDecimalFPFailsBeforeFullBuffering(String modeDesc, final int mode,
boolean exponent)
throws Exception
{
RecordingStreamReadConstraints constraints =
new RecordingStreamReadConstraints(STRICT_MAX_NUM_LEN);
final String doc = (exponent ? "1e" : "1.") + repeatDigit('1', OVERSIZED_INT_LEN) + " ";
final JsonFactory f = JsonFactory.builder()
.recyclerPool(JsonRecyclerPools.nonRecyclingPool())
.streamReadConstraints(constraints)
.build();
try (JsonParser p = createParser(f, mode, doc)) {
StreamConstraintsException e = assertThrows(StreamConstraintsException.class,
() -> p.nextToken());
verifyException(e, "Number value length (");
verifyException(e, "exceeds the maximum allowed");
}
int failingLength = constraints.failingFPLength();
assertTrue(failingLength > 0,
modeDesc + " did not record failing FP length validation");
assertTrue(failingLength < (OVERSIZED_INT_LEN / 10),
modeDesc + " FP length validation occurred too late: "
+ failingLength + " digits for "
+ OVERSIZED_INT_LEN + "-digit input");
}
private void _testDecimalFloatAtMaxLengthStillPasses(final int mode, boolean negative)
throws Exception
{
// Total length (int + fraction) exactly at maximum:
final String digits = "1." + repeatDigit('7', STRICT_MAX_NUM_LEN - 1);
final String value = negative ? ("-" + digits) : digits;
JsonFactory f = JsonFactory.builder()
.recyclerPool(JsonRecyclerPools.nonRecyclingPool())
.streamReadConstraints(StreamReadConstraints.builder()
.maxNumberLength(STRICT_MAX_NUM_LEN)
.build())
.build();
try (JsonParser p = createParser(f, mode, value + " ")) {
assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
assertEquals(new BigDecimal(value), p.getDecimalValue());
assertNull(p.nextToken());
}
}
private JsonParser createParserForLongDecimalInteger(final int mode,
RecordingStreamReadConstraints constraints)
{
final String doc = repeatDigit('1', OVERSIZED_INT_LEN) + " ";
final JsonFactory f = JsonFactory.builder()
.recyclerPool(JsonRecyclerPools.nonRecyclingPool())
.streamReadConstraints(constraints)
.build();
return createParser(f, mode, doc);
}
private static String repeatDigit(char digit, int count) {
char[] chars = new char[count];
for (int i = 0; i < count; ++i) {
chars[i] = digit;
}
return new String(chars);
}
private static final class RecordingStreamReadConstraints
extends StreamReadConstraints
{
private int _failingIntegerLength;
private int _failingFPLength;
RecordingStreamReadConstraints(int maxNumLen) {
super(DEFAULT_MAX_DEPTH, DEFAULT_MAX_DOC_LEN, DEFAULT_MAX_TOKEN_COUNT,
maxNumLen, DEFAULT_MAX_STRING_LEN, DEFAULT_MAX_NAME_LEN);
}
@Override
public void validateIntegerLength(int length) throws StreamConstraintsException {
try {
super.validateIntegerLength(length);
} catch (StreamConstraintsException e) {
_failingIntegerLength = length;
throw e;
}
}
@Override
public void validateFPLength(int length) throws StreamConstraintsException {
try {
super.validateFPLength(length);
} catch (StreamConstraintsException e) {
_failingFPLength = length;
throw e;
}
}
int failingIntegerLength() {
return _failingIntegerLength;
}
int failingFPLength() {
return _failingFPLength;
}
}
}