classification
Title: can't set big int-like objects to items in array 'Q', 'L' and 'I'
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.7, Python 3.6, Python 3.5
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: serhiy.storchaka Nosy List: Oren Milman, mark.dickinson, meador.inge, serhiy.storchaka
Priority: normal Keywords: patch

Created on 2016-09-28 10:09 by Oren Milman, last changed 2017-03-24 22:38 by serhiy.storchaka. This issue is now closed.

Files
File name Uploaded Description Edit
patchedCPythonTestOutput_ver1.txt Oren Milman, 2016-09-28 10:09 test output of CPython with my patches (tested on my PC) - ver1
CPythonTestOutput.txt Oren Milman, 2016-09-28 10:11 test output of CPython without my patches (tested on my PC)
issue28298_ver1.diff Oren Milman, 2016-09-28 10:11 proposed patches diff file - ver1 review
Pull Requests
URL Status Linked Edit
PR 570 merged Oren Milman, 2017-03-08 23:40
PR 579 merged Oren Milman, 2017-03-09 13:33
PR 588 merged Oren Milman, 2017-03-09 18:51
PR 703 larry, 2017-03-17 21:00
Messages (13)
msg277604 - (view) Author: Oren Milman (Oren Milman) * Date: 2016-09-28 10:09
------------ current state ------------
On my Windows 10, on a 32-bit Python, The following runs fine:
    import array
    array.array('L').append(2 ** 32 - 1)

However, in the following, an OverflowError('Python int too large to convert to C long') is raised on the last line:
    import array
    class LikeInt:
        def __init__(self, intVal):
            self.intVal = intVal
        def __int__(self):
            return self.intVal
    array.array('L').append(LikeInt(2 ** 32 - 1))

The reason for this behavior is the implementation of the function LL_setitem (in Modules/arraymodule.c) (edited brutally for brevity):
    LL_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
    {
        unsigned long x;
        if (PyLong_Check(v)) {
            x = PyLong_AsUnsignedLong(v);
        }
        else {
            long y;
            PyArg_Parse(v, "l;array item must be integer", &y);
            x = (unsigned long)y;
        }
        (ap->ob_item)[i] = x;
    }
The problem is that PyArg_Parse is used to convert a Python int into a C long. So PyArg_Parse fails when it is given a Python int which is in range(LONG_MAX + 1, ULONG_MAX + 1), even though such Python int can be stored in a C unsigned long.

It is quite the same for array('I') and array('Q') (i.e. in II_setitem and in QQ_setitem, respectively).

With regard to relevant changes made in the past, PyArg_Parse was always used  (in case '!PyLong_Check(v)'), since adding the original versions of:
    * II_setitem and LL_setitem (back then, they were called I_setitem and L_setitem, respectively), in changeset 4875 (https://hg.python.org/cpython/rev/911040e1bb11)
    * QQ_setitem, in changeset 72430 (https://hg.python.org/cpython/rev/15659e0e2b2e)


------------ proposed changes ------------
    1. In Modules/arraymodule.c, change the implementation of LL_setitem (and likewise, of II_setitem and QQ_setitem) roughly to the following:
        LL_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
        {
            unsigned long x;
            if (!PyLong_Check(v)) {
                v = _PyLong_FromNbInt(v);
            }
            x = PyLong_AsUnsignedLong(v);
            (ap->ob_item)[i] = x;
        }

    2. In Lib/test/test_array.py, add tests:
        * to verify the bug is fixed, i.e. test the bounds of setting int-like objects to items in an integers array
        * to verify float objects can't be set to items in an integers array (as is already the behavior, but there aren't any tests for it)

    3. While we are in Lib/test/test_array.py, remove any checks whether 'long long' is available, as it must be available, since changeset 103105 (https://hg.python.org/cpython/rev/9206a86f7321).

Note that issue #12974 (opened in 2011) proposes deprecating the ability to set int-like objects to items in an integers array. (I am not sure how that affects my patch, but I guess it should be noted.)


------------ diff ------------
The proposed patches diff file is attached.

(Note that the I didn't propose to change the perplexing error message 'unsigned int is greater than maximum' in II_setitem, as there are many such messages in the codebase, and I am working on a patch for them as part of issue #15988.)


------------ tests ------------
I ran 'python_d.exe -m test -j3' (on my 64-bit Windows 10) with and without the patches, and got quite the same output. (That also means my new tests in test_array passed.)
The outputs of both runs are attached.
msg277685 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-29 08:10
One comment here: it's not the presence of `__int__` that makes a type integer-like; it's the presence of `__index__`. (Decimal and float both supply `__int__`, but shouldn't be regarded as integer-like, for example.) I'm guessing that we're going to have the same issues with `__index__` in place of `__int__`, though.
msg277704 - (view) Author: Oren Milman (Oren Milman) * Date: 2016-09-29 12:48
You are right about the terminology of course. My bad.

Anyway, the bug is not related to __index__ (or nb_index), because the three aforementioned functions are using (in case '!PyLong_Check(v)') PyArg_Parse with the formats 'l' or 'L'.
Ultimately, convertsimple (in Python/getargs.c) would call PyLong_AsLong or PyLong_AsLongLong (respectively).
These two (in case '!PyLong_Check(v)') only try to use nb_int, as documented:
    * https://docs.python.org/3.7/c-api/long.html?highlight=pylong_aslong#c.PyLong_AsLong
    * https://docs.python.org/3.7/c-api/long.html?highlight=pylong_aslong#c.PyLong_AsLongLong

But just in case, I ran the following:
    import array
    class LikeInt:
        def __init__(self, intVal):
            self.intVal = intVal
        def __index__(self):
            return self.intVal
    array.array('L').append(LikeInt(0))
and got a 'TypeError: an integer is required (got type LikeInt)'
msg277707 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2016-09-29 16:15
> and got a 'TypeError: an integer is required (got type LikeInt)'

Right, see #12974 for more on arrays, `__int__` and `__index__`.
msg277729 - (view) Author: Oren Milman (Oren Milman) * Date: 2016-09-29 21:15
Ah, I should have read more about __int__ and __index__ before writing my last reply.
So IIUC, this is a somewhat painful issue, which could be resolved by:
    * #20092
    * replacing _PyLong_FromNbInt with PyNumber_Index in:
        - Objects/longobject.c (as you suggested in https://bugs.python.org/issue12965#msg146252)
        - Modules/zlibmodule.c in ssize_t_converter
I didn't find any issue for the second one. If there is really no such issue, why is that? (I am sorry if I am the thousandth one to ask)
The first paragraph of your comment in https://bugs.python.org/issue21111#msg215660 suggests it might break a lot of code in the wild, but shouldn't we start with raising a deprecation warning?

With regard to my patch - should I make the following changes?
    1. in Modules/arraymodule.c - add a call to PyNumber_Index before the call to _PyLong_FromNbInt (and move them into a helper function, as done in Modules/_struct.c)
    2. in Lib/test/test_array.py:
        * replace the name 'LikeInt' with 'IntCompatible' (I am not sure this is an appropriate name either. If you have a better name in mind, please share :) )
        * add tests for LikeInt (this time, a class with a __index__ method)
    3. In Doc/library/array.rst:
        * add a note saying that setting int-like objects (i.e. objects with __index__) to items in an integers array is possible
        * while we are here, remove the note about the availability of 'q' and 'Q' only in certain platforms
msg288866 - (view) Author: Oren Milman (Oren Milman) * Date: 2017-03-03 07:20
ping
msg289218 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-03-08 08:30
LGTM. But maybe combine PyFloat_Check+_PyLong_FromNbInt in one helper function?

Could you please create a PR Oren?
msg289234 - (view) Author: Oren Milman (Oren Milman) * Date: 2017-03-08 13:02
yes and yes.
I would start with a PR for 3.7.

and thanks for the review :)
msg289336 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-03-10 06:14
Thank you for your contribution Oren.
msg289341 - (view) Author: Oren Milman (Oren Milman) * Date: 2017-03-10 08:07
Thanks for the reviews :)
msg290243 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-03-24 22:37
New changeset e2c88bdd6bb3efbc81389958d62daf6dd0d6eda7 by Serhiy Storchaka (orenmn) in branch '3.5':
bpo-28298: make array 'Q', 'L' and 'I' accept big intables as elements
https://github.com/python/cpython/commit/e2c88bdd6bb3efbc81389958d62daf6dd0d6eda7
msg290251 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-03-24 22:38
New changeset 26d013e00f9d4adbcf3e084bbc890c799ff70407 by Serhiy Storchaka (orenmn) in branch '3.6':
[3.6] bpo-28298: make array 'Q', 'L' and 'I' accept big intables as elements (#579)
https://github.com/python/cpython/commit/26d013e00f9d4adbcf3e084bbc890c799ff70407
msg290252 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-03-24 22:38
New changeset 964281af59d7a17d923c4d72357e48832b774e39 by Serhiy Storchaka (orenmn) in branch 'master':
bpo-28298: make array 'Q', 'L' and 'I' accept big intables as elements (#570)
https://github.com/python/cpython/commit/964281af59d7a17d923c4d72357e48832b774e39
History
Date User Action Args
2017-03-24 22:38:54serhiy.storchakasetmessages: + msg290252
2017-03-24 22:38:40serhiy.storchakasetmessages: + msg290251
2017-03-24 22:37:28serhiy.storchakasetmessages: + msg290243
2017-03-17 21:00:34larrysetpull_requests: + pull_request604
2017-03-15 09:06:14serhiy.storchakalinkissue15988 dependencies
2017-03-10 08:07:37Oren Milmansetmessages: + msg289341
2017-03-10 06:14:23serhiy.storchakasetstatus: open -> closed
resolution: fixed
messages: + msg289336

stage: commit review -> resolved
2017-03-09 18:51:35Oren Milmansetpull_requests: + pull_request485
2017-03-09 13:33:31Oren Milmansetpull_requests: + pull_request476
2017-03-08 23:40:59Oren Milmansetpull_requests: + pull_request466
2017-03-08 13:02:58Oren Milmansetmessages: + msg289234
2017-03-08 08:30:20serhiy.storchakasetstage: patch review -> commit review
messages: + msg289218
versions: + Python 3.5, Python 3.6
2017-03-03 07:40:55serhiy.storchakasetstage: patch review
2017-03-03 07:40:47serhiy.storchakasetassignee: serhiy.storchaka
2017-03-03 07:20:16Oren Milmansetmessages: + msg288866
2016-09-29 21:15:45Oren Milmansetmessages: + msg277729
2016-09-29 16:15:19mark.dickinsonsetmessages: + msg277707
2016-09-29 12:48:44Oren Milmansetmessages: + msg277704
2016-09-29 08:10:51mark.dickinsonsetmessages: + msg277685
2016-09-28 11:48:09serhiy.storchakasetnosy: + mark.dickinson, meador.inge
2016-09-28 11:47:13serhiy.storchakasetnosy: + serhiy.storchaka
2016-09-28 10:14:35Oren Milmansetversions: + Python 3.7
2016-09-28 10:11:34Oren Milmansetfiles: + issue28298_ver1.diff
keywords: + patch
2016-09-28 10:11:08Oren Milmansetfiles: + CPythonTestOutput.txt
2016-09-28 10:09:35Oren Milmancreate