From c4dd84e4d505baa90d8d1675c1f60f8d6ce90db8 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Wed, 16 Jan 2019 20:42:31 -0800 Subject: [PATCH] Fix #508 --- release-notes/VERSION-2.x | 2 + .../jackson/core/base/ParserBase.java | 14 +++-- .../jackson/core/base/ParserMinimalBase.java | 55 +++++++++++++++++-- .../core/exc/InputCoercionException.java | 4 +- .../json/async/AsyncNumberCoercionTest.java | 34 +++++++++--- .../jackson/core/read/NumberCoercionTest.java | 33 ++++++++--- .../jackson/core/read/NumberOverflowTest.java | 9 +-- .../jackson/core/read/NumberParsingTest.java | 7 ++- 8 files changed, 123 insertions(+), 35 deletions(-) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 573a39e2dc..3ddc36702d 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -28,6 +28,8 @@ JSON library. #496: Create `StreamWriteFeature` to take over non-json-specific `JsonGenerator.Feature`s #502: Make `DefaultPrettyPrinter.createInstance()` to fail for sub-classes #506: Add missing type parameter for `TypeReference` in `ObjectCodec` +#508: Add new exception type `InputCoercionException` to be used for failed coercions + like overflow for `int` 2.9.8 (15-Dec-2018) diff --git a/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java b/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java index 92a555f715..981e796dcd 100644 --- a/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java +++ b/src/main/java/com/fasterxml/jackson/core/base/ParserBase.java @@ -846,7 +846,7 @@ private void _parseSlowInt(int expType) throws IOException } else { // 16-Oct-2018, tatu: Need to catch "too big" early due to [jackson-core#488] if ((expType == NR_INT) || (expType == NR_LONG)) { - _reportTooLongInt(expType, numStr); + _reportTooLongIntegral(expType, numStr); } if ((expType == NR_DOUBLE) || (expType == NR_FLOAT)) { _numberDouble = NumberInput.parseDouble(numStr); @@ -864,11 +864,13 @@ private void _parseSlowInt(int expType) throws IOException } // @since 2.9.8 - protected void _reportTooLongInt(int expType, String rawNum) throws IOException + protected void _reportTooLongIntegral(int expType, String rawNum) throws IOException { - final String numDesc = _longIntegerDesc(rawNum); - _reportError("Numeric value (%s) out of range of %s", numDesc, - (expType == NR_LONG) ? "long" : "int"); + if (expType == NR_INT) { + reportOverflowInt(rawNum); + } else { + reportOverflowLong(rawNum); + } } /* @@ -884,7 +886,7 @@ protected void convertNumberToInt() throws IOException // Let's verify it's lossless conversion by simple roundtrip int result = (int) _numberLong; if (((long) result) != _numberLong) { - _reportError("Numeric value ("+getText()+") out of range of int"); + reportOverflowInt(getText(), currentToken()); } _numberInt = result; } else if ((_numTypesValid & NR_BIGINT) != 0) { diff --git a/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java b/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java index 6ea6d3c5a3..fe9a047505 100644 --- a/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java +++ b/src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java @@ -5,6 +5,7 @@ import java.math.BigInteger; import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.exc.InputCoercionException; import com.fasterxml.jackson.core.io.JsonEOFException; import com.fasterxml.jackson.core.io.NumberInput; import com.fasterxml.jackson.core.util.ByteArrayBuilder; @@ -540,18 +541,64 @@ protected void reportUnexpectedNumberChar(int ch, String comment) throws JsonPar _reportError(msg); } + /** + * Method called to throw an exception for input token that looks like a number + * based on first character(s), but is not valid according to rules of format. + * In case of JSON this also includes invalid forms like positive sign and + * leading zeroes. + */ protected void reportInvalidNumber(String msg) throws JsonParseException { _reportError("Invalid numeric value: "+msg); } + /** + * Method called to throw an exception for integral (not floating point) input + * token with value outside of Java signed 32-bit range when requested as {@link int}. + * Result will be {@link InputCoercionException} being thrown. + */ protected void reportOverflowInt() throws IOException { - _reportError(String.format("Numeric value (%s) out of range of int (%d - %s)", - _longIntegerDesc(getText()), Integer.MIN_VALUE, Integer.MAX_VALUE)); + reportOverflowInt(getText()); } + // @since 2.10 + protected void reportOverflowInt(String numDesc) throws IOException { + reportOverflowInt(numDesc, JsonToken.VALUE_NUMBER_INT); + } + + // @since 2.10 + protected void reportOverflowInt(String numDesc, JsonToken inputType) throws IOException { + _reportInputCoercion(String.format("Numeric value (%s) out of range of int (%d - %s)", + _longIntegerDesc(numDesc), Integer.MIN_VALUE, Integer.MAX_VALUE), + inputType, Integer.TYPE); + } + + /** + * Method called to throw an exception for integral (not floating point) input + * token with value outside of Java signed 64-bit range when requested as {@link long}. + * Result will be {@link InputCoercionException} being thrown. + */ protected void reportOverflowLong() throws IOException { - _reportError(String.format("Numeric value (%s) out of range of long (%d - %s)", - _longIntegerDesc(getText()), Long.MIN_VALUE, Long.MAX_VALUE)); + reportOverflowLong(getText()); + } + + // @since 2.10 + protected void reportOverflowLong(String numDesc) throws IOException { + reportOverflowLong(numDesc, JsonToken.VALUE_NUMBER_INT); + } + + // @since 2.10 + protected void reportOverflowLong(String numDesc, JsonToken inputType) throws IOException { + _reportInputCoercion(String.format("Numeric value (%s) out of range of long (%d - %s)", + _longIntegerDesc(numDesc), Long.MIN_VALUE, Long.MAX_VALUE), + inputType, Long.TYPE); + } + + /** + * @since 2.10 + */ + protected void _reportInputCoercion(String msg, JsonToken inputType, Class targetType) + throws InputCoercionException { + throw new InputCoercionException(this, msg, inputType, targetType); } // @since 2.9.8 diff --git a/src/main/java/com/fasterxml/jackson/core/exc/InputCoercionException.java b/src/main/java/com/fasterxml/jackson/core/exc/InputCoercionException.java index 3e3190fe00..033abc49b4 100644 --- a/src/main/java/com/fasterxml/jackson/core/exc/InputCoercionException.java +++ b/src/main/java/com/fasterxml/jackson/core/exc/InputCoercionException.java @@ -30,8 +30,8 @@ public class InputCoercionException extends StreamReadException { * sets processor (accessible via {@link #getProcessor()}) to * specified parser. */ - public InputCoercionException(JsonParser p, JsonToken inputType, Class targetType, - String msg) { + public InputCoercionException(JsonParser p, String msg, + JsonToken inputType, Class targetType) { super(p, msg); _inputType = inputType; _targetType = targetType; diff --git a/src/test/java/com/fasterxml/jackson/core/json/async/AsyncNumberCoercionTest.java b/src/test/java/com/fasterxml/jackson/core/json/async/AsyncNumberCoercionTest.java index 6fe26937d0..dd7a59a0cb 100644 --- a/src/test/java/com/fasterxml/jackson/core/json/async/AsyncNumberCoercionTest.java +++ b/src/test/java/com/fasterxml/jackson/core/json/async/AsyncNumberCoercionTest.java @@ -5,9 +5,9 @@ import java.math.BigInteger; import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser.NumberType; import com.fasterxml.jackson.core.async.AsyncTestBase; +import com.fasterxml.jackson.core.exc.InputCoercionException; import com.fasterxml.jackson.core.testsupport.AsyncReaderWrapper; import com.fasterxml.jackson.core.JsonToken; @@ -72,8 +72,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } long small = -1L + Integer.MIN_VALUE; p = createParser(String.valueOf(small)); @@ -83,8 +85,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } // double -> error @@ -94,8 +98,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } p = createParser(String.valueOf(small)+".0"); assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); @@ -103,8 +109,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } // BigInteger -> error @@ -114,8 +122,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } p = createParser(String.valueOf(small)); assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); @@ -123,8 +133,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } } @@ -176,8 +188,10 @@ public void testToLongFailing() throws Exception try { p.getLongValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of long"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Long.TYPE, e.getTargetType()); } BigInteger small = BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.TEN); p = createParser(String.valueOf(small)); @@ -186,8 +200,10 @@ public void testToLongFailing() throws Exception try { p.getLongValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of long"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Long.TYPE, e.getTargetType()); } } diff --git a/src/test/java/com/fasterxml/jackson/core/read/NumberCoercionTest.java b/src/test/java/com/fasterxml/jackson/core/read/NumberCoercionTest.java index 8b83f098e4..5aaca0b47a 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/NumberCoercionTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/NumberCoercionTest.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser.NumberType; import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.core.exc.InputCoercionException; public class NumberCoercionTest extends BaseTest { @@ -66,8 +67,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } long small = -1L + Integer.MIN_VALUE; p = createParser(mode, String.valueOf(small)); @@ -77,8 +80,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } // double -> error @@ -88,8 +93,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } p = createParser(mode, String.valueOf(small)+".0"); assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken()); @@ -97,8 +104,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } // BigInteger -> error @@ -108,8 +117,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } p = createParser(mode, String.valueOf(small)); assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken()); @@ -117,8 +128,10 @@ public void testToIntFailing() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } } } @@ -175,8 +188,10 @@ public void testToLongFailing() throws Exception try { p.getLongValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of long"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Long.TYPE, e.getTargetType()); } BigInteger small = BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.TEN); p = createParser(mode, String.valueOf(small)); @@ -185,8 +200,10 @@ public void testToLongFailing() throws Exception try { p.getLongValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of long"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Long.TYPE, e.getTargetType()); } } } diff --git a/src/test/java/com/fasterxml/jackson/core/read/NumberOverflowTest.java b/src/test/java/com/fasterxml/jackson/core/read/NumberOverflowTest.java index a344b428ee..8de42c935d 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/NumberOverflowTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/NumberOverflowTest.java @@ -3,6 +3,7 @@ import java.math.BigInteger; import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.exc.InputCoercionException; public class NumberOverflowTest extends com.fasterxml.jackson.core.BaseTest @@ -39,7 +40,7 @@ public void testSimpleLongOverflow() throws Exception try { long x = p.getLongValue(); fail("Expected an exception for underflow (input "+p.getText()+"): instead, got long value: "+x); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of long"); } p.close(); @@ -49,7 +50,7 @@ public void testSimpleLongOverflow() throws Exception try { long x = p.getLongValue(); fail("Expected an exception for underflow (input "+p.getText()+"): instead, got long value: "+x); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of long"); } p.close(); @@ -70,7 +71,7 @@ public void testMaliciousLongOverflow() throws Exception try { p.getLongValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of long"); verifyException(e, "Integer with "+BIG_NUM_LEN+" digits"); } @@ -90,7 +91,7 @@ public void testMaliciousIntOverflow() throws Exception try { p.getIntValue(); fail("Should not pass"); - } catch (JsonParseException e) { + } catch (InputCoercionException e) { verifyException(e, "out of range of int"); verifyException(e, "Integer with "+BIG_NUM_LEN+" digits"); } diff --git a/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java b/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java index cc95cb6ecb..13fe5b78a0 100644 --- a/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java +++ b/src/test/java/com/fasterxml/jackson/core/read/NumberParsingTest.java @@ -7,6 +7,7 @@ import java.math.BigInteger; import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.exc.InputCoercionException; import com.fasterxml.jackson.core.json.JsonReadFeature; /** @@ -125,8 +126,10 @@ private void _testSimpleLong(int mode) throws Exception // Should get an exception if trying to convert to int try { p.getIntValue(); - } catch (JsonParseException pe) { - verifyException(pe, "out of range"); + } catch (InputCoercionException e) { + verifyException(e, "out of range"); + assertEquals(JsonToken.VALUE_NUMBER_INT, e.getInputType()); + assertEquals(Integer.TYPE, e.getTargetType()); } assertEquals((double) EXP_L, p.getDoubleValue()); assertEquals(BigDecimal.valueOf((long) EXP_L), p.getDecimalValue());