v1-0001-jsonb_plpython-Check-for-negative-PySequence_Size-result.patch
application/octet-stream
Filename: v1-0001-jsonb_plpython-Check-for-negative-PySequence_Size-result.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch v1-0001
Subject: jsonb_plpython: Check for negative PySequence_Size() result
| File | + | − |
|---|---|---|
| contrib/jsonb_plpython/expected/jsonb_plpython.out | 36 | 0 |
| contrib/jsonb_plpython/jsonb_plpython.c | 2 | 0 |
| contrib/jsonb_plpython/sql/jsonb_plpython.sql | 31 | 0 |
From 644610f27fe9898cc5d7736a6db40449b79ab2f1 Mon Sep 17 00:00:00 2001
From: Ewan Young <kdbase.hack@gmail.com>
Date: Wed, 8 Jul 2026 01:51:50 +0800
Subject: [PATCH v1] jsonb_plpython: Check for negative PySequence_Size()
result
Commit 8612f0b7ce0 hardened the jsonb and hstore PL/Python transforms
against broken container objects, adding a pcount < 0 check after
PyMapping_Size() in PLyMapping_ToJsonbValue(). The sibling function
PLySequence_ToJsonbValue() was left without the equivalent check on the
result of PySequence_Size().
PySequence_Size() returns -1, with a Python exception set, for an object
that passes PySequence_Check() but whose length cannot be determined:
for example one whose __len__() raises, or a legacy sequence that
implements __getitem__() but no __len__() at all. Because the negative
result was used directly as the loop bound, the loop body was simply
skipped, so the transform silently produced an empty jsonb array instead
of reporting the error. Worse, the Python error indicator was left set,
which could cause a subsequent, unrelated PL/Python call in the same
session to fail with a confusing "returned a result with an error set"
SystemError.
Fix this the same way the mapping side was fixed, by checking for a
negative result and surfacing the underlying Python exception.
Discussion: https://postgr.es/m/CAMbWs49BKM9wP6m8bCXEpHwQKp7usvOGV6Jf=J7FYr_BCpxLqg@mail.gmail.com
Backpatch-through: 14
---
.../expected/jsonb_plpython.out | 36 +++++++++++++++++++
contrib/jsonb_plpython/jsonb_plpython.c | 2 ++
contrib/jsonb_plpython/sql/jsonb_plpython.sql | 31 ++++++++++++++++
3 files changed, 69 insertions(+)
diff --git a/contrib/jsonb_plpython/expected/jsonb_plpython.out b/contrib/jsonb_plpython/expected/jsonb_plpython.out
index 8d3f5328809..dac06e4918d 100644
--- a/contrib/jsonb_plpython/expected/jsonb_plpython.out
+++ b/contrib/jsonb_plpython/expected/jsonb_plpython.out
@@ -393,3 +393,39 @@ DETAIL: ValueError: len failed
CONTEXT: Traceback (most recent call last):
while creating return value
PL/Python function "test_broken_len_mapping"
+-- A custom sequence whose __len__() raises should be reported as an error,
+-- not silently produce an empty array
+CREATE FUNCTION test_broken_len_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+ def __getitem__(self, i):
+ return i
+ def __len__(self):
+ raise ValueError('len failed')
+return C()
+$$;
+SELECT test_broken_len_sequence();
+ERROR: could not get size of Python sequence
+DETAIL: ValueError: len failed
+CONTEXT: Traceback (most recent call last):
+while creating return value
+PL/Python function "test_broken_len_sequence"
+-- Likewise for a sequence-like object that has no __len__() at all
+CREATE FUNCTION test_no_len_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+ def __getitem__(self, i):
+ if i < 3:
+ return i
+ raise IndexError
+return C()
+$$;
+SELECT test_no_len_sequence();
+ERROR: could not get size of Python sequence
+DETAIL: TypeError: object of type 'C' has no len()
+CONTEXT: while creating return value
+PL/Python function "test_no_len_sequence"
diff --git a/contrib/jsonb_plpython/jsonb_plpython.c b/contrib/jsonb_plpython/jsonb_plpython.c
index dc17c9ee570..e142f2bf215 100644
--- a/contrib/jsonb_plpython/jsonb_plpython.c
+++ b/contrib/jsonb_plpython/jsonb_plpython.c
@@ -341,6 +341,8 @@ PLySequence_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
PyObject *volatile value = NULL;
pcount = PySequence_Size(obj);
+ if (pcount < 0)
+ PLy_elog(ERROR, "could not get size of Python sequence");
pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL);
diff --git a/contrib/jsonb_plpython/sql/jsonb_plpython.sql b/contrib/jsonb_plpython/sql/jsonb_plpython.sql
index fd8485c89c1..acceb088bff 100644
--- a/contrib/jsonb_plpython/sql/jsonb_plpython.sql
+++ b/contrib/jsonb_plpython/sql/jsonb_plpython.sql
@@ -258,3 +258,34 @@ return d
$$;
SELECT test_broken_len_mapping();
+
+-- A custom sequence whose __len__() raises should be reported as an error,
+-- not silently produce an empty array
+CREATE FUNCTION test_broken_len_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+ def __getitem__(self, i):
+ return i
+ def __len__(self):
+ raise ValueError('len failed')
+return C()
+$$;
+
+SELECT test_broken_len_sequence();
+
+-- Likewise for a sequence-like object that has no __len__() at all
+CREATE FUNCTION test_no_len_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+ def __getitem__(self, i):
+ if i < 3:
+ return i
+ raise IndexError
+return C()
+$$;
+
+SELECT test_no_len_sequence();
--
2.47.3