From bd80c2dfce1fc09766d4586de06ddfba92ef4c30 Mon Sep 17 00:00:00 2001 From: Ivan Hetman Date: Tue, 7 Nov 2023 21:02:58 +0200 Subject: [PATCH] classlib: fix parse and other issues in Long and Integer --- .../teavm/classlib/java/lang/TInteger.java | 18 +- .../org/teavm/classlib/java/lang/TLong.java | 29 +++- .../teavm/classlib/java/lang/IntegerTest.java | 99 ++++++++++- .../teavm/classlib/java/lang/LongTest.java | 155 +++++++++++++++++- 4 files changed, 285 insertions(+), 16 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java index 343b7df73..f5e13b498 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java @@ -95,11 +95,12 @@ public class TInteger extends TNumber implements TComparable { break; } int value = 0; + int maxValue = 1 + TInteger.MAX_VALUE / radix; if (index == endIndex) { throw new TNumberFormatException(); } while (index < endIndex) { - int digit = TCharacter.getNumericValue(s.charAt(index++)); + int digit = decodeDigit(s.charAt(index++)); if (digit < 0) { throw new TNumberFormatException("String contains invalid digits: " + s.subSequence(beginIndex, endIndex)); @@ -108,6 +109,9 @@ public class TInteger extends TNumber implements TComparable { throw new TNumberFormatException("String contains digits out of radix " + radix + ": " + s.subSequence(beginIndex, endIndex)); } + if (value > maxValue) { + throw new TNumberFormatException("The value is too big for integer type"); + } value = radix * value + digit; if (value < 0) { if (index == endIndex && value == MIN_VALUE && negative) { @@ -205,8 +209,8 @@ public class TInteger extends TNumber implements TComparable { } public static TInteger decode(String nm) throws TNumberFormatException { - if (nm == null || nm.isEmpty()) { - throw new TNumberFormatException("Can't parse empty or null string"); + if (nm.isEmpty()) { + throw new TNumberFormatException("Can't parse empty string"); } int index = 0; boolean negaive = false; @@ -239,11 +243,15 @@ public class TInteger extends TNumber implements TComparable { throw new TNumberFormatException("The string does not represent a number"); } int value = 0; + int maxValue = 1 + TInteger.MAX_VALUE / radix; while (index < nm.length()) { int digit = decodeDigit(nm.charAt(index++)); - if (digit >= radix) { + if (digit < 0 || digit >= radix) { throw new TNumberFormatException("The string does not represent a number"); } + if (value > maxValue) { + throw new TNumberFormatException("The value is too big for integer type"); + } value = value * radix + digit; if (value < 0) { if (negaive && value == MIN_VALUE && index == nm.length()) { @@ -263,7 +271,7 @@ public class TInteger extends TNumber implements TComparable { } else if (c >= 'A' && c <= 'Z') { return c - 'A' + 10; } else { - return 255; + return -1; } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java index eaaddc5d1..f145b9dcb 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java @@ -72,8 +72,12 @@ public class TLong extends TNumber implements TComparable { break; } long value = 0; + long maxValue = 1 + TLong.MAX_VALUE / radix; + if (index == endIndex) { + throw new TNumberFormatException(); + } while (index < endIndex) { - int digit = TCharacter.getNumericValue(s.charAt(index++)); + int digit = decodeDigit(s.charAt(index++)); if (digit < 0) { throw new TNumberFormatException("String contains invalid digits: " + s.subSequence(beginIndex, endIndex)); @@ -82,12 +86,15 @@ public class TLong extends TNumber implements TComparable { throw new TNumberFormatException("String contains digits out of radix " + radix + ": " + s.subSequence(beginIndex, endIndex)); } + if (value > maxValue) { + throw new TNumberFormatException("The value is too big for long type"); + } value = radix * value + digit; if (value < 0) { if (index == endIndex && value == MIN_VALUE && negative) { return MIN_VALUE; } - throw new TNumberFormatException("The value is too big for int type: " + throw new TNumberFormatException("The value is too big for long type: " + s.subSequence(beginIndex, endIndex)); } } @@ -107,8 +114,8 @@ public class TLong extends TNumber implements TComparable { } public static TLong decode(TString nm) throws TNumberFormatException { - if (nm == null || nm.isEmpty()) { - throw new TNumberFormatException("Can't parse empty or null string"); + if (nm.isEmpty()) { + throw new TNumberFormatException("Can't parse empty string"); } int index = 0; boolean negaive = false; @@ -141,11 +148,15 @@ public class TLong extends TNumber implements TComparable { throw new TNumberFormatException("The string does not represent a number"); } long value = 0; + long maxValue = 1 + TLong.MAX_VALUE / radix; while (index < nm.length()) { int digit = decodeDigit(nm.charAt(index++)); - if (digit >= radix) { + if (digit < 0 || digit >= radix) { throw new TNumberFormatException("The string does not represent a number"); } + if (value > maxValue) { + throw new TNumberFormatException("The value is too big for long type"); + } value = value * radix + digit; if (value < 0) { if (negaive && value == MIN_VALUE && index == nm.length()) { @@ -165,7 +176,7 @@ public class TLong extends TNumber implements TComparable { } else if (c >= 'A' && c <= 'Z') { return c - 'A' + 10; } else { - return 255; + return -1; } } @Override @@ -358,9 +369,9 @@ public class TLong extends TNumber implements TComparable { } public static long reverseBytes(long i) { - i = ((i & 0xFF00FF00FF00FF00L) >> 8) | ((i & 0x00FF00FF00FF00FFL) << 8); - i = ((i & 0xFFFF0000FFFF0000L) >> 16) | ((i & 0x0000FFFF0000FFFFL) << 16); - i = (i >> 32) | (i << 32); + i = ((i & 0xFF00FF00FF00FF00L) >>> 8) | ((i & 0x00FF00FF00FF00FFL) << 8); + i = ((i & 0xFFFF0000FFFF0000L) >>> 16) | ((i & 0x0000FFFF0000FFFFL) << 16); + i = (i >>> 32) | (i << 32); return i; } diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/IntegerTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/IntegerTest.java index a706bc467..49a6aa54e 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/IntegerTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/IntegerTest.java @@ -18,6 +18,7 @@ package org.teavm.classlib.java.lang; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.TeaVMTestRunner; @@ -48,12 +49,72 @@ public class IntegerTest { assertEquals(473, Integer.parseInt("[473]", 1, 4, 10)); assertEquals(42, Integer.parseInt("[+42]", 1, 4, 10)); assertEquals(-255, Integer.parseInt("[-FF]", 1, 4, 16)); + try { + Integer.parseInt("[-FF]", 1, 5, 16); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.parseInt("[-FF]", 1, 6, 16); + fail(); + } catch (IndexOutOfBoundsException e) { + // ok + } + try { + Integer.parseInt("[-FF]", 1, 2, 16); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.parseInt("[-FF]", 5, 4, 16); + fail(); + } catch (IndexOutOfBoundsException e) { + // ok + } } @Test - public void parsesMinInteger() { + public void parsesCornerCases() { assertEquals(-2147483648, Integer.parseInt("-2147483648", 10)); assertEquals(-2147483648, Integer.parseInt("-80000000", 16)); + try { + Integer.parseInt("FFFF", 10); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.parseInt("2147483648", 10); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.parseInt("-2147483649", 10); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.parseInt("80000000", 16); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.parseInt("-80000001", 16); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.parseInt("99999999999", 10); + fail(); + } catch (NumberFormatException e) { + // ok + } } @Test(expected = NumberFormatException.class) @@ -86,6 +147,42 @@ public class IntegerTest { assertEquals(Integer.valueOf(65535), Integer.decode("+0xFFFF")); assertEquals(Integer.valueOf(-255), Integer.decode("-0xFF")); assertEquals(Integer.valueOf(2748), Integer.decode("+#ABC")); + try { + Integer.decode(null); // undocumented NPE + fail(); + } catch (NullPointerException e) { + // ok + } + try { + Integer.decode("2147483648"); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.decode("-2147483649"); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.decode("0x80000000"); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.decode("-0x80000001"); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Integer.decode("99999999999"); + fail(); + } catch (NumberFormatException e) { + // ok + } } @Test diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/LongTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/LongTest.java index e0697782f..0920b99f3 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/LongTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/LongTest.java @@ -17,6 +17,7 @@ package org.teavm.classlib.java.lang; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.SkipJVM; @@ -24,13 +25,36 @@ import org.teavm.junit.TeaVMTestRunner; @RunWith(TeaVMTestRunner.class) public class LongTest { - @Test public void parsesLongInSubstring() { assertEquals(0, Long.parseLong("[0]", 1, 2, 10)); assertEquals(473, Long.parseLong("[473]", 1, 4, 10)); assertEquals(42, Long.parseLong("[+42]", 1, 4, 10)); assertEquals(-255, Long.parseLong("[-FF]", 1, 4, 16)); + try { + Long.parseLong("[-FF]", 1, 5, 16); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.parseLong("[-FF]", 1, 6, 16); + fail(); + } catch (IndexOutOfBoundsException e) { + // ok + } + try { + Long.parseLong("[-FF]", 1, 2, 16); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.parseLong("[-FF]", 5, 4, 16); + fail(); + } catch (IndexOutOfBoundsException e) { + // ok + } } @Test @@ -109,4 +133,133 @@ public class LongTest { assertEquals("-1000000000000000000000000000000000000000000000000000000000000000", Long.toString(Long.MIN_VALUE, 2)); } + + @Test + public void reverseBytes() { + assertEquals(0xAABBCCDD00112233L, Long.reverseBytes(0x33221100DDCCBBAAL)); + assertEquals(0x1122334455667788L, Long.reverseBytes(0x8877665544332211L)); + assertEquals(0x0011223344556677L, Long.reverseBytes(0x7766554433221100L)); + assertEquals(0x2000000000000002L, Long.reverseBytes(0x0200000000000020L)); + } + + @Test + public void decode() { + assertEquals("Returned incorrect value for hex string", 255L, + Long.decode("0xFF").longValue()); + assertEquals("Returned incorrect value for dec string", -89000L, + Long.decode("-89000").longValue()); + assertEquals("Returned incorrect value for 0 decimal", 0, + Long.decode("0").longValue()); + assertEquals("Returned incorrect value for 0 hex", 0, + Long.decode("0x0").longValue()); + assertEquals("Returned incorrect value for most negative value decimal", 0x8000000000000000L, + Long.decode("-9223372036854775808").longValue()); + assertEquals("Returned incorrect value for most negative value hex", 0x8000000000000000L, + Long.decode("-0x8000000000000000").longValue()); + assertEquals("Returned incorrect value for most positive value decimal", 0x7fffffffffffffffL, + Long.decode("9223372036854775807").longValue()); + assertEquals("Returned incorrect value for most positive value hex", 0x7fffffffffffffffL, + Long.decode("0x7fffffffffffffff").longValue()); + assertEquals("Failed for 07654321765432", 07654321765432L, + Long.decode("07654321765432").longValue()); + try { + Long.decode(null); // undocumented NPE + fail(); + } catch (NullPointerException e) { + // ok + } + try { + Long.decode("999999999999999999999999999999999999999999999999999999"); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.decode("9223372036854775808"); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.decode("-9223372036854775809"); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.decode("0x8000000000000000"); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.decode("-0x8000000000000001"); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.decode("42325917317067571199"); + fail(); + } catch (NumberFormatException e) { + // ok + } + } + + @Test + public void test_parseLong() { + assertEquals("Returned incorrect value", + 100000000L, Long.parseLong("100000000", 10)); + assertEquals("Returned incorrect value from hex string", 68719476735L, + Long.parseLong("FFFFFFFFF", 16)); + assertEquals("Returned incorrect value from octal string: " + Long.parseLong("77777777777"), + 8589934591L, Long.parseLong("77777777777", 8)); + assertEquals("Returned incorrect value for 0 hex", 0, Long.parseLong("0", 16)); + assertEquals("Returned incorrect value for most negative value hex", 0x8000000000000000L, + Long.parseLong("-8000000000000000", 16)); + assertEquals("Returned incorrect value for most positive value hex", 0x7fffffffffffffffL, + Long.parseLong("7fffffffffffffff", 16)); + assertEquals("Returned incorrect value for 0 decimal", 0, + Long.parseLong("0", 10)); + assertEquals("Returned incorrect value for most negative value decimal", 0x8000000000000000L, + Long.parseLong("-9223372036854775808", 10)); + assertEquals("Returned incorrect value for most positive value decimal", 0x7fffffffffffffffL, + Long.parseLong("9223372036854775807", 10)); + try { + Long.parseLong("999999999999", 8); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.parseLong("9223372036854775808", 10); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.parseLong("-9223372036854775809", 10); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.parseLong("8000000000000000", 16); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.parseLong("-8000000000000001", 16); + fail(); + } catch (NumberFormatException e) { + // ok + } + try { + Long.parseLong("42325917317067571199", 10); + fail(); + } catch (NumberFormatException e) { + // ok + } + } }