Skip to content

Commit 918403c

Browse files
bpo-29816: Shift operation now has less opportunity to raise OverflowError. (#680)
ValueError always is raised rather than OverflowError for negative counts. Shifting zero with non-negative count always returns zero.
1 parent 762bf40 commit 918403c

File tree

3 files changed

+82
-24
lines changed

3 files changed

+82
-24
lines changed

Lib/test/test_long.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -906,20 +906,48 @@ def test_correctly_rounded_true_division(self):
906906
self.check_truediv(-x, y)
907907
self.check_truediv(-x, -y)
908908

909+
def test_negative_shift_count(self):
910+
with self.assertRaises(ValueError):
911+
42 << -3
912+
with self.assertRaises(ValueError):
913+
42 << -(1 << 1000)
914+
with self.assertRaises(ValueError):
915+
42 >> -3
916+
with self.assertRaises(ValueError):
917+
42 >> -(1 << 1000)
918+
909919
def test_lshift_of_zero(self):
910920
self.assertEqual(0 << 0, 0)
911921
self.assertEqual(0 << 10, 0)
912922
with self.assertRaises(ValueError):
913923
0 << -1
924+
self.assertEqual(0 << (1 << 1000), 0)
925+
with self.assertRaises(ValueError):
926+
0 << -(1 << 1000)
914927

915928
@support.cpython_only
916929
def test_huge_lshift_of_zero(self):
917930
# Shouldn't try to allocate memory for a huge shift. See issue #27870.
918931
# Other implementations may have a different boundary for overflow,
919932
# or not raise at all.
920933
self.assertEqual(0 << sys.maxsize, 0)
921-
with self.assertRaises(OverflowError):
922-
0 << (sys.maxsize + 1)
934+
self.assertEqual(0 << (sys.maxsize + 1), 0)
935+
936+
@support.cpython_only
937+
@support.bigmemtest(sys.maxsize + 1000, memuse=2/15 * 2, dry_run=False)
938+
def test_huge_lshift(self, size):
939+
self.assertEqual(1 << (sys.maxsize + 1000), 1 << 1000 << sys.maxsize)
940+
941+
def test_huge_rshift(self):
942+
self.assertEqual(42 >> (1 << 1000), 0)
943+
self.assertEqual((-42) >> (1 << 1000), -1)
944+
945+
@support.cpython_only
946+
@support.bigmemtest(sys.maxsize + 500, memuse=2/15, dry_run=False)
947+
def test_huge_rshift_of_huge(self, size):
948+
huge = ((1 << 500) + 11) << sys.maxsize
949+
self.assertEqual(huge >> (sys.maxsize + 1), (1 << 499) + 5)
950+
self.assertEqual(huge >> (sys.maxsize + 1000), 0)
923951

924952
def test_small_ints(self):
925953
for i in range(-5, 257):

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ What's New in Python 3.7.0 alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- bpo-29816: Shift operation now has less opportunity to raise OverflowError.
14+
ValueError always is raised rather than OverflowError for negative counts.
15+
Shifting zero with non-negative count always returns zero.
16+
1317
- bpo-24821: Fixed the slowing down to 25 times in the searching of some
1418
unlucky Unicode characters.
1519

Objects/longobject.c

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4277,15 +4277,54 @@ long_bool(PyLongObject *v)
42774277
return Py_SIZE(v) != 0;
42784278
}
42794279

4280+
/* wordshift, remshift = divmod(shiftby, PyLong_SHIFT) */
4281+
static int
4282+
divmod_shift(PyLongObject *shiftby, Py_ssize_t *wordshift, digit *remshift)
4283+
{
4284+
assert(PyLong_Check((PyObject *)shiftby));
4285+
assert(Py_SIZE(shiftby) >= 0);
4286+
Py_ssize_t lshiftby = PyLong_AsSsize_t((PyObject *)shiftby);
4287+
if (lshiftby >= 0) {
4288+
*wordshift = lshiftby / PyLong_SHIFT;
4289+
*remshift = lshiftby % PyLong_SHIFT;
4290+
return 0;
4291+
}
4292+
/* PyLong_Check(shiftby) is true and Py_SIZE(shiftby) >= 0, so it must
4293+
be that PyLong_AsSsize_t raised an OverflowError. */
4294+
assert(PyErr_ExceptionMatches(PyExc_OverflowError));
4295+
PyErr_Clear();
4296+
PyLongObject *wordshift_obj = divrem1(shiftby, PyLong_SHIFT, remshift);
4297+
if (wordshift_obj == NULL) {
4298+
return -1;
4299+
}
4300+
*wordshift = PyLong_AsSsize_t((PyObject *)wordshift_obj);
4301+
Py_DECREF(wordshift_obj);
4302+
if (*wordshift >= 0 && *wordshift < PY_SSIZE_T_MAX / (Py_ssize_t)sizeof(digit)) {
4303+
return 0;
4304+
}
4305+
PyErr_Clear();
4306+
/* Clip the value. With such large wordshift the right shift
4307+
returns 0 and the left shift raises an error in _PyLong_New(). */
4308+
*wordshift = PY_SSIZE_T_MAX / sizeof(digit);
4309+
*remshift = 0;
4310+
return 0;
4311+
}
4312+
42804313
static PyObject *
42814314
long_rshift(PyLongObject *a, PyLongObject *b)
42824315
{
42834316
PyLongObject *z = NULL;
4284-
Py_ssize_t shiftby, newsize, wordshift, loshift, hishift, i, j;
4285-
digit lomask, himask;
4317+
Py_ssize_t newsize, wordshift, hishift, i, j;
4318+
digit loshift, lomask, himask;
42864319

42874320
CHECK_BINOP(a, b);
42884321

4322+
if (Py_SIZE(b) < 0) {
4323+
PyErr_SetString(PyExc_ValueError,
4324+
"negative shift count");
4325+
return NULL;
4326+
}
4327+
42894328
if (Py_SIZE(a) < 0) {
42904329
/* Right shifting negative numbers is harder */
42914330
PyLongObject *a1, *a2;
@@ -4300,19 +4339,11 @@ long_rshift(PyLongObject *a, PyLongObject *b)
43004339
Py_DECREF(a2);
43014340
}
43024341
else {
4303-
shiftby = PyLong_AsSsize_t((PyObject *)b);
4304-
if (shiftby == -1L && PyErr_Occurred())
4305-
return NULL;
4306-
if (shiftby < 0) {
4307-
PyErr_SetString(PyExc_ValueError,
4308-
"negative shift count");
4342+
if (divmod_shift(b, &wordshift, &loshift) < 0)
43094343
return NULL;
4310-
}
4311-
wordshift = shiftby / PyLong_SHIFT;
4312-
newsize = Py_ABS(Py_SIZE(a)) - wordshift;
4344+
newsize = Py_SIZE(a) - wordshift;
43134345
if (newsize <= 0)
43144346
return PyLong_FromLong(0);
4315-
loshift = shiftby % PyLong_SHIFT;
43164347
hishift = PyLong_SHIFT - loshift;
43174348
lomask = ((digit)1 << hishift) - 1;
43184349
himask = PyLong_MASK ^ lomask;
@@ -4336,27 +4367,22 @@ long_lshift(PyObject *v, PyObject *w)
43364367
PyLongObject *a = (PyLongObject*)v;
43374368
PyLongObject *b = (PyLongObject*)w;
43384369
PyLongObject *z = NULL;
4339-
Py_ssize_t shiftby, oldsize, newsize, wordshift, remshift, i, j;
4370+
Py_ssize_t oldsize, newsize, wordshift, i, j;
4371+
digit remshift;
43404372
twodigits accum;
43414373

43424374
CHECK_BINOP(a, b);
43434375

4344-
shiftby = PyLong_AsSsize_t((PyObject *)b);
4345-
if (shiftby == -1L && PyErr_Occurred())
4346-
return NULL;
4347-
if (shiftby < 0) {
4376+
if (Py_SIZE(b) < 0) {
43484377
PyErr_SetString(PyExc_ValueError, "negative shift count");
43494378
return NULL;
43504379
}
4351-
43524380
if (Py_SIZE(a) == 0) {
43534381
return PyLong_FromLong(0);
43544382
}
43554383

4356-
/* wordshift, remshift = divmod(shiftby, PyLong_SHIFT) */
4357-
wordshift = shiftby / PyLong_SHIFT;
4358-
remshift = shiftby - wordshift * PyLong_SHIFT;
4359-
4384+
if (divmod_shift(b, &wordshift, &remshift) < 0)
4385+
return NULL;
43604386
oldsize = Py_ABS(Py_SIZE(a));
43614387
newsize = oldsize + wordshift;
43624388
if (remshift)

0 commit comments

Comments
 (0)