Skip to content

Commit ce9c629

Browse files
authored
Add pre-validation for parsing "stringified" Floating-Point numbers, missing length checks (#4253)
1 parent 6edd415 commit ce9c629

File tree

5 files changed

+77
-39
lines changed

5 files changed

+77
-39
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Project: jackson-databind
2020
default typing in `ObjectMapper`
2121
(reported by @dvhvsekhar)
2222
#4248: `ThrowableDeserializer` does not handle `null` well for `cause`
23+
#4250: Add input validation for `NumberDeserializers` deserializers
24+
for "stringified" FP numbers
2325

2426
2.16.1 (not yet released)
2527

src/main/java/com/fasterxml/jackson/databind/deser/std/NumberDeserializers.java

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -652,9 +652,15 @@ protected final Float _parseFloat(JsonParser p, DeserializationContext ctxt)
652652
if (_checkTextualNull(ctxt, text)) {
653653
return (Float) getNullValue(ctxt);
654654
}
655-
try {
656-
return NumberInput.parseFloat(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
657-
} catch (IllegalArgumentException iae) { }
655+
// 09-Dec-2023, tatu: To avoid parser having to validate input, pre-validate:
656+
if (NumberInput.looksLikeValidNumber(text)) {
657+
p.streamReadConstraints().validateFPLength(text.length());
658+
try {
659+
return NumberInput.parseFloat(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
660+
} catch (IllegalArgumentException iae) {
661+
if (true) throw new Error();
662+
}
663+
}
658664
return (Float) ctxt.handleWeirdStringValue(_valueClass, text,
659665
"not a valid `Float` value");
660666
}
@@ -751,10 +757,13 @@ protected final Double _parseDouble(JsonParser p, DeserializationContext ctxt) t
751757
if (_checkTextualNull(ctxt, text)) {
752758
return (Double) getNullValue(ctxt);
753759
}
754-
p.streamReadConstraints().validateFPLength(text.length());
755-
try {
756-
return _parseDouble(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
757-
} catch (IllegalArgumentException iae) { }
760+
// 09-Dec-2023, tatu: To avoid parser having to validate input, pre-validate:
761+
if (NumberInput.looksLikeValidNumber(text)) {
762+
p.streamReadConstraints().validateFPLength(text.length());
763+
try {
764+
return _parseDouble(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
765+
} catch (IllegalArgumentException iae) { }
766+
}
758767
return (Double) ctxt.handleWeirdStringValue(_valueClass, text,
759768
"not a valid `Double` value");
760769
}
@@ -843,30 +852,31 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
843852
return Double.NaN;
844853
}
845854
try {
846-
if (!_isIntNumber(text)) {
855+
if (_isIntNumber(text)) {
856+
p.streamReadConstraints().validateIntegerLength(text.length());
857+
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
858+
return NumberInput.parseBigInteger(text, p.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
859+
}
860+
long value = NumberInput.parseLong(text);
861+
if (!ctxt.isEnabled(DeserializationFeature.USE_LONG_FOR_INTS)) {
862+
if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
863+
return Integer.valueOf((int) value);
864+
}
865+
}
866+
return value;
867+
}
868+
// 09-Dec-2023, tatu: To avoid parser having to validate input, pre-validate:
869+
if (NumberInput.looksLikeValidNumber(text)) {
847870
p.streamReadConstraints().validateFPLength(text.length());
848871
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)) {
849872
return NumberInput.parseBigDecimal(
850873
text, p.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
851874
}
852-
return Double.valueOf(
853-
NumberInput.parseDouble(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER)));
854-
}
855-
p.streamReadConstraints().validateIntegerLength(text.length());
856-
if (ctxt.isEnabled(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS)) {
857-
return NumberInput.parseBigInteger(text, p.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
858-
}
859-
long value = NumberInput.parseLong(text);
860-
if (!ctxt.isEnabled(DeserializationFeature.USE_LONG_FOR_INTS)) {
861-
if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) {
862-
return Integer.valueOf((int) value);
863-
}
875+
return NumberInput.parseDouble(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
864876
}
865-
return Long.valueOf(value);
866-
} catch (IllegalArgumentException iae) {
867-
return ctxt.handleWeirdStringValue(_valueClass, text,
868-
"not a valid number");
869-
}
877+
} catch (IllegalArgumentException iae) { }
878+
return ctxt.handleWeirdStringValue(_valueClass, text,
879+
"not a valid number");
870880
}
871881

872882
/**
@@ -966,10 +976,12 @@ public BigInteger deserialize(JsonParser p, DeserializationContext ctxt) throws
966976
// note: no need to call `coerce` as this is never primitive
967977
return getNullValue(ctxt);
968978
}
969-
p.streamReadConstraints().validateIntegerLength(text.length());
970-
try {
971-
return NumberInput.parseBigInteger(text, p.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
972-
} catch (IllegalArgumentException iae) { }
979+
if (_isIntNumber(text)) {
980+
p.streamReadConstraints().validateIntegerLength(text.length());
981+
try {
982+
return NumberInput.parseBigInteger(text, p.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
983+
} catch (IllegalArgumentException iae) { }
984+
}
973985
return (BigInteger) ctxt.handleWeirdStringValue(_valueClass, text,
974986
"not a valid representation");
975987
}
@@ -1036,10 +1048,13 @@ public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt)
10361048
// note: no need to call `coerce` as this is never primitive
10371049
return getNullValue(ctxt);
10381050
}
1039-
p.streamReadConstraints().validateFPLength(text.length());
1040-
try {
1041-
return NumberInput.parseBigDecimal(text, p.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
1042-
} catch (IllegalArgumentException iae) { }
1051+
// 09-Dec-2023, tatu: To avoid parser having to validate input, pre-validate:
1052+
if (NumberInput.looksLikeValidNumber(text)) {
1053+
p.streamReadConstraints().validateFPLength(text.length());
1054+
try {
1055+
return NumberInput.parseBigDecimal(text, p.isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
1056+
} catch (IllegalArgumentException iae) { }
1057+
}
10431058
return (BigDecimal) ctxt.handleWeirdStringValue(_valueClass, text,
10441059
"not a valid representation");
10451060
}

src/main/java/com/fasterxml/jackson/databind/deser/std/StdDeserializer.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ protected final byte _parseBytePrimitive(JsonParser p, DeserializationContext ct
608608
_verifyNullForPrimitiveCoercion(ctxt, text);
609609
return (byte) 0;
610610
}
611+
p.streamReadConstraints().validateIntegerLength(text.length());
611612
int value;
612613
try {
613614
value = NumberInput.parseInt(text);
@@ -679,6 +680,7 @@ protected final short _parseShortPrimitive(JsonParser p, DeserializationContext
679680
_verifyNullForPrimitiveCoercion(ctxt, text);
680681
return (short) 0;
681682
}
683+
p.streamReadConstraints().validateIntegerLength(text.length());
682684
int value;
683685
try {
684686
value = NumberInput.parseInt(text);
@@ -758,6 +760,7 @@ protected final int _parseIntPrimitive(DeserializationContext ctxt, String text)
758760
{
759761
try {
760762
if (text.length() > 9) {
763+
ctxt.getParser().streamReadConstraints().validateIntegerLength(text.length());
761764
long l = NumberInput.parseLong(text);
762765
if (_intOverflow(l)) {
763766
Number v = (Number) ctxt.handleWeirdStringValue(Integer.TYPE, text,
@@ -831,6 +834,7 @@ protected final Integer _parseInteger(DeserializationContext ctxt, String text)
831834
{
832835
try {
833836
if (text.length() > 9) {
837+
ctxt.getParser().streamReadConstraints().validateIntegerLength(text.length());
834838
long l = NumberInput.parseLong(text);
835839
if (_intOverflow(l)) {
836840
return (Integer) ctxt.handleWeirdStringValue(Integer.class, text,
@@ -909,6 +913,7 @@ protected final long _parseLongPrimitive(JsonParser p, DeserializationContext ct
909913
*/
910914
protected final long _parseLongPrimitive(DeserializationContext ctxt, String text) throws IOException
911915
{
916+
ctxt.getParser().streamReadConstraints().validateIntegerLength(text.length());
912917
try {
913918
return NumberInput.parseLong(text);
914919
} catch (IllegalArgumentException iae) { }
@@ -974,6 +979,7 @@ protected final Long _parseLong(JsonParser p, DeserializationContext ctxt,
974979
*/
975980
protected final Long _parseLong(DeserializationContext ctxt, String text) throws IOException
976981
{
982+
ctxt.getParser().streamReadConstraints().validateIntegerLength(text.length());
977983
try {
978984
return NumberInput.parseLong(text);
979985
} catch (IllegalArgumentException iae) { }
@@ -1051,13 +1057,20 @@ protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext
10511057

10521058
/**
10531059
* @since 2.9
1060+
*
1061+
* @deprecated Since 2.17 use {@link #_parseFloatPrimitive(JsonParser, DeserializationContext, String)}
10541062
*/
1063+
@Deprecated // since 2.17
10551064
protected final float _parseFloatPrimitive(DeserializationContext ctxt, String text)
10561065
throws IOException
10571066
{
1058-
try {
1059-
return NumberInput.parseFloat(text);
1060-
} catch (IllegalArgumentException iae) { }
1067+
// 09-Dec-2023, tatu: To avoid parser having to validate input, pre-validate:
1068+
if (NumberInput.looksLikeValidNumber(text)) {
1069+
ctxt.getParser().streamReadConstraints().validateFPLength(text.length());
1070+
try {
1071+
return NumberInput.parseFloat(text, false);
1072+
} catch (IllegalArgumentException iae) { }
1073+
}
10611074
Number v = (Number) ctxt.handleWeirdStringValue(Float.TYPE, text,
10621075
"not a valid `float` value");
10631076
return _nonNullNumber(v).floatValue();
@@ -1069,9 +1082,13 @@ protected final float _parseFloatPrimitive(DeserializationContext ctxt, String t
10691082
protected final float _parseFloatPrimitive(JsonParser p, DeserializationContext ctxt, String text)
10701083
throws IOException
10711084
{
1072-
try {
1073-
return NumberInput.parseFloat(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
1074-
} catch (IllegalArgumentException iae) { }
1085+
// 09-Dec-2023, tatu: To avoid parser having to validate input, pre-validate:
1086+
if (NumberInput.looksLikeValidNumber(text)) {
1087+
ctxt.getParser().streamReadConstraints().validateFPLength(text.length());
1088+
try {
1089+
return NumberInput.parseFloat(text, p.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
1090+
} catch (IllegalArgumentException iae) { }
1091+
}
10751092
Number v = (Number) ctxt.handleWeirdStringValue(Float.TYPE, text,
10761093
"not a valid `float` value");
10771094
return _nonNullNumber(v).floatValue();

src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,8 @@ private Number getNumberValue(final boolean preferBigNumbers) throws IOException
19171917
String str = (String) value;
19181918
final int len = str.length();
19191919
if (_currToken == JsonToken.VALUE_NUMBER_INT) {
1920+
// 08-Dec-2024, tatu: Note -- deferred numbers' validity (wrt input token)
1921+
// has been verified by underlying `JsonParser`: no need to check again
19201922
if (preferBigNumbers
19211923
// 01-Feb-2023, tatu: Not really accurate but we'll err on side
19221924
// of not losing accuracy (should really check 19-char case,

src/test/java/com/fasterxml/jackson/databind/deser/lazy/LazyIgnoralForNumbers3730Test.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.fasterxml.jackson.databind.ObjectReader;
1313
import com.fasterxml.jackson.databind.json.JsonMapper;
1414

15+
import org.junit.Ignore;
1516
import org.junit.runner.RunWith;
1617
import org.mockito.Mockito;
1718
import org.powermock.core.classloader.annotations.PrepareForTest;
@@ -27,6 +28,7 @@
2728
*/
2829
@RunWith(PowerMockRunner.class)
2930
@PrepareForTest(fullyQualifiedNames = "com.fasterxml.jackson.core.io.NumberInput")
31+
@Ignore("Powermock stopped working with NumberUtil refactoring see [databind#4250]")
3032
public class LazyIgnoralForNumbers3730Test extends BaseMapTest
3133
{
3234
static class ExtractFieldsNoDefaultConstructor3730 {

0 commit comments

Comments
 (0)