blob: 0f35f8a64045ee23960282ba6051d191e45ba79c [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "fory/python/pyfory.h"
#include <algorithm>
#include <cstring>
#include <exception>
#include <limits>
#include <memory>
#include <vector>
#include "fory/type/type.h"
#include "fory/util/stream.h"
#include "fory/util/string_util.h"
#if PY_VERSION_HEX < 0x030A0000
static inline PyObject *Fory_PyNewRefCompat(PyObject *obj) {
Py_INCREF(obj);
return obj;
}
#define FORY_PY_NEWREF(obj) Fory_PyNewRefCompat(obj)
#define FORY_PY_SET_CHECK_EXACT(obj) (Py_TYPE(obj) == &PySet_Type)
#else
#define FORY_PY_NEWREF(obj) Py_NewRef(obj)
#define FORY_PY_SET_CHECK_EXACT(obj) PySet_CheckExact(obj)
#endif
static PyObject **py_sequence_get_items(PyObject *collection) {
if (PyList_CheckExact(collection)) {
return ((PyListObject *)collection)->ob_item;
} else if (PyTuple_CheckExact(collection)) {
return ((PyTupleObject *)collection)->ob_item;
}
return nullptr;
}
namespace fory {
static std::string fetch_python_error_message() {
PyObject *type = nullptr;
PyObject *value = nullptr;
PyObject *traceback = nullptr;
PyErr_Fetch(&type, &value, &traceback);
PyErr_NormalizeException(&type, &value, &traceback);
std::string message = "python stream read failed";
if (value != nullptr) {
PyObject *value_str = PyObject_Str(value);
if (value_str != nullptr) {
const char *c_str = PyUnicode_AsUTF8(value_str);
if (c_str != nullptr) {
message = c_str;
}
Py_DECREF(value_str);
} else {
PyErr_Clear();
}
}
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
return message;
}
enum class PythonStreamReadMethod {
ReadInto,
RecvInto,
RecvIntoUnderscore,
};
static const char *
python_stream_read_method_name(PythonStreamReadMethod method) {
switch (method) {
case PythonStreamReadMethod::ReadInto:
return "readinto";
case PythonStreamReadMethod::RecvInto:
return "recvinto";
case PythonStreamReadMethod::RecvIntoUnderscore:
return "recv_into";
}
return "readinto";
}
static bool resolve_python_stream_read_method(PyObject *stream,
PythonStreamReadMethod *method,
std::string *error_message) {
struct MethodCandidate {
const char *name;
PythonStreamReadMethod method;
};
constexpr MethodCandidate k_candidates[] = {
{"readinto", PythonStreamReadMethod::ReadInto},
{"recv_into", PythonStreamReadMethod::RecvIntoUnderscore},
{"recvinto", PythonStreamReadMethod::RecvInto},
};
for (const auto &candidate : k_candidates) {
const int has_method = PyObject_HasAttrString(stream, candidate.name);
if (has_method < 0) {
*error_message = fetch_python_error_message();
return false;
}
if (has_method == 0) {
continue;
}
PyObject *method_obj = PyObject_GetAttrString(stream, candidate.name);
if (method_obj == nullptr) {
*error_message = fetch_python_error_message();
return false;
}
const bool is_callable = PyCallable_Check(method_obj) != 0;
Py_DECREF(method_obj);
if (is_callable) {
*method = candidate.method;
return true;
}
}
*error_message = "stream object must provide readinto(buffer), "
"recv_into(buffer, size) or recvinto(buffer, size) method";
return false;
}
static bool resolve_python_stream_write_method(PyObject *stream,
std::string *error_message) {
const int has_write = PyObject_HasAttrString(stream, "write");
if (has_write < 0) {
*error_message = fetch_python_error_message();
return false;
}
if (has_write == 0) {
*error_message = "stream object must provide write(data) method";
return false;
}
PyObject *method_obj = PyObject_GetAttrString(stream, "write");
if (method_obj == nullptr) {
*error_message = fetch_python_error_message();
return false;
}
const bool is_callable = PyCallable_Check(method_obj) != 0;
Py_DECREF(method_obj);
if (!is_callable) {
*error_message = "stream.write must be callable";
return false;
}
return true;
}
class PyOutputStream final : public OutputStream {
public:
explicit PyOutputStream(PyObject *stream, uint32_t buffer_size = 4096)
: OutputStream(buffer_size), stream_(stream) {
FORY_CHECK(stream_ != nullptr) << "stream must not be null";
Py_INCREF(stream_);
}
~PyOutputStream() override {
if (stream_ != nullptr) {
PyGILState_STATE gil_state = PyGILState_Ensure();
Py_DECREF(stream_);
PyGILState_Release(gil_state);
stream_ = nullptr;
}
}
protected:
Result<void, Error> write_to_stream(const uint8_t *src,
uint32_t length) override {
if (length == 0) {
return Result<void, Error>();
}
if (src == nullptr) {
return Unexpected(Error::invalid("output source pointer is null"));
}
if (stream_ == nullptr) {
return Unexpected(Error::io_error("output stream is null"));
}
PyGILState_STATE gil_state = PyGILState_Ensure();
uint32_t total_written = 0;
while (total_written < length) {
const uint32_t remaining = length - total_written;
// Contract: stream.write must consume bytes synchronously before return.
// The memoryview below is a transient view over serializer-managed
// storage and is not safe to retain after write(...) returns.
PyObject *chunk = PyMemoryView_FromMemory(
reinterpret_cast<char *>(
const_cast<uint8_t *>(src + static_cast<size_t>(total_written))),
static_cast<Py_ssize_t>(remaining), PyBUF_READ);
if (chunk == nullptr) {
const std::string message = fetch_python_error_message();
PyGILState_Release(gil_state);
return Unexpected(Error::io_error(message));
}
PyObject *written_obj = PyObject_CallMethod(stream_, "write", "O", chunk);
Py_DECREF(chunk);
if (written_obj == nullptr) {
const std::string message = fetch_python_error_message();
PyGILState_Release(gil_state);
return Unexpected(Error::io_error(message));
}
if (written_obj == Py_None) {
Py_DECREF(written_obj);
total_written = length;
break;
}
const long long wrote_value = PyLong_AsLongLong(written_obj);
Py_DECREF(written_obj);
if (wrote_value == -1 && PyErr_Occurred() != nullptr) {
const std::string message = fetch_python_error_message();
PyGILState_Release(gil_state);
return Unexpected(Error::io_error(message));
}
if (wrote_value <= 0) {
PyGILState_Release(gil_state);
return Unexpected(
Error::io_error("stream write returned non-positive bytes"));
}
const uint64_t wrote_u64 = static_cast<uint64_t>(wrote_value);
if (wrote_u64 >= remaining) {
total_written = length;
} else {
total_written += static_cast<uint32_t>(wrote_u64);
}
}
PyGILState_Release(gil_state);
return Result<void, Error>();
}
Result<void, Error> flush_stream() override {
if (stream_ == nullptr) {
return Unexpected(Error::io_error("output stream is null"));
}
PyGILState_STATE gil_state = PyGILState_Ensure();
const int has_flush = PyObject_HasAttrString(stream_, "flush");
if (has_flush < 0) {
const std::string message = fetch_python_error_message();
PyGILState_Release(gil_state);
return Unexpected(Error::io_error(message));
}
if (has_flush == 0) {
PyGILState_Release(gil_state);
return Result<void, Error>();
}
PyObject *result = PyObject_CallMethod(stream_, "flush", nullptr);
if (result == nullptr) {
const std::string message = fetch_python_error_message();
PyGILState_Release(gil_state);
return Unexpected(Error::io_error(message));
}
Py_DECREF(result);
PyGILState_Release(gil_state);
return Result<void, Error>();
}
private:
PyObject *stream_ = nullptr;
};
class PyInputStream final : public InputStream {
public:
explicit PyInputStream(PyObject *stream, uint32_t buffer_size,
PythonStreamReadMethod read_method)
: stream_(stream), read_method_(read_method),
read_method_name_(python_stream_read_method_name(read_method)),
data_(std::max<uint32_t>(buffer_size, static_cast<uint32_t>(1))),
initial_buffer_size_(
std::max<uint32_t>(buffer_size, static_cast<uint32_t>(1))),
owned_buffer_(std::make_unique<Buffer>()) {
FORY_CHECK(stream_ != nullptr) << "stream must not be null";
Py_INCREF(stream_);
bind_buffer(owned_buffer_.get());
}
~PyInputStream() override {
if (stream_ != nullptr) {
PyGILState_STATE gil_state = PyGILState_Ensure();
Py_DECREF(stream_);
PyGILState_Release(gil_state);
stream_ = nullptr;
}
}
Result<void, Error> fill_buffer(uint32_t min_fill_size) override {
if (min_fill_size == 0 || remaining_size() >= min_fill_size) {
return Result<void, Error>();
}
const uint32_t read_pos = buffer_->reader_index_;
const uint32_t deficit = min_fill_size - remaining_size();
constexpr uint64_t k_max_u32 = std::numeric_limits<uint32_t>::max();
const uint64_t required = static_cast<uint64_t>(buffer_->size_) + deficit;
if (required > k_max_u32) {
return Unexpected(
Error::out_of_bound("stream buffer size exceeds uint32 range"));
}
if (required > data_.size()) {
uint64_t new_size =
std::max<uint64_t>(required, static_cast<uint64_t>(data_.size()) * 2);
if (new_size > k_max_u32) {
new_size = k_max_u32;
}
reserve(static_cast<uint32_t>(new_size));
}
uint32_t write_pos = buffer_->size_;
while (remaining_size() < min_fill_size) {
uint32_t writable = static_cast<uint32_t>(data_.size()) - write_pos;
auto read_result = recv_into(data_.data() + write_pos, writable);
if (FORY_PREDICT_FALSE(!read_result.ok())) {
return Unexpected(std::move(read_result).error());
}
uint32_t read_bytes = std::move(read_result).value();
if (read_bytes == 0) {
return Unexpected(Error::buffer_out_of_bound(read_pos, min_fill_size,
remaining_size()));
}
write_pos += read_bytes;
buffer_->size_ = write_pos;
}
return Result<void, Error>();
}
Result<void, Error> read_to(uint8_t *dst, uint32_t length) override {
if (length == 0) {
return Result<void, Error>();
}
Error error;
if (FORY_PREDICT_FALSE(!buffer_->ensure_readable(length, error))) {
return Unexpected(std::move(error));
}
std::memcpy(dst, buffer_->data_ + buffer_->reader_index_,
static_cast<size_t>(length));
buffer_->reader_index_ += length;
return Result<void, Error>();
}
Result<void, Error> skip(uint32_t size) override {
if (size == 0) {
return Result<void, Error>();
}
Error error;
buffer_->increase_reader_index(size, error);
if (FORY_PREDICT_FALSE(!error.ok())) {
return Unexpected(std::move(error));
}
return Result<void, Error>();
}
void shrink_buffer() override {
if (buffer_ == nullptr) {
return;
}
const uint32_t read_pos = buffer_->reader_index_;
// Keep Python-backed InputStream shrink behavior aligned with C++:
// best-effort compaction only after both the global floor (4096) and the
// configured stream buffer size threshold are crossed.
if (FORY_PREDICT_TRUE(read_pos <= 4096 ||
read_pos < initial_buffer_size_)) {
return;
}
const uint32_t remaining = remaining_size();
if (read_pos > 0) {
if (remaining > 0) {
std::memmove(data_.data(), data_.data() + read_pos,
static_cast<size_t>(remaining));
}
buffer_->reader_index_ = 0;
buffer_->size_ = remaining;
buffer_->writer_index_ = remaining;
}
const uint32_t current_capacity = static_cast<uint32_t>(data_.size());
uint32_t target_capacity = current_capacity;
if (current_capacity > initial_buffer_size_) {
if (remaining == 0) {
target_capacity = initial_buffer_size_;
} else if (remaining <= current_capacity / 4) {
const uint32_t doubled =
remaining > std::numeric_limits<uint32_t>::max() / 2
? std::numeric_limits<uint32_t>::max()
: remaining * 2;
target_capacity = std::max<uint32_t>(
initial_buffer_size_,
std::max<uint32_t>(doubled, static_cast<uint32_t>(1)));
}
}
if (target_capacity < current_capacity) {
data_.resize(target_capacity);
data_.shrink_to_fit();
buffer_->data_ = data_.data();
}
}
Result<void, Error> unread(uint32_t size) override {
if (FORY_PREDICT_FALSE(size > buffer_->reader_index_)) {
return Unexpected(Error::buffer_out_of_bound(buffer_->reader_index_, size,
buffer_->size_));
}
buffer_->reader_index_ -= size;
return Result<void, Error>();
}
Buffer &get_buffer() override { return *buffer_; }
void bind_buffer(Buffer *buffer) override {
Buffer *target = buffer == nullptr ? owned_buffer_.get() : buffer;
if (target == nullptr) {
if (buffer_ != nullptr) {
buffer_->input_stream_ = nullptr;
}
buffer_ = nullptr;
return;
}
if (buffer_ == target) {
buffer_->data_ = data_.data();
buffer_->own_data_ = false;
buffer_->wrapped_vector_ = nullptr;
buffer_->input_stream_ = this;
return;
}
Buffer *source = buffer_;
if (source != nullptr) {
target->size_ = source->size_;
target->writer_index_ = source->writer_index_;
target->reader_index_ = source->reader_index_;
source->input_stream_ = nullptr;
} else {
target->size_ = 0;
target->writer_index_ = 0;
target->reader_index_ = 0;
}
buffer_ = target;
buffer_->data_ = data_.data();
buffer_->own_data_ = false;
buffer_->wrapped_vector_ = nullptr;
buffer_->input_stream_ = this;
}
private:
Result<uint32_t, Error> recv_into(void *dst, uint32_t length) {
if (length == 0) {
return 0U;
}
PyGILState_STATE gil_state = PyGILState_Ensure();
PyObject *memory_view =
PyMemoryView_FromMemory(reinterpret_cast<char *>(dst),
static_cast<Py_ssize_t>(length), PyBUF_WRITE);
if (memory_view == nullptr) {
std::string message = fetch_python_error_message();
PyGILState_Release(gil_state);
return Unexpected(Error::io_error(message));
}
PyObject *read_bytes_obj = nullptr;
switch (read_method_) {
case PythonStreamReadMethod::ReadInto:
read_bytes_obj =
PyObject_CallMethod(stream_, read_method_name_, "O", memory_view);
break;
case PythonStreamReadMethod::RecvInto:
case PythonStreamReadMethod::RecvIntoUnderscore:
read_bytes_obj =
PyObject_CallMethod(stream_, read_method_name_, "On", memory_view,
static_cast<Py_ssize_t>(length));
break;
}
Py_DECREF(memory_view);
if (read_bytes_obj == nullptr) {
std::string message = fetch_python_error_message();
PyGILState_Release(gil_state);
return Unexpected(Error::io_error(message));
}
Py_ssize_t read_bytes = PyLong_AsSsize_t(read_bytes_obj);
Py_DECREF(read_bytes_obj);
if (read_bytes == -1 && PyErr_Occurred()) {
std::string message = fetch_python_error_message();
PyGILState_Release(gil_state);
return Unexpected(Error::io_error(message));
}
PyGILState_Release(gil_state);
if (read_bytes < 0 ||
static_cast<uint64_t>(read_bytes) > static_cast<uint64_t>(length)) {
return Unexpected(Error::io_error("python stream " +
std::string(read_method_name_) +
" returned invalid length"));
}
return static_cast<uint32_t>(read_bytes);
}
uint32_t remaining_size() const {
return buffer_->size_ - buffer_->reader_index_;
}
void reserve(uint32_t new_size) {
data_.resize(new_size);
buffer_->data_ = data_.data();
}
PyObject *stream_ = nullptr;
PythonStreamReadMethod read_method_;
const char *read_method_name_ = nullptr;
std::vector<uint8_t> data_;
uint32_t initial_buffer_size_ = 1;
Buffer *buffer_ = nullptr;
std::unique_ptr<Buffer> owned_buffer_;
};
enum class PythonCollectionKind : uint8_t {
List = 0,
Tuple = 1,
Set = 2,
};
static PythonCollectionKind
resolve_python_collection_kind(PyObject *collection) {
if (PyList_CheckExact(collection)) {
return PythonCollectionKind::List;
}
if (PyTuple_CheckExact(collection)) {
return PythonCollectionKind::Tuple;
}
if (FORY_PY_SET_CHECK_EXACT(collection)) {
return PythonCollectionKind::Set;
}
PyErr_Format(PyExc_TypeError,
"fastpath only supports list/tuple/set collections, got %.200s",
Py_TYPE(collection)->tp_name);
return PythonCollectionKind::List;
}
static void set_buffer_error(const Error &error) {
PyErr_SetString(PyExc_BufferError, error.to_string().c_str());
}
static bool py_long_to_int64(PyObject *value, int64_t *out) {
int overflow = 0;
long long converted = PyLong_AsLongLongAndOverflow(value, &overflow);
if (converted == -1 && PyErr_Occurred()) {
return false;
}
if (overflow != 0) {
PyErr_SetString(PyExc_OverflowError,
"integer out of range for int64 fastpath");
return false;
}
*out = static_cast<int64_t>(converted);
return true;
}
static bool can_use_list_sequence_fastpath(PyObject **items, Py_ssize_t size,
uint8_t type_id) {
// This gate is not only about type checks:
// it enforces "no Python callback during conversion" for the raw ob_item
// path. If conversion can invoke user code (e.g. __int__/__float__/subclass
// hooks), a list may be mutated while iterating raw pointers, which is
// unsafe.
switch (static_cast<TypeId>(type_id)) {
case TypeId::STRING:
for (Py_ssize_t i = 0; i < size; ++i) {
if (!PyUnicode_CheckExact(items[i])) {
return false;
}
}
return true;
case TypeId::VARINT64:
case TypeId::VARINT32:
case TypeId::INT8:
case TypeId::INT16:
case TypeId::INT32:
for (Py_ssize_t i = 0; i < size; ++i) {
if (!PyLong_CheckExact(items[i])) {
return false;
}
}
return true;
case TypeId::BOOL:
for (Py_ssize_t i = 0; i < size; ++i) {
if (items[i] != Py_True && items[i] != Py_False) {
return false;
}
}
return true;
case TypeId::FLOAT64:
for (Py_ssize_t i = 0; i < size; ++i) {
if (!PyFloat_CheckExact(items[i])) {
return false;
}
}
return true;
default:
return false;
}
}
template <typename T>
static bool py_long_to_integral_range(PyObject *value, const char *type_name,
T *out) {
int64_t converted = 0;
if (!py_long_to_int64(value, &converted)) {
return false;
}
constexpr int64_t k_min = static_cast<int64_t>(std::numeric_limits<T>::min());
constexpr int64_t k_max = static_cast<int64_t>(std::numeric_limits<T>::max());
if (converted < k_min || converted > k_max) {
PyErr_Format(PyExc_OverflowError, "integer out of range for %s", type_name);
return false;
}
*out = static_cast<T>(converted);
return true;
}
template <typename T>
static bool py_long_to_unsigned_range(PyObject *value, const char *type_name,
T *out) {
const unsigned long long converted = PyLong_AsUnsignedLongLong(value);
if (converted == static_cast<unsigned long long>(-1) &&
PyErr_Occurred() != nullptr) {
return false;
}
constexpr unsigned long long k_max =
static_cast<unsigned long long>(std::numeric_limits<T>::max());
if (converted > k_max) {
PyErr_Format(PyExc_OverflowError, "integer out of range for %s", type_name);
return false;
}
*out = static_cast<T>(converted);
return true;
}
static int write_python_string(Buffer *buffer, PyObject *value) {
if (FORY_PREDICT_FALSE(!PyUnicode_Check(value))) {
PyErr_Format(PyExc_TypeError, "expected str, got %.200s",
Py_TYPE(value)->tp_name);
return -1;
}
if (FORY_PREDICT_FALSE(PyUnicode_READY(value) < 0)) {
return -1;
}
const Py_ssize_t length = PyUnicode_GET_LENGTH(value);
const int kind = PyUnicode_KIND(value);
const void *data = PyUnicode_DATA(value);
uint64_t header = 0;
uint32_t buffer_size = 0;
if (kind == PyUnicode_1BYTE_KIND) {
if (FORY_PREDICT_FALSE(length > std::numeric_limits<uint32_t>::max())) {
PyErr_SetString(PyExc_OverflowError,
"string too large for fastpath encoding");
return -1;
}
buffer_size = static_cast<uint32_t>(length);
header = (static_cast<uint64_t>(length) << 2U) | 0ULL;
} else if (kind == PyUnicode_2BYTE_KIND) {
const uint64_t bytes = static_cast<uint64_t>(length) << 1U;
if (FORY_PREDICT_FALSE(bytes > std::numeric_limits<uint32_t>::max())) {
PyErr_SetString(PyExc_OverflowError,
"string too large for fastpath encoding");
return -1;
}
buffer_size = static_cast<uint32_t>(bytes);
// Keep wire format exactly aligned with Buffer.write_string in buffer.pyx.
header = (static_cast<uint64_t>(length) << 3U) | 1ULL;
} else {
Py_ssize_t utf8_size = 0;
const char *utf8 = PyUnicode_AsUTF8AndSize(value, &utf8_size);
if (FORY_PREDICT_FALSE(utf8 == nullptr)) {
return -1;
}
if (FORY_PREDICT_FALSE(utf8_size > std::numeric_limits<uint32_t>::max())) {
PyErr_SetString(PyExc_OverflowError,
"string too large for fastpath encoding");
return -1;
}
data = utf8;
buffer_size = static_cast<uint32_t>(utf8_size);
header = (static_cast<uint64_t>(buffer_size) << 2U) | 2ULL;
}
buffer->write_var_uint64(header);
if (buffer_size == 0) {
return 0;
}
const uint32_t writer_index = buffer->writer_index();
buffer->grow(buffer_size);
buffer->unsafe_put(writer_index, data, buffer_size);
buffer->increase_writer_index(buffer_size);
return 0;
}
static PyObject *read_python_string(Buffer *buffer) {
Error error;
const uint64_t header = buffer->read_var_uint64(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
const uint64_t size64 = header >> 2U;
if (FORY_PREDICT_FALSE(size64 > std::numeric_limits<uint32_t>::max())) {
PyErr_SetString(PyExc_OverflowError,
"string length too large for fastpath decoding");
return nullptr;
}
const uint32_t size = static_cast<uint32_t>(size64);
const uint32_t encoding = static_cast<uint32_t>(header & 0b11ULL);
if (size == 0) {
return PyUnicode_FromStringAndSize("", 0);
}
uint32_t reader_index = buffer->reader_index();
if (FORY_PREDICT_FALSE(size > buffer->size() - reader_index)) {
if (FORY_PREDICT_FALSE(!buffer->ensure_readable(size, error))) {
set_buffer_error(error);
return nullptr;
}
reader_index = buffer->reader_index();
}
const char *data =
reinterpret_cast<const char *>(buffer->data() + reader_index);
buffer->reader_index(reader_index + size);
if (encoding == 0) {
return PyUnicode_DecodeLatin1(data, static_cast<Py_ssize_t>(size),
"strict");
}
if (encoding == 1) {
if (FORY_PREDICT_FALSE((size & 1U) != 0U)) {
PyErr_SetString(PyExc_ValueError, "invalid utf16 string length");
return nullptr;
}
const auto *utf16_data = reinterpret_cast<const uint16_t *>(data);
if (utf16_has_surrogate_pairs(utf16_data,
static_cast<size_t>(size >> 1U))) {
int byteorder = -1; // little-endian
return PyUnicode_DecodeUTF16(data, static_cast<Py_ssize_t>(size),
"strict", &byteorder);
}
return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, data,
static_cast<Py_ssize_t>(size >> 1U));
}
if (encoding == 2) {
return PyUnicode_DecodeUTF8(data, static_cast<Py_ssize_t>(size), "strict");
}
PyErr_Format(PyExc_ValueError, "unsupported string encoding tag: %u",
encoding);
return nullptr;
}
static int write_primitive_item(Buffer *buffer, PyObject *value,
uint8_t type_id) {
switch (static_cast<TypeId>(type_id)) {
case TypeId::STRING:
return write_python_string(buffer, value);
case TypeId::VARINT64:
case TypeId::INT64: {
int64_t v = 0;
if (FORY_PREDICT_FALSE(!py_long_to_int64(value, &v))) {
return -1;
}
if (static_cast<TypeId>(type_id) == TypeId::INT64) {
buffer->write_int64(v);
} else {
buffer->write_var_int64(v);
}
return 0;
}
case TypeId::VARINT32:
case TypeId::INT32: {
int32_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_integral_range<int32_t>(value, "int32", &v))) {
return -1;
}
if (static_cast<TypeId>(type_id) == TypeId::INT32) {
buffer->write_int32(v);
} else {
buffer->write_var_int32(v);
}
return 0;
}
case TypeId::VAR_UINT64:
case TypeId::UINT64:
case TypeId::TAGGED_UINT64: {
uint64_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_unsigned_range<uint64_t>(value, "uint64", &v))) {
return -1;
}
if (static_cast<TypeId>(type_id) == TypeId::VAR_UINT64) {
buffer->write_var_uint64(v);
} else if (static_cast<TypeId>(type_id) == TypeId::TAGGED_UINT64) {
buffer->write_tagged_uint64(v);
} else {
buffer->write_int64(static_cast<int64_t>(v));
}
return 0;
}
case TypeId::VAR_UINT32:
case TypeId::UINT32: {
uint32_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_unsigned_range<uint32_t>(value, "uint32", &v))) {
return -1;
}
if (static_cast<TypeId>(type_id) == TypeId::VAR_UINT32) {
buffer->write_var_uint32(v);
} else {
buffer->write_uint32(v);
}
return 0;
}
case TypeId::BOOL:
if (value == Py_True) {
buffer->write_int8(1);
return 0;
}
if (value == Py_False) {
buffer->write_int8(0);
return 0;
}
{
const int truthy = PyObject_IsTrue(value);
if (FORY_PREDICT_FALSE(truthy < 0)) {
return -1;
}
buffer->write_int8(truthy ? 1 : 0);
}
return 0;
case TypeId::FLOAT32:
case TypeId::FLOAT64: {
double v;
if (PyFloat_CheckExact(value)) {
v = reinterpret_cast<PyFloatObject *>(value)->ob_fval;
} else {
v = PyFloat_AsDouble(value);
if (FORY_PREDICT_FALSE(v == -1.0 && PyErr_Occurred())) {
return -1;
}
}
if (static_cast<TypeId>(type_id) == TypeId::FLOAT32) {
buffer->write_float(static_cast<float>(v));
} else {
buffer->write_double(v);
}
return 0;
}
case TypeId::UINT8: {
uint8_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_unsigned_range<uint8_t>(value, "uint8", &v))) {
return -1;
}
buffer->write_uint8(v);
return 0;
}
case TypeId::INT8: {
int8_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_integral_range<int8_t>(value, "int8", &v))) {
return -1;
}
buffer->write_int8(v);
return 0;
}
case TypeId::UINT16: {
uint16_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_unsigned_range<uint16_t>(value, "uint16", &v))) {
return -1;
}
buffer->write_uint16(v);
return 0;
}
case TypeId::INT16: {
int16_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_integral_range<int16_t>(value, "int16", &v))) {
return -1;
}
buffer->write_int16(v);
return 0;
}
case TypeId::TAGGED_INT64: {
int64_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_integral_range<int64_t>(value, "int64", &v))) {
return -1;
}
buffer->write_tagged_int64(v);
return 0;
}
default:
PyErr_Format(PyExc_ValueError, "unsupported primitive fastpath type id: %u",
static_cast<unsigned>(type_id));
return -1;
}
}
static int write_primitive_sequence(PyObject **items, Py_ssize_t size,
Buffer *buffer, uint8_t type_id) {
switch (static_cast<TypeId>(type_id)) {
case TypeId::STRING:
for (Py_ssize_t i = 0; i < size; ++i) {
if (FORY_PREDICT_FALSE(write_python_string(buffer, items[i]) != 0)) {
return -1;
}
}
return 0;
case TypeId::VARINT64:
if (FORY_PREDICT_FALSE(static_cast<uint64_t>(size) >
std::numeric_limits<uint32_t>::max() / 9ULL)) {
PyErr_SetString(PyExc_OverflowError, "varint64 collection too large");
return -1;
}
{
const uint32_t max_byte_size = static_cast<uint32_t>(size) * 9U;
const uint32_t writer_index = buffer->writer_index();
buffer->grow(max_byte_size);
uint32_t offset = writer_index;
for (Py_ssize_t i = 0; i < size; ++i) {
int64_t v = 0;
if (FORY_PREDICT_FALSE(!py_long_to_int64(items[i], &v))) {
return -1;
}
const uint64_t zigzag =
(static_cast<uint64_t>(v) << 1) ^ static_cast<uint64_t>(v >> 63);
offset += buffer->put_var_uint64(offset, zigzag);
}
buffer->increase_writer_index(offset - writer_index);
}
return 0;
case TypeId::VARINT32:
if (FORY_PREDICT_FALSE(static_cast<uint64_t>(size) >
std::numeric_limits<uint32_t>::max() / 5ULL)) {
PyErr_SetString(PyExc_OverflowError, "varint32 collection too large");
return -1;
}
{
const uint32_t max_byte_size = static_cast<uint32_t>(size) * 5U;
const uint32_t writer_index = buffer->writer_index();
buffer->grow(max_byte_size);
uint32_t offset = writer_index;
for (Py_ssize_t i = 0; i < size; ++i) {
int32_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_integral_range<int32_t>(items[i], "int32", &v))) {
return -1;
}
const uint32_t zigzag =
(static_cast<uint32_t>(v) << 1) ^ static_cast<uint32_t>(v >> 31);
offset += buffer->put_var_uint32(offset, zigzag);
}
buffer->increase_writer_index(offset - writer_index);
}
return 0;
case TypeId::BOOL: {
const uint64_t byte_size64 = static_cast<uint64_t>(size) * sizeof(bool);
if (FORY_PREDICT_FALSE(byte_size64 >
std::numeric_limits<uint32_t>::max())) {
PyErr_SetString(PyExc_OverflowError, "bool collection too large");
return -1;
}
const uint32_t byte_size = static_cast<uint32_t>(byte_size64);
const uint32_t writer_index = buffer->writer_index();
buffer->grow(byte_size);
uint32_t offset = writer_index;
for (Py_ssize_t i = 0; i < size; ++i) {
buffer->unsafe_put_byte(offset++,
static_cast<uint8_t>(items[i] == Py_True));
}
buffer->increase_writer_index(byte_size);
return 0;
}
case TypeId::FLOAT64: {
const uint64_t byte_size64 = static_cast<uint64_t>(size) * sizeof(double);
if (FORY_PREDICT_FALSE(byte_size64 >
std::numeric_limits<uint32_t>::max())) {
PyErr_SetString(PyExc_OverflowError, "float collection too large");
return -1;
}
const uint32_t byte_size = static_cast<uint32_t>(byte_size64);
const uint32_t writer_index = buffer->writer_index();
buffer->grow(byte_size);
uint32_t offset = writer_index;
for (Py_ssize_t i = 0; i < size; ++i) {
PyObject *value = items[i];
double v;
if (PyFloat_CheckExact(value)) {
v = reinterpret_cast<PyFloatObject *>(value)->ob_fval;
} else {
v = PyFloat_AsDouble(value);
if (FORY_PREDICT_FALSE(v == -1.0 && PyErr_Occurred())) {
return -1;
}
}
buffer->unsafe_put(offset, v);
offset += sizeof(double);
}
buffer->increase_writer_index(byte_size);
return 0;
}
case TypeId::INT8: {
const uint64_t byte_size64 = static_cast<uint64_t>(size) * sizeof(int8_t);
if (FORY_PREDICT_FALSE(byte_size64 >
std::numeric_limits<uint32_t>::max())) {
PyErr_SetString(PyExc_OverflowError, "int8 collection too large");
return -1;
}
const uint32_t byte_size = static_cast<uint32_t>(byte_size64);
const uint32_t writer_index = buffer->writer_index();
buffer->grow(byte_size);
uint32_t offset = writer_index;
for (Py_ssize_t i = 0; i < size; ++i) {
int8_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_integral_range<int8_t>(items[i], "int8", &v))) {
return -1;
}
buffer->unsafe_put_byte(offset++, v);
}
buffer->increase_writer_index(byte_size);
return 0;
}
case TypeId::INT16: {
const uint64_t byte_size64 = static_cast<uint64_t>(size) * sizeof(int16_t);
if (FORY_PREDICT_FALSE(byte_size64 >
std::numeric_limits<uint32_t>::max())) {
PyErr_SetString(PyExc_OverflowError, "int16 collection too large");
return -1;
}
const uint32_t byte_size = static_cast<uint32_t>(byte_size64);
const uint32_t writer_index = buffer->writer_index();
buffer->grow(byte_size);
uint32_t offset = writer_index;
for (Py_ssize_t i = 0; i < size; ++i) {
int16_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_integral_range<int16_t>(items[i], "int16", &v))) {
return -1;
}
buffer->unsafe_put(offset, v);
offset += sizeof(int16_t);
}
buffer->increase_writer_index(byte_size);
return 0;
}
case TypeId::INT32: {
const uint64_t byte_size64 = static_cast<uint64_t>(size) * sizeof(int32_t);
if (FORY_PREDICT_FALSE(byte_size64 >
std::numeric_limits<uint32_t>::max())) {
PyErr_SetString(PyExc_OverflowError, "int32 collection too large");
return -1;
}
const uint32_t byte_size = static_cast<uint32_t>(byte_size64);
const uint32_t writer_index = buffer->writer_index();
buffer->grow(byte_size);
uint32_t offset = writer_index;
for (Py_ssize_t i = 0; i < size; ++i) {
int32_t v = 0;
if (FORY_PREDICT_FALSE(
!py_long_to_integral_range<int32_t>(items[i], "int32", &v))) {
return -1;
}
buffer->unsafe_put(offset, v);
offset += sizeof(int32_t);
}
buffer->increase_writer_index(byte_size);
return 0;
}
default:
PyErr_Format(PyExc_ValueError, "unsupported primitive fastpath type id: %u",
static_cast<unsigned>(type_id));
return -1;
}
}
static PyObject *read_primitive_item(Buffer *buffer, uint8_t type_id) {
Error error;
switch (static_cast<TypeId>(type_id)) {
case TypeId::STRING:
return read_python_string(buffer);
case TypeId::VARINT64:
case TypeId::INT64: {
const int64_t v = static_cast<TypeId>(type_id) == TypeId::INT64
? buffer->read_int64(error)
: buffer->read_var_int64(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyLong_FromLongLong(v);
}
case TypeId::VARINT32:
case TypeId::INT32: {
const int32_t v = static_cast<TypeId>(type_id) == TypeId::INT32
? buffer->read_int32(error)
: buffer->read_var_int32(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyLong_FromLong(v);
}
case TypeId::VAR_UINT64:
case TypeId::UINT64:
case TypeId::TAGGED_UINT64: {
uint64_t v = 0;
if (static_cast<TypeId>(type_id) == TypeId::VAR_UINT64) {
v = buffer->read_var_uint64(error);
} else if (static_cast<TypeId>(type_id) == TypeId::TAGGED_UINT64) {
v = buffer->read_tagged_uint64(error);
} else {
v = buffer->read_uint64(error);
}
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyLong_FromUnsignedLongLong(v);
}
case TypeId::VAR_UINT32:
case TypeId::UINT32: {
const uint32_t v = static_cast<TypeId>(type_id) == TypeId::UINT32
? buffer->read_uint32(error)
: buffer->read_var_uint32(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyLong_FromUnsignedLong(v);
}
case TypeId::BOOL: {
const uint8_t v = buffer->read_uint8(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyBool_FromLong(v != 0);
}
case TypeId::FLOAT32: {
const float v = buffer->read_float(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyFloat_FromDouble(static_cast<double>(v));
}
case TypeId::FLOAT64: {
const double v = buffer->read_double(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyFloat_FromDouble(v);
}
case TypeId::UINT8: {
const uint8_t v = buffer->read_uint8(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyLong_FromUnsignedLong(v);
}
case TypeId::INT8: {
const int8_t v = buffer->read_int8(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyLong_FromLong(v);
}
case TypeId::UINT16: {
const uint16_t v = buffer->read_uint16(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyLong_FromUnsignedLong(v);
}
case TypeId::INT16: {
const int16_t v = buffer->read_int16(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyLong_FromLong(v);
}
case TypeId::TAGGED_INT64: {
const int64_t v = buffer->read_tagged_int64(error);
if (FORY_PREDICT_FALSE(!error.ok())) {
set_buffer_error(error);
return nullptr;
}
return PyLong_FromLongLong(v);
}
default:
PyErr_Format(PyExc_ValueError, "unsupported primitive fastpath type id: %u",
static_cast<unsigned>(type_id));
return nullptr;
}
}
template <typename SetItemFn>
static int read_primitive_sequence_indexed(Buffer *buffer, Py_ssize_t size,
uint8_t type_id,
SetItemFn set_item) {
Error error;
switch (static_cast<TypeId>(type_id)) {
case TypeId::STRING:
for (Py_ssize_t i = 0; i < size; ++i) {
PyObject *item = read_python_string(buffer);
if (FORY_PREDICT_FALSE(item == nullptr)) {
return -1;
}
set_item(i, item);
}
return 0;
case TypeId::VARINT64: {
const uint8_t *data = buffer->data();
const uint8_t *ptr = data + buffer->reader_index();
const uint8_t *end = data + buffer->size();
for (Py_ssize_t i = 0; i < size; ++i) {
if (FORY_PREDICT_FALSE(ptr >= end)) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading varint64");
return -1;
}
uint64_t raw = 0;
const uint8_t b0 = *ptr++;
if ((b0 & 0x80) == 0) {
raw = b0;
} else {
if (FORY_PREDICT_FALSE(ptr >= end)) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading varint64");
return -1;
}
const uint8_t b1 = *ptr++;
raw = static_cast<uint64_t>(b0 & 0x7F) |
(static_cast<uint64_t>(b1 & 0x7F) << 7);
if (FORY_PREDICT_FALSE((b1 & 0x80) != 0)) {
const uint32_t offset = static_cast<uint32_t>((ptr - data) - 2);
uint32_t read_bytes = 0;
raw = buffer->get_var_uint64(offset, &read_bytes);
if (FORY_PREDICT_FALSE(read_bytes == 0)) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading varint64");
return -1;
}
ptr = data + offset + read_bytes;
}
}
const int64_t v =
static_cast<int64_t>((raw >> 1) ^ -static_cast<int64_t>(raw & 1ULL));
PyObject *item = PyLong_FromLongLong(v);
if (FORY_PREDICT_FALSE(item == nullptr)) {
return -1;
}
set_item(i, item);
}
buffer->reader_index(static_cast<uint32_t>(ptr - data));
}
return 0;
case TypeId::VARINT32: {
const uint8_t *data = buffer->data();
const uint8_t *ptr = data + buffer->reader_index();
const uint8_t *end = data + buffer->size();
for (Py_ssize_t i = 0; i < size; ++i) {
if (FORY_PREDICT_FALSE(ptr >= end)) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading varint32");
return -1;
}
uint32_t raw = 0;
const uint8_t b0 = *ptr++;
if ((b0 & 0x80) == 0) {
raw = b0;
} else {
if (FORY_PREDICT_FALSE(ptr >= end)) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading varint32");
return -1;
}
const uint8_t b1 = *ptr++;
raw = static_cast<uint32_t>(b0 & 0x7F) |
(static_cast<uint32_t>(b1 & 0x7F) << 7);
if (FORY_PREDICT_FALSE((b1 & 0x80) != 0)) {
const uint32_t offset = static_cast<uint32_t>((ptr - data) - 2);
uint32_t read_bytes = 0;
raw = buffer->get_var_uint32(offset, &read_bytes);
if (FORY_PREDICT_FALSE(read_bytes == 0)) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading varint32");
return -1;
}
ptr = data + offset + read_bytes;
}
}
const int32_t v =
static_cast<int32_t>((raw >> 1) ^ -static_cast<int32_t>(raw & 1U));
PyObject *item = PyLong_FromLong(v);
if (FORY_PREDICT_FALSE(item == nullptr)) {
return -1;
}
set_item(i, item);
}
buffer->reader_index(static_cast<uint32_t>(ptr - data));
}
return 0;
case TypeId::BOOL:
if (FORY_PREDICT_FALSE(static_cast<uint64_t>(size) >
buffer->remaining_size())) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading bool");
return -1;
}
{
uint32_t offset = buffer->reader_index();
const uint8_t *data = buffer->data() + offset;
for (Py_ssize_t i = 0; i < size; ++i) {
PyObject *item =
data[i] != 0 ? FORY_PY_NEWREF(Py_True) : FORY_PY_NEWREF(Py_False);
set_item(i, item);
}
buffer->reader_index(offset + static_cast<uint32_t>(size));
}
return 0;
case TypeId::FLOAT64:
if (FORY_PREDICT_FALSE(static_cast<uint64_t>(size) >
buffer->remaining_size() / sizeof(double))) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading float64");
return -1;
}
{
uint32_t offset = buffer->reader_index();
const uint8_t *data = buffer->data() + offset;
for (Py_ssize_t i = 0; i < size; ++i) {
double v;
std::memcpy(&v, data + i * sizeof(double), sizeof(double));
PyObject *item = PyFloat_FromDouble(v);
if (FORY_PREDICT_FALSE(item == nullptr)) {
return -1;
}
set_item(i, item);
}
buffer->reader_index(offset +
static_cast<uint32_t>(size * sizeof(double)));
}
return 0;
case TypeId::INT8:
if (FORY_PREDICT_FALSE(static_cast<uint64_t>(size) >
buffer->remaining_size())) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading int8");
return -1;
}
{
uint32_t offset = buffer->reader_index();
const int8_t *data =
reinterpret_cast<const int8_t *>(buffer->data() + offset);
for (Py_ssize_t i = 0; i < size; ++i) {
PyObject *item = PyLong_FromLong(data[i]);
if (FORY_PREDICT_FALSE(item == nullptr)) {
return -1;
}
set_item(i, item);
}
buffer->reader_index(offset + static_cast<uint32_t>(size));
}
return 0;
case TypeId::INT16:
if (FORY_PREDICT_FALSE(static_cast<uint64_t>(size) >
buffer->remaining_size() / sizeof(int16_t))) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading int16");
return -1;
}
{
uint32_t offset = buffer->reader_index();
const uint8_t *data = buffer->data() + offset;
for (Py_ssize_t i = 0; i < size; ++i) {
int16_t v;
std::memcpy(&v, data + i * sizeof(int16_t), sizeof(int16_t));
PyObject *item = PyLong_FromLong(v);
if (FORY_PREDICT_FALSE(item == nullptr)) {
return -1;
}
set_item(i, item);
}
buffer->reader_index(offset +
static_cast<uint32_t>(size * sizeof(int16_t)));
}
return 0;
case TypeId::INT32:
if (FORY_PREDICT_FALSE(static_cast<uint64_t>(size) >
buffer->remaining_size() / sizeof(int32_t))) {
PyErr_SetString(PyExc_BufferError,
"buffer out of bound while reading int32");
return -1;
}
{
uint32_t offset = buffer->reader_index();
const uint8_t *data = buffer->data() + offset;
for (Py_ssize_t i = 0; i < size; ++i) {
int32_t v;
std::memcpy(&v, data + i * sizeof(int32_t), sizeof(int32_t));
PyObject *item = PyLong_FromLong(v);
if (FORY_PREDICT_FALSE(item == nullptr)) {
return -1;
}
set_item(i, item);
}
buffer->reader_index(offset +
static_cast<uint32_t>(size * sizeof(int32_t)));
}
return 0;
default:
PyErr_Format(PyExc_ValueError, "unsupported primitive fastpath type id: %u",
static_cast<unsigned>(type_id));
return -1;
}
}
int Fory_PyPrimitiveCollectionWriteToBuffer(PyObject *collection,
Buffer *buffer, uint8_t type_id) {
PyObject **items = py_sequence_get_items(collection);
if (items != nullptr) {
const Py_ssize_t size = Py_SIZE(collection);
// Refactor guard:
// - tuple is immutable, so raw item pointer iteration is always safe.
// - list is mutable, so use raw pointer path only when
// can_use_list_sequence_fastpath(...) proves callback-free conversion.
// Otherwise we must fall back to iterator path for safety.
if (!PyList_CheckExact(collection) ||
can_use_list_sequence_fastpath(items, size, type_id)) {
return write_primitive_sequence(items, size, buffer, type_id);
}
}
PyObject *iterator = PyObject_GetIter(collection);
if (FORY_PREDICT_FALSE(iterator == nullptr)) {
return -1;
}
int rc = 0;
for (PyObject *item = PyIter_Next(iterator); item != nullptr;
item = PyIter_Next(iterator)) {
if (FORY_PREDICT_FALSE(write_primitive_item(buffer, item, type_id) != 0)) {
Py_DECREF(item);
rc = -1;
break;
}
Py_DECREF(item);
}
if (FORY_PREDICT_FALSE(rc == 0 && PyErr_Occurred() != nullptr)) {
rc = -1;
}
Py_DECREF(iterator);
return rc;
}
int Fory_PyPrimitiveCollectionReadFromBuffer(PyObject *collection,
Buffer *buffer, Py_ssize_t size,
uint8_t type_id) {
if (FORY_PREDICT_FALSE(size < 0)) {
PyErr_SetString(PyExc_ValueError, "negative collection size");
return -1;
}
const PythonCollectionKind kind = resolve_python_collection_kind(collection);
if (FORY_PREDICT_FALSE(PyErr_Occurred() != nullptr)) {
return -1;
}
if (kind == PythonCollectionKind::List && Py_SIZE(collection) < size) {
PyErr_SetString(PyExc_ValueError,
"list collection size is smaller than requested read size");
return -1;
}
if (kind == PythonCollectionKind::Tuple && Py_SIZE(collection) < size) {
PyErr_SetString(
PyExc_ValueError,
"tuple collection size is smaller than requested read size");
return -1;
}
if (!buffer->has_input_stream() && kind == PythonCollectionKind::List) {
return read_primitive_sequence_indexed(
buffer, size, type_id, [collection](Py_ssize_t i, PyObject *item) {
PyList_SET_ITEM(collection, i, item);
});
}
if (!buffer->has_input_stream() && kind == PythonCollectionKind::Tuple) {
return read_primitive_sequence_indexed(
buffer, size, type_id, [collection](Py_ssize_t i, PyObject *item) {
PyTuple_SET_ITEM(collection, i, item);
});
}
for (Py_ssize_t i = 0; i < size; ++i) {
PyObject *item = read_primitive_item(buffer, type_id);
if (FORY_PREDICT_FALSE(item == nullptr)) {
return -1;
}
if (kind == PythonCollectionKind::List) {
PyList_SET_ITEM(collection, i, item);
} else if (kind == PythonCollectionKind::Tuple) {
PyTuple_SET_ITEM(collection, i, item);
} else {
if (FORY_PREDICT_FALSE(PySet_Add(collection, item) < 0)) {
Py_DECREF(item);
return -1;
}
Py_DECREF(item);
}
}
return 0;
}
int Fory_PyWriteBasicFieldToBuffer(PyObject *value, Buffer *buffer,
uint8_t type_id) {
return write_primitive_item(buffer, value, type_id);
}
PyObject *Fory_PyReadBasicFieldFromBuffer(Buffer *buffer, uint8_t type_id) {
return read_primitive_item(buffer, type_id);
}
int Fory_PyCreateBufferFromStream(PyObject *stream, uint32_t buffer_size,
Buffer **out, std::string *error_message) {
if (stream == nullptr) {
*error_message = "stream must not be null";
return -1;
}
PythonStreamReadMethod read_method = PythonStreamReadMethod::ReadInto;
if (!resolve_python_stream_read_method(stream, &read_method, error_message)) {
return -1;
}
try {
auto input_stream =
std::make_shared<PyInputStream>(stream, buffer_size, read_method);
*out = new Buffer(*input_stream);
return 0;
} catch (const std::exception &e) {
*error_message = e.what();
return -1;
}
}
int Fory_PyCreateOutputStream(PyObject *stream, OutputStream **out,
std::string *error_message) {
if (stream == nullptr) {
*error_message = "stream must not be null";
return -1;
}
// See PyOutputStream::write_to_stream contract: the provided sink must not
// retain passed write buffers after write(...) returns.
if (!resolve_python_stream_write_method(stream, error_message)) {
return -1;
}
try {
*out = new PyOutputStream(stream, 4096);
return 0;
} catch (const std::exception &e) {
*error_message = e.what();
return -1;
}
}
int Fory_PyBindBufferToOutputStream(Buffer *buffer, OutputStream *output_stream,
std::string *error_message) {
if (buffer == nullptr) {
*error_message = "buffer must not be null";
return -1;
}
if (output_stream == nullptr) {
*error_message = "output stream must not be null";
return -1;
}
buffer->bind_output_stream(output_stream);
return 0;
}
int Fory_PyClearBufferOutputStream(Buffer *buffer, std::string *error_message) {
if (buffer == nullptr) {
*error_message = "buffer must not be null";
return -1;
}
buffer->clear_output_stream();
return 0;
}
} // namespace fory