From 7b00705bc32ab3b7b00a9fca65c6f8403507dac1 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 28 Aug 2026 17:58:05 +0200 Subject: [PATCH] [cpyrt] Add CStringArrayConverter::ToMemory Assigning to a const char** data member inherited the buffer-only ToMemory of the base converter, so a Python string was rejected with a property type mismatch. Accept a string as well, with the buffer copy factored into a helper covering the fixed and unbounded cases and the lifetime tie-in. --- src/cpyrt/Converters.cxx | 46 +++++++++++++++++++++++++++++++++++ src/cpyrt/DeclareConverters.h | 1 + test/test_lowlevel.py | 19 +++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/src/cpyrt/Converters.cxx b/src/cpyrt/Converters.cxx index 4771e80..8e1e7ec 100644 --- a/src/cpyrt/Converters.cxx +++ b/src/cpyrt/Converters.cxx @@ -1710,6 +1710,41 @@ bool cpyrt::StdSpanConverter::SetArg(PyObject* pyobject, Parameter& para, #endif // __cplusplus >= 202002L +namespace { + +template +bool ToArrayFromBuffer(PyObject* owner, void* address, PyObject* ctxt, + const void* buf, Py_ssize_t buflen, cpyrt::dims_t& shape, + bool isFixed) { + if (buflen == 0) + return false; + + Py_ssize_t oldsz = 1; + for (Py_ssize_t idim = 0; idim < shape.ndim(); ++idim) { + if (shape[idim] == cpyrt::UNKNOWN_SIZE) { + oldsz = -1; + break; + } + oldsz *= shape[idim]; + } + if (shape.ndim() != cpyrt::UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { + PyErr_SetString(PyExc_ValueError, "buffer too large for value"); + return false; + } + + if (isFixed) + memcpy(*(type**)address, buf, (0 < buflen ? buflen : 1) * sizeof(type)); + else { + *(type**)address = (type*)buf; + shape.ndim(1); + shape[0] = buflen; + SetLifeLine(ctxt, owner, (intptr_t)address); + } + return true; +} + +} // namespace + //---------------------------------------------------------------------------- #define CPPJIT_IMPL_ARRAY_CONVERTER(name, ctype, type, code, suffix) \ cpyrt::name##ArrayConverter::name##ArrayConverter(cdims_t dims) \ @@ -1917,6 +1952,17 @@ PyObject* cpyrt::CStringArrayConverter::FromMemory(void* address) { return CreateLowLevelViewString(*(const char***)address, fShape); } +bool cpyrt::CStringArrayConverter::ToMemory(PyObject* value, void* address, + PyObject* ctxt) { + // Unlike the other array converters, this one also accepts a Python string. + Py_ssize_t len; + if (const char* cstr = cpyrt_PyText_AsStringAndSize(value, &len)) { + return ToArrayFromBuffer(value, address, ctxt, cstr, len, fShape, + fIsFixed); + } + return SCharArrayConverter::ToMemory(value, address, ctxt); +} + //---------------------------------------------------------------------------- PyObject* cpyrt::NonConstCStringArrayConverter::FromMemory(void* address) { if (fIsFixed) diff --git a/src/cpyrt/DeclareConverters.h b/src/cpyrt/DeclareConverters.h index efbf797..3347e18 100644 --- a/src/cpyrt/DeclareConverters.h +++ b/src/cpyrt/DeclareConverters.h @@ -249,6 +249,7 @@ class CStringArrayConverter : public SCharArrayConverter { using SCharArrayConverter::SCharArrayConverter; bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void* address) override; + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; std::string GetFailureMsg() override { return "[CStringArrayConverter]"; }; private: diff --git a/test/test_lowlevel.py b/test/test_lowlevel.py index 8bb395e..b6ad122 100644 --- a/test/test_lowlevel.py +++ b/test/test_lowlevel.py @@ -1159,3 +1159,22 @@ def test08_reshape_sets_unknown_dimensions_only(self): # a freshly constructed view has no dimensions to set v = cppjit._backend.LowLevelView() raises(TypeError, v.reshape, ()) + + +class TestCSTRINGARRAY: + def test01_cstring_array_from_str(self): + """A Python string can be assigned to a const char** data member""" + + import cppjit + + cppjit.cppdef("""\ + namespace CStringArray { + struct S { const char** names = nullptr; }; + const char* as_chars(S& s) { return (const char*)s.names; } + }""") + + ns = cppjit.gbl.CStringArray + s = ns.S() + + s.names = "abc" + assert ns.as_chars(s) == "abc"