You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/embed/rust/src/micropython/obj.rs

413 lines
11 KiB

use core::convert::{TryFrom, TryInto};
use cstr_core::CStr;
use crate::error::Error;
use super::{ffi, runtime::catch_exception};
pub type Obj = ffi::mp_obj_t;
pub type ObjBase = ffi::mp_obj_base_t;
impl PartialEq for Obj {
fn eq(&self, other: &Self) -> bool {
self.as_bits() == other.as_bits()
}
}
impl Eq for Obj {}
impl Obj {
pub const unsafe fn from_ptr(ptr: *mut cty::c_void) -> Self {
Self(ptr)
}
pub const fn as_ptr(self) -> *mut cty::c_void {
self.0
}
pub const unsafe fn from_bits(bits: cty::uintptr_t) -> Self {
Self(bits as _)
}
pub fn as_bits(self) -> cty::uintptr_t {
self.0 as _
}
}
impl Obj {
pub fn is_small_int(self) -> bool {
// micropython/py/obj.h mp_obj_is_small_int
self.as_bits() & 1 != 0
}
pub fn is_qstr(self) -> bool {
// micropython/py/obj.h mp_obj_is_qstr
self.as_bits() & 7 == 2
}
pub fn is_immediate(self) -> bool {
// micropython/py/obj.h mp_obj_is_immediate_obj
self.as_bits() & 7 == 6
}
pub fn is_ptr(self) -> bool {
// micropython/py/obj.h mp_obj_is_obj
self.as_bits() & 3 == 0
}
pub fn is_null(self) -> bool {
// obj == NULL
self.as_bits() == 0
}
}
impl Obj {
pub const fn const_null() -> Self {
// micropython/py/obj.h
// #define MP_OBJ_NULL (MP_OBJ_FROM_PTR((void *)0))
unsafe { Self::from_bits(0) }
}
pub const fn const_stop_iteration() -> Self {
// micropython/py/obj.h
// #define MP_OBJ_STOP_ITERATION (MP_OBJ_FROM_PTR((void *)0))
unsafe { Self::from_bits(0) }
}
pub const fn const_none() -> Self {
// micropython/py/obj.h
// #define mp_const_none MP_OBJ_NEW_IMMEDIATE_OBJ(0)
unsafe { Self::immediate(0) }
}
pub const fn const_false() -> Self {
// micropython/py/obj.h
// #define mp_const_false MP_OBJ_NEW_IMMEDIATE_OBJ(1)
unsafe { Self::immediate(1) }
}
pub const fn const_true() -> Self {
// micropython/py/obj.h
// #define mp_const_true MP_OBJ_NEW_IMMEDIATE_OBJ(3)
unsafe { Self::immediate(3) }
}
const unsafe fn immediate(val: usize) -> Self {
// SAFETY:
// - `val` is in `0..=3` range.
// - MicroPython compiled with `MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A`.
// - MicroPython compiled with `MICROPY_OBJ_IMMEDIATE_OBJS`.
// micropython/py/obj.h #define MP_OBJ_NEW_IMMEDIATE_OBJ(val)
// ((mp_obj_t)(((val) << 3) | 6))
unsafe { Self::from_bits((val << 3) | 6) }
}
}
impl Obj {
pub fn call_with_n_args(self, args: &[Obj]) -> Result<Obj, Error> {
// SAFETY:
// - Each of `args` has no lifetime bounds.
// EXCEPTION: Calls Python code so can raise arbitrarily.
catch_exception(|| unsafe {
ffi::mp_call_function_n_kw(self, args.len(), 0, args.as_ptr())
})
}
}
//
// # Converting `Obj` into plain data.
//
impl TryFrom<Obj> for bool {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
// TODO: Avoid type casts on the Python side.
// SAFETY:
// - `obj` can be anything uPy understands.
// EXCEPTION: Can call Python code (on custom instances) and therefore raise.
let is_true = catch_exception(|| unsafe { ffi::mp_obj_is_true(obj) })?;
Ok(is_true)
}
}
impl TryFrom<Obj> for i32 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let mut int: ffi::mp_int_t = 0;
// TODO: Avoid type casts on the Python side.
// SAFETY:
// - `int` is a mutable pointer of the right type.
// - `obj` can be anything uPy understands.
// EXCEPTION: Can raise if `obj` is int but cannot fit into `cty::mp_int_t`.
let result =
catch_exception(|| unsafe { ffi::mp_obj_get_int_maybe(obj.as_ptr(), &mut int) })?;
if result {
Ok(int.try_into()?)
} else {
Err(Error::TypeError)
}
}
}
impl TryFrom<Obj> for i64 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let mut ll: cty::c_longlong = 0;
// SAFETY:
// - `ll` is a mutable variable of the right type.
// - `obj` can be anything uPy understands.
// EXCEPTION: Does not raise.
if unsafe { ffi::trezor_obj_get_ll_checked(obj, &mut ll) } {
Ok(ll)
} else {
Err(Error::TypeError)
}
}
}
//
// # Converting plain data into `Obj`.
//
impl From<bool> for Obj {
fn from(val: bool) -> Self {
if val {
Obj::const_true()
} else {
Obj::const_false()
}
}
}
impl TryFrom<i32> for Obj {
type Error = Error;
fn try_from(val: i32) -> Result<Self, Self::Error> {
// `mp_obj_new_int` accepts a `mp_int_t` argument, which is word-sized. We
// primarily target 32-bit architecture, and therefore keep the primary signed
// conversion type as `i32`, but convert through `into()` if the types differ.
// EXCEPTION: Can raise if `val` is larger than smallint and allocation fails.
catch_exception(|| unsafe { ffi::mp_obj_new_int(val.into()) })
}
}
impl TryFrom<i64> for Obj {
type Error = Error;
fn try_from(val: i64) -> Result<Self, Self::Error> {
// Because `mp_obj_new_int_from_ll` allocates even if `val` fits into small-int,
// we try to go through `mp_obj_new_int` first.
match i32::try_from(val) {
Ok(smaller_val) => smaller_val.try_into(),
// EXCEPTION: Will raise if allocation fails.
Err(_) => catch_exception(|| unsafe { ffi::mp_obj_new_int_from_ll(val) }),
}
}
}
impl TryFrom<u32> for Obj {
type Error = Error;
fn try_from(val: u32) -> Result<Self, Self::Error> {
// `mp_obj_new_int_from_uint` accepts a `mp_uint_t` argument, which is
// word-sized. We primarily target 32-bit architecture, and therefore keep
// the primary unsigned conversion type as `u32`, but convert through `into()`
// if the types differ.
// EXCEPTION: Can raise if `val` is larger than smallint and allocation fails.
catch_exception(|| unsafe { ffi::mp_obj_new_int_from_uint(val.into()) })
}
}
impl TryFrom<u64> for Obj {
type Error = Error;
fn try_from(val: u64) -> Result<Self, Self::Error> {
// Because `mp_obj_new_int_from_ull` allocates even if `val` fits into
// small-int, we try to go through `mp_obj_new_int_from_uint` first.
match u32::try_from(val) {
Ok(smaller_val) => smaller_val.try_into(),
// EXCEPTION: Will raise if allocation fails.
Err(_) => catch_exception(|| unsafe { ffi::mp_obj_new_int_from_ull(val) }),
}
}
}
/// Byte slices are converted into `bytes` MicroPython objects, by allocating
/// new space on the heap and copying.
impl TryFrom<&[u8]> for Obj {
type Error = Error;
fn try_from(val: &[u8]) -> Result<Self, Self::Error> {
// SAFETY:
// - Should work with any data
// EXCEPTION: Will raise if allocation fails.
catch_exception(|| unsafe { ffi::mp_obj_new_bytes(val.as_ptr(), val.len()) })
}
}
/// String slices are converted into `str` MicroPython objects. Strings that are
/// already interned will turn up as QSTRs, strings not found in the QSTR pool
/// will be allocated on the heap and copied.
impl TryFrom<&str> for Obj {
type Error = Error;
fn try_from(val: &str) -> Result<Self, Self::Error> {
// SAFETY:
// - `str` is guaranteed to be UTF-8.
// EXCEPTION: Will raise if allocation fails.
catch_exception(|| unsafe { ffi::mp_obj_new_str(val.as_ptr().cast(), val.len()) })
}
}
/// ROM null-terminated strings can be converted to `str` MicroPython objects
/// without making a copy on the heap, only allocating the `mp_obj_str_t`
/// struct.
impl TryFrom<&'static CStr> for Obj {
type Error = Error;
fn try_from(val: &'static CStr) -> Result<Self, Self::Error> {
// SAFETY:
// - `CStr` is guaranteed to be null-terminated UTF-8.
// - the argument is static so it will remain valid for the lifetime of result.
let obj = unsafe { ffi::trezor_obj_str_from_rom_text(val.as_ptr()) };
if obj.is_null() {
Err(Error::AllocationFailed)
} else {
Ok(obj)
}
}
}
impl TryFrom<(Obj, Obj)> for Obj {
type Error = Error;
fn try_from(val: (Obj, Obj)) -> Result<Self, Self::Error> {
// SAFETY:
// - Should work with any micropython objects.
// EXCEPTION: Will raise if allocation fails.
let values = [val.0, val.1];
let obj = catch_exception(|| unsafe { ffi::mp_obj_new_tuple(2, values.as_ptr()) })?;
if obj.is_null() {
Err(Error::AllocationFailed)
} else {
Ok(obj)
}
}
}
//
// # Additional conversions based on the methods above.
//
impl From<u8> for Obj {
fn from(val: u8) -> Self {
// `u8` will fit into smallint so no error should happen here.
unwrap!(u32::from(val).try_into())
}
}
impl From<u16> for Obj {
fn from(val: u16) -> Self {
// `u16` will fit into smallint so no error should happen here.
unwrap!(u32::from(val).try_into())
}
}
impl TryFrom<usize> for Obj {
type Error = Error;
fn try_from(val: usize) -> Result<Self, Self::Error> {
// Willingly truncate the bits on 128-bit architectures.
(val as u64).try_into()
}
}
impl TryFrom<Obj> for u8 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let val = i32::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl TryFrom<Obj> for u16 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let val = i32::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl TryFrom<Obj> for u32 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
let val = i64::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl TryFrom<Obj> for u64 {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
// TODO: Support full range.
let val = i64::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl TryFrom<Obj> for usize {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
// TODO: Support full range.
let val = i64::try_from(obj)?;
let this = Self::try_from(val)?;
Ok(this)
}
}
impl<T> From<Option<T>> for Obj
where
T: Into<Obj>,
{
fn from(val: Option<T>) -> Self {
match val {
Some(v) => v.into(),
None => Self::const_none(),
}
}
}
impl Obj {
/// Conversion to Rust types with typed `None`.
pub fn try_into_option<T>(self) -> Result<Option<T>, Error>
where
T: TryFrom<Obj>,
<T as TryFrom<Obj>>::Error: Into<Error>,
{
if self == Obj::const_none() {
return Ok(None);
}
match self.try_into() {
Ok(x) => Ok(Some(x)),
Err(e) => Err(e.into()),
}
}
}