Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ctypes: wrong exception message in docs #99266

Closed
kamilturek opened this issue Nov 8, 2022 · 3 comments · Fixed by #99760
Closed

ctypes: wrong exception message in docs #99266

kamilturek opened this issue Nov 8, 2022 · 3 comments · Fixed by #99760
Labels
docs Documentation in the Doc dir

Comments

@kamilturek
Copy link
Contributor

kamilturek commented Nov 8, 2022

Documentation

The ctypes docs say that the message of the ArgumentError for the presented example should be as follows:
image

In fact, it looks like this:

>>> strchr = libc.strchr
>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> 
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 2: TypeError: wrong type

I will prepare a PR.

@kamilturek kamilturek added the docs Documentation in the Doc dir label Nov 8, 2022
@eryksun
Copy link
Contributor

eryksun commented Nov 9, 2022

Note that the better error message from the c_set() set function gets cleared by PyCSimpleType_from_param() before it looks for an _as_parameter_ attribute on the object. We're left with the less useful message "wrong type". The original exception could be saved and then restored if the object doesn't have an _as_parameter_ attribute.

c_set():

static PyObject *
c_set(void *ptr, PyObject *value, Py_ssize_t size)
{
if (PyBytes_Check(value) && PyBytes_GET_SIZE(value) == 1) {
*(char *)ptr = PyBytes_AS_STRING(value)[0];
_RET(value);
}
if (PyByteArray_Check(value) && PyByteArray_GET_SIZE(value) == 1) {
*(char *)ptr = PyByteArray_AS_STRING(value)[0];
_RET(value);
}
if (PyLong_Check(value))
{
long longval = PyLong_AsLong(value);
if (longval < 0 || longval >= 256)
goto error;
*(char *)ptr = (char)longval;
_RET(value);
}
error:
PyErr_Format(PyExc_TypeError,
"one character bytes, bytearray or integer expected");
return NULL;
}

PyCSimpleType_from_param():

/*
* This is a *class method*.
* Convert a parameter into something that ConvParam can handle.
*/
static PyObject *
PyCSimpleType_from_param(PyObject *type, PyObject *value)
{
StgDictObject *dict;
const char *fmt;
PyCArgObject *parg;
struct fielddesc *fd;
PyObject *as_parameter;
int res;
/* If the value is already an instance of the requested type,
we can use it as is */
res = PyObject_IsInstance(value, type);
if (res == -1)
return NULL;
if (res) {
Py_INCREF(value);
return value;
}
dict = PyType_stgdict(type);
if (!dict) {
PyErr_SetString(PyExc_TypeError,
"abstract class");
return NULL;
}
/* I think we can rely on this being a one-character string */
fmt = PyUnicode_AsUTF8(dict->proto);
assert(fmt);
fd = _ctypes_get_fielddesc(fmt);
assert(fd);
parg = PyCArgObject_new();
if (parg == NULL)
return NULL;
parg->tag = fmt[0];
parg->pffi_type = fd->pffi_type;
parg->obj = fd->setfunc(&parg->value, value, 0);
if (parg->obj)
return (PyObject *)parg;
PyErr_Clear();
Py_DECREF(parg);
if (_PyObject_LookupAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
return NULL;
}
if (as_parameter) {
if (_Py_EnterRecursiveCall("while processing _as_parameter_")) {
Py_DECREF(as_parameter);
return NULL;
}
value = PyCSimpleType_from_param(type, as_parameter);
_Py_LeaveRecursiveCall();
Py_DECREF(as_parameter);
return value;
}
PyErr_SetString(PyExc_TypeError,
"wrong type");
return NULL;
}

Actually, I don't see why it doesn't first check for an _as_parameter_ attribute on value, and I don't see why it needs to recursively call PyCSimpleType_from_param().

@kamilturek
Copy link
Contributor Author

As per the docs, _as_parameter_ can return another object with the _as_parameter_ attribute. I believe that's why the recursion is needed.

The from_param() class method receives the Python object passed to the function call, it should do a typecheck or whatever is needed to make sure this object is acceptable, and then return the object itself, its as_parameter attribute, or whatever you want to pass as the C function argument in this case. Again, the result should be an integer, string, bytes, a ctypes instance, or an object with an as_parameter attribute.

https://docs.python.org/3/library/ctypes.html

@kamilturek
Copy link
Contributor Author

kamilturek commented Nov 24, 2022

@eryksun I followed your suggestion of saving and restoring the exception. I opened a PR.

kumaraditya303 added a commit to kamilturek/cpython that referenced this issue Dec 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir
Projects
None yet
2 participants