Skip to content

Commit 453280a

Browse files
committed
gh-156500: Reject non-positional parameters in wrap_dll_function()
argtypes was built from every annotated parameter, so an annotated keyword-only, *args or **kwargs parameter contributed an extra positional entry. Such a parameter has no positional counterpart to describe, so it now raises ValueError at decoration time.
1 parent 24e5a55 commit 453280a

3 files changed

Lines changed: 42 additions & 2 deletions

File tree

Doc/library/ctypes.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,8 +715,11 @@ Specifying function pointers using type annotations
715715
and do not have to match the underlying C implementation.
716716

717717
If the decorated function does not have a return type annotation, a
718-
:exc:`ValueError` is raised. If the name of the function does not exist
719-
in *dll*, an :exc:`AttributeError` is raised.
718+
:exc:`ValueError` is raised. A :exc:`ValueError` is also raised if it has a
719+
keyword-only, ``*args`` or ``**kwargs`` parameter, since
720+
:attr:`~ctypes._CFuncPtr.argtypes` describes positional arguments only. If
721+
the name of the function does not exist in *dll*, an :exc:`AttributeError`
722+
is raised.
720723

721724
For example::
722725

Lib/ctypes/util.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from dataclasses import dataclass
55

66
lazy import functools
7+
lazy import inspect
78
lazy import shutil
89
lazy import subprocess
910

@@ -509,6 +510,13 @@ def decorator(func):
509510
except KeyError as error:
510511
raise ValueError(f"{name!r} missing return type annotation") from error
511512

513+
for param in inspect.signature(func).parameters.values():
514+
if param.kind not in (param.POSITIONAL_ONLY,
515+
param.POSITIONAL_OR_KEYWORD):
516+
raise ValueError(f"{name!r} has non-positional parameter "
517+
f"{param.name!r}; argtypes describes "
518+
f"positional arguments only")
519+
512520
ptr.restype = restype
513521
ptr.argtypes = tuple(annotations.values())
514522
functools.update_wrapper(ptr, func, updated=())

Lib/test/test_ctypes/test_funcptr.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,35 @@ def noexist():
153153
def PyObject_GetAttrString(op: ctypes.py_object, attr: ctypes.c_char_p):
154154
pass
155155

156+
def test_wrap_dll_function_non_positional(self):
157+
# argtypes describes positional arguments only, so a parameter that
158+
# cannot be passed positionally is rejected.
159+
regex = "'PyObject_GetAttr' has non-positional parameter"
160+
161+
with self.assertRaisesRegex(ValueError, regex):
162+
@wrap_dll_function(ctypes.pythonapi)
163+
def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
164+
*args: ctypes.c_int) -> ctypes.py_object:
165+
pass
166+
167+
with self.assertRaisesRegex(ValueError, regex):
168+
@wrap_dll_function(ctypes.pythonapi)
169+
def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
170+
**kwargs: ctypes.c_int) -> ctypes.py_object:
171+
pass
172+
173+
with self.assertRaisesRegex(ValueError, regex):
174+
@wrap_dll_function(ctypes.pythonapi)
175+
def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
176+
*, kwonly: ctypes.c_int) -> ctypes.py_object:
177+
pass
178+
179+
with self.assertRaisesRegex(ValueError, regex):
180+
@wrap_dll_function(ctypes.pythonapi)
181+
def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
182+
*, kwonly) -> ctypes.py_object:
183+
pass
184+
156185
def test_wrap_dll_function_str_ann(self):
157186
from test.test_ctypes import wrap_str_ann
158187
version = wrap_str_ann.Py_GetVersion()

0 commit comments

Comments
 (0)