diff --git a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java index 1588d31ece8..48bf82939fd 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -32,26 +32,53 @@ public class DefaultOperations { static { // Number - Number Arithmetics.registerOperation(Operator.ADDITION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return left.longValue() + right.longValue(); + if (Utils.isInteger(left, right)) { + long result = left.longValue() + right.longValue(); + // catches overflow, from Math.addExact(long, long) + if (((left.longValue() ^ result) & (right.longValue() ^ result)) >= 0) + return result; + } return left.doubleValue() + right.doubleValue(); }); Arithmetics.registerOperation(Operator.SUBTRACTION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return left.longValue() - right.longValue(); + if (Utils.isInteger(left, right)) { + long result = left.longValue() - right.longValue(); + // catches overflow, from Math.addExact(long, long) + if (((left.longValue() ^ result) & (right.longValue() ^ result)) >= 0) + return result; + } return left.doubleValue() - right.doubleValue(); }); Arithmetics.registerOperation(Operator.MULTIPLICATION, Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return left.longValue() * right.longValue(); - return left.doubleValue() * right.doubleValue(); + if (!Utils.isInteger(left, right)) + return left.doubleValue() * right.doubleValue(); + + // catch overflow, from Math.multiplyExact(long, long) + long longLeft = left.longValue(); + long longRight = right.longValue(); + long ax = Math.abs(longLeft); + long ay = Math.abs(longRight); + + long result = left.longValue() * right.longValue(); + + if (((ax | ay) >>> 31 != 0)) { + // Some bits greater than 2^31 that might cause overflow + // Check the result using the divide operator + // and check for the special case of Long.MIN_VALUE * -1 + if (((longRight != 0) && (result / longRight != longLeft)) || + (longLeft == Long.MIN_VALUE && longRight == -1)) { + return left.doubleValue() * right.doubleValue(); + } + } + return result; }); Arithmetics.registerOperation(Operator.DIVISION, Number.class, (left, right) -> left.doubleValue() / right.doubleValue()); Arithmetics.registerOperation(Operator.EXPONENTIATION, Number.class, (left, right) -> Math.pow(left.doubleValue(), right.doubleValue())); Arithmetics.registerDifference(Number.class, (left, right) -> { - if (Utils.isInteger(left, right)) - return Math.abs(left.longValue() - right.longValue()); - return Math.abs(left.doubleValue() - right.doubleValue()); + double result = Math.abs(left.doubleValue() - right.doubleValue()); + if (Utils.isInteger(left, right) && result < Long.MAX_VALUE && result > Long.MIN_VALUE) + return (long) result; + return result; }); Arithmetics.registerDefaultValue(Number.class, () -> 0L); diff --git a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java index 8535ca69cb7..c58e13d2953 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java +++ b/src/main/java/ch/njol/skript/expressions/ExprJoinSplit.java @@ -19,8 +19,10 @@ package ch.njol.skript.expressions; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import ch.njol.skript.SkriptConfig; +import ch.njol.skript.lang.Literal; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -36,14 +38,11 @@ import ch.njol.util.Kleenean; import ch.njol.util.StringUtils; -/** - * @author Peter Güttinger - */ @Name("Join & Split") @Description("Joins several texts with a common delimiter (e.g. \", \"), or splits a text into multiple texts at a given delimiter.") @Examples({ - "message \"Online players: %join all players with \"\" | \"\"%\" # %all players% would use the default \"x, y, and z\"", - "set {_s::*} to the string argument split at \",\"" + "message \"Online players: %join all players' names with \"\" | \"\"%\" # %all players% would use the default \"x, y, and z\"", + "set {_s::*} to the string argument split at \",\"" }) @Since("2.1, 2.5.2 (regex support), 2.7 (case sensitivity)") public class ExprJoinSplit extends SimpleExpression { @@ -61,33 +60,47 @@ public class ExprJoinSplit extends SimpleExpression { private boolean regex; private boolean caseSensitivity; - @SuppressWarnings("null") private Expression strings; - @Nullable - private Expression delimiter; + private @Nullable Expression delimiter; + + private @Nullable Pattern pattern; @Override - @SuppressWarnings({"unchecked", "null"}) public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { join = matchedPattern == 0; regex = matchedPattern >= 3; caseSensitivity = SkriptConfig.caseSensitive.value() || parseResult.hasTag("case"); + //noinspection unchecked strings = (Expression) exprs[0]; + //noinspection unchecked delimiter = (Expression) exprs[1]; + if (!join && delimiter instanceof Literal) { + String stringPattern = ((Literal) delimiter).getSingle(); + try { + this.pattern = compilePattern(stringPattern); + } catch (PatternSyntaxException e) { + Skript.error("'" + stringPattern + "' is not a valid regular expression"); + return false; + } + } return true; } @Override - @Nullable - protected String[] get(Event event) { + protected String @Nullable [] get(Event event) { String[] strings = this.strings.getArray(event); String delimiter = this.delimiter != null ? this.delimiter.getSingle(event) : ""; if (strings.length == 0 || delimiter == null) return new String[0]; - if (join) { + if (join) return new String[] {StringUtils.join(strings, delimiter)}; - } else { - return strings[0].split(regex ? delimiter : (caseSensitivity ? "" : "(?i)") + Pattern.quote(delimiter), -1); + try { + Pattern pattern = this.pattern; + if (pattern == null) + pattern = compilePattern(delimiter); + return pattern.split(strings[0], -1); + } catch (PatternSyntaxException e) { + return new String[0]; } } @@ -103,10 +116,28 @@ public Class getReturnType() { @Override public String toString(@Nullable Event event, boolean debug) { - if (join) - return "join " + strings.toString(event, debug) + (delimiter != null ? " with " + delimiter.toString(event, debug) : ""); - return (regex ? "regex " : "") + "split " + strings.toString(event, debug) + (delimiter != null ? " at " + delimiter.toString(event, debug) : "") - + (regex ? "" : "(case sensitive: " + caseSensitivity + ")"); + StringBuilder builder = new StringBuilder(); + if (join) { + builder.append("join ").append(strings.toString(event, debug)); + if (delimiter != null) + builder.append(" with ").append(delimiter.toString(event, debug)); + return builder.toString(); + } + + assert delimiter != null; + if (regex) + builder.append("regex "); + builder.append("split ") + .append(strings.toString(event, debug)) + .append(" at ") + .append(delimiter.toString(event, debug)); + if (!regex) + builder.append(" (case sensitive: ").append(caseSensitivity).append(")"); + return builder.toString(); + } + + private Pattern compilePattern(String delimiter) { + return Pattern.compile(regex ? delimiter : (caseSensitivity ? "" : "(?i)") + Pattern.quote(delimiter)); } } diff --git a/src/test/skript/tests/regressions/7159-regex exceptions not handled.sk b/src/test/skript/tests/regressions/7159-regex exceptions not handled.sk new file mode 100644 index 00000000000..691fe8e0da8 --- /dev/null +++ b/src/test/skript/tests/regressions/7159-regex exceptions not handled.sk @@ -0,0 +1,12 @@ +test "regex exceptions not handled": + parse: + set {_split::*} to regex split "test" at "\b{_name}\b" + assert last parse logs is "'\b{_name}\b' is not a valid regular expression" with "regex split did not error with invalid regex literal" + + set {_pattern} to "\b{_name}\b" + set {_split::*} to regex split "test" at {_pattern} + assert {_split::*} is not set with "regex split returned a value with invalid regex expression" + + assert regex split "apple,banana;cherry" at "[,;]" is "apple", "banana" and "cherry" with "regex split did not split correctly with literal" + set {_pattern} to "[,;]" + assert regex split "apple,banana;cherry" at {_pattern} is "apple", "banana" and "cherry" with "regex split did not split correctly with expression" diff --git a/src/test/skript/tests/regressions/7209-long overflow.sk b/src/test/skript/tests/regressions/7209-long overflow.sk new file mode 100644 index 00000000000..bff56f0f57f --- /dev/null +++ b/src/test/skript/tests/regressions/7209-long overflow.sk @@ -0,0 +1,34 @@ +test "long overflow, addition": + set {_double-const} to 9000000000000000000.0 + set {_long-const} to 9000000000000000000 + + loop 100 times: + set {_double} to {_double} + {_double-const} + set {_long} to {_long} + {_long-const} + assert {_double} is {_long} with "long value did not overflow to a double" + + assert {_long} is 9000000000000000000 * 100.0 with "wrong final value" + +test "long underflow, subtraction": + set {_double-const} to 9000000000000000000.0 + set {_long-const} to 9000000000000000000 + + loop 100 times: + set {_double} to {_double} - {_double-const} + set {_long} to {_long} - {_long-const} + assert {_double} is {_long} with "long value did not overflow to a double" + + assert {_long} is 9000000000000000000 * -100.0 with "wrong final value" + +test "long overflow, multiplication": + set {_double} to 10.0 + set {_long} to 10 + + loop 100 times: + set {_double} to {_double} * 10 + set {_long} to {_long} * 10 + assert {_double} is {_long} with "long value did not overflow to a double" + + # the 6 is due to floating point error + assert {_long} is 100000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000 with "wrong final value" +