diff --git a/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java b/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java index 236abe859..c988639e2 100644 --- a/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java +++ b/src/main/java/org/apache/xmlbeans/impl/util/XsTypeConverter.java @@ -93,6 +93,7 @@ public static float lexFloat(CharSequence cs) */ public static float lexFloat(CharSequence cs, boolean strict) throws NumberFormatException { + rejectInvalidNumber(cs); final String v = cs.toString(); switch (v) { case POS_INF_LEX: @@ -162,6 +163,7 @@ public static double lexDouble(CharSequence cs) */ public static double lexDouble(CharSequence cs, boolean strict) throws NumberFormatException { + rejectInvalidNumber(cs); final String v = cs.toString(); switch (v) { case POS_INF_LEX: @@ -212,6 +214,7 @@ public static String printDouble(double value) { // ======================== decimal ======================== public static BigDecimal lexDecimal(CharSequence cs) throws NumberFormatException { + rejectInvalidNumber(cs); final String v = cs.toString(); //TODO: review this @@ -300,6 +303,7 @@ public static String printInteger(BigInteger value) { // ======================== long ======================== public static long lexLong(CharSequence cs) throws NumberFormatException { + rejectInvalidNumber(cs); rejectSignAfterPlus(cs); final String v = cs.toString(); return Long.parseLong(trimInitialPlus(v)); @@ -358,7 +362,7 @@ public static String printShort(short value) { // ======================== int ======================== public static int lexInt(CharSequence cs) throws NumberFormatException { - return parseInt(cs); + return parseIntXsdNumber(cs, Integer.MIN_VALUE, Integer.MAX_VALUE); } public static int lexInt(CharSequence cs, Collection errors) { @@ -642,10 +646,6 @@ private static String trimTrailingZeros(String xsd_decimal) { return xsd_decimal; } - private static int parseInt(CharSequence cs) { - return parseIntXsdNumber(cs, Integer.MIN_VALUE, Integer.MAX_VALUE); - } - private static short parseShort(CharSequence cs) { return (short) parseIntXsdNumber(cs, Short.MIN_VALUE, Short.MAX_VALUE); } @@ -655,57 +655,59 @@ private static byte parseByte(CharSequence cs) { } private static int parseIntXsdNumber(CharSequence ch, int min_value, int max_value) { - // int parser on a CharSequence - int length = ch.length(); - if (length < 1) { - throw new NumberFormatException("For input string: \"" + ch + "\""); - } - - int sign = 1; - int result = 0; - int start = 0; - int limit; - int limit2; - - char c = ch.charAt(0); - if (c == '-') { - start++; - limit = (min_value / 10); - limit2 = -(min_value % 10); - } else if (c == '+') { - start++; - sign = -1; - limit = -(max_value / 10); - limit2 = (max_value % 10); - } else { - sign = -1; - limit = -(max_value / 10); - limit2 = (max_value % 10); + rejectInvalidNumber(ch); + + final int len = ch.length(); + int i = 0; + boolean negative = false; + + // Sign + char first = ch.charAt(0); + if (first == '-') { + negative = true; + i = 1; + } else if (first == '+') { + i = 1; } - for (int i = 0; i < length - start; i++) { - c = ch.charAt(i + start); - int v = (c >= '0' && c <= '9') ? c - '0' : -1; + if (i == len) { + throw new NumberFormatException("For input string: \"" + ch + "\""); // just "+" or "-" + } - if (v < 0) { + long result = 0; // Use long to avoid intermediate overflow + + while (i < len) { + char c = ch.charAt(i++); + int digit = c - '0'; + if (digit < 0 || digit > 9) { throw new NumberFormatException("For input string: \"" + ch + "\""); } - if (result < limit || (result == limit && v > limit2)) { + // Early overflow detection + if (result > (Long.MAX_VALUE / 10)) { throw new NumberFormatException("For input string: \"" + ch + "\""); } + result = result * 10 + digit; + } + + if (negative) { + result = -result; + } - result = Math.toIntExact(result * 10L - v); + if (result < min_value || result > max_value) { + throw new NumberFormatException(String.format( + "For input string: \"%s\"; min-allowed=%d, max-allowed=%d", + ch, min_value, max_value)); } - return Math.multiplyExact(sign, result); + return Math.toIntExact(result); } // ======================== anyURI ======================== /** * Checks the regular expression of URI, defined by RFC2369 http://www.ietf.org/rfc/rfc2396.txt Appendix B. - * Note: The whitespace normalization rule collapse must be applied priot to calling this method. + * Note: The whitespace normalization rule collapse must be applied prior to calling this method. * * @param lexical_value the lexical value * @return same input value if input value is in the lexical space @@ -744,4 +746,10 @@ public static CharSequence lexAnyURI(CharSequence lexical_value) { return lexical_value; } + + private static void rejectInvalidNumber(CharSequence cs) { + if (cs == null || cs.length() == 0) { + throw new NumberFormatException("For input string: \"" + cs + "\""); + } + } } diff --git a/src/test/java/misc/checkin/XsTypeConverterTest.java b/src/test/java/misc/checkin/XsTypeConverterTest.java index 18a6b085e..615bfbfb9 100644 --- a/src/test/java/misc/checkin/XsTypeConverterTest.java +++ b/src/test/java/misc/checkin/XsTypeConverterTest.java @@ -35,8 +35,31 @@ public class XsTypeConverterTest { @Test void lexIntAcceptsAscii() { assertEquals(123, XsTypeConverter.lexInt("123")); + assertEquals(123, XsTypeConverter.lexInt("00123")); assertEquals(-123, XsTypeConverter.lexInt("-123")); assertEquals(123, XsTypeConverter.lexInt("+123")); + assertEquals(Integer.MAX_VALUE, XsTypeConverter.lexInt(Integer.toString(Integer.MAX_VALUE))); + assertEquals(Integer.MIN_VALUE, XsTypeConverter.lexInt(Integer.toString(Integer.MIN_VALUE))); + } + + @Test + void lexLongAcceptsAscii() { + assertEquals(123L, XsTypeConverter.lexLong("123")); + assertEquals(123L, XsTypeConverter.lexLong("00123")); + assertEquals(-123L, XsTypeConverter.lexLong("-123")); + assertEquals(123L, XsTypeConverter.lexLong("+123")); + assertEquals(Long.MAX_VALUE, XsTypeConverter.lexLong(Long.toString(Long.MAX_VALUE))); + assertEquals(Long.MIN_VALUE, XsTypeConverter.lexLong(Long.toString(Long.MIN_VALUE))); + } + + @Test + void lexShortAcceptsAscii() { + assertEquals(123, XsTypeConverter.lexShort("123")); + assertEquals(123, XsTypeConverter.lexShort("00123")); + assertEquals(-123, XsTypeConverter.lexShort("-123")); + assertEquals(123, XsTypeConverter.lexShort("+123")); + assertEquals(Short.MAX_VALUE, XsTypeConverter.lexShort(Short.toString(Short.MAX_VALUE))); + assertEquals(Short.MIN_VALUE, XsTypeConverter.lexShort(Short.toString(Short.MIN_VALUE))); } @Test @@ -46,9 +69,14 @@ void lexIntRejectsNonAsciiDigits() { assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexInt(DEVANAGARI_123)); } + @Test + void lexIntRejectsEmptyOrNull() { + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexInt(null)); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexInt("")); + } + @Test void lexShortRejectsNonAsciiDigits() { - assertEquals(123, XsTypeConverter.lexShort("123")); assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexShort(FULLWIDTH_123)); assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexShort(ARABIC_123)); } @@ -105,6 +133,7 @@ void lexFloatStrictRejectsNonXsdLexicalForms() { @Test void lexFloatStrictAcceptsValidValues() { assertEquals(1.0f, XsTypeConverter.lexFloat("1.0", true)); + assertEquals(1.0f, XsTypeConverter.lexFloat("01.0", true)); assertEquals(1500.0f, XsTypeConverter.lexFloat("1.5e3", true)); assertEquals(Float.POSITIVE_INFINITY, XsTypeConverter.lexFloat("INF", true)); assertEquals(Float.NEGATIVE_INFINITY, XsTypeConverter.lexFloat("-INF", true)); @@ -114,6 +143,7 @@ void lexFloatStrictAcceptsValidValues() { @Test void lexDoubleAcceptsValidValues() { assertEquals(1.0, XsTypeConverter.lexDouble("1.0")); + assertEquals(1.0, XsTypeConverter.lexDouble("01.0")); assertEquals(Double.POSITIVE_INFINITY, XsTypeConverter.lexDouble("INF")); assertEquals(Double.NEGATIVE_INFINITY, XsTypeConverter.lexDouble("-INF")); assertEquals(1500.0, XsTypeConverter.lexDouble("1.5e3")); @@ -140,6 +170,7 @@ void lexDoubleStrictRejectsNonXsdLexicalForms() { @Test void lexDoubleStrictAcceptsValidValues() { assertEquals(1.0, XsTypeConverter.lexDouble("1.0", true)); + assertEquals(1.0, XsTypeConverter.lexDouble("01.0", true)); assertEquals(1500.0, XsTypeConverter.lexDouble("1.5e3", true)); assertEquals(Double.POSITIVE_INFINITY, XsTypeConverter.lexDouble("INF", true)); assertEquals(Double.NEGATIVE_INFINITY, XsTypeConverter.lexDouble("-INF", true)); @@ -176,6 +207,12 @@ void lexLongRejectsDoubleSign() { assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexLong("+-5")); } + @Test + void lexLongRejectsEmptyOrNull() { + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexLong(null)); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexLong("")); + } + @Test void lexIntegerRejectsDoubleSign() { // "+-5" was already caught; "++5" leaked through as 5. @@ -190,6 +227,7 @@ void lexDecimalRejectsEmptyString() { // an empty value used to read charAt(-1) in trimTrailingZeros and // throw StringIndexOutOfBoundsException instead of NumberFormatException. assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexDecimal("")); + assertThrows(NumberFormatException.class, () -> XsTypeConverter.lexDecimal(null)); } @Test