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 d3f51b29bb9..e6397a1e767 100644 --- a/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java +++ b/src/main/java/ch/njol/skript/classes/data/DefaultOperations.java @@ -21,6 +21,7 @@ import ch.njol.skript.util.Date; import ch.njol.skript.util.Timespan; import ch.njol.skript.util.Utils; +import ch.njol.util.Math2; import org.bukkit.util.Vector; import org.skriptlang.skript.lang.arithmetic.Arithmetics; import org.skriptlang.skript.lang.arithmetic.Operator; @@ -84,7 +85,7 @@ public class DefaultOperations { }); // Timespan - Timespan - Arithmetics.registerOperation(Operator.ADDITION, Timespan.class, (left, right) -> new Timespan(left.getMilliSeconds() + right.getMilliSeconds())); + Arithmetics.registerOperation(Operator.ADDITION, Timespan.class, (left, right) -> new Timespan(Math2.addClamped(left.getMilliSeconds(), right.getMilliSeconds()))); Arithmetics.registerOperation(Operator.SUBTRACTION, Timespan.class, (left, right) -> new Timespan(Math.max(0, left.getMilliSeconds() - right.getMilliSeconds()))); Arithmetics.registerDifference(Timespan.class, (left, right) -> new Timespan(Math.abs(left.getMilliSeconds() - right.getMilliSeconds()))); Arithmetics.registerDefaultValue(Timespan.class, Timespan::new); @@ -95,7 +96,7 @@ public class DefaultOperations { long scalar = right.longValue(); if (scalar < 0) return null; - return new Timespan(left.getMilliSeconds() * scalar); + return new Timespan(Math2.multiplyClamped(left.getMilliSeconds(), scalar)); }, (left, right) -> { long scalar = left.longValue(); if (scalar < 0) diff --git a/src/main/java/ch/njol/util/Math2.java b/src/main/java/ch/njol/util/Math2.java index c16018d341e..d17ac6910d1 100644 --- a/src/main/java/ch/njol/util/Math2.java +++ b/src/main/java/ch/njol/util/Math2.java @@ -155,6 +155,31 @@ public static long round(double value) { public static float safe(float value) { return Float.isFinite(value) ? value : 0; } + + /** + * @param x the first value + * @param y the second value + * @return the sum of x and y, or {@link Long#MAX_VALUE} in case of an overflow + */ + public static long addClamped(long x, long y) { + long result = x + y; + // Logic extracted from Math#addExact to avoid having to catch an expensive exception + boolean causedOverflow = ((x ^ result) & (y ^ result)) < 0; + if (causedOverflow) + return Long.MAX_VALUE; + return result; + } + + public static long multiplyClamped(long x, long y) { + long result = x * y; + long ax = Math.abs(x); + long ay = Math.abs(y); + // Logic extracted from Math#multiplyExact to avoid having to catch an expensive exception + if (((ax | ay) >>> 31 != 0) && (((y != 0) && (result / y != x)) || (x == Long.MIN_VALUE && y == -1))) + // If either x or y is negative return the min value, otherwise return the max value + return x < 0 == y < 0 ? Long.MAX_VALUE : Long.MIN_VALUE; + return result; + } @Deprecated @ScheduledForRemoval diff --git a/src/test/skript/tests/regressions/6328-timespan operations can cause exceptions if there's an overflow.sk b/src/test/skript/tests/regressions/6328-timespan operations can cause exceptions if there's an overflow.sk new file mode 100644 index 00000000000..1267fc7c081 --- /dev/null +++ b/src/test/skript/tests/regressions/6328-timespan operations can cause exceptions if there's an overflow.sk @@ -0,0 +1,3 @@ +test "timespan overflow exception": + assert 1000000000 years + 10000000 years is set with "timespan addition overflow did not return max value" + assert 1000000000 years * 10000000 is set with "timespan addition overflow did not return max value" \ No newline at end of file