diff mbox series

libgpiod: bindings: python: Fix PyDict_Next contiguous assumption and pypy

Message ID 20241201231313.42935-1-george@george-graphics.co.uk
State Superseded
Headers show
Series libgpiod: bindings: python: Fix PyDict_Next contiguous assumption and pypy | expand

Commit Message

George Harker Dec. 1, 2024, 11:13 p.m. UTC
PyDict_Next does not guarantee pos is contiguous, and pypy increments
past the end of the dict size.  Patch fixes reliance on pos for constructing
args for gpiod call.

As per discussion here https://github.com/pypy/pypy/issues/5142

---
 bindings/python/gpiod/ext/request.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

Comments

Bartosz Golaszewski Dec. 2, 2024, 8:28 a.m. UTC | #1
On Mon, Dec 2, 2024 at 12:15 AM George Harker
<george@george-graphics.co.uk> wrote:
>
> PyDict_Next does not guarantee pos is contiguous, and pypy increments
> past the end of the dict size.  Patch fixes reliance on pos for constructing
> args for gpiod call.
>
> As per discussion here https://github.com/pypy/pypy/issues/5142
>

Ah, it's right there in the documentation too[1]:

"Its value represents offsets within the internal dictionary
structure, and since the structure is sparse, the offsets are not
consecutive."

Thanks for the catch.

I wasn't aware pypy actually reimplements the entire C API of cpython.
How would I go about building libgpiod python bindings with pypy?

Bart

[1] https://docs.python.org/3/c-api/dict.html#c.PyDict_Next
Bartosz Golaszewski Dec. 2, 2024, 8:33 a.m. UTC | #2
On Mon, Dec 2, 2024 at 12:15 AM George Harker
<george@george-graphics.co.uk> wrote:
>
> PyDict_Next does not guarantee pos is contiguous, and pypy increments
> past the end of the dict size.  Patch fixes reliance on pos for constructing
> args for gpiod call.
>

Just a couple nits below:

> As per discussion here https://github.com/pypy/pypy/issues/5142
>

Can you add your Signed-off-by: tag here?

> ---
>  bindings/python/gpiod/ext/request.c | 11 ++++++++---
>  1 file changed, 8 insertions(+), 3 deletions(-)
>
> diff --git a/bindings/python/gpiod/ext/request.c b/bindings/python/gpiod/ext/request.c
> index e1a2a42..4e49289 100644
> --- a/bindings/python/gpiod/ext/request.c
> +++ b/bindings/python/gpiod/ext/request.c
> @@ -206,6 +206,7 @@ static PyObject *request_set_values(request_object *self, PyObject *args)
>  {
>         PyObject *values, *key, *val, *val_stripped;
>         Py_ssize_t pos = 0;
> +       Py_ssize_t index = 0;

Please put this on the same line as pos.

>         int ret;
>
>         ret = PyArg_ParseTuple(args, "O", &values);
> @@ -214,8 +215,10 @@ static PyObject *request_set_values(request_object *self, PyObject *args)
>
>         clear_buffers(self);
>
> +       // Note: pos may not be contiguous and in pypy, is incremented
> +       // past the end of the dict size.

Please use /* */ style comments for consistency and don't mention pypy
- it's actually python spec and just by chance it worked fine in
cpython so far.

>         while (PyDict_Next(values, &pos, &key, &val)) {
> -               self->offsets[pos - 1] = Py_gpiod_PyLongAsUnsignedInt(key);
> +               self->offsets[index] = Py_gpiod_PyLongAsUnsignedInt(key);
>                 if (PyErr_Occurred())
>                         return NULL;
>
> @@ -223,15 +226,17 @@ static PyObject *request_set_values(request_object *self, PyObject *args)
>                 if (!val_stripped)
>                         return NULL;
>
> -               self->values[pos - 1] = PyLong_AsLong(val_stripped);
> +               self->values[index] = PyLong_AsLong(val_stripped);
>                 Py_DECREF(val_stripped);
>                 if (PyErr_Occurred())
>                         return NULL;
> +
> +               index += 1;

index++ ?

>         }
>
>         Py_BEGIN_ALLOW_THREADS;
>         ret = gpiod_line_request_set_values_subset(self->request,
> -                                                  pos,
> +                                                  index,
>                                                    self->offsets,
>                                                    self->values);
>         Py_END_ALLOW_THREADS;
> --
> 2.39.5
>
>

Thanks,
Bartosz
Bartosz Golaszewski Dec. 2, 2024, 8:35 a.m. UTC | #3
On Mon, Dec 2, 2024 at 9:33 AM Bartosz Golaszewski <brgl@bgdev.pl> wrote:
>
> On Mon, Dec 2, 2024 at 12:15 AM George Harker
> <george@george-graphics.co.uk> wrote:
> >
> > PyDict_Next does not guarantee pos is contiguous, and pypy increments
> > past the end of the dict size.  Patch fixes reliance on pos for constructing
> > args for gpiod call.
> >
>
> Just a couple nits below:
>

One more thing: the commit title should look like this:

[libgpiod][PATCH] bindings: python: Fix PyDict_Next contiguous
assumption and pypy

The [libgpiod] bit should be a mailing list tag.

Bart
diff mbox series

Patch

diff --git a/bindings/python/gpiod/ext/request.c b/bindings/python/gpiod/ext/request.c
index e1a2a42..4e49289 100644
--- a/bindings/python/gpiod/ext/request.c
+++ b/bindings/python/gpiod/ext/request.c
@@ -206,6 +206,7 @@  static PyObject *request_set_values(request_object *self, PyObject *args)
 {
 	PyObject *values, *key, *val, *val_stripped;
 	Py_ssize_t pos = 0;
+	Py_ssize_t index = 0;
 	int ret;
 
 	ret = PyArg_ParseTuple(args, "O", &values);
@@ -214,8 +215,10 @@  static PyObject *request_set_values(request_object *self, PyObject *args)
 
 	clear_buffers(self);
 
+	// Note: pos may not be contiguous and in pypy, is incremented
+	// past the end of the dict size.
 	while (PyDict_Next(values, &pos, &key, &val)) {
-		self->offsets[pos - 1] = Py_gpiod_PyLongAsUnsignedInt(key);
+		self->offsets[index] = Py_gpiod_PyLongAsUnsignedInt(key);
 		if (PyErr_Occurred())
 			return NULL;
 
@@ -223,15 +226,17 @@  static PyObject *request_set_values(request_object *self, PyObject *args)
 		if (!val_stripped)
 			return NULL;
 
-		self->values[pos - 1] = PyLong_AsLong(val_stripped);
+		self->values[index] = PyLong_AsLong(val_stripped);
 		Py_DECREF(val_stripped);
 		if (PyErr_Occurred())
 			return NULL;
+
+		index += 1;
 	}
 
 	Py_BEGIN_ALLOW_THREADS;
 	ret = gpiod_line_request_set_values_subset(self->request,
-						   pos,
+						   index,
 						   self->offsets,
 						   self->values);
 	Py_END_ALLOW_THREADS;