This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Remove undefined behavior from IV shifting
authorKarl Williamson <khw@cpan.org>
Fri, 3 May 2019 19:57:47 +0000 (13:57 -0600)
committerKarl Williamson <khw@cpan.org>
Fri, 24 May 2019 23:09:30 +0000 (17:09 -0600)
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.

asan_ignore
pp.c

index e0f5685..f520546 100644 (file)
 
 fun:Perl_pp_i_*
 
-# Perl's << is defined as using the underlying C's << operator, with the
-# same undefined behaviour for shifts greater than the word size.
-# (UVs normally, IVs with 'use integer')
-
-fun:Perl_pp_left_shift
 
 # this function numifies the field width in eg printf "%10f".
 # It has its own overflow detection, so don't warn about it
diff --git a/pp.c b/pp.c
index 6e9ab38..62a548b 100644 (file)
--- 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)