| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "object_converter.h" |
| |
| #include <Python.h> |
| #include <zircon/types.h> |
| |
| #include <cinttypes> |
| #include <sstream> |
| |
| #include <fuchsia_controller_abi/utils.h> |
| |
| #include "object.h" |
| #include "src/lib/fidl_codec/wire_object.h" |
| #include "utils.h" |
| |
| namespace fuchsia_controller::fidl_codec::converter { |
| |
| namespace { |
| |
| namespace fc = fuchsia_controller; |
| |
| // Helper func. This attempts to lookup an attribute on an object while not setting an error if the |
| // attribute does not exist. Can still return an error generally, and is indicated by returning |
| // nullptr. This is just guaranteed not to occur if the attribute doesn't exist. |
| // |
| // If no attr was found, return Py_None. If found, it will return a PyObject |
| // pointer as a new reference. |
| // |
| // Returns a NEW reference object. |
| PyObject* GetAttr(PyObject* target, std::string_view attr) { |
| auto name_obj = fc::abi::utils::Object( |
| PyUnicode_FromStringAndSize(attr.data(), static_cast<Py_ssize_t>(attr.size()))); |
| // This shouldn't crop up since the IR would have to somehow contain non-unicode strings, which |
| // would cause this code to fail far before it ever reaches this point. |
| if (name_obj == nullptr) { |
| return nullptr; |
| } |
| auto child_value = PyObject_GetAttr(target, name_obj.get()); |
| if (child_value == nullptr) { |
| // If child_value is NULL, then the error has been set to an AttributeError, so must be |
| // cleared. Otherwise an exception will be thrown. |
| PyErr_Clear(); |
| Py_RETURN_NONE; |
| } |
| return child_value; |
| } |
| } // namespace |
| |
| bool ObjectConverter::HandleNone(const ::fidl_codec::Type* type) { |
| if (obj_ != Py_None) { |
| return false; |
| } |
| if (!type->Nullable()) { |
| PyErr_Format(PyExc_TypeError, "Converting None to non-nullable FIDL value: %s", |
| type->ToString().c_str()); |
| } else { |
| result_ = std::make_unique<::fidl_codec::NullValue>(); |
| } |
| return true; |
| } |
| |
| void ObjectConverter::VisitStringType(const ::fidl_codec::StringType* type) { |
| if (HandleNone(type)) { |
| return; |
| } |
| Py_ssize_t size; |
| const char* str = PyUnicode_AsUTF8AndSize(obj_, &size); |
| if (str) { |
| result_ = std::make_unique<::fidl_codec::StringValue>(std::string(str, size)); |
| } |
| } |
| |
| void ObjectConverter::VisitInteger(bool is_signed) { |
| if (obj_ == Py_None) { |
| PyErr_SetString(PyExc_TypeError, "Received NoneType object. Unable to convert to integer"); |
| return; |
| } |
| if (is_signed) { |
| int overflow; |
| auto repr = PyLong_AsLongLongAndOverflow(obj_, &overflow); |
| if (overflow != 0 && repr == -1) { |
| auto repr = fc::abi::utils::Object(PyObject_Repr(obj_)); |
| PyErr_Format(PyExc_OverflowError, "converting \"%s\" to an integer.", |
| PyUnicode_AsUTF8AndSize(repr.get(), nullptr)); |
| return; |
| } |
| if (repr == -1 && PyErr_Occurred()) { |
| return; |
| } |
| bool negate = is_signed && repr < 0; |
| if (negate) { |
| repr = -repr; |
| } |
| result_ = std::make_unique<::fidl_codec::IntegerValue>(static_cast<uint64_t>(repr), negate); |
| } else { |
| auto repr = utils::PyLong_AsU64(obj_); |
| if (repr == utils::MINUS_ONE_U64 && PyErr_Occurred()) { |
| return; |
| } |
| result_ = std::make_unique<::fidl_codec::IntegerValue>(repr, false); |
| } |
| } |
| |
| void ObjectConverter::VisitBoolType(const ::fidl_codec::BoolType* type) { |
| if (!PyObject_IsSubclass(reinterpret_cast<PyObject*>(&PyBool_Type), |
| reinterpret_cast<PyObject*>(Py_TYPE(obj_)))) { |
| PyErr_SetString(PyExc_TypeError, "expected bool type"); |
| return; |
| } |
| result_ = std::make_unique<::fidl_codec::BoolValue>(obj_ == Py_True ? 1 : 0); |
| } |
| |
| void ObjectConverter::VisitEmptyPayloadType(const ::fidl_codec::EmptyPayloadType* type) { |
| if (obj_ != Py_None) { |
| PyErr_SetString(PyExc_TypeError, "expected None for empty payload"); |
| return; |
| } |
| result_ = std::make_unique<::fidl_codec::EmptyPayloadValue>(); |
| } |
| |
| void ObjectConverter::VisitStructType(const ::fidl_codec::StructType* type) { |
| if (HandleNone(type)) { |
| return; |
| } |
| std::function<PyObject*(const std::string&)> get_item = [this](const std::string& name) mutable { |
| return PyObject_GetAttrString(obj_, name.c_str()); |
| }; |
| Py_ssize_t idx = 0; |
| if (PyList_Check(obj_)) { |
| get_item = [this, &idx](const std::string& /*name*/) mutable { |
| auto res = PyList_GetItem(obj_, idx++); |
| Py_XINCREF(res); |
| return res; |
| }; |
| } |
| auto res = std::make_unique<::fidl_codec::StructValue>(type->struct_definition()); |
| for (const auto& member : type->struct_definition().members()) { |
| if (!member) { |
| continue; |
| } |
| auto child_item = fc::abi::utils::Object(get_item(utils::NormalizeMemberName(member->name()))); |
| if (child_item == nullptr) { |
| return; |
| } |
| auto child = ObjectConverter::Convert(child_item.get(), member->type()); |
| if (!child) { |
| return; |
| } |
| res->AddField(member.get(), std::move(child)); |
| } |
| result_ = std::move(res); |
| } |
| |
| void ObjectConverter::VisitTableType(const ::fidl_codec::TableType* type) { |
| auto res = std::make_unique<::fidl_codec::TableValue>(type->table_definition()); |
| for (const auto& member : type->table_definition().members()) { |
| if (!member) { |
| continue; |
| } |
| auto child_value = |
| fc::abi::utils::Object(GetAttr(obj_, utils::NormalizeMemberName(member->name()))); |
| if (child_value == nullptr) { |
| return; |
| } |
| if (child_value == Py_None) { |
| continue; |
| } |
| auto converted = ObjectConverter::Convert(child_value.get(), member->type()); |
| if (converted == nullptr) { |
| return; |
| } |
| res->AddMember(member.get(), std::move(converted)); |
| } |
| result_ = std::move(res); |
| } |
| |
| void ObjectConverter::VisitUnionType(const ::fidl_codec::UnionType* type) { |
| if (HandleNone(type)) { |
| return; |
| } |
| for (const auto& member : type->union_definition().members()) { |
| if (!member) { |
| continue; |
| } |
| auto child_value = |
| fc::abi::utils::Object(GetAttr(obj_, utils::NormalizeMemberName(member->name()))); |
| if (child_value == nullptr) { |
| return; |
| } |
| if (child_value == Py_None) { |
| continue; |
| } |
| auto res = ObjectConverter::Convert(child_value.get(), member->type()); |
| if (res != nullptr) { |
| result_ = std::make_unique<::fidl_codec::UnionValue>(*member, std::move(res)); |
| } |
| return; |
| } |
| PyErr_Format(PyExc_TypeError, "No known union variants found set for '%s' of type: %s", |
| type->Name().c_str(), type->ToString().c_str()); |
| } |
| |
| void ObjectConverter::VisitType(const ::fidl_codec::Type* type) { |
| std::stringstream ss; |
| ::fidl_codec::PrettyPrinter pp(ss, ::fidl_codec::WithoutColors, true, "", 100, false, 0); |
| type->PrettyPrint(pp); |
| PyErr_Format(PyExc_TypeError, "Unknown FIDL type: '%s'. Full type: %s", type->Name().c_str(), |
| ss.str().c_str()); |
| } |
| |
| void ObjectConverter::VisitSequence(const ::fidl_codec::ElementSequenceType* type, |
| std::optional<size_t> count) { |
| if (!count && HandleNone(type)) { |
| return; |
| } |
| |
| if (!PySequence_Check(obj_)) { |
| PyErr_SetString(PyExc_TypeError, "Expected sequence type"); |
| return; |
| } |
| |
| auto size = PySequence_Size(obj_); |
| if (count && static_cast<uint32_t>(size) != *count) { |
| PyErr_Format(PyExc_RuntimeError, "Expected sequence of length %" PRIu32, *count); |
| return; |
| } |
| |
| auto res = std::make_unique<::fidl_codec::VectorValue>(); |
| for (Py_ssize_t i = 0; i < size; ++i) { |
| PyObject* item = PySequence_GetItem(obj_, i); |
| if (!item) { |
| return; |
| } |
| |
| auto converted = ObjectConverter::Convert(item, type->component_type()); |
| if (converted == nullptr) { |
| return; |
| } |
| res->AddValue(std::move(converted)); |
| } |
| result_ = std::move(res); |
| } |
| |
| void ObjectConverter::VisitArrayType(const ::fidl_codec::ArrayType* type) { |
| VisitSequence(type, type->count()); |
| } |
| |
| void ObjectConverter::VisitVectorType(const ::fidl_codec::VectorType* type) { |
| VisitSequence(type, std::nullopt); |
| } |
| |
| void ObjectConverter::VisitUint8Type(const ::fidl_codec::Uint8Type* type) { VisitInteger(false); } |
| |
| void ObjectConverter::VisitUint16Type(const ::fidl_codec::Uint16Type* type) { VisitInteger(false); } |
| |
| void ObjectConverter::VisitUint32Type(const ::fidl_codec::Uint32Type* type) { VisitInteger(false); } |
| |
| void ObjectConverter::VisitUint64Type(const ::fidl_codec::Uint64Type* type) { VisitInteger(false); } |
| |
| void ObjectConverter::VisitInt8Type(const ::fidl_codec::Int8Type* type) { VisitInteger(true); } |
| |
| void ObjectConverter::VisitInt16Type(const ::fidl_codec::Int16Type* type) { VisitInteger(true); } |
| |
| void ObjectConverter::VisitInt32Type(const ::fidl_codec::Int32Type* type) { VisitInteger(true); } |
| |
| void ObjectConverter::VisitInt64Type(const ::fidl_codec::Int64Type* type) { VisitInteger(true); } |
| |
| void ObjectConverter::VisitEnumType(const ::fidl_codec::EnumType* type) { |
| fc::abi::utils::Object abs(PyNumber_Absolute(obj_)); |
| fc::abi::utils::Object strict_obj(GetAttr(obj_, "__strict__")); |
| bool strict = PyBool_Check(strict_obj.get()) && Py_True == strict_obj.get(); |
| bool negative = false; |
| if (!PyObject_RichCompareBool(abs.get(), obj_, Py_EQ)) { |
| negative = true; |
| } |
| auto as_int = utils::PyLong_AsU64(abs.get()); |
| if (as_int == utils::MINUS_ONE_U64 && PyErr_Occurred()) { |
| return; |
| } |
| // If this is non-strict, just encode it as-is and call it a day. |
| if (!strict) { |
| result_ = std::make_unique<::fidl_codec::IntegerValue>(as_int, negative); |
| return; |
| } |
| |
| for (const auto& member : type->enum_definition().members()) { |
| if (member.absolute_value() == as_int && member.negative() == negative) { |
| result_ = |
| std::make_unique<::fidl_codec::IntegerValue>(member.absolute_value(), member.negative()); |
| return; |
| } |
| } |
| PyErr_Format(PyExc_TypeError, "Unexpected enum value for type '%s' == %" PRIu64, |
| type->Name().c_str(), as_int); |
| } |
| |
| void ObjectConverter::VisitBitsType(const ::fidl_codec::BitsType* type) { |
| int overflow; |
| auto repr = PyLong_AsLongLongAndOverflow(obj_, &overflow); |
| if (overflow != 0 && repr == -1) { |
| PyErr_SetString(PyExc_OverflowError, "Overflow while converting PyLong to 64 bit value"); |
| return; |
| } |
| if (repr == -1 && PyErr_Occurred()) { |
| return; |
| } |
| result_ = std::make_unique<::fidl_codec::IntegerValue>(repr, false); |
| } |
| |
| void ObjectConverter::VisitHandleType(const ::fidl_codec::HandleType* type) { |
| if (HandleNone(type)) { |
| return; |
| } |
| // For the time being just assumes the FIDL handle is going to be the raw handle number and |
| // nothing else, so expecting a PyLong. |
| auto handle = utils::PyLong_AsU32(obj_); |
| if (handle == utils::MINUS_ONE_U32 && PyErr_Occurred()) { |
| return; |
| } |
| zx_handle_disposition_t handle_disp = { |
| .operation = ZX_HANDLE_OP_MOVE, |
| .handle = handle, |
| .type = type->ObjectType(), |
| .rights = type->Rights(), |
| .result = ZX_OK, |
| }; |
| result_ = std::make_unique<::fidl_codec::HandleValue>(handle_disp); |
| } |
| |
| void ObjectConverter::VisitFloat32Type(const ::fidl_codec::Float32Type* type) { |
| double res = PyFloat_AsDouble(obj_); |
| if (res == -1.0 && PyErr_Occurred()) { |
| return; |
| } |
| result_ = std::make_unique<::fidl_codec::DoubleValue>(static_cast<float>(res)); |
| } |
| |
| void ObjectConverter::VisitFloat64Type(const ::fidl_codec::Float64Type* type) { |
| double res = PyFloat_AsDouble(obj_); |
| if (res == -1.0 && PyErr_Occurred()) { |
| return; |
| } |
| result_ = std::make_unique<::fidl_codec::DoubleValue>(res); |
| } |
| |
| } // namespace fuchsia_controller::fidl_codec::converter |