From da8254c82b31dcec53ef59c75cbbfbc1db1bb1b0 Mon Sep 17 00:00:00 2001 From: Nikita Kuprins Date: Tue, 15 Sep 2026 01:12:16 +0300 Subject: [PATCH 1/3] fix: reject out-of-range integral cell values --- .../byteconverter/ByteNumberConverter.java | 2 +- .../integer/IntegerNumberConverter.java | 2 +- .../longconverter/LongNumberConverter.java | 2 +- .../shortconverter/ShortNumberConverter.java | 2 +- .../apache/fesod/sheet/util/NumberUtils.java | 79 +++++++++++++++++-- .../fesod/sheet/converter/ConverterTest.java | 10 +++ .../fesod/sheet/util/NumberUtilsTest.java | 62 +++++++++++++++ 7 files changed, 147 insertions(+), 12 deletions(-) diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/byteconverter/ByteNumberConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/byteconverter/ByteNumberConverter.java index 50a27cf4e..7010d42df 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/byteconverter/ByteNumberConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/byteconverter/ByteNumberConverter.java @@ -53,7 +53,7 @@ public CellDataTypeEnum supportExcelTypeKey() { @Override public Byte convertToJavaData( ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return cellData.getNumberValue().byteValue(); + return NumberUtils.toByte(cellData.getNumberValue()); } @Override diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/integer/IntegerNumberConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/integer/IntegerNumberConverter.java index 628b4a1b5..6817cb7b4 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/integer/IntegerNumberConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/integer/IntegerNumberConverter.java @@ -54,7 +54,7 @@ public CellDataTypeEnum supportExcelTypeKey() { @Override public Integer convertToJavaData( ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return cellData.getNumberValue().intValue(); + return NumberUtils.toInt(cellData.getNumberValue()); } @Override diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/longconverter/LongNumberConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/longconverter/LongNumberConverter.java index 83e2b1350..c6dff784e 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/longconverter/LongNumberConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/longconverter/LongNumberConverter.java @@ -54,7 +54,7 @@ public CellDataTypeEnum supportExcelTypeKey() { @Override public Long convertToJavaData( ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return cellData.getNumberValue().longValue(); + return NumberUtils.toLong(cellData.getNumberValue()); } @Override diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/shortconverter/ShortNumberConverter.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/shortconverter/ShortNumberConverter.java index be290348e..f0ad50d26 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/shortconverter/ShortNumberConverter.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/converters/shortconverter/ShortNumberConverter.java @@ -54,7 +54,7 @@ public CellDataTypeEnum supportExcelTypeKey() { @Override public Short convertToJavaData( ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { - return cellData.getNumberValue().shortValue(); + return NumberUtils.toShort(cellData.getNumberValue()); } @Override diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java index 4d5163700..180f47143 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java @@ -118,9 +118,9 @@ public static WriteCellData formatToCellData(Number num, ExcelContentProperty */ public static Short parseShort(String string, ExcelContentProperty contentProperty) throws ParseException { if (!hasFormat(contentProperty)) { - return new BigDecimal(string).shortValue(); + return toShort(new BigDecimal(string)); } - return parse(string, contentProperty).shortValue(); + return toShort(new BigDecimal(parse(string, contentProperty).toString())); } /** @@ -132,9 +132,9 @@ public static Short parseShort(String string, ExcelContentProperty contentProper */ public static Long parseLong(String string, ExcelContentProperty contentProperty) throws ParseException { if (!hasFormat(contentProperty)) { - return new BigDecimal(string).longValue(); + return toLong(new BigDecimal(string)); } - return parse(string, contentProperty).longValue(); + return toLong(new BigDecimal(parse(string, contentProperty).toString())); } /** @@ -146,9 +146,9 @@ public static Long parseLong(String string, ExcelContentProperty contentProperty */ public static Integer parseInteger(String string, ExcelContentProperty contentProperty) throws ParseException { if (!hasFormat(contentProperty)) { - return new BigDecimal(string).intValue(); + return toInt(new BigDecimal(string)); } - return parse(string, contentProperty).intValue(); + return toInt(new BigDecimal(parse(string, contentProperty).toString())); } /** @@ -189,9 +189,52 @@ public static BigDecimal parseBigDecimal(String string, ExcelContentProperty con */ public static Byte parseByte(String string, ExcelContentProperty contentProperty) throws ParseException { if (!hasFormat(contentProperty)) { - return new BigDecimal(string).byteValue(); + return toByte(new BigDecimal(string)); } - return parse(string, contentProperty).byteValue(); + return toByte(new BigDecimal(parse(string, contentProperty).toString())); + } + + /** + * Truncates towards zero like {@link BigDecimal#byteValue()}, but throws instead of wrapping around. + * + * @throws ArithmeticException if the integral part is out of range for {@code byte} + */ + public static byte toByte(BigDecimal value) { + return checkRange(value, IntegralRange.BYTE).byteValue(); + } + + /** + * Truncates towards zero like {@link BigDecimal#shortValue()}, but throws instead of wrapping around. + * + * @throws ArithmeticException if the integral part is out of range for {@code short} + */ + public static short toShort(BigDecimal value) { + return checkRange(value, IntegralRange.SHORT).shortValue(); + } + + /** + * Truncates towards zero like {@link BigDecimal#intValue()}, but throws instead of wrapping around. + * + * @throws ArithmeticException if the integral part is out of range for {@code int} + */ + public static int toInt(BigDecimal value) { + return checkRange(value, IntegralRange.INTEGER).intValue(); + } + + /** + * Truncates towards zero like {@link BigDecimal#longValue()}, but throws instead of wrapping around. + * + * @throws ArithmeticException if the integral part is out of range for {@code long} + */ + public static long toLong(BigDecimal value) { + return checkRange(value, IntegralRange.LONG).longValue(); + } + + private static BigDecimal checkRange(BigDecimal value, IntegralRange range) { + if (value.compareTo(range.lower) <= 0 || value.compareTo(range.upper) >= 0) { + throw new ArithmeticException(value + " is out of range for " + range.type); + } + return value; } /** @@ -257,4 +300,24 @@ private static DecimalFormat getCacheDecimalFormat(String format, RoundingMode r public static void removeThreadLocalCache() { DECIMAL_FORMAT_THREAD_LOCAL.remove(); } + + /** + * Exclusive bounds: a value strictly between them truncates towards zero into the target type's range. + */ + private enum IntegralRange { + BYTE(Byte.MIN_VALUE, Byte.MAX_VALUE, "Byte"), + SHORT(Short.MIN_VALUE, Short.MAX_VALUE, "Short"), + INTEGER(Integer.MIN_VALUE, Integer.MAX_VALUE, "Integer"), + LONG(Long.MIN_VALUE, Long.MAX_VALUE, "Long"); + + private final BigDecimal lower; + private final BigDecimal upper; + private final String type; + + IntegralRange(long min, long max, String type) { + this.lower = BigDecimal.valueOf(min).subtract(BigDecimal.ONE); + this.upper = BigDecimal.valueOf(max).add(BigDecimal.ONE); + this.type = type; + } + } } diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ConverterTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ConverterTest.java index e35527d44..cb25098cf 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ConverterTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/converter/ConverterTest.java @@ -171,6 +171,9 @@ void integerConverters() throws Exception { IntegerNumberConverter numberConverter = new IntegerNumberConverter(); assertEquals(42, toJava(numberConverter, new ReadCellData<>(new BigDecimal("42.9")))); + Assertions.assertThrows( + ArithmeticException.class, + () -> toJava(numberConverter, new ReadCellData<>(new BigDecimal("2147483648")))); WriteConverterContext writeContext = new WriteConverterContext<>(); writeContext.setValue(42); WriteCellData numberCell = numberConverter.convertToExcelData(writeContext); @@ -191,6 +194,9 @@ void longConverters() throws Exception { LongNumberConverter numberConverter = new LongNumberConverter(); assertEquals(99L, toJava(numberConverter, new ReadCellData<>(new BigDecimal("99.1")))); + Assertions.assertThrows( + ArithmeticException.class, + () -> toJava(numberConverter, new ReadCellData<>(new BigDecimal("9223372036854775808")))); WriteConverterContext writeContext = new WriteConverterContext<>(); writeContext.setValue(99L); assertEquals( @@ -215,6 +221,8 @@ void shortConverters() throws Exception { ShortNumberConverter numberConverter = new ShortNumberConverter(); assertEquals((short) 7, toJava(numberConverter, new ReadCellData<>(new BigDecimal("7.8")))); + Assertions.assertThrows( + ArithmeticException.class, () -> toJava(numberConverter, new ReadCellData<>(new BigDecimal("32768")))); WriteConverterContext writeContext = new WriteConverterContext<>(); writeContext.setValue((short) 7); assertEquals( @@ -239,6 +247,8 @@ void byteConverters() throws Exception { ByteNumberConverter numberConverter = new ByteNumberConverter(); assertEquals((byte) 3, toJava(numberConverter, new ReadCellData<>(new BigDecimal("3.9")))); + Assertions.assertThrows( + ArithmeticException.class, () -> toJava(numberConverter, new ReadCellData<>(new BigDecimal("128")))); assertEquals(0, toExcel(numberConverter, (byte) 3).getNumberValue().compareTo(new BigDecimal("3"))); ByteStringConverter stringConverter = new ByteStringConverter(); diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/NumberUtilsTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/NumberUtilsTest.java index 4e16b9567..32502fe3b 100644 --- a/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/NumberUtilsTest.java +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/util/NumberUtilsTest.java @@ -343,6 +343,68 @@ void test_parseShort_noFormat() throws ParseException { Assertions.assertEquals((short) 789, resultEmptyFormat); } + @Test + void test_parseInteger_noFormat_truncatesFraction() throws ParseException { + Assertions.assertEquals(1, NumberUtils.parseInteger("1.9", null)); + Assertions.assertEquals(-1, NumberUtils.parseInteger("-1.9", null)); + } + + @Test + void test_parseIntegral_noFormat_outOfRange() { + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.parseByte("128", null)); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.parseShort("32768", null)); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.parseInteger("13800138000", null)); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.parseLong("9223372036854775808", null)); + } + + @Test + void test_parseIntegral_withFormat_outOfRange() { + Mockito.when(contentProperty.getNumberFormatProperty()).thenReturn(numberFormatProperty); + Mockito.when(numberFormatProperty.getFormat()).thenReturn("#,###"); + Mockito.when(numberFormatProperty.getRoundingMode()).thenReturn(RoundingMode.HALF_UP); + + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.parseByte("128", contentProperty)); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.parseShort("32,768", contentProperty)); + Assertions.assertThrows( + ArithmeticException.class, () -> NumberUtils.parseInteger("13,800,138,000", contentProperty)); + Assertions.assertThrows( + ArithmeticException.class, () -> NumberUtils.parseLong("9,223,372,036,854,775,808", contentProperty)); + } + + @Test + void test_toByte_bounds() { + Assertions.assertEquals(Byte.MAX_VALUE, NumberUtils.toByte(new BigDecimal("127.9"))); + Assertions.assertEquals(Byte.MIN_VALUE, NumberUtils.toByte(new BigDecimal("-128.9"))); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.toByte(new BigDecimal("128"))); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.toByte(new BigDecimal("-129"))); + } + + @Test + void test_toShort_bounds() { + Assertions.assertEquals(Short.MAX_VALUE, NumberUtils.toShort(new BigDecimal("32767.9"))); + Assertions.assertEquals(Short.MIN_VALUE, NumberUtils.toShort(new BigDecimal("-32768.9"))); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.toShort(new BigDecimal("32768"))); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.toShort(new BigDecimal("-32769"))); + } + + @Test + void test_toInt_bounds() { + Assertions.assertEquals(Integer.MAX_VALUE, NumberUtils.toInt(new BigDecimal("2147483647.9"))); + Assertions.assertEquals(Integer.MIN_VALUE, NumberUtils.toInt(new BigDecimal("-2147483648.9"))); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.toInt(new BigDecimal("2147483648"))); + Assertions.assertThrows(ArithmeticException.class, () -> NumberUtils.toInt(new BigDecimal("-2147483649"))); + } + + @Test + void test_toLong_bounds() { + Assertions.assertEquals(Long.MAX_VALUE, NumberUtils.toLong(new BigDecimal("9223372036854775807.9"))); + Assertions.assertEquals(Long.MIN_VALUE, NumberUtils.toLong(new BigDecimal("-9223372036854775808.9"))); + Assertions.assertThrows( + ArithmeticException.class, () -> NumberUtils.toLong(new BigDecimal("9223372036854775808"))); + Assertions.assertThrows( + ArithmeticException.class, () -> NumberUtils.toLong(new BigDecimal("-9223372036854775809"))); + } + @Test void test_parse_Error() { Mockito.when(contentProperty.getNumberFormatProperty()).thenReturn(numberFormatProperty); From 1168fec440c7911e570459b2c87d9333dead6eb1 Mon Sep 17 00:00:00 2001 From: Nikita Kuprins Date: Tue, 15 Sep 2026 01:18:12 +0300 Subject: [PATCH 2/3] refactor: parse integral types via parseBigDecimal --- .../apache/fesod/sheet/util/NumberUtils.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java index 180f47143..ff356c74e 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java @@ -117,10 +117,7 @@ public static WriteCellData formatToCellData(Number num, ExcelContentProperty * @return */ public static Short parseShort(String string, ExcelContentProperty contentProperty) throws ParseException { - if (!hasFormat(contentProperty)) { - return toShort(new BigDecimal(string)); - } - return toShort(new BigDecimal(parse(string, contentProperty).toString())); + return toShort(parseBigDecimal(string, contentProperty)); } /** @@ -131,10 +128,7 @@ public static Short parseShort(String string, ExcelContentProperty contentProper * @return */ public static Long parseLong(String string, ExcelContentProperty contentProperty) throws ParseException { - if (!hasFormat(contentProperty)) { - return toLong(new BigDecimal(string)); - } - return toLong(new BigDecimal(parse(string, contentProperty).toString())); + return toLong(parseBigDecimal(string, contentProperty)); } /** @@ -145,10 +139,7 @@ public static Long parseLong(String string, ExcelContentProperty contentProperty * @return An integer converted from a string */ public static Integer parseInteger(String string, ExcelContentProperty contentProperty) throws ParseException { - if (!hasFormat(contentProperty)) { - return toInt(new BigDecimal(string)); - } - return toInt(new BigDecimal(parse(string, contentProperty).toString())); + return toInt(parseBigDecimal(string, contentProperty)); } /** @@ -188,10 +179,7 @@ public static BigDecimal parseBigDecimal(String string, ExcelContentProperty con * @return */ public static Byte parseByte(String string, ExcelContentProperty contentProperty) throws ParseException { - if (!hasFormat(contentProperty)) { - return toByte(new BigDecimal(string)); - } - return toByte(new BigDecimal(parse(string, contentProperty).toString())); + return toByte(parseBigDecimal(string, contentProperty)); } /** From 34786acdfb32a4e338d8da7eb80e17e197d4d977 Mon Sep 17 00:00:00 2001 From: Nikita Kuprins Date: Tue, 15 Sep 2026 11:36:50 +0300 Subject: [PATCH 3/3] chore: remove unnecessary comment --- .../src/main/java/org/apache/fesod/sheet/util/NumberUtils.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java index ff356c74e..28734d0ff 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/util/NumberUtils.java @@ -289,9 +289,6 @@ public static void removeThreadLocalCache() { DECIMAL_FORMAT_THREAD_LOCAL.remove(); } - /** - * Exclusive bounds: a value strictly between them truncates towards zero into the target type's range. - */ private enum IntegralRange { BYTE(Byte.MIN_VALUE, Byte.MAX_VALUE, "Byte"), SHORT(Short.MIN_VALUE, Short.MAX_VALUE, "Short"),