From bc22ff45aee05510399feb06cef632522f3e7b44 Mon Sep 17 00:00:00 2001 From: Pieter12345 Date: Sat, 30 Nov 2024 03:55:21 +0100 Subject: [PATCH 1/3] Support max hex/oct/bin literals + Fix their uncaught exceptions - Support max 64-bit hex/oct/bin value literals, rather than only up to 63 bits. - Fix uncaught exceptions when supplying hex/oct/bin literals that do not fit 63 bits (64 bits with the above mentioned change). - Add tests for these number formats. --- .../java/com/laytonsmith/core/Static.java | 41 ++++++++++++++++--- .../java/com/laytonsmith/core/TestStatic.java | 16 ++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/laytonsmith/core/Static.java b/src/main/java/com/laytonsmith/core/Static.java index 4eaf4cbe4c..6dca7663c6 100644 --- a/src/main/java/com/laytonsmith/core/Static.java +++ b/src/main/java/com/laytonsmith/core/Static.java @@ -592,21 +592,52 @@ public static Construct resolveConstruct(String val, Target t, boolean returnBar throw new CREFormatException("Hex numbers must only contain digits 0-9, and the letters A-F, but \"" + val + "\" was found.", t); } if(VALID_HEX.matcher(val).matches()) { - //Hex number - return new CInt(Long.parseLong(val.substring(2), 16), t); + + // Parse hex number. Special handling for 16-digit numbers to support setting all 64 bits of the value. + long longVal; + if(val.length() > 16 + 2) { + throw new CREFormatException("Hex numbers must contain at most 16 digits, but \"" + val + "\" was found.", t); + } else if(val.length() == 16 + 2) { + longVal = (Long.parseLong(val.substring(2, 10), 16) << 32) | Long.parseLong(val.substring(10, 18), 16); + } else { + longVal = Long.parseLong(val.substring(2), 16); + } + return new CInt(longVal, t); } if(INVALID_BINARY.matcher(val).matches()) { throw new CREFormatException("Binary numbers must only contain digits 0 and 1, but \"" + val + "\" was found.", t); } if(VALID_BINARY.matcher(val).matches()) { - //Binary number - return new CInt(Long.parseLong(val.substring(2), 2), t); + + // Parse binary number. Special handling for 64-digit numbers to support setting all 64 bits of the value. + long longVal; + if(val.length() > 64 + 2) { + throw new CREFormatException("Binary numbers must contain at most 64 digits, but \"" + val + "\" was found.", t); + } else if(val.length() == 64 + 2) { + longVal = (Long.parseLong(val.substring(2, 34), 2) << 32) | Long.parseLong(val.substring(34, 66), 2); + } else { + longVal = Long.parseLong(val.substring(2), 2); + } + return new CInt(longVal, t); } if(INVALID_OCTAL.matcher(val).matches()) { throw new CREFormatException("Octal numbers must only contain digits 0-7, but \"" + val + "\" was found.", t); } if(VALID_OCTAL.matcher(val).matches()) { - return new CInt(Long.parseLong(val.substring(2), 8), t); + + // Parse octal number. Special handling for 8-digit numbers to support setting all 64 bits of the value. + long longVal; + if(val.length() > 22 + 2) { + throw new CREFormatException("Octal numbers must contain at most 22 digits, but \"" + val + "\" was found.", t); + } else if(val.length() == 22 + 2) { + if(val.charAt(2) != '1') { + throw new CREFormatException("Octal number exceeds maximum 64-bit value 0o1777777777777777777777. Found \"" + val + "\".", t); + } + longVal = Long.parseLong(val.substring(3), 8) | (1L << 63); + } else { + longVal = Long.parseLong(val.substring(2), 8); + } + return new CInt(longVal, t); } if(INVALID_DECIMAL.matcher(val).matches()) { throw new CREFormatException("Decimal numbers must only contain digits, but \"" + val + "\" was found.", t); diff --git a/src/test/java/com/laytonsmith/core/TestStatic.java b/src/test/java/com/laytonsmith/core/TestStatic.java index 9475c79050..120bcb001d 100644 --- a/src/test/java/com/laytonsmith/core/TestStatic.java +++ b/src/test/java/com/laytonsmith/core/TestStatic.java @@ -111,6 +111,22 @@ public void testResolveConstruct() { assertTrue(Static.resolveConstruct("1.1", Target.UNKNOWN) instanceof CDouble); assertTrue(Static.resolveConstruct("astring", Target.UNKNOWN) instanceof CString); assertTrue(Static.resolveConstruct("string", Target.UNKNOWN) instanceof CClassType); + assertTrue(getResolveConstructLong("0xFF") == 0xFF); + assertTrue(getResolveConstructLong("0xABCDEF0123456789") == 0xABCDEF0123456789L); // All chars. + assertTrue(getResolveConstructLong("0xFFAFFFFFFFF0FFFF") == 0xFFAFFFFFFFF0FFFFL); + assertTrue(getResolveConstructLong("0xFFFFFFFFFFFFFFFF") == 0xFFFFFFFFFFFFFFFFL); // Max value. + assertTrue(getResolveConstructLong("0b100") == 0b100); + assertTrue(getResolveConstructLong("0b1111011111111011111111111011111111111111111111110111111111111110") + == 0b1111011111111011111111111011111111111111111111110111111111111110L); + assertTrue(getResolveConstructLong("0b1111111111111111111111111111111111111111111111111111111111111111") + == 0b1111111111111111111111111111111111111111111111111111111111111111L); // Max value. + assertTrue(getResolveConstructLong("0o76543210") == 076543210L); // All chars. + assertTrue(getResolveConstructLong("0o1737745677477125767277") == 01737745677477125767277L); + assertTrue(getResolveConstructLong("0o1777777777777777777777") == 01777777777777777777777L); // Max value. + } + + private static long getResolveConstructLong(String val) { + return ((CInt) Static.resolveConstruct(val, Target.UNKNOWN)).getInt(); } } From f7d69912b69d05ee691a7bd4b263ce39c8aa41e7 Mon Sep 17 00:00:00 2001 From: Pieter12345 Date: Sun, 1 Dec 2024 01:45:58 +0100 Subject: [PATCH 2/3] Convert hex/oct/bin CREFormatException to compile exception --- .../java/com/laytonsmith/core/MethodScriptCompiler.java | 7 ++++++- .../com/laytonsmith/core/MethodScriptCompilerTest.java | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java b/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java index f7f56e298b..acc43b761b 100644 --- a/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java +++ b/src/main/java/com/laytonsmith/core/MethodScriptCompiler.java @@ -1763,7 +1763,12 @@ public static ParseTree compile(TokenStream stream, Environment environment, } continue; } else if(t.type == TType.LIT) { - Construct c = Static.resolveConstruct(t.val(), t.target, true); + Construct c; + try { + c = Static.resolveConstruct(t.val(), t.target, true); + } catch (ConfigRuntimeException ex) { + throw new ConfigCompileException(ex); + } if((c instanceof CInt || c instanceof CDecimal) && next1.type == TType.DOT && next2.type == TType.LIT) { // make CDouble/CDecimal here because otherwise Long.parseLong() will remove // minus zero before decimals and leading zeroes after decimals diff --git a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java index e9266c0405..6cad83d9bf 100644 --- a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java +++ b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java @@ -20,7 +20,6 @@ import com.laytonsmith.core.environments.CommandHelperEnvironment; import com.laytonsmith.core.environments.Environment; import com.laytonsmith.core.exceptions.AbstractCompileException; -import com.laytonsmith.core.exceptions.CRE.CREFormatException; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigCompileGroupException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; @@ -1019,7 +1018,7 @@ public void testLiteralDecimal() throws Exception { assertEquals("ms.lang.decimal", SRun("typeof(0m1234567890987654321.1234567890987654321)", fakePlayer)); } - @Test(expected = CREFormatException.class) + @Test(expected = AbstractCompileException.class) public void testLiteralBinary() throws Exception { SRun("0b2", fakePlayer); } From 2e04eae318a9dff6fa6254a45da9fc06d30c2551 Mon Sep 17 00:00:00 2001 From: Pieter12345 Date: Sun, 1 Dec 2024 01:48:44 +0100 Subject: [PATCH 3/3] Minor refactoring No functional changes. --- .../com/laytonsmith/core/MethodScriptCompilerTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java index 6cad83d9bf..56b31bd1b4 100644 --- a/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java +++ b/src/test/java/com/laytonsmith/core/MethodScriptCompilerTest.java @@ -238,7 +238,7 @@ public void testExecute3() { = "["; MethodScriptCompiler.execute(MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, null, null, true), null, envs), env, null, null); fail("Test passed, but wasn't supposed to"); - } catch (ConfigCompileException | ConfigCompileGroupException ex) { + } catch (AbstractCompileException ex) { //Passed } try { @@ -246,7 +246,7 @@ public void testExecute3() { = "]"; MethodScriptCompiler.execute(MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, null, null, true), null, envs), env, null, null); fail("Test passed, but wasn't supposed to"); - } catch (ConfigCompileException | ConfigCompileGroupException ex) { + } catch (AbstractCompileException ex) { //Passed } } @@ -450,7 +450,7 @@ public void testCompile1() { String config = "/cmd [$p] $q = msg('')"; MethodScriptCompiler.preprocess(MethodScriptCompiler.lex(config, null, null, false), envs).get(0).compile(env); fail("Test passed, but wasn't supposed to"); - } catch (ConfigCompileException | ConfigCompileGroupException ex) { + } catch (AbstractCompileException ex) { //Passed } } @@ -495,7 +495,7 @@ public void testCompile2() { String config = "/cmd [$p=player()] = msg('')"; MethodScriptCompiler.preprocess(MethodScriptCompiler.lex(config, null, null, false), envs).get(0).compile(env); fail("Test passed, but wasn't supposed to"); - } catch (ConfigCompileException | ConfigCompileGroupException ex) { + } catch (AbstractCompileException ex) { //Passed } }