Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ PHP NEWS
. Fixed JIT segfault on switch over a string reference holding a
non-string value. (jorgsowa)
. Fixed AArch64 JIT encoding of invalid logical immediates. (jorgsowa)
. Fixed unsound shift_left_overflows range when shift wraps. (mkrishna793)

- PDO:
. Fixed invalid ATTR_ORACLE_NULLS values being accepted and truncated.
Expand Down
12 changes: 7 additions & 5 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -762,12 +762,14 @@ static inline zend_long safe_shift_left(zend_long n, zend_long s) {
}

static inline bool shift_left_overflows(zend_long n, zend_long s) {
/* This considers shifts that shift in the sign bit to be overflowing as well */
if (n >= 0) {
return s >= SIZEOF_ZEND_LONG * 8 - 1 || safe_shift_left(n, s) < n;
} else {
return s >= SIZEOF_ZEND_LONG * 8 || safe_shift_left(n, s) > n;
if (UNEXPECTED((zend_ulong)s >= SIZEOF_ZEND_LONG * 8)) {
return true;
}
if (UNEXPECTED(s == SIZEOF_ZEND_LONG * 8 - 1)) {
return n != 0 && n != -1;
}
zend_long shifted = (zend_long)((zend_ulong)n << (zend_ulong)s);
return (shifted >> s) != n;
}

/* If b does not divide a exactly, return the two adjacent values between which the real result
Expand Down
51 changes: 51 additions & 0 deletions Zend/tests/shift_left_overflow_range.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--TEST--
Too narrow range inferred for SL when the shift wraps
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip 64-bit only"); ?>
--FILE--
<?php
/* $i is inferred as RANGE[1..5]. Shifting it by 62 wraps for every $i >= 2,
* so the result range may not be derived from the two endpoints: both of them
* happen to yield 1 << 62, while the values in between do not. */
function shift_positive(): array {
$out = [];
for ($i = 1; $i <= 5; $i++) {
$out[] = $i << 62;
}
return $out;
}

/* Likewise for RANGE[-3..-1] shifted by 63: both endpoints yield LONG_MIN,
* but -2 << 63 is 0. */
function shift_negative(): array {
$out = [];
for ($i = -1; $i >= -3; $i--) {
$out[] = $i << 63;
}
return $out;
}

var_dump(shift_positive());
var_dump(shift_negative());
?>
--EXPECT--
array(5) {
[0]=>
int(4611686018427387904)
[1]=>
int(-9223372036854775808)
[2]=>
int(-4611686018427387904)
[3]=>
int(0)
[4]=>
int(4611686018427387904)
}
array(3) {
[0]=>
int(-9223372036854775808)
[1]=>
int(0)
[2]=>
int(-9223372036854775808)
}
Loading