Skip to content

Commit

Permalink
Prevent long overflow in arithemetic (SkriptLang#7210)
Browse files Browse the repository at this point in the history
* catch long overflows

* don't compromise with precision

uses Math.xExact code to catch overflows instead of relying on doubles.

* Apply suggestions from code review

Co-authored-by: Efnilite <[email protected]>

---------

Co-authored-by: Efnilite <[email protected]>
  • Loading branch information
2 people authored and Burbulinis committed Dec 3, 2024
1 parent 541dc12 commit 44a5f24
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 10 deletions.
47 changes: 37 additions & 10 deletions src/main/java/ch/njol/skript/classes/data/DefaultOperations.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
34 changes: 34 additions & 0 deletions src/test/skript/tests/regressions/7209-long overflow.sk
Original file line number Diff line number Diff line change
@@ -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"

0 comments on commit 44a5f24

Please sign in to comment.