sh: Mask off shift distance (<< and >>) in arithmetic.

In C, shift distances equal to or larger than the number of bits in the
operand result in undefined behaviour. As part of eliminating undefined
behaviour in arithmetic, mask off the distance like Java and JavaScript
specify and C on x86 usually does.

Assumption: conversion from unsigned to signed retains the two's complement
bits.
Assumption: uintmax_t has no padding bits.
This commit is contained in:
jilles 2014-08-15 22:36:41 +00:00
parent 82e14020fd
commit 948728c4a6
3 changed files with 44 additions and 2 deletions

View File

@ -139,9 +139,10 @@ static arith_t do_binop(int op, arith_t a, arith_t b)
case ARITH_SUB:
return (uintmax_t)a - (uintmax_t)b;
case ARITH_LSHIFT:
return (uintmax_t)a << b;
return (uintmax_t)a <<
((uintmax_t)b & (sizeof(uintmax_t) * CHAR_BIT - 1));
case ARITH_RSHIFT:
return a >> b;
return a >> ((uintmax_t)b & (sizeof(uintmax_t) * CHAR_BIT - 1));
case ARITH_LT:
return a < b;
case ARITH_LE:

View File

@ -20,6 +20,7 @@ FILES+= arith10.0
FILES+= arith11.0
FILES+= arith12.0
FILES+= arith13.0
FILES+= arith14.0
FILES+= assign1.0
FILES+= cmdsubst1.0
FILES+= cmdsubst2.0

View File

@ -0,0 +1,40 @@
# $FreeBSD$
# Check that <</>> use the low bits of the shift count.
if [ $((1<<16<<16)) = 0 ]; then
width=32
elif [ $((1<<32<<32)) = 0 ]; then
width=64
elif [ $((1<<64<<64)) = 0 ]; then
width=128
elif [ $((1<<64>>64)) = 1 ]; then
# Integers are wider than 128 bits; assume arbitrary precision.
# Nothing to test here.
exit 0
else
echo "Cannot determine integer width"
exit 2
fi
twowidth=$((width * 2))
j=43 k=$((1 << (width - 2))) r=0
i=0
while [ $i -lt $twowidth ]; do
if [ "$((j << i))" != "$((j << (i + width)))" ]; then
echo "Problem with $j << $i"
r=2
fi
i=$((i + 1))
done
i=0
while [ $i -lt $twowidth ]; do
if [ "$((k >> i))" != "$((k >> (i + width)))" ]; then
echo "Problem with $k >> $i"
r=2
fi
i=$((i + 1))
done
exit $r