From 9dd5ad5f871f7b93654073a3f8ce3e1d9b8d9b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kraus?= Date: Mon, 17 Jul 2023 16:28:31 +0200 Subject: [PATCH] BigInteger scale limit counts absolute value now. (#100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomáš Kraus --- .../org/eclipse/parsson/JsonMessages.java | 10 ++- .../org/eclipse/parsson/JsonNumberImpl.java | 12 ++-- .../org/eclipse/parsson/JsonParserImpl.java | 2 +- .../org/eclipse/parsson/api/JsonConfig.java | 4 +- .../org/eclipse/parsson/messages.properties | 3 + .../tests/JsonBigDecimalScaleLimitTest.java | 27 ++++++-- .../eclipse/parsson/tests/JsonNumberTest.java | 61 ++++++++++++++++--- 7 files changed, 96 insertions(+), 23 deletions(-) diff --git a/impl/src/main/java/org/eclipse/parsson/JsonMessages.java b/impl/src/main/java/org/eclipse/parsson/JsonMessages.java index 26f08a6a..09ab8cda 100644 --- a/impl/src/main/java/org/eclipse/parsson/JsonMessages.java +++ b/impl/src/main/java/org/eclipse/parsson/JsonMessages.java @@ -116,7 +116,11 @@ static String PARSER_INPUT_ENC_DETECT_FAILED() { static String PARSER_INPUT_ENC_DETECT_IOERR() { return localize("parser.input.enc.detect.ioerr"); } - + + static String PARSER_INPUT_NESTED_TOO_DEEP(int limit) { + return localize("parser.input.nested.too.deep", limit); + } + static String DUPLICATE_KEY(String name) { return localize("parser.duplicate.key", name); } @@ -162,6 +166,10 @@ static String READER_READ_ALREADY_CALLED() { return localize("reader.read.already.called"); } + // JSON number messages + static String NUMBER_SCALE_LIMIT_EXCEPTION(int value, int limit) { + return localize("number.scale.limit.exception", value, limit); + } // obj builder messages static String OBJBUILDER_NAME_NULL() { diff --git a/impl/src/main/java/org/eclipse/parsson/JsonNumberImpl.java b/impl/src/main/java/org/eclipse/parsson/JsonNumberImpl.java index 3e34d7ab..2dcb552d 100644 --- a/impl/src/main/java/org/eclipse/parsson/JsonNumberImpl.java +++ b/impl/src/main/java/org/eclipse/parsson/JsonNumberImpl.java @@ -287,25 +287,21 @@ public double doubleValue() { @Override public BigInteger bigIntegerValue() { BigDecimal bd = bigDecimalValue(); - if (bd.scale() <= bigIntegerScaleLimit) { + if (Math.abs(bd.scale()) <= bigIntegerScaleLimit) { return bd.toBigInteger(); } throw new UnsupportedOperationException( - String.format( - "Scale value %d of this BigInteger exceeded maximal allowed value of %d", - bd.scale(), bigIntegerScaleLimit)); + JsonMessages.NUMBER_SCALE_LIMIT_EXCEPTION(bd.scale(), bigIntegerScaleLimit)); } @Override public BigInteger bigIntegerValueExact() { BigDecimal bd = bigDecimalValue(); - if (bd.scale() <= bigIntegerScaleLimit) { + if (Math.abs(bd.scale()) <= bigIntegerScaleLimit) { return bd.toBigIntegerExact(); } throw new UnsupportedOperationException( - String.format( - "Scale value %d of this BigInteger exceeded maximal allowed value of %d", - bd.scale(), bigIntegerScaleLimit)); + JsonMessages.NUMBER_SCALE_LIMIT_EXCEPTION(bd.scale(), bigIntegerScaleLimit)); } @Override diff --git a/impl/src/main/java/org/eclipse/parsson/JsonParserImpl.java b/impl/src/main/java/org/eclipse/parsson/JsonParserImpl.java index 5c0002c9..217af798 100644 --- a/impl/src/main/java/org/eclipse/parsson/JsonParserImpl.java +++ b/impl/src/main/java/org/eclipse/parsson/JsonParserImpl.java @@ -393,7 +393,7 @@ private static final class Stack { private void push(Context context) { if (++size >= limit) { - throw new RuntimeException("Input is too deeply nested " + size); + throw new RuntimeException(JsonMessages.PARSER_INPUT_NESTED_TOO_DEEP(size)); } context.next = head; head = context; diff --git a/impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java b/impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java index 343a255c..6e0db85d 100644 --- a/impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java +++ b/impl/src/main/java/org/eclipse/parsson/api/JsonConfig.java @@ -19,8 +19,8 @@ public interface JsonConfig { /** - * Configuration property to limit maximum value of BigInteger scale value. - * This property limits maximum value of scale value to be allowed + * Configuration property to limit maximum absolute value of BigInteger scale. + * This property limits maximum absolute value of scale to be allowed * in {@link jakarta.json.JsonNumber#bigIntegerValue()} * and {@link jakarta.json.JsonNumber#bigIntegerValueExact()} implemented methods. * Default value is set to {@code 100000}. diff --git a/impl/src/main/resources/org/eclipse/parsson/messages.properties b/impl/src/main/resources/org/eclipse/parsson/messages.properties index 1c693105..6162b37b 100644 --- a/impl/src/main/resources/org/eclipse/parsson/messages.properties +++ b/impl/src/main/resources/org/eclipse/parsson/messages.properties @@ -44,6 +44,7 @@ parser.scope.err=Cannot be called for value {0} parser.input.enc.detect.failed=Cannot auto-detect encoding, not enough chars parser.input.enc.detect.ioerr=I/O error while auto-detecting the encoding of stream parser.duplicate.key=Duplicate key ''{0}'' is not allowed +parser.input.nested.too.deep=Input is too deeply nested {0} generator.flush.io.err=I/O error while flushing generated JSON generator.close.io.err=I/O error while closing JsonGenerator @@ -58,6 +59,8 @@ writer.write.already.called=write/writeObject/writeArray/close method is already reader.read.already.called=read/readObject/readArray/close method is already called +number.scale.limit.exception=Scale value {0} of this BigInteger exceeded maximal allowed absolute value of {1} + objbuilder.name.null=Name in JsonObject's name/value pair cannot be null objbuilder.value.null=Value in JsonObject's name/value pair cannot be null objbuilder.object.builder.null=Object builder that is used to create a value in JsonObject's name/value pair cannot be null diff --git a/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalScaleLimitTest.java b/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalScaleLimitTest.java index ed59a5c4..960c2c88 100644 --- a/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalScaleLimitTest.java +++ b/impl/src/test/java/org/eclipse/parsson/tests/JsonBigDecimalScaleLimitTest.java @@ -28,13 +28,15 @@ */ public class JsonBigDecimalScaleLimitTest extends TestCase { + private static final int MAX_BIGINTEGER_SCALE = 50000; + public JsonBigDecimalScaleLimitTest(String testName) { super(testName); } @Override protected void setUp() { - System.setProperty(JsonConfig.MAX_BIGINTEGER_SCALE, "50000"); + System.setProperty(JsonConfig.MAX_BIGINTEGER_SCALE, Integer.toString(MAX_BIGINTEGER_SCALE)); } @Override @@ -61,9 +63,26 @@ public void testSystemPropertyBigIntegerScaleAboveLimit() { fail("No exception was thrown from bigIntegerValue with scale over limit"); } catch (UnsupportedOperationException e) { // UnsupportedOperationException is expected to be thrown - assertEquals( - "Scale value 50001 of this BigInteger exceeded maximal allowed value of 50000", - e.getMessage()); + JsonNumberTest.assertExceptionMessageContainsNumber(e, 50001); + JsonNumberTest.assertExceptionMessageContainsNumber(e, MAX_BIGINTEGER_SCALE); + } + System.clearProperty("org.eclipse.parsson.maxBigIntegerScale"); + } + + // Test BigInteger scale value limit set from system property using value above limit. + // Call shall throw specific UnsupportedOperationException exception. + // Default value is 100000 and system property lowered it to 50000 so value with scale -50001 + // test shall fail with exception message matching modified limits. + public void testSystemPropertyBigIntegerNegScaleAboveLimit() { + BigDecimal value = new BigDecimal("3.1415926535897932384626433") + .setScale(-50001, RoundingMode.HALF_UP); + try { + Json.createValue(value).bigIntegerValue(); + fail("No exception was thrown from bigIntegerValue with scale over limit"); + } catch (UnsupportedOperationException e) { + // UnsupportedOperationException is expected to be thrown + JsonNumberTest.assertExceptionMessageContainsNumber(e, -50001); + JsonNumberTest.assertExceptionMessageContainsNumber(e, MAX_BIGINTEGER_SCALE); } System.clearProperty("org.eclipse.parsson.maxBigIntegerScale"); } diff --git a/impl/src/test/java/org/eclipse/parsson/tests/JsonNumberTest.java b/impl/src/test/java/org/eclipse/parsson/tests/JsonNumberTest.java index c47a0b61..8f97feee 100644 --- a/impl/src/test/java/org/eclipse/parsson/tests/JsonNumberTest.java +++ b/impl/src/test/java/org/eclipse/parsson/tests/JsonNumberTest.java @@ -21,6 +21,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; +import java.text.MessageFormat; import java.util.Map; import jakarta.json.Json; @@ -61,6 +62,9 @@ public class JsonNumberTest extends TestCase { // π as JsonNumber with 1101 source characters private static final String Π_1101 = Π_1100 + "5"; + // Default maximum value of BigInteger scale value limit from JsonContext + private static final int DEFAULT_MAX_BIGINTEGER_SCALE = 100000; + public JsonNumberTest(String testName) { super(testName); } @@ -273,7 +277,7 @@ public void testDefaultBigIntegerScaleBellowLimit() { Json.createValue(value).bigIntegerValue(); } - // Test default BigInteger scale value limit using value above limit. + // Test default BigInteger scale value limit using positive value above limit. // Call shall throw specific UnsupportedOperationException exception. public void testDefaultBigIntegerScaleAboveLimit() { BigDecimal value = new BigDecimal("3.1415926535897932384626433") @@ -283,9 +287,23 @@ public void testDefaultBigIntegerScaleAboveLimit() { fail("No exception was thrown from bigIntegerValue with scale over limit"); } catch (UnsupportedOperationException e) { // UnsupportedOperationException is expected to be thrown - assertEquals( - "Scale value 100001 of this BigInteger exceeded maximal allowed value of 100000", - e.getMessage()); + assertExceptionMessageContainsNumber(e, 100001); + assertExceptionMessageContainsNumber(e, DEFAULT_MAX_BIGINTEGER_SCALE); + } + } + + // Test default BigInteger scale value limit using negative value above limit. + // Call shall throw specific UnsupportedOperationException exception. + public void testDefaultBigIntegerNegScaleAboveLimit() { + BigDecimal value = new BigDecimal("3.1415926535897932384626433") + .setScale(-100001, RoundingMode.HALF_UP); + try { + Json.createValue(value).bigIntegerValue(); + fail("No exception was thrown from bigIntegerValue with scale over limit"); + } catch (UnsupportedOperationException e) { + // UnsupportedOperationException is expected to be thrown + assertExceptionMessageContainsNumber(e, -100001); + assertExceptionMessageContainsNumber(e, DEFAULT_MAX_BIGINTEGER_SCALE); } } @@ -307,9 +325,31 @@ public void testConfigBigIntegerScaleAboveLimit() { fail("No exception was thrown from bigIntegerValue with scale over limit"); } catch (UnsupportedOperationException e) { // UnsupportedOperationException is expected to be thrown - assertEquals( - "Scale value 50001 of this BigInteger exceeded maximal allowed value of 50000", - e.getMessage()); + assertExceptionMessageContainsNumber(e, 50001); + assertExceptionMessageContainsNumber(e, 50000); + } + } + + // Test BigInteger scale value limit set from config Map using value above limit. + // Call shall throw specific UnsupportedOperationException exception. + // Config Map limit is stored in target JsonObject and shall be present for later value manipulation. + // Default value is 100000 and config Map property lowered it to 50000 so value with scale -50001 + // test shall fail with exception message matching modified limits. + public void testConfigBigIntegerNegScaleAboveLimit() { + BigDecimal value = new BigDecimal("3.1415926535897932384626433") + .setScale(-50001, RoundingMode.HALF_UP); + Map config = Map.of(JsonConfig.MAX_BIGINTEGER_SCALE, "50000"); + try { + JsonObject jsonObject = Json.createBuilderFactory(config) + .createObjectBuilder() + .add("bigDecimal", value) + .build(); + jsonObject.getJsonNumber("bigDecimal").bigIntegerValue(); + fail("No exception was thrown from bigIntegerValue with scale over limit"); + } catch (UnsupportedOperationException e) { + // UnsupportedOperationException is expected to be thrown + assertExceptionMessageContainsNumber(e, -50001); + assertExceptionMessageContainsNumber(e, 50000); } } @@ -365,6 +405,13 @@ public void testLargeBigDecimalAboveCustomLimit() { } } + static void assertExceptionMessageContainsNumber(Exception e, int number) { + // Format the number as being written to message from messages bundle + String numberString = MessageFormat.format("{0}", number); + assertTrue("Substring \"" + numberString + "\" was not found in \"" + e.getMessage() + "\"", + e.getMessage().contains(numberString)); + } + private static class CustomNumber extends Number { private static final long serialVersionUID = 1L;