diff options
author | Karl Williamson <khw@cpan.org> | 2019-05-03 13:57:47 -0600 |
---|---|---|
committer | Karl Williamson <khw@cpan.org> | 2019-05-24 17:09:30 -0600 |
commit | 814735a391b874af8f00eaf89469e5ec7f38cd4a (patch) | |
tree | b7f652cf8c5eac3042fcc8304ff8df3418f573c7 | |
parent | bae047b68c92622bb4bb04499e36cdaa48138909 (diff) | |
download | perl-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.
-rw-r--r-- | asan_ignore | 5 | ||||
-rw-r--r-- | pp.c | 21 |
2 files changed, 20 insertions, 6 deletions
diff --git a/asan_ignore b/asan_ignore index e0f5685bc1..f520546210 100644 --- a/asan_ignore +++ b/asan_ignore @@ -18,11 +18,6 @@ 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 @@ -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) |