You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
ctypes ignores when a DLL function is called with too many arguments #82439
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
assignee=Noneclosed_at=Nonecreated_at=<Date2019-09-23.14:35:01.156>labels= ['type-bug', '3.8', '3.9', 'ctypes', 'OS-windows', 'docs']
title='ctypes ignores when a DLL function is called with too many arguments'updated_at=<Date2019-09-24.16:56:54.631>user='https://github.com/s-m-e'
Expected result: If the function is called with too many (positional) arguments (in the example 3 instead of 1), a ValueError should be raised. This is the case for at least CPython 3.4 to 3.7.
Actual result: "Nothing", i.e. no exception. The described test "fails". The function is called without an error - with CPython 3.8.0b4.
If this behavior is intended, is has not been (as far as I can tell) documented.
According to the docs, raising ValueError in this case has been a deprecated feature since 3.6.2, and the ability to do so no longer exists in 3.8. The documentation needs to be updated to reflect the new behavior.
In bpo-35947, the customized libffi_msvc was replaced by standard libffi. ffi_call() in libffi_msvc returned a stack cleanup delta. This allowed raising ValueError in _call_function_pointer if the delta value was non-zero for an x86 32-bit stdcall function (callee cleanup). A positive delta implied too many arguments, and a negative delta implied too few arguments. The standard libffi ffi_call() has no return value, so the code for raising ValueError was removed.
---
On a related note, PyCFuncPtr_call can be relaxed to allow the argument count to exceed the length of argtypes for x64 stdcall (but not x86). The 64-bit calling convention is the same for cdecl, stdcall, fastcall, and thiscall. It uses caller cleanup, like x86 cdecl, so we can skip raising TypeError in this case, just like we already do for cdecl.
Thanks a lot for the clarification, Eryk. I did not notice that it was deprecated behavior.
For the "too many arguments" case I guess this is not an issue. However, just for the symmetry of things, I also looked at calling a function with TOO FEW arguments.
As expected after your explanation, also no error in CPython 3.8 (i.e. the test fails, while is passes on CPython <= 3.7). If I run this manually, I even get a "result":
>>>dll=ctypes.windll.LoadLibrary('tests/demo_dll.dll')
>>>mul_ints=dll.mul_ints>>>a=mul_ints(7)
>>>a445564# !>>>445564/7# Just looking at where this result is coming from ...63652.0
Re-starting Python (3.8) and (intentionally) misconfiguring the function interestingly also does not change the result:
>>>dll=ctypes.windll.LoadLibrary('tests/demo_dll.dll')
>>>mul_ints=dll.mul_ints>>>mul_ints.argtypes= (ctypes.c_int16,)
>>>a=mul_ints(7)
>>>a445564# Apparently, this is deterministic?!?
Just out of curiosity, where is this data coming from?
This ONLY way to prevent things like this to happen is to actually correctly configure the function:
Just out of curiosity, where is this data coming from?
In general it's just random data on the stack. It's not worth investigating where this particular value comes from. It could be the low word of the address of a stack-allocated buffer, such as the buffer where ffi_call writes the return value.
This ONLY way to prevent things like this to happen is to actually
correctly configure the function:
...
>>> mul_ints.argtypes = (ctypes.c_int16, ctypes.c_int16)
Yes, the docs should explain that TypeError is raised for the wrong number of arguments when argtypes is assigned, and it should be noted that cdecl (CDLL) allows passing more (but not fewer) arguments than what argtypes defines. As I mentioned previously, we could extend this cdecl behavior to stdcall (WinDLL) for the x64 architecture, since it's caller cleanup like cdecl.
Activity
s-m-e commentedon Sep 23, 2019
A c-function with the following signature ...
... is being called with ctypes:
Expected result: If the function is called with too many (positional) arguments (in the example 3 instead of 1), a
ValueErrorshould be raised. This is the case for at least CPython 3.4 to 3.7.Actual result: "Nothing", i.e. no exception. The described test "fails". The function is called without an error - with CPython 3.8.0b4.
If this behavior is intended, is has not been (as far as I can tell) documented.
eryksun commentedon Sep 23, 2019
According to the docs, raising ValueError in this case has been a deprecated feature since 3.6.2, and the ability to do so no longer exists in 3.8. The documentation needs to be updated to reflect the new behavior.
https://docs.python.org/3.8/library/ctypes.html#calling-functions
In bpo-35947, the customized libffi_msvc was replaced by standard libffi. ffi_call() in libffi_msvc returned a stack cleanup delta. This allowed raising ValueError in _call_function_pointer if the delta value was non-zero for an x86 32-bit stdcall function (callee cleanup). A positive delta implied too many arguments, and a negative delta implied too few arguments. The standard libffi ffi_call() has no return value, so the code for raising ValueError was removed.
---
On a related note, PyCFuncPtr_call can be relaxed to allow the argument count to exceed the length of argtypes for x64 stdcall (but not x86). The 64-bit calling convention is the same for cdecl, stdcall, fastcall, and thiscall. It uses caller cleanup, like x86 cdecl, so we can skip raising TypeError in this case, just like we already do for cdecl.
s-m-e commentedon Sep 24, 2019
Thanks a lot for the clarification, Eryk. I did not notice that it was deprecated behavior.
For the "too many arguments" case I guess this is not an issue. However, just for the symmetry of things, I also looked at calling a function with TOO FEW arguments.
As expected after your explanation, also no error in CPython 3.8 (i.e. the test fails, while is passes on CPython <= 3.7). If I run this manually, I even get a "result":
Re-starting Python (3.8) and (intentionally) misconfiguring the function interestingly also does not change the result:
Just out of curiosity, where is this data coming from?
This ONLY way to prevent things like this to happen is to actually correctly configure the function:
This should very CLEARLY be mentioned in the documentation ...
eryksun commentedon Sep 24, 2019
In general it's just random data on the stack. It's not worth investigating where this particular value comes from. It could be the low word of the address of a stack-allocated buffer, such as the buffer where ffi_call writes the return value.
Yes, the docs should explain that TypeError is raised for the wrong number of arguments when argtypes is assigned, and it should be noted that cdecl (CDLL) allows passing more (but not fewer) arguments than what argtypes defines. As I mentioned previously, we could extend this cdecl behavior to stdcall (WinDLL) for the x64 architecture, since it's caller cleanup like cdecl.
gh-82439: Clarify the behavior of `argtypes` under different calling …
[3.15] gh-82439: Clarify the behavior of `argtypes` under different c…
[3.14] gh-82439: Clarify the behavior of `argtypes` under different c…