This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: ctypes silently truncates ints larger than C int
Type: behavior Stage:
Components: ctypes Versions: Python 3.10, Python 3.9, Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Christopher Meng, Ivan.Pozdeev, eryksun, petr.viktorin
Priority: normal Keywords:

Created on 2015-07-29 12:11 by petr.viktorin, last changed 2022-04-11 14:58 by admin.

Messages (4)
msg247565 - (view) Author: Petr Viktorin (petr.viktorin) * (Python committer) Date: 2015-07-29 12:11
A Python int larger than a C int but smaller than a C long is silently truncated to int when passed to a ctypes function without C type information attached.

Ints longer than C long fail with an OverflowError; I believe the same should happen for numbers that don't fit in a C int.


Reproducer (for 64-bit systems):

    from ctypes import cdll, ArgumentError
    libc = cdll.LoadLibrary("libc.so.6")


    # Silently truncated
    libc.printf(b"%x\n", 0x1234567890)

    try:
        # OverflowError raised
        libc.printf(b"%x\n", 2 ** 64)
    except ArgumentError as e:
        print(e)


see callproc.c, function ConvParam, after the PyLong_Check.
msg247566 - (view) Author: Petr Viktorin (petr.viktorin) * (Python committer) Date: 2015-07-29 12:14
Originally found here: https://bugzilla.redhat.com/show_bug.cgi?id=1244261
msg247620 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2015-07-29 20:35
I think the window to change this is closed. Section 16.16.2.7 in the docs states that for c_int "no overflow checking is done". I'm sure there's code that relies on that behavior, just as I'm sure there's code that relies on it for the default conversion.

That said, it does shine a light on an existing inconsistency. c_int's setfunc (i_set in cfield.c) truncates values that are larger than a C long. This is due to its use of PyLong_AsUnsignedLongMask in 3.x and PyInt_AsUnsignedLongMask in 2.x. 

For example:

    from ctypes import CDLL, CFUNCTYPE, c_int, c_char_p

    gsyms = CDLL(None) # POSIX
    printf = CFUNCTYPE(c_int, c_char_p, c_int)(('printf', gsyms))

    >>> n = printf(b'%#x\n', 2**64+1)
    0x1

OTOH, without a prototype the default C int conversion fails in this case because it calls PyLong_AsUnsignedLong or PyLong_AsLong.

    >>> n = gsyms.printf(b'%#x\n', 2**64+1)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ctypes.ArgumentError: argument 2: <class 'OverflowError'>: int too long to convert

Section 16.16.1.3 states that "Python integers are passed as the platforms default C int type, their value is masked to fit into the C type". I think either ConvParam should change to use PyLong_AsUnsignedLongMask, or the docs should state that the number has to be in the inclusive range LONG_MIN to ULONG_MAX (e.g. -2**63 to 2**64-1 in 64-bit Linux), else ArgumentError is raised.
msg253417 - (view) Author: Ivan Pozdeev (Ivan.Pozdeev) * Date: 2015-10-24 21:21
I wouldn't call it so carved in stone:

1) The note at `ctypes.c_int' (https://docs.python.org/2/library/ctypes.html?highlight=c_int#ctypes.c_int) is meant for explicit conversion - since the entry is about using `c_int' is Python code, not converting something to `int' anywhere in Python's codebase. (thus, eryksun's statement is but a liberal interpretation)

2) 15.17.1.3. (https://docs.python.org/2/library/ctypes.html?highlight=c_int#calling-functions) is a tutorial, rather than reference, section, and as such, is not an authoritative source. And the corresponding reference section, 15.17.2.3. (https://docs.python.org/2/library/ctypes.html?highlight=c_int#foreign-functions) has nothing of the kind.
Basically, when seen in a non-normative text, such a piece of information is interpreted as a "taking into a secret": "this is what happens behind the scenes - so you better understand it - but don't rely on this too much".

Anyway, to produce convincing evidence to change anything, one probably needs to dig up the rationale for the current behaviour and see what adheres to it / if it still stands.

Sure, this violates Python Zen (koans 2 and 12), yet we probably need something better that this to convince core devs that this is a _bug_.
History
Date User Action Args
2022-04-11 14:58:19adminsetgithub: 68935
2021-02-26 17:01:10eryksunsettype: behavior
versions: + Python 3.8, Python 3.9, Python 3.10, - Python 2.7, Python 3.4, Python 3.5, Python 3.6
2015-10-24 21:21:38Ivan.Pozdeevsetnosy: + Ivan.Pozdeev
messages: + msg253417
2015-08-04 07:49:03Christopher Mengsetnosy: + Christopher Meng
2015-07-29 20:35:52eryksunsetnosy: + eryksun
messages: + msg247620
2015-07-29 12:14:04petr.viktorinsetmessages: + msg247566
2015-07-29 12:11:26petr.viktorincreate