classlib: fix parse and other issues in Long and Integer

This commit is contained in:
Ivan Hetman 2023-11-07 21:02:58 +02:00 committed by GitHub
parent 9c6f23d280
commit bd80c2dfce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 285 additions and 16 deletions

View File

@ -95,11 +95,12 @@ public class TInteger extends TNumber implements TComparable<TInteger> {
break; break;
} }
int value = 0; int value = 0;
int maxValue = 1 + TInteger.MAX_VALUE / radix;
if (index == endIndex) { if (index == endIndex) {
throw new TNumberFormatException(); throw new TNumberFormatException();
} }
while (index < endIndex) { while (index < endIndex) {
int digit = TCharacter.getNumericValue(s.charAt(index++)); int digit = decodeDigit(s.charAt(index++));
if (digit < 0) { if (digit < 0) {
throw new TNumberFormatException("String contains invalid digits: " throw new TNumberFormatException("String contains invalid digits: "
+ s.subSequence(beginIndex, endIndex)); + s.subSequence(beginIndex, endIndex));
@ -108,6 +109,9 @@ public class TInteger extends TNumber implements TComparable<TInteger> {
throw new TNumberFormatException("String contains digits out of radix " + radix + ": " throw new TNumberFormatException("String contains digits out of radix " + radix + ": "
+ s.subSequence(beginIndex, endIndex)); + s.subSequence(beginIndex, endIndex));
} }
if (value > maxValue) {
throw new TNumberFormatException("The value is too big for integer type");
}
value = radix * value + digit; value = radix * value + digit;
if (value < 0) { if (value < 0) {
if (index == endIndex && value == MIN_VALUE && negative) { if (index == endIndex && value == MIN_VALUE && negative) {
@ -205,8 +209,8 @@ public class TInteger extends TNumber implements TComparable<TInteger> {
} }
public static TInteger decode(String nm) throws TNumberFormatException { public static TInteger decode(String nm) throws TNumberFormatException {
if (nm == null || nm.isEmpty()) { if (nm.isEmpty()) {
throw new TNumberFormatException("Can't parse empty or null string"); throw new TNumberFormatException("Can't parse empty string");
} }
int index = 0; int index = 0;
boolean negaive = false; boolean negaive = false;
@ -239,11 +243,15 @@ public class TInteger extends TNumber implements TComparable<TInteger> {
throw new TNumberFormatException("The string does not represent a number"); throw new TNumberFormatException("The string does not represent a number");
} }
int value = 0; int value = 0;
int maxValue = 1 + TInteger.MAX_VALUE / radix;
while (index < nm.length()) { while (index < nm.length()) {
int digit = decodeDigit(nm.charAt(index++)); int digit = decodeDigit(nm.charAt(index++));
if (digit >= radix) { if (digit < 0 || digit >= radix) {
throw new TNumberFormatException("The string does not represent a number"); 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; value = value * radix + digit;
if (value < 0) { if (value < 0) {
if (negaive && value == MIN_VALUE && index == nm.length()) { if (negaive && value == MIN_VALUE && index == nm.length()) {
@ -263,7 +271,7 @@ public class TInteger extends TNumber implements TComparable<TInteger> {
} else if (c >= 'A' && c <= 'Z') { } else if (c >= 'A' && c <= 'Z') {
return c - 'A' + 10; return c - 'A' + 10;
} else { } else {
return 255; return -1;
} }
} }

View File

@ -72,8 +72,12 @@ public class TLong extends TNumber implements TComparable<TLong> {
break; break;
} }
long value = 0; long value = 0;
long maxValue = 1 + TLong.MAX_VALUE / radix;
if (index == endIndex) {
throw new TNumberFormatException();
}
while (index < endIndex) { while (index < endIndex) {
int digit = TCharacter.getNumericValue(s.charAt(index++)); int digit = decodeDigit(s.charAt(index++));
if (digit < 0) { if (digit < 0) {
throw new TNumberFormatException("String contains invalid digits: " throw new TNumberFormatException("String contains invalid digits: "
+ s.subSequence(beginIndex, endIndex)); + s.subSequence(beginIndex, endIndex));
@ -82,12 +86,15 @@ public class TLong extends TNumber implements TComparable<TLong> {
throw new TNumberFormatException("String contains digits out of radix " + radix + ": " throw new TNumberFormatException("String contains digits out of radix " + radix + ": "
+ s.subSequence(beginIndex, endIndex)); + s.subSequence(beginIndex, endIndex));
} }
if (value > maxValue) {
throw new TNumberFormatException("The value is too big for long type");
}
value = radix * value + digit; value = radix * value + digit;
if (value < 0) { if (value < 0) {
if (index == endIndex && value == MIN_VALUE && negative) { if (index == endIndex && value == MIN_VALUE && negative) {
return MIN_VALUE; 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)); + s.subSequence(beginIndex, endIndex));
} }
} }
@ -107,8 +114,8 @@ public class TLong extends TNumber implements TComparable<TLong> {
} }
public static TLong decode(TString nm) throws TNumberFormatException { public static TLong decode(TString nm) throws TNumberFormatException {
if (nm == null || nm.isEmpty()) { if (nm.isEmpty()) {
throw new TNumberFormatException("Can't parse empty or null string"); throw new TNumberFormatException("Can't parse empty string");
} }
int index = 0; int index = 0;
boolean negaive = false; boolean negaive = false;
@ -141,11 +148,15 @@ public class TLong extends TNumber implements TComparable<TLong> {
throw new TNumberFormatException("The string does not represent a number"); throw new TNumberFormatException("The string does not represent a number");
} }
long value = 0; long value = 0;
long maxValue = 1 + TLong.MAX_VALUE / radix;
while (index < nm.length()) { while (index < nm.length()) {
int digit = decodeDigit(nm.charAt(index++)); int digit = decodeDigit(nm.charAt(index++));
if (digit >= radix) { if (digit < 0 || digit >= radix) {
throw new TNumberFormatException("The string does not represent a number"); 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; value = value * radix + digit;
if (value < 0) { if (value < 0) {
if (negaive && value == MIN_VALUE && index == nm.length()) { if (negaive && value == MIN_VALUE && index == nm.length()) {
@ -165,7 +176,7 @@ public class TLong extends TNumber implements TComparable<TLong> {
} else if (c >= 'A' && c <= 'Z') { } else if (c >= 'A' && c <= 'Z') {
return c - 'A' + 10; return c - 'A' + 10;
} else { } else {
return 255; return -1;
} }
} }
@Override @Override
@ -358,9 +369,9 @@ public class TLong extends TNumber implements TComparable<TLong> {
} }
public static long reverseBytes(long i) { public static long reverseBytes(long i) {
i = ((i & 0xFF00FF00FF00FF00L) >> 8) | ((i & 0x00FF00FF00FF00FFL) << 8); i = ((i & 0xFF00FF00FF00FF00L) >>> 8) | ((i & 0x00FF00FF00FF00FFL) << 8);
i = ((i & 0xFFFF0000FFFF0000L) >> 16) | ((i & 0x0000FFFF0000FFFFL) << 16); i = ((i & 0xFFFF0000FFFF0000L) >>> 16) | ((i & 0x0000FFFF0000FFFFL) << 16);
i = (i >> 32) | (i << 32); i = (i >>> 32) | (i << 32);
return i; return i;
} }

View File

@ -18,6 +18,7 @@ package org.teavm.classlib.java.lang;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.teavm.junit.TeaVMTestRunner; import org.teavm.junit.TeaVMTestRunner;
@ -48,12 +49,72 @@ public class IntegerTest {
assertEquals(473, Integer.parseInt("[473]", 1, 4, 10)); assertEquals(473, Integer.parseInt("[473]", 1, 4, 10));
assertEquals(42, Integer.parseInt("[+42]", 1, 4, 10)); assertEquals(42, Integer.parseInt("[+42]", 1, 4, 10));
assertEquals(-255, Integer.parseInt("[-FF]", 1, 4, 16)); 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 @Test
public void parsesMinInteger() { public void parsesCornerCases() {
assertEquals(-2147483648, Integer.parseInt("-2147483648", 10)); assertEquals(-2147483648, Integer.parseInt("-2147483648", 10));
assertEquals(-2147483648, Integer.parseInt("-80000000", 16)); 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) @Test(expected = NumberFormatException.class)
@ -86,6 +147,42 @@ public class IntegerTest {
assertEquals(Integer.valueOf(65535), Integer.decode("+0xFFFF")); assertEquals(Integer.valueOf(65535), Integer.decode("+0xFFFF"));
assertEquals(Integer.valueOf(-255), Integer.decode("-0xFF")); assertEquals(Integer.valueOf(-255), Integer.decode("-0xFF"));
assertEquals(Integer.valueOf(2748), Integer.decode("+#ABC")); 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 @Test

View File

@ -17,6 +17,7 @@ package org.teavm.classlib.java.lang;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.teavm.junit.SkipJVM; import org.teavm.junit.SkipJVM;
@ -24,13 +25,36 @@ import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
public class LongTest { public class LongTest {
@Test @Test
public void parsesLongInSubstring() { public void parsesLongInSubstring() {
assertEquals(0, Long.parseLong("[0]", 1, 2, 10)); assertEquals(0, Long.parseLong("[0]", 1, 2, 10));
assertEquals(473, Long.parseLong("[473]", 1, 4, 10)); assertEquals(473, Long.parseLong("[473]", 1, 4, 10));
assertEquals(42, Long.parseLong("[+42]", 1, 4, 10)); assertEquals(42, Long.parseLong("[+42]", 1, 4, 10));
assertEquals(-255, Long.parseLong("[-FF]", 1, 4, 16)); 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 @Test
@ -109,4 +133,133 @@ public class LongTest {
assertEquals("-1000000000000000000000000000000000000000000000000000000000000000", assertEquals("-1000000000000000000000000000000000000000000000000000000000000000",
Long.toString(Long.MIN_VALUE, 2)); 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
}
}
} }