summaryrefslogtreecommitdiff
path: root/pp.c
diff options
context:
space:
mode:
authorKarl Williamson <khw@cpan.org>2019-05-03 13:57:47 -0600
committerKarl Williamson <khw@cpan.org>2019-05-24 17:09:30 -0600
commit814735a391b874af8f00eaf89469e5ec7f38cd4a (patch)
treeb7f652cf8c5eac3042fcc8304ff8df3418f573c7 /pp.c
parentbae047b68c92622bb4bb04499e36cdaa48138909 (diff)
downloadperl-814735a391b874af8f00eaf89469e5ec7f38cd4a.tar.gz
Remove undefined behavior from IV shifting
It is undefined behavior to shift a negative integer to the left. This commit avoids that by treating the value as unsigned, then casting back to integer for return.
Diffstat (limited to 'pp.c')
-rw-r--r--pp.c21
1 files changed, 20 insertions, 1 deletions
diff --git a/pp.c b/pp.c
index 6e9ab38f7f..62a548bc0a 100644
--- a/pp.c
+++ b/pp.c
@@ -1991,11 +1991,30 @@ static IV S_iv_shift(IV iv, int shift, bool left)
shift = -shift;
left = !left;
}
+
if (UNLIKELY(shift >= IV_BITS)) {
return iv < 0 && !left ? -1 : 0;
}
- return left ? iv << shift : iv >> shift;
+ /* For left shifts, perl 5 has chosen to treat the value as unsigned for
+ * the * purposes of shifting, then cast back to signed. This is very
+ * different from perl 6:
+ *
+ * $ perl6 -e 'say -2 +< 5'
+ * -64
+ *
+ * $ ./perl -le 'print -2 << 5'
+ * 18446744073709551552
+ * */
+ if (left) {
+ if (iv == IV_MIN) { /* Casting this to a UV is undefined behavior */
+ return 0;
+ }
+ return (IV) (((UV) iv) << shift);
+ }
+
+ /* Here is right shift */
+ return iv >> shift;
}
#define UV_LEFT_SHIFT(uv, shift) S_uv_shift(uv, shift, TRUE)