Repository: tursodatabase/limbo (rust)

Issue:
`SELECT 1 << -1e19` causes negate with overflow
`SELECT 1 << -1e19` causes a panic: 

```
thread 'main' panicked at core/vdbe/insn.rs:978:22:
attempt to negate with overflow
```

Here, the `rhs` is i64::MIN and overflows when negating.
https://github.com/tursodatabase/limbo/blob/669317c11e24b56ab256d88c81161c9e69252ee8/core/vdbe/insn.rs#L978

Found while fuzzing in https://github.com/tursodatabase/limbo/pull/1116. The example above is the simpler version of what libFuzzer found:

```
thread '<unnamed>' panicked at /Users/levy/Documents/limbo/core/vdbe/insn.rs:978:22:
attempt to negate with overflow
[...]
Output of `std::fmt::Debug`:

        Binary(
            LeftShift,
            Value(
                Real(
                    3.2188265645842836e-57,
                ),
            ),
            Value(
                Real(
                    -3.3807029758822175e221,
                ),
            ),
        )
```

Sqlite returns just `0` given the same input.

Pull request description:
Fix `compute_shl` negate with overflow
Fixes #1156 

- Changed `compute_shl` implementation to handle negation with overflow.
- Added TCL tests.

Patch:
diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs
index 1e2af182..583a094b 100644
--- a/core/vdbe/insn.rs
+++ b/core/vdbe/insn.rs
@@ -975,7 +975,27 @@ pub fn exec_shift_left(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue
 }
 
 fn compute_shl(lhs: i64, rhs: i64) -> i64 {
-    compute_shr(lhs, -rhs)
+    if rhs == 0 {
+        lhs
+    } else if rhs > 0 {
+        // for positive shifts, if it's too large return 0
+        if rhs >= 64 {
+            0
+        } else {
+            lhs << rhs
+        }
+    } else {
+        // for negative shifts, check if it's i64::MIN to avoid overflow on negation
+        if rhs == i64::MIN || rhs <= -64 {
+            if lhs < 0 {
+                -1
+            } else {
+                0
+            }
+        } else {
+            lhs >> (-rhs)
+        }
+    }
 }
 
 pub fn exec_shift_right(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue {
@@ -1013,15 +1033,24 @@ pub fn exec_shift_right(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValu
 fn compute_shr(lhs: i64, rhs: i64) -> i64 {
     if rhs == 0 {
         lhs
-    } else if rhs >= 64 && lhs >= 0 || rhs <= -64 {
-        0
-    } else if rhs >= 64 && lhs < 0 {
-        -1
-    } else if rhs < 0 {
-        // if negative do left shift
-        lhs << (-rhs)
+    } else if rhs > 0 {
+        // for positive right shifts
+        if rhs >= 64 {
+            if lhs < 0 {
+                -1
+            } else {
+                0
+            }
+        } else {
+            lhs >> rhs
+        }
     } else {
-        lhs >> rhs
+        // for negative right shifts, check if it's i64::MIN to avoid overflow
+        if rhs == i64::MIN || -rhs >= 64 {
+            0
+        } else {
+            lhs << (-rhs)
+        }
     }
 }
 


Test patch:
diff --git a/testing/select.test b/testing/select.test
index f10f33b2..f8b03307 100755
--- a/testing/select.test
+++ b/testing/select.test
@@ -165,3 +165,53 @@ do_execsql_test select-not-like-expression {
 do_execsql_test select-like-expression {
     select 2 % 0.5
 } {}
+
+do_execsql_test select_shl_large_negative_float {
+    SELECT 1 << -1e19;
+    SELECT 1 << -9223372036854775808;  -- i64::MIN
+    SELECT 1 << 9223372036854775807;   -- i64::MAX
+} {0 0 0}
+
+do_execsql_test select_shl_basic {
+    SELECT 1 << 0, 1 << 1, 1 << 2, 1 << 3;
+    SELECT 2 << 0, 2 << 1, 2 << 2, 2 << 3;
+} {1|2|4|8
+2|4|8|16}
+
+do_execsql_test select_shl_negative_numbers {
+    SELECT -1 << 0, -1 << 1, -1 << 2, -1 << 3;
+    SELECT -2 << 0, -2 << 1, -2 << 2, -2 << 3;
+} {-1|-2|-4|-8
+-2|-4|-8|-16}
+do_execsql_test select_shl_negative_shifts {
+    SELECT 8 << -1, 8 << -2, 8 << -3, 8 << -4;
+    SELECT -8 << -1, -8 << -2, -8 << -3, -8 << -4;
+} {4|2|1|0
+-4|-2|-1|-1}
+
+do_execsql_test select_shl_large_shifts {
+    SELECT 1 << 62, 1 << 63, 1 << 64;
+    SELECT -1 << 62, -1 << 63, -1 << 64;
+} {4611686018427387904|-9223372036854775808|0
+-4611686018427387904|-9223372036854775808|0}
+
+do_execsql_test select_shl_text_conversion {
+    SELECT '1' << '2';
+    SELECT '8' << '-2';
+    SELECT '-4' << '2';
+} {4 2 -16}
+
+do_execsql_test select_shl_chained {
+    SELECT (1 << 2) << 3;
+    SELECT (2 << 1) << (1 << 1);
+} {32 16}
+
+do_execsql_test select_shl_numeric_types {
+    SELECT CAST(1 AS INTEGER) << 2;
+    SELECT 1.0 << 2;
+    SELECT 1.5 << 2;
+} {4 4 4}
+
+do_execsql_test select_fuzz_failure_case {
+  SELECT (-9 << ((-6) << (9)) >> ((5)) % -10 - + - (-9));
+} {-16}
\ No newline at end of file