Skip to content

Commit 77f100d

Browse files
committed
gh-156500: Skip 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. argtypes describes positional arguments only, so the generated function pointer asked for an argument the C function does not have and could not be called by any call matching its own stub. Build argtypes from co_varnames[:co_argcount] instead, which keeps the existing rule that unannotated parameters are skipped.
1 parent 24e5a55 commit 77f100d

3 files changed

Lines changed: 40 additions & 3 deletions

File tree

Doc/library/ctypes.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -711,8 +711,10 @@ Specifying function pointers using type annotations
711711
"""Optional docstring. There should be no function body."""
712712

713713
The body of the decorated function is ignored, and any parameters that are
714-
missing type annotations are skipped. The names of the parameters are ignored
715-
and do not have to match the underlying C implementation.
714+
missing type annotations are skipped. Variadic and keyword-only parameters
715+
are skipped as well, since :attr:`~ctypes._CFuncPtr.argtypes` describes
716+
positional arguments only. The names of the parameters are ignored and do
717+
not have to match the underlying C implementation.
716718

717719
If the decorated function does not have a return type annotation, a
718720
:exc:`ValueError` is raised. If the name of the function does not exist

Lib/ctypes/util.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,8 +509,11 @@ def decorator(func):
509509
except KeyError as error:
510510
raise ValueError(f"{name!r} missing return type annotation") from error
511511

512+
code = func.__code__
513+
positional = code.co_varnames[:code.co_argcount]
514+
512515
ptr.restype = restype
513-
ptr.argtypes = tuple(annotations.values())
516+
ptr.argtypes = tuple(annotations[n] for n in positional if n in annotations)
514517
functools.update_wrapper(ptr, func, updated=())
515518

516519
return ptr

Lib/test/test_ctypes/test_funcptr.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,38 @@ 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 variadic and
158+
# keyword-only parameters must not contribute an entry.
159+
@wrap_dll_function(ctypes.pythonapi)
160+
def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
161+
*args: ctypes.c_int) -> ctypes.py_object:
162+
pass
163+
164+
self.assertEqual(PyObject_GetAttr.argtypes,
165+
(ctypes.py_object, ctypes.py_object))
166+
167+
@wrap_dll_function(ctypes.pythonapi)
168+
def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
169+
**kwargs: ctypes.c_int) -> ctypes.py_object:
170+
pass
171+
172+
self.assertEqual(PyObject_GetAttr.argtypes,
173+
(ctypes.py_object, ctypes.py_object))
174+
175+
@wrap_dll_function(ctypes.pythonapi)
176+
def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
177+
*, kwonly: ctypes.c_int) -> ctypes.py_object:
178+
pass
179+
180+
self.assertEqual(PyObject_GetAttr.argtypes,
181+
(ctypes.py_object, ctypes.py_object))
182+
183+
class Foo:
184+
a = "abc"
185+
186+
self.assertEqual(PyObject_GetAttr(Foo, "a"), "abc")
187+
156188
def test_wrap_dll_function_str_ann(self):
157189
from test.test_ctypes import wrap_str_ann
158190
version = wrap_str_ann.Py_GetVersion()

0 commit comments

Comments
 (0)