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
}
}