mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-24 07:18:09 +00:00
feat(core/rust): catch, handle and propagate uPy exceptions
This commit is contained in:
parent
8abcb6f8cc
commit
b666895303
1
core/.changelog.d/1811.changed
Normal file
1
core/.changelog.d/1811.changed
Normal file
@ -0,0 +1 @@
|
|||||||
|
Errors from protobuf decoding are now more expressive.
|
@ -20,7 +20,9 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "memzero.h"
|
#include "memzero.h"
|
||||||
|
#include "py/obj.h"
|
||||||
#include "py/objint.h"
|
#include "py/objint.h"
|
||||||
|
#include "py/objstr.h"
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
|
|
||||||
static bool mpz_as_ll_checked(const mpz_t *i, long long *value) {
|
static bool mpz_as_ll_checked(const mpz_t *i, long long *value) {
|
||||||
@ -74,3 +76,19 @@ mp_obj_t trezor_obj_call_protected(void (*func)(void *), void *arg) {
|
|||||||
return MP_OBJ_FROM_PTR(nlr.ret_val);
|
return MP_OBJ_FROM_PTR(nlr.ret_val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mp_obj_t trezor_obj_str_from_rom_text(const char *str) {
|
||||||
|
// taken from mp_obj_new_exception_msg
|
||||||
|
mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t);
|
||||||
|
if (o_str == NULL) return NULL;
|
||||||
|
|
||||||
|
o_str->base.type = &mp_type_str;
|
||||||
|
o_str->len = strlen(str);
|
||||||
|
o_str->data = (const byte *)str;
|
||||||
|
#if MICROPY_ROM_TEXT_COMPRESSION
|
||||||
|
o_str->hash = 0; // will be computed only if string object is accessed
|
||||||
|
#else
|
||||||
|
o_str->hash = qstr_compute_hash(o_str->data, o_str->len);
|
||||||
|
#endif
|
||||||
|
return MP_OBJ_FROM_PTR(o_str);
|
||||||
|
}
|
||||||
|
@ -79,4 +79,6 @@ bool trezor_obj_get_ll_checked(mp_obj_t obj, long long *value);
|
|||||||
|
|
||||||
mp_obj_t trezor_obj_call_protected(void (*func)(void *), void *arg);
|
mp_obj_t trezor_obj_call_protected(void (*func)(void *), void *arg);
|
||||||
|
|
||||||
|
mp_obj_t trezor_obj_str_from_rom_text(const char *str);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -61,6 +61,7 @@ fn generate_micropython_bindings() {
|
|||||||
.allowlist_function("mp_call_function_n_kw")
|
.allowlist_function("mp_call_function_n_kw")
|
||||||
.allowlist_function("trezor_obj_get_ll_checked")
|
.allowlist_function("trezor_obj_get_ll_checked")
|
||||||
.allowlist_function("trezor_obj_get_ull_checked")
|
.allowlist_function("trezor_obj_get_ull_checked")
|
||||||
|
.allowlist_function("trezor_obj_str_from_rom_text")
|
||||||
// buffer
|
// buffer
|
||||||
.allowlist_function("mp_get_buffer")
|
.allowlist_function("mp_get_buffer")
|
||||||
.allowlist_var("MP_BUFFER_READ")
|
.allowlist_var("MP_BUFFER_READ")
|
||||||
@ -93,9 +94,19 @@ fn generate_micropython_bindings() {
|
|||||||
.allowlist_function("mp_map_init")
|
.allowlist_function("mp_map_init")
|
||||||
.allowlist_function("mp_map_init_fixed_table")
|
.allowlist_function("mp_map_init_fixed_table")
|
||||||
.allowlist_function("mp_map_lookup")
|
.allowlist_function("mp_map_lookup")
|
||||||
// runtime
|
// exceptions
|
||||||
.allowlist_function("mp_raise_ValueError")
|
.allowlist_function("nlr_jump")
|
||||||
|
.allowlist_function("mp_obj_new_exception")
|
||||||
|
.allowlist_function("mp_obj_new_exception_msg")
|
||||||
|
.allowlist_function("mp_obj_new_exception_arg1")
|
||||||
|
.allowlist_function("mp_obj_new_exception_args")
|
||||||
.allowlist_function("trezor_obj_call_protected")
|
.allowlist_function("trezor_obj_call_protected")
|
||||||
|
.allowlist_var("mp_type_AttributeError")
|
||||||
|
.allowlist_var("mp_type_KeyError")
|
||||||
|
.allowlist_var("mp_type_MemoryError")
|
||||||
|
.allowlist_var("mp_type_OverflowError")
|
||||||
|
.allowlist_var("mp_type_ValueError")
|
||||||
|
.allowlist_var("mp_type_TypeError")
|
||||||
// typ
|
// typ
|
||||||
.allowlist_var("mp_type_type");
|
.allowlist_var("mp_type_type");
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#include "py/gc.h"
|
#include "py/gc.h"
|
||||||
|
#include "py/nlr.h"
|
||||||
#include "py/obj.h"
|
#include "py/obj.h"
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
|
|
||||||
|
@ -1,46 +1,60 @@
|
|||||||
use core::convert::Infallible;
|
use core::convert::{Infallible, TryInto};
|
||||||
use core::fmt;
|
|
||||||
|
|
||||||
use cstr_core::CStr;
|
use cstr_core::CStr;
|
||||||
|
|
||||||
|
use crate::micropython::{ffi, obj::Obj, qstr::Qstr};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Missing,
|
TypeError,
|
||||||
OutOfRange,
|
OutOfRange,
|
||||||
InvalidType,
|
MissingKwargs,
|
||||||
NotBuffer,
|
AllocationFailed,
|
||||||
NotInt,
|
CaughtException(Obj),
|
||||||
InvalidOperation,
|
KeyError(Obj),
|
||||||
|
AttributeError(Qstr),
|
||||||
|
ValueError(&'static CStr),
|
||||||
|
ValueErrorParam(&'static CStr, Obj),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn as_cstr(&self) -> &'static CStr {
|
/// Create an exception instance matching the error code.
|
||||||
// SAFETY: Safe because we are passing in \0-terminated literals.
|
/// The result of this call should only be used to immediately raise the
|
||||||
|
/// exception, because the object is not guaranteed to remain intact.
|
||||||
|
/// Micropython might reuse the same space for creating a different
|
||||||
|
/// exception.
|
||||||
|
pub unsafe fn to_obj(self) -> Obj {
|
||||||
unsafe {
|
unsafe {
|
||||||
let cstr = |s: &'static str| CStr::from_bytes_with_nul_unchecked(s.as_bytes());
|
// SAFETY:
|
||||||
|
// - first argument is a reference to a valid exception type
|
||||||
|
// EXCEPTION: Sensibly, `new_exception_*` does not raise.
|
||||||
match self {
|
match self {
|
||||||
Error::Missing => cstr("Missing\0"),
|
Error::TypeError => ffi::mp_obj_new_exception(&ffi::mp_type_TypeError),
|
||||||
Error::OutOfRange => cstr("OutOfRange\0"),
|
Error::OutOfRange => ffi::mp_obj_new_exception(&ffi::mp_type_OverflowError),
|
||||||
Error::InvalidType => cstr("InvalidType\0"),
|
Error::MissingKwargs => ffi::mp_obj_new_exception(&ffi::mp_type_TypeError),
|
||||||
Error::NotBuffer => cstr("NotBuffer\0"),
|
Error::AllocationFailed => ffi::mp_obj_new_exception(&ffi::mp_type_MemoryError),
|
||||||
Error::NotInt => cstr("NotInt\0"),
|
Error::CaughtException(obj) => obj,
|
||||||
Error::InvalidOperation => cstr("InvalidOperation\0"),
|
Error::KeyError(key) => {
|
||||||
|
ffi::mp_obj_new_exception_arg1(&ffi::mp_type_KeyError, key.into())
|
||||||
|
}
|
||||||
|
Error::ValueError(msg) => {
|
||||||
|
ffi::mp_obj_new_exception_msg(&ffi::mp_type_ValueError, msg.as_ptr())
|
||||||
|
}
|
||||||
|
Error::ValueErrorParam(msg, param) => {
|
||||||
|
if let Ok(msg) = msg.try_into() {
|
||||||
|
let args: [Obj; 2] = [msg, param.into()];
|
||||||
|
ffi::mp_obj_new_exception_args(&ffi::mp_type_ValueError, 2, args.as_ptr())
|
||||||
|
} else {
|
||||||
|
ffi::mp_obj_new_exception(&ffi::mp_type_ValueError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Error::AttributeError(attr) => {
|
||||||
|
ffi::mp_obj_new_exception_arg1(&ffi::mp_type_AttributeError, attr.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Error> for &'static CStr {
|
|
||||||
fn from(val: Error) -> Self {
|
|
||||||
val.as_cstr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.as_cstr().fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements a conversion from `core::convert::Infallible` to `Error` to so
|
// Implements a conversion from `core::convert::Infallible` to `Error` to so
|
||||||
// that code generic over `TryFrom` can work with values covered by the blanket
|
// that code generic over `TryFrom` can work with values covered by the blanket
|
||||||
// impl for `Into`: `https://doc.rust-lang.org/std/convert/enum.Infallible.html`
|
// impl for `Into`: `https://doc.rust-lang.org/std/convert/enum.Infallible.html`
|
||||||
|
@ -112,10 +112,12 @@ fn get_buffer_info(obj: Obj, flags: u32) -> Result<ffi::mp_buffer_info_t, Error>
|
|||||||
// `bufinfo.buf` contains a pointer to data of `bufinfo.len` bytes. Later
|
// `bufinfo.buf` contains a pointer to data of `bufinfo.len` bytes. Later
|
||||||
// we consider this data either GC-allocated or effectively `'static`, embedding
|
// we consider this data either GC-allocated or effectively `'static`, embedding
|
||||||
// them in `Buffer`/`BufferMut`.
|
// them in `Buffer`/`BufferMut`.
|
||||||
|
// EXCEPTION: Does not raise for Micropython's builtin types, and we don't
|
||||||
|
// implement custom buffer protocols.
|
||||||
if unsafe { ffi::mp_get_buffer(obj, &mut bufinfo, flags as _) } {
|
if unsafe { ffi::mp_get_buffer(obj, &mut bufinfo, flags as _) } {
|
||||||
Ok(bufinfo)
|
Ok(bufinfo)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::NotBuffer)
|
Err(Error::TypeError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use core::convert::TryFrom;
|
|||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
use super::{ffi, gc::Gc, map::Map, obj::Obj};
|
use super::{ffi, gc::Gc, map::Map, obj::Obj, runtime::catch_exception};
|
||||||
|
|
||||||
/// Insides of the MicroPython `dict` object.
|
/// Insides of the MicroPython `dict` object.
|
||||||
pub type Dict = ffi::mp_obj_dict_t;
|
pub type Dict = ffi::mp_obj_dict_t;
|
||||||
@ -10,13 +10,14 @@ pub type Dict = ffi::mp_obj_dict_t;
|
|||||||
impl Dict {
|
impl Dict {
|
||||||
/// Allocate a new dictionary on the GC heap, empty, but with a capacity of
|
/// Allocate a new dictionary on the GC heap, empty, but with a capacity of
|
||||||
/// `capacity` items.
|
/// `capacity` items.
|
||||||
pub fn alloc_with_capacity(capacity: usize) -> Gc<Self> {
|
pub fn alloc_with_capacity(capacity: usize) -> Result<Gc<Self>, Error> {
|
||||||
unsafe {
|
catch_exception(|| unsafe {
|
||||||
// SAFETY: We expect that `ffi::mp_obj_new_dict` either returns a GC-allocated
|
// SAFETY: We expect that `ffi::mp_obj_new_dict` either returns a GC-allocated
|
||||||
// pointer to `ffi::mp_obj_dict_t` or raises (i.e. on allocation failure).
|
// pointer to `ffi::mp_obj_dict_t` or raises.
|
||||||
|
// EXCEPTION: Will raise if allocation fails.
|
||||||
let ptr = ffi::mp_obj_new_dict(capacity);
|
let ptr = ffi::mp_obj_new_dict(capacity);
|
||||||
Gc::from_raw(ptr.as_ptr().cast())
|
Gc::from_raw(ptr.as_ptr().cast())
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a dictionary definition by taking ownership of given [`Map`].
|
/// Constructs a dictionary definition by taking ownership of given [`Map`].
|
||||||
@ -61,7 +62,7 @@ impl TryFrom<Obj> for Gc<Dict> {
|
|||||||
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
|
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
|
||||||
Ok(this)
|
Ok(this)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InvalidType)
|
Err(Error::TypeError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ use core::{
|
|||||||
ptr::{self, NonNull},
|
ptr::{self, NonNull},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
|
|
||||||
/// A pointer type for values on the garbage-collected heap.
|
/// A pointer type for values on the garbage-collected heap.
|
||||||
@ -15,7 +17,7 @@ pub struct Gc<T: ?Sized>(NonNull<T>);
|
|||||||
impl<T> Gc<T> {
|
impl<T> Gc<T> {
|
||||||
/// Allocate memory on the heap managed by the MicroPython garbage collector
|
/// Allocate memory on the heap managed by the MicroPython garbage collector
|
||||||
/// and then place `v` into it. `v` will _not_ get its destructor called.
|
/// and then place `v` into it. `v` will _not_ get its destructor called.
|
||||||
pub fn new(v: T) -> Self {
|
pub fn new(v: T) -> Result<Self, Error> {
|
||||||
let layout = Layout::for_value(&v);
|
let layout = Layout::for_value(&v);
|
||||||
// TODO: Assert that `layout.align()` is the same as the GC alignment.
|
// TODO: Assert that `layout.align()` is the same as the GC alignment.
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
@ -23,10 +25,15 @@ impl<T> Gc<T> {
|
|||||||
// not support custom alignment.
|
// not support custom alignment.
|
||||||
// - `ptr` is guaranteed to stay valid as long as it's reachable from the stack
|
// - `ptr` is guaranteed to stay valid as long as it's reachable from the stack
|
||||||
// or the MicroPython heap.
|
// or the MicroPython heap.
|
||||||
|
// EXCEPTION: Returns null instead of raising.
|
||||||
unsafe {
|
unsafe {
|
||||||
let raw = ffi::gc_alloc(layout.size(), 0).cast();
|
let raw = ffi::gc_alloc(layout.size(), 0);
|
||||||
ptr::write(raw, v);
|
if raw.is_null() {
|
||||||
Self::from_raw(raw)
|
return Err(Error::AllocationFailed);
|
||||||
|
}
|
||||||
|
let typed = raw.cast();
|
||||||
|
ptr::write(typed, v);
|
||||||
|
Ok(Self::from_raw(typed))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ use crate::error::Error;
|
|||||||
use crate::micropython::obj::Obj;
|
use crate::micropython::obj::Obj;
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
|
use super::runtime::catch_exception;
|
||||||
|
|
||||||
pub struct IterBuf {
|
pub struct IterBuf {
|
||||||
iter_buf: ffi::mp_obj_iter_buf_t,
|
iter_buf: ffi::mp_obj_iter_buf_t,
|
||||||
@ -26,6 +27,7 @@ pub struct Iter<'a> {
|
|||||||
iter: Obj,
|
iter: Obj,
|
||||||
iter_buf: &'a mut IterBuf,
|
iter_buf: &'a mut IterBuf,
|
||||||
finished: bool,
|
finished: bool,
|
||||||
|
caught_exception: Obj,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iter<'a> {
|
impl<'a> Iter<'a> {
|
||||||
@ -35,16 +37,24 @@ impl<'a> Iter<'a> {
|
|||||||
// uses memory from the passed `iter_buf`. We maintain this invariant by
|
// uses memory from the passed `iter_buf`. We maintain this invariant by
|
||||||
// taking a mut ref to `IterBuf` and tying it to the lifetime of returned
|
// taking a mut ref to `IterBuf` and tying it to the lifetime of returned
|
||||||
// `Iter`.
|
// `Iter`.
|
||||||
// - Raises if `o` is not iterable and in other cases as well.
|
|
||||||
// - Returned obj is referencing into `iter_buf`.
|
// - Returned obj is referencing into `iter_buf`.
|
||||||
// TODO: Use a fn that doesn't raise on non-iterable object.
|
// EXCEPTION: Raises if `o` is not iterable and possibly in other cases too.
|
||||||
let iter = unsafe { ffi::mp_getiter(o, &mut iter_buf.iter_buf) };
|
let iter = catch_exception(|| unsafe { ffi::mp_getiter(o, &mut iter_buf.iter_buf) })?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
iter,
|
iter,
|
||||||
iter_buf,
|
iter_buf,
|
||||||
finished: false,
|
finished: false,
|
||||||
|
caught_exception: Obj::const_null(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn error(&self) -> Option<Error> {
|
||||||
|
if self.caught_exception.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Error::CaughtException(self.caught_exception))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for Iter<'a> {
|
impl<'a> Iterator for Iter<'a> {
|
||||||
@ -57,13 +67,19 @@ impl<'a> Iterator for Iter<'a> {
|
|||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - We assume that `mp_iternext` returns objects without any lifetime
|
// - We assume that `mp_iternext` returns objects without any lifetime
|
||||||
// invariants, i.e. heap-allocated, unlike `mp_getiter`.
|
// invariants, i.e. heap-allocated, unlike `mp_getiter`.
|
||||||
// - Can raise.
|
// EXCEPTION: Can raise from the underlying iterator.
|
||||||
let item = unsafe { ffi::mp_iternext(self.iter) };
|
let item = catch_exception(|| unsafe { ffi::mp_iternext(self.iter) });
|
||||||
if item == Obj::const_stop_iteration() {
|
match item {
|
||||||
self.finished = true;
|
Err(Error::CaughtException(exc)) => {
|
||||||
None
|
self.caught_exception = exc;
|
||||||
} else {
|
None
|
||||||
Some(item)
|
}
|
||||||
|
Err(_) => panic!("unexpected error"),
|
||||||
|
Ok(item) if item == Obj::const_stop_iteration() => {
|
||||||
|
self.finished = true;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Ok(item) => Some(item),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,25 +2,29 @@ use core::convert::TryFrom;
|
|||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
use super::{ffi, gc::Gc, obj::Obj};
|
use super::{ffi, gc::Gc, obj::Obj, runtime::catch_exception};
|
||||||
|
|
||||||
pub type List = ffi::mp_obj_list_t;
|
pub type List = ffi::mp_obj_list_t;
|
||||||
|
|
||||||
impl List {
|
impl List {
|
||||||
pub fn alloc(values: &[Obj]) -> Gc<Self> {
|
pub fn alloc(values: &[Obj]) -> Result<Gc<Self>, Error> {
|
||||||
// SAFETY: Although `values` are copied into the new list and not mutated,
|
// SAFETY: Although `values` are copied into the new list and not mutated,
|
||||||
// `mp_obj_new_list` is taking them through a mut pointer.
|
// `mp_obj_new_list` is taking them through a mut pointer.
|
||||||
unsafe {
|
// EXCEPTION: Will raise if allocation fails.
|
||||||
|
catch_exception(|| unsafe {
|
||||||
let list = ffi::mp_obj_new_list(values.len(), values.as_ptr() as *mut Obj);
|
let list = ffi::mp_obj_new_list(values.len(), values.as_ptr() as *mut Obj);
|
||||||
Gc::from_raw(list.as_ptr().cast())
|
Gc::from_raw(list.as_ptr().cast())
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append(&mut self, value: Obj) {
|
pub fn append(&mut self, value: Obj) -> Result<(), Error> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = self as *mut Self;
|
let ptr = self as *mut Self;
|
||||||
let list = Obj::from_ptr(ptr.cast());
|
let list = Obj::from_ptr(ptr.cast());
|
||||||
ffi::mp_obj_list_append(list, value);
|
// EXCEPTION: Will raise if allocation fails.
|
||||||
|
catch_exception(|| {
|
||||||
|
ffi::mp_obj_list_append(list, value);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,7 +48,7 @@ impl TryFrom<Obj> for Gc<List> {
|
|||||||
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
|
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
|
||||||
Ok(this)
|
Ok(this)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InvalidType)
|
Err(Error::TypeError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use crate::{
|
|||||||
micropython::{obj::Obj, qstr::Qstr},
|
micropython::{obj::Obj, qstr::Qstr},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::ffi;
|
use super::{ffi, runtime::catch_exception};
|
||||||
|
|
||||||
pub type Map = ffi::mp_map_t;
|
pub type Map = ffi::mp_map_t;
|
||||||
pub type MapElem = ffi::mp_map_elem_t;
|
pub type MapElem = ffi::mp_map_elem_t;
|
||||||
@ -42,20 +42,24 @@ impl Map {
|
|||||||
impl Map {
|
impl Map {
|
||||||
pub fn from_fixed<'a>(table: &'a [MapElem]) -> MapRef<'a> {
|
pub fn from_fixed<'a>(table: &'a [MapElem]) -> MapRef<'a> {
|
||||||
let mut map = MaybeUninit::uninit();
|
let mut map = MaybeUninit::uninit();
|
||||||
// SAFETY: `mp_map_init` completely initializes all fields of `map`.
|
// SAFETY: `mp_map_init_fixed_table` completely initializes all fields of `map`.
|
||||||
unsafe {
|
unsafe {
|
||||||
|
// EXCEPTION: Does not raise.
|
||||||
ffi::mp_map_init_fixed_table(map.as_mut_ptr(), table.len(), table.as_ptr().cast());
|
ffi::mp_map_init_fixed_table(map.as_mut_ptr(), table.len(), table.as_ptr().cast());
|
||||||
MapRef::new(map.assume_init())
|
MapRef::new(map.assume_init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_capacity(capacity: usize) -> Self {
|
pub fn with_capacity(capacity: usize) -> Result<Self, Error> {
|
||||||
let mut map = MaybeUninit::uninit();
|
let mut map = MaybeUninit::uninit();
|
||||||
// SAFETY: `mp_map_init` completely initializes all fields of `map`, allocating
|
// SAFETY: `mp_map_init` completely initializes all fields of `map`, allocating
|
||||||
// the backing storage for `capacity` items on the heap.
|
// the backing storage for `capacity` items on the heap.
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::mp_map_init(map.as_mut_ptr(), capacity);
|
// EXCEPTION: Will raise if allocation fails.
|
||||||
map.assume_init()
|
catch_exception(|| {
|
||||||
|
ffi::mp_map_init(map.as_mut_ptr(), capacity);
|
||||||
|
})?;
|
||||||
|
Ok(map.assume_init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,18 +90,19 @@ impl Map {
|
|||||||
// cast to mut ptr is therefore safe.
|
// cast to mut ptr is therefore safe.
|
||||||
unsafe {
|
unsafe {
|
||||||
let map = self as *const Self as *mut Self;
|
let map = self as *const Self as *mut Self;
|
||||||
|
// EXCEPTION: Does not raise for MP_MAP_LOOKUP.
|
||||||
let elem = ffi::mp_map_lookup(map, index, ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP)
|
let elem = ffi::mp_map_lookup(map, index, ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(Error::Missing)?;
|
.ok_or(Error::KeyError(index))?;
|
||||||
Ok(elem.value)
|
Ok(elem.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&mut self, index: impl Into<Obj>, value: impl Into<Obj>) {
|
pub fn set(&mut self, index: impl Into<Obj>, value: impl Into<Obj>) -> Result<(), Error> {
|
||||||
self.set_obj(index.into(), value.into())
|
self.set_obj(index.into(), value.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_obj(&mut self, index: Obj, value: Obj) {
|
pub fn set_obj(&mut self, index: Obj, value: Obj) -> Result<(), Error> {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - `mp_map_lookup` with `_mp_map_lookup_kind_t_MP_MAP_LOOKUP_ADD_IF_NOT_FOUND
|
// - `mp_map_lookup` with `_mp_map_lookup_kind_t_MP_MAP_LOOKUP_ADD_IF_NOT_FOUND
|
||||||
// returns a pointer to a `mp_map_elem_t` value with a lifetime valid for the
|
// returns a pointer to a `mp_map_elem_t` value with a lifetime valid for the
|
||||||
@ -107,15 +112,19 @@ impl Map {
|
|||||||
// replace it.
|
// replace it.
|
||||||
unsafe {
|
unsafe {
|
||||||
let map = self as *mut Self;
|
let map = self as *mut Self;
|
||||||
let elem = ffi::mp_map_lookup(
|
// EXCEPTION: Will raise if allocation fails.
|
||||||
map,
|
let elem = catch_exception(|| {
|
||||||
index,
|
ffi::mp_map_lookup(
|
||||||
ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP_ADD_IF_NOT_FOUND,
|
map,
|
||||||
)
|
index,
|
||||||
|
ffi::_mp_map_lookup_kind_t_MP_MAP_LOOKUP_ADD_IF_NOT_FOUND,
|
||||||
|
)
|
||||||
|
})?
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
elem.value = value;
|
elem.value = value;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, index: impl Into<Obj>) {
|
pub fn delete(&mut self, index: impl Into<Obj>) {
|
||||||
@ -125,6 +134,7 @@ impl Map {
|
|||||||
pub fn delete_obj(&mut self, index: Obj) {
|
pub fn delete_obj(&mut self, index: Obj) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let map = self as *mut Self;
|
let map = self as *mut Self;
|
||||||
|
// EXCEPTION: Does not raise for MP_MAP_LOOKUP_REMOVE_IF_FOUND.
|
||||||
ffi::mp_map_lookup(
|
ffi::mp_map_lookup(
|
||||||
map,
|
map,
|
||||||
index,
|
index,
|
||||||
@ -134,9 +144,9 @@ impl Map {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Map {
|
impl Map {
|
||||||
fn clone(&self) -> Self {
|
pub fn try_clone(&self) -> Result<Self, Error> {
|
||||||
let mut map = Self::with_capacity(self.len());
|
let mut map = Self::with_capacity(self.len())?;
|
||||||
unsafe {
|
unsafe {
|
||||||
ptr::copy_nonoverlapping(self.table, map.table, self.len());
|
ptr::copy_nonoverlapping(self.table, map.table, self.len());
|
||||||
}
|
}
|
||||||
@ -144,13 +154,7 @@ impl Clone for Map {
|
|||||||
map.set_all_keys_are_qstrs(self.all_keys_are_qstrs());
|
map.set_all_keys_are_qstrs(self.all_keys_are_qstrs());
|
||||||
map.set_is_ordered(self.is_ordered());
|
map.set_is_ordered(self.is_ordered());
|
||||||
map.set_is_fixed(0);
|
map.set_is_fixed(0);
|
||||||
map
|
Ok(map)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Map {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::with_capacity(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
use core::convert::{TryFrom, TryInto};
|
use core::convert::{TryFrom, TryInto};
|
||||||
use core::num::TryFromIntError;
|
use core::num::TryFromIntError;
|
||||||
|
|
||||||
|
use cstr_core::CStr;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
use super::ffi;
|
use super::ffi;
|
||||||
|
use super::runtime::catch_exception;
|
||||||
|
|
||||||
pub type Obj = ffi::mp_obj_t;
|
pub type Obj = ffi::mp_obj_t;
|
||||||
pub type ObjBase = ffi::mp_obj_base_t;
|
pub type ObjBase = ffi::mp_obj_base_t;
|
||||||
@ -99,11 +102,13 @@ impl Obj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Obj {
|
impl Obj {
|
||||||
pub fn call_with_n_args(self, args: &[Obj]) -> Obj {
|
pub fn call_with_n_args(self, args: &[Obj]) -> Result<Obj, Error> {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - Can call python code and therefore raise.
|
|
||||||
// - Each of `args` has no lifetime bounds.
|
// - Each of `args` has no lifetime bounds.
|
||||||
unsafe { ffi::mp_call_function_n_kw(self, args.len(), 0, args.as_ptr()) }
|
// EXCEPTION: Calls Python code so can raise arbitrarily.
|
||||||
|
catch_exception(|| unsafe {
|
||||||
|
ffi::mp_call_function_n_kw(self, args.len(), 0, args.as_ptr())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,8 +122,9 @@ impl TryFrom<Obj> for bool {
|
|||||||
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
|
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
|
||||||
// TODO: Avoid type casts on the Python side.
|
// TODO: Avoid type casts on the Python side.
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - Can call python code and therefore raise.
|
// - `obj` can be anything uPy understands.
|
||||||
if unsafe { ffi::mp_obj_is_true(obj) } {
|
// EXCEPTION: Can call Python code (on custom instances) and therefore raise.
|
||||||
|
if catch_exception(|| unsafe { ffi::mp_obj_is_true(obj) })? {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else {
|
} else {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
@ -134,12 +140,15 @@ impl TryFrom<Obj> for i32 {
|
|||||||
|
|
||||||
// TODO: Avoid type casts on the Python side.
|
// TODO: Avoid type casts on the Python side.
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - Can raise if `obj` is int but cannot fit into `cty::mp_int_t`.
|
// - `int` is a mutable pointer of the right type.
|
||||||
if unsafe { ffi::mp_obj_get_int_maybe(obj.as_ptr(), &mut int) } {
|
// - `obj` can be anything uPy understands.
|
||||||
let int = int.try_into()?;
|
// EXCEPTION: Can raise if `obj` is int but cannot fit into `cty::mp_int_t`.
|
||||||
Ok(int)
|
let result =
|
||||||
|
catch_exception(|| unsafe { ffi::mp_obj_get_int_maybe(obj.as_ptr(), &mut int) })?;
|
||||||
|
if result {
|
||||||
|
Ok(int.try_into()?)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::NotInt)
|
Err(Error::TypeError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,10 +159,14 @@ impl TryFrom<Obj> for i64 {
|
|||||||
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
|
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
|
||||||
let mut ll: cty::c_longlong = 0;
|
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) } {
|
if unsafe { ffi::trezor_obj_get_ll_checked(obj, &mut ll) } {
|
||||||
Ok(ll)
|
Ok(ll)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::NotInt)
|
Err(Error::TypeError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,76 +185,104 @@ impl From<bool> for Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i32> for Obj {
|
impl TryFrom<i32> for Obj {
|
||||||
fn from(val: i32) -> Self {
|
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
|
// `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
|
// primarily target 32-bit architecture, and therefore keep the primary signed
|
||||||
// conversion type as `i32`, but convert through `into()` if the types differ.
|
// conversion type as `i32`, but convert through `into()` if the types differ.
|
||||||
|
|
||||||
// SAFETY:
|
// EXCEPTION: Can raise if `val` is larger than smallint and allocation fails.
|
||||||
// - Can raise if allocation fails.
|
catch_exception(|| unsafe { ffi::mp_obj_new_int(val.into()) })
|
||||||
unsafe { ffi::mp_obj_new_int(val.into()) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i64> for Obj {
|
impl TryFrom<i64> for Obj {
|
||||||
fn from(val: i64) -> Self {
|
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,
|
// 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.
|
// we try to go through `mp_obj_new_int` first.
|
||||||
match i32::try_from(val) {
|
match i32::try_from(val) {
|
||||||
Ok(smaller_val) => smaller_val.into(),
|
Ok(smaller_val) => smaller_val.try_into(),
|
||||||
// SAFETY:
|
// EXCEPTION: Will raise if allocation fails.
|
||||||
// - Can raise if allocation fails.
|
Err(_) => catch_exception(|| unsafe { ffi::mp_obj_new_int_from_ll(val) }),
|
||||||
Err(_) => unsafe { ffi::mp_obj_new_int_from_ll(val) },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u32> for Obj {
|
impl TryFrom<u32> for Obj {
|
||||||
fn from(val: u32) -> Self {
|
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
|
// `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
|
// word-sized. We primarily target 32-bit architecture, and therefore keep
|
||||||
// the primary unsigned conversion type as `u32`, but convert through `into()`
|
// the primary unsigned conversion type as `u32`, but convert through `into()`
|
||||||
// if the types differ.
|
// if the types differ.
|
||||||
|
|
||||||
// SAFETY:
|
// EXCEPTION: Can raise if `val` is larger than smallint and allocation fails.
|
||||||
// - Can raise if allocation fails.
|
catch_exception(|| unsafe { ffi::mp_obj_new_int_from_uint(val.into()) })
|
||||||
unsafe { ffi::mp_obj_new_int_from_uint(val.into()) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u64> for Obj {
|
impl TryFrom<u64> for Obj {
|
||||||
fn from(val: u64) -> Self {
|
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
|
// 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.
|
// small-int, we try to go through `mp_obj_new_int_from_uint` first.
|
||||||
match u32::try_from(val) {
|
match u32::try_from(val) {
|
||||||
Ok(smaller_val) => smaller_val.into(),
|
Ok(smaller_val) => smaller_val.try_into(),
|
||||||
// SAFETY:
|
// EXCEPTION: Will raise if allocation fails.
|
||||||
// - Can raise if allocation fails.
|
Err(_) => catch_exception(|| unsafe { ffi::mp_obj_new_int_from_ull(val) }),
|
||||||
Err(_) => unsafe { ffi::mp_obj_new_int_from_ull(val) },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Byte slices are converted into `bytes` MicroPython objects, by allocating
|
/// Byte slices are converted into `bytes` MicroPython objects, by allocating
|
||||||
/// new space on the heap and copying.
|
/// new space on the heap and copying.
|
||||||
impl From<&[u8]> for Obj {
|
impl TryFrom<&[u8]> for Obj {
|
||||||
fn from(val: &[u8]) -> Self {
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(val: &[u8]) -> Result<Self, Self::Error> {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - Can raise if allocation fails.
|
// - Should work with any data
|
||||||
unsafe { ffi::mp_obj_new_bytes(val.as_ptr(), val.len()) }
|
// 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
|
/// 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
|
/// already interned will turn up as QSTRs, strings not found in the QSTR pool
|
||||||
/// will be allocated on the heap and copied.
|
/// will be allocated on the heap and copied.
|
||||||
impl From<&str> for Obj {
|
impl TryFrom<&str> for Obj {
|
||||||
fn from(val: &str) -> Self {
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(val: &str) -> Result<Self, Self::Error> {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - Can raise if allocation fails.
|
|
||||||
// - `str` is guaranteed to be UTF-8.
|
// - `str` is guaranteed to be UTF-8.
|
||||||
unsafe { ffi::mp_obj_new_str(val.as_ptr().cast(), val.len()) }
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,20 +292,24 @@ impl From<&str> for Obj {
|
|||||||
|
|
||||||
impl From<u8> for Obj {
|
impl From<u8> for Obj {
|
||||||
fn from(val: u8) -> Self {
|
fn from(val: u8) -> Self {
|
||||||
u32::from(val).into()
|
// u8 will fit into smallint so no error should happen here
|
||||||
|
u32::from(val).try_into().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u16> for Obj {
|
impl From<u16> for Obj {
|
||||||
fn from(val: u16) -> Self {
|
fn from(val: u16) -> Self {
|
||||||
u32::from(val).into()
|
// u16 will fit into smallint so no error should happen here
|
||||||
|
u32::from(val).try_into().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<usize> for Obj {
|
impl TryFrom<usize> for Obj {
|
||||||
fn from(val: usize) -> Self {
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(val: usize) -> Result<Self, Self::Error> {
|
||||||
// Willingly truncate the bits on 128-bit architectures.
|
// Willingly truncate the bits on 128-bit architectures.
|
||||||
(val as u64).into()
|
(val as u64).try_into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ impl TryFrom<Obj> for Qstr {
|
|||||||
if value.is_qstr() {
|
if value.is_qstr() {
|
||||||
Ok(Self::from_obj_bits(value.as_bits()))
|
Ok(Self::from_obj_bits(value.as_bits()))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InvalidType)
|
Err(Error::TypeError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
|
|
||||||
use cstr_core::CStr;
|
use crate::error::Error;
|
||||||
|
|
||||||
use super::{ffi, obj::Obj};
|
use super::{ffi, obj::Obj};
|
||||||
|
|
||||||
pub fn raise_value_error(msg: &'static CStr) -> ! {
|
/// Raise a micropython exception via NLR jump.
|
||||||
|
/// Jumps directly out of the context without running any destructors,
|
||||||
|
/// finalizers, etc. This is very likely to break a lot of Rust's assumptions.
|
||||||
|
/// Should only be called in places where you really don't care anymore.
|
||||||
|
pub unsafe fn raise_exception(err: Error) -> ! {
|
||||||
unsafe {
|
unsafe {
|
||||||
ffi::mp_raise_ValueError(msg.as_ptr());
|
// SAFETY:
|
||||||
|
// - argument must be an exception instance
|
||||||
|
// (err.to_obj() should return the right thing)
|
||||||
|
ffi::nlr_jump(err.to_obj().as_ptr());
|
||||||
}
|
}
|
||||||
panic!();
|
panic!();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute `func` while catching MicroPython exceptions. Returns `Ok` in the
|
/// Execute `func` while catching MicroPython exceptions. Returns `Ok` in the
|
||||||
/// successful case, and `Err` with the caught `Obj` in case of a raise.
|
/// successful case, and `Err` with the caught `Obj` in case of a raise.
|
||||||
pub fn except<F, T>(mut func: F) -> Result<T, Obj>
|
pub fn catch_exception<F, T>(mut func: F) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
F: FnMut() -> T,
|
F: FnMut() -> T,
|
||||||
{
|
{
|
||||||
@ -29,15 +36,16 @@ where
|
|||||||
let mut wrapper = || {
|
let mut wrapper = || {
|
||||||
result = MaybeUninit::new(func());
|
result = MaybeUninit::new(func());
|
||||||
};
|
};
|
||||||
// `wrapper` is a closure, and to pass it over the FFI, we split it into a function
|
// `wrapper` is a closure, and to pass it over the FFI, we split it into a
|
||||||
// pointer, and a user-data pointer. `ffi::trezor_obj_call_protected` then calls
|
// function pointer, and a user-data pointer.
|
||||||
// the `callback` with the `argument`.
|
// `ffi::trezor_obj_call_protected` then calls the `callback` with the
|
||||||
|
// `argument`.
|
||||||
let (callback, argument) = split_func_into_callback_and_argument(&mut wrapper);
|
let (callback, argument) = split_func_into_callback_and_argument(&mut wrapper);
|
||||||
let exception = ffi::trezor_obj_call_protected(Some(callback), argument);
|
let exception = ffi::trezor_obj_call_protected(Some(callback), argument);
|
||||||
if exception == Obj::const_none() {
|
if exception == Obj::const_none() {
|
||||||
Ok(result.assume_init())
|
Ok(result.assume_init())
|
||||||
} else {
|
} else {
|
||||||
Err(exception)
|
Err(Error::CaughtException(exception))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,15 +81,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn except_returns_ok_on_no_exception() {
|
fn except_returns_ok_on_no_exception() {
|
||||||
let result = except(|| 1);
|
let result = catch_exception(|| 1);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(result.unwrap(), 1);
|
assert_eq!(result.unwrap(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn except_catches_value_error() {
|
fn except_catches_raised() {
|
||||||
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"msg\0") };
|
let result = catch_exception(|| raise_exception(Error::TypeError));
|
||||||
let result = except(|| raise_value_error(&msg));
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ use crate::{
|
|||||||
util,
|
util,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::error;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
defs::{self, FieldDef, FieldType, MsgDef},
|
defs::{self, FieldDef, FieldType, MsgDef},
|
||||||
obj::{MsgDefObj, MsgObj},
|
obj::{MsgDefObj, MsgObj},
|
||||||
@ -17,8 +19,8 @@ use super::{
|
|||||||
pub extern "C" fn protobuf_type_for_name(name: Obj) -> Obj {
|
pub extern "C" fn protobuf_type_for_name(name: Obj) -> Obj {
|
||||||
util::try_or_raise(|| {
|
util::try_or_raise(|| {
|
||||||
let name = Qstr::try_from(name)?;
|
let name = Qstr::try_from(name)?;
|
||||||
let def = MsgDef::for_name(name.to_u16()).ok_or(Error::Missing)?;
|
let def = MsgDef::for_name(name.to_u16()).ok_or_else(|| Error::KeyError(name.into()))?;
|
||||||
let obj = MsgDefObj::alloc(def).into();
|
let obj = MsgDefObj::alloc(def)?.into();
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -27,8 +29,8 @@ pub extern "C" fn protobuf_type_for_name(name: Obj) -> Obj {
|
|||||||
pub extern "C" fn protobuf_type_for_wire(wire_id: Obj) -> Obj {
|
pub extern "C" fn protobuf_type_for_wire(wire_id: Obj) -> Obj {
|
||||||
util::try_or_raise(|| {
|
util::try_or_raise(|| {
|
||||||
let wire_id = u16::try_from(wire_id)?;
|
let wire_id = u16::try_from(wire_id)?;
|
||||||
let def = MsgDef::for_wire_id(wire_id).ok_or(Error::Missing)?;
|
let def = MsgDef::for_wire_id(wire_id).ok_or_else(|| Error::KeyError(wire_id.into()))?;
|
||||||
let obj = MsgDefObj::alloc(def).into();
|
let obj = MsgDefObj::alloc(def)?.into();
|
||||||
Ok(obj)
|
Ok(obj)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -45,7 +47,7 @@ pub extern "C" fn protobuf_decode(buf: Obj, msg_def: Obj, enable_experimental: O
|
|||||||
// explicitly allowed. Messages can also mark certain fields as
|
// explicitly allowed. Messages can also mark certain fields as
|
||||||
// experimental (not the whole message). This is enforced during the
|
// experimental (not the whole message). This is enforced during the
|
||||||
// decoding.
|
// decoding.
|
||||||
return Err(Error::InvalidType);
|
return Err(error::experimental_not_enabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
let stream = &mut InputStream::new(&buf);
|
let stream = &mut InputStream::new(&buf);
|
||||||
@ -70,7 +72,7 @@ impl Decoder {
|
|||||||
stream: &mut InputStream,
|
stream: &mut InputStream,
|
||||||
msg: &MsgDef,
|
msg: &MsgDef,
|
||||||
) -> Result<Obj, Error> {
|
) -> Result<Obj, Error> {
|
||||||
let mut obj = self.empty_message(msg);
|
let mut obj = self.empty_message(msg)?;
|
||||||
// SAFETY: We assume that `obj` is not aliased here.
|
// SAFETY: We assume that `obj` is not aliased here.
|
||||||
let map = unsafe { Gc::as_mut(&mut obj) }.map_mut();
|
let map = unsafe { Gc::as_mut(&mut obj) }.map_mut();
|
||||||
self.decode_fields_into(stream, msg, map)?;
|
self.decode_fields_into(stream, msg, map)?;
|
||||||
@ -82,11 +84,11 @@ impl Decoder {
|
|||||||
/// Create a new message instance and fill it from `values`, handling the
|
/// Create a new message instance and fill it from `values`, handling the
|
||||||
/// default and required fields correctly.
|
/// default and required fields correctly.
|
||||||
pub fn message_from_values(&self, values: &Map, msg: &MsgDef) -> Result<Obj, Error> {
|
pub fn message_from_values(&self, values: &Map, msg: &MsgDef) -> Result<Obj, Error> {
|
||||||
let mut obj = self.empty_message(msg);
|
let mut obj = self.empty_message(msg)?;
|
||||||
// SAFETY: We assume that `obj` is not aliased here.
|
// SAFETY: We assume that `obj` is not aliased here.
|
||||||
let map = unsafe { Gc::as_mut(&mut obj) }.map_mut();
|
let map = unsafe { Gc::as_mut(&mut obj) }.map_mut();
|
||||||
for elem in values.elems() {
|
for elem in values.elems() {
|
||||||
map.set(elem.key, elem.value);
|
map.set(elem.key, elem.value)?;
|
||||||
}
|
}
|
||||||
self.decode_defaults_into(msg, map)?;
|
self.decode_defaults_into(msg, map)?;
|
||||||
self.assign_required_into(msg, map)?;
|
self.assign_required_into(msg, map)?;
|
||||||
@ -95,7 +97,7 @@ impl Decoder {
|
|||||||
|
|
||||||
/// Allocate the backing message object with enough pre-allocated space for
|
/// Allocate the backing message object with enough pre-allocated space for
|
||||||
/// all fields.
|
/// all fields.
|
||||||
pub fn empty_message(&self, msg: &MsgDef) -> Gc<MsgObj> {
|
pub fn empty_message(&self, msg: &MsgDef) -> Result<Gc<MsgObj>, Error> {
|
||||||
MsgObj::alloc_with_capacity(msg.fields.len(), msg)
|
MsgObj::alloc_with_capacity(msg.fields.len(), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,14 +128,14 @@ impl Decoder {
|
|||||||
// SAFETY: We assume that `list` is not aliased here. This holds for
|
// SAFETY: We assume that `list` is not aliased here. This holds for
|
||||||
// uses in `message_from_stream` and `message_from_values`, because we
|
// uses in `message_from_stream` and `message_from_values`, because we
|
||||||
// start with an empty `Map` and fill with unique lists.
|
// start with an empty `Map` and fill with unique lists.
|
||||||
unsafe { Gc::as_mut(&mut list) }.append(field_value);
|
unsafe { Gc::as_mut(&mut list) }.append(field_value)?;
|
||||||
} else {
|
} else {
|
||||||
let list = List::alloc(&[field_value]);
|
let list = List::alloc(&[field_value])?;
|
||||||
map.set(field_name, list);
|
map.set(field_name, list)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Singular field, assign the value directly.
|
// Singular field, assign the value directly.
|
||||||
map.set(field_name, field_value);
|
map.set(field_name, field_value)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -148,7 +150,7 @@ impl Decoder {
|
|||||||
stream.read(len)?;
|
stream.read(len)?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidType);
|
return Err(error::unknown_field_type());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,7 +171,9 @@ impl Decoder {
|
|||||||
// We need to look to the field descriptor to know how to interpret the value
|
// We need to look to the field descriptor to know how to interpret the value
|
||||||
// after the field tag.
|
// after the field tag.
|
||||||
while let Ok(field_tag) = stream.read_byte() {
|
while let Ok(field_tag) = stream.read_byte() {
|
||||||
let field = msg.field(field_tag).ok_or(Error::Missing)?;
|
let field = msg
|
||||||
|
.field(field_tag)
|
||||||
|
.ok_or_else(|| Error::KeyError(field_tag.into()))?;
|
||||||
let field_name = Qstr::from(field.name);
|
let field_name = Qstr::from(field.name);
|
||||||
if map.contains_key(field_name) {
|
if map.contains_key(field_name) {
|
||||||
// Field already has a value assigned, skip it.
|
// Field already has a value assigned, skip it.
|
||||||
@ -183,13 +187,13 @@ impl Decoder {
|
|||||||
stream.read(len)?;
|
stream.read(len)?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::InvalidType);
|
return Err(error::unknown_field_type());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Decode the value and assign it.
|
// Decode the value and assign it.
|
||||||
let field_value = self.decode_field(stream, field)?;
|
let field_value = self.decode_field(stream, field)?;
|
||||||
map.set(field_name, field_value);
|
map.set(field_name, field_value)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -206,14 +210,14 @@ impl Decoder {
|
|||||||
}
|
}
|
||||||
if field.is_required() {
|
if field.is_required() {
|
||||||
// Required field is missing, abort.
|
// Required field is missing, abort.
|
||||||
return Err(Error::Missing);
|
return Err(error::missing_required_field(field_name));
|
||||||
}
|
}
|
||||||
if field.is_repeated() {
|
if field.is_repeated() {
|
||||||
// Optional repeated field, set to a new empty list.
|
// Optional repeated field, set to a new empty list.
|
||||||
map.set(field_name, List::alloc(&[]));
|
map.set(field_name, List::alloc(&[])?)?;
|
||||||
} else {
|
} else {
|
||||||
// Optional singular field, set to None.
|
// Optional singular field, set to None.
|
||||||
map.set(field_name, Obj::const_none());
|
map.set(field_name, Obj::const_none())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -222,14 +226,14 @@ impl Decoder {
|
|||||||
/// Decode one field value from the input stream.
|
/// Decode one field value from the input stream.
|
||||||
fn decode_field(&self, stream: &mut InputStream, field: &FieldDef) -> Result<Obj, Error> {
|
fn decode_field(&self, stream: &mut InputStream, field: &FieldDef) -> Result<Obj, Error> {
|
||||||
if field.is_experimental() && !self.enable_experimental {
|
if field.is_experimental() && !self.enable_experimental {
|
||||||
return Err(Error::InvalidType);
|
return Err(error::experimental_not_enabled());
|
||||||
}
|
}
|
||||||
let num = stream.read_uvarint()?;
|
let num = stream.read_uvarint()?;
|
||||||
match field.get_type() {
|
match field.get_type() {
|
||||||
FieldType::UVarInt => Ok(num.into()),
|
FieldType::UVarInt => Ok(num.try_into()?),
|
||||||
FieldType::SVarInt => {
|
FieldType::SVarInt => {
|
||||||
let signed_int = zigzag::to_signed(num);
|
let signed_int = zigzag::to_signed(num);
|
||||||
Ok(signed_int.into())
|
Ok(signed_int.try_into()?)
|
||||||
}
|
}
|
||||||
FieldType::Bool => {
|
FieldType::Bool => {
|
||||||
let boolean = num != 0;
|
let boolean = num != 0;
|
||||||
@ -238,20 +242,21 @@ impl Decoder {
|
|||||||
FieldType::Bytes => {
|
FieldType::Bytes => {
|
||||||
let buf_len = num.try_into()?;
|
let buf_len = num.try_into()?;
|
||||||
let buf = stream.read(buf_len)?;
|
let buf = stream.read(buf_len)?;
|
||||||
Ok(buf.into())
|
buf.try_into()
|
||||||
}
|
}
|
||||||
FieldType::String => {
|
FieldType::String => {
|
||||||
let buf_len = num.try_into()?;
|
let buf_len = num.try_into()?;
|
||||||
let buf = stream.read(buf_len)?;
|
let buf = stream.read(buf_len)?;
|
||||||
let unicode = str::from_utf8(buf).map_err(|_| Error::InvalidType)?;
|
let unicode =
|
||||||
Ok(unicode.into())
|
str::from_utf8(buf).map_err(|_| error::invalid_value(field.name.into()))?;
|
||||||
|
unicode.try_into()
|
||||||
}
|
}
|
||||||
FieldType::Enum(enum_type) => {
|
FieldType::Enum(enum_type) => {
|
||||||
let enum_val = num.try_into()?;
|
let enum_val = num.try_into()?;
|
||||||
if enum_type.values.contains(&enum_val) {
|
if enum_type.values.contains(&enum_val) {
|
||||||
Ok(enum_val.into())
|
Ok(enum_val.into())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InvalidType)
|
Err(error::invalid_value(field.name.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FieldType::Msg(msg_type) => {
|
FieldType::Msg(msg_type) => {
|
||||||
@ -277,7 +282,7 @@ impl<'a> InputStream<'a> {
|
|||||||
let buf = self
|
let buf = self
|
||||||
.buf
|
.buf
|
||||||
.get(self.pos..self.pos + len)
|
.get(self.pos..self.pos + len)
|
||||||
.ok_or(Error::Missing)?;
|
.ok_or_else(error::end_of_buffer)?;
|
||||||
self.pos += len;
|
self.pos += len;
|
||||||
Ok(Self::new(buf))
|
Ok(Self::new(buf))
|
||||||
}
|
}
|
||||||
@ -286,13 +291,17 @@ impl<'a> InputStream<'a> {
|
|||||||
let buf = self
|
let buf = self
|
||||||
.buf
|
.buf
|
||||||
.get(self.pos..self.pos + len)
|
.get(self.pos..self.pos + len)
|
||||||
.ok_or(Error::Missing)?;
|
.ok_or_else(error::end_of_buffer)?;
|
||||||
self.pos += len;
|
self.pos += len;
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_byte(&mut self) -> Result<u8, Error> {
|
pub fn read_byte(&mut self) -> Result<u8, Error> {
|
||||||
let val = self.buf.get(self.pos).copied().ok_or(Error::Missing)?;
|
let val = self
|
||||||
|
.buf
|
||||||
|
.get(self.pos)
|
||||||
|
.copied()
|
||||||
|
.ok_or_else(error::end_of_buffer)?;
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
Ok(val)
|
Ok(val)
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ pub fn find_name_by_msg_offset(msg_offset: u16) -> Result<u16, Error> {
|
|||||||
.filter(|def| def.msg_offset == msg_offset)
|
.filter(|def| def.msg_offset == msg_offset)
|
||||||
.next()
|
.next()
|
||||||
.map(|def| def.msg_name)
|
.map(|def| def.msg_name)
|
||||||
.ok_or(Error::Missing)
|
.ok_or_else(|| Error::KeyError(msg_offset.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_msg_offset_by_name(msg_name: u16) -> Option<u16> {
|
fn find_msg_offset_by_name(msg_name: u16) -> Option<u16> {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use core::convert::TryFrom;
|
use core::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
@ -15,6 +15,7 @@ use crate::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
defs::{FieldDef, FieldType, MsgDef},
|
defs::{FieldDef, FieldType, MsgDef},
|
||||||
|
error,
|
||||||
obj::MsgObj,
|
obj::MsgObj,
|
||||||
zigzag,
|
zigzag,
|
||||||
};
|
};
|
||||||
@ -28,7 +29,7 @@ pub extern "C" fn protobuf_len(obj: Obj) -> Obj {
|
|||||||
|
|
||||||
Encoder.encode_message(stream, &obj.def(), &obj)?;
|
Encoder.encode_message(stream, &obj.def(), &obj)?;
|
||||||
|
|
||||||
Ok(stream.len.into())
|
Ok(stream.len.try_into()?)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ pub extern "C" fn protobuf_encode(buf: Obj, obj: Obj) -> Obj {
|
|||||||
|
|
||||||
Encoder.encode_message(stream, &obj.def(), &obj)?;
|
Encoder.encode_message(stream, &obj.def(), &obj)?;
|
||||||
|
|
||||||
Ok(stream.len().into())
|
Ok(stream.len().try_into()?)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +221,7 @@ impl<'a> OutputStream for BufferStream<'a> {
|
|||||||
*pos += len;
|
*pos += len;
|
||||||
buf.copy_from_slice(val);
|
buf.copy_from_slice(val);
|
||||||
})
|
})
|
||||||
.ok_or(Error::Missing)
|
.ok_or_else(error::end_of_buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_byte(&mut self, val: u8) -> Result<(), Error> {
|
fn write_byte(&mut self, val: u8) -> Result<(), Error> {
|
||||||
@ -231,6 +232,6 @@ impl<'a> OutputStream for BufferStream<'a> {
|
|||||||
*pos += 1;
|
*pos += 1;
|
||||||
*buf = val;
|
*buf = val;
|
||||||
})
|
})
|
||||||
.ok_or(Error::Missing)
|
.ok_or_else(error::end_of_buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
core/embed/rust/src/protobuf/error.rs
Normal file
32
core/embed/rust/src/protobuf/error.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use cstr_core::CStr;
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::micropython::qstr::Qstr;
|
||||||
|
|
||||||
|
/* XXX const version of from_bytes_with_nul_unchecked is nightly-only */
|
||||||
|
|
||||||
|
pub fn experimental_not_enabled() -> Error {
|
||||||
|
let msg =
|
||||||
|
unsafe { CStr::from_bytes_with_nul_unchecked(b"Experimental features are disabled.\0") };
|
||||||
|
Error::ValueError(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unknown_field_type() -> Error {
|
||||||
|
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"Unknown field type.\0") };
|
||||||
|
Error::ValueError(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn missing_required_field(field: Qstr) -> Error {
|
||||||
|
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"Missing required field\0") };
|
||||||
|
Error::ValueErrorParam(msg, field.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invalid_value(field: Qstr) -> Error {
|
||||||
|
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"Invalid value for field\0") };
|
||||||
|
Error::ValueErrorParam(msg, field.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_of_buffer() -> Error {
|
||||||
|
let msg = unsafe { CStr::from_bytes_with_nul_unchecked(b"End of buffer.\0") };
|
||||||
|
Error::ValueError(msg)
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
mod decode;
|
mod decode;
|
||||||
mod defs;
|
mod defs;
|
||||||
mod encode;
|
mod encode;
|
||||||
|
mod error;
|
||||||
mod obj;
|
mod obj;
|
||||||
mod zigzag;
|
mod zigzag;
|
||||||
|
@ -26,10 +26,10 @@ pub struct MsgObj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MsgObj {
|
impl MsgObj {
|
||||||
pub fn alloc_with_capacity(capacity: usize, msg: &MsgDef) -> Gc<Self> {
|
pub fn alloc_with_capacity(capacity: usize, msg: &MsgDef) -> Result<Gc<Self>, Error> {
|
||||||
Gc::new(Self {
|
Gc::new(Self {
|
||||||
base: Self::obj_type().to_base(),
|
base: Self::obj_type().to_base(),
|
||||||
map: Map::with_capacity(capacity),
|
map: Map::with_capacity(capacity)?,
|
||||||
msg_wire_id: msg.wire_id,
|
msg_wire_id: msg.wire_id,
|
||||||
msg_offset: msg.offset,
|
msg_offset: msg.offset,
|
||||||
})
|
})
|
||||||
@ -79,23 +79,23 @@ impl MsgObj {
|
|||||||
// Conversion to dict. Allocate a new dict object with a copy of our map
|
// Conversion to dict. Allocate a new dict object with a copy of our map
|
||||||
// and return it. This is a bit different from how uPy does it now, because
|
// and return it. This is a bit different from how uPy does it now, because
|
||||||
// we're returning a mutable dict.
|
// we're returning a mutable dict.
|
||||||
Ok(Gc::new(Dict::with_map(self.map.clone())).into())
|
Ok(Gc::new(Dict::with_map(self.map.try_clone()?))?.into())
|
||||||
}
|
}
|
||||||
_ => Err(Error::Missing),
|
_ => Err(Error::AttributeError(attr)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setattr(&mut self, attr: Qstr, value: Obj) -> Result<(), Error> {
|
fn setattr(&mut self, attr: Qstr, value: Obj) -> Result<(), Error> {
|
||||||
if value == Obj::const_null() {
|
if value == Obj::const_null() {
|
||||||
// this would be a delattr
|
// this would be a delattr
|
||||||
return Err(Error::InvalidOperation);
|
return Err(Error::TypeError);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.map.contains_key(attr) {
|
if self.map.contains_key(attr) {
|
||||||
self.map.set(attr, value);
|
self.map.set(attr, value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::Missing)
|
Err(Error::AttributeError(attr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ impl TryFrom<Obj> for Gc<MsgObj> {
|
|||||||
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
|
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
|
||||||
Ok(this)
|
Ok(this)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InvalidType)
|
Err(Error::TypeError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,11 +152,12 @@ pub struct MsgDefObj {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MsgDefObj {
|
impl MsgDefObj {
|
||||||
pub fn alloc(def: MsgDef) -> Gc<Self> {
|
pub fn alloc(def: MsgDef) -> Result<Gc<Self>, Error> {
|
||||||
Gc::new(Self {
|
let this = Gc::new(Self {
|
||||||
base: Self::obj_type().to_base(),
|
base: Self::obj_type().to_base(),
|
||||||
def,
|
def,
|
||||||
})
|
})?;
|
||||||
|
Ok(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn msg(&self) -> &MsgDef {
|
pub fn msg(&self) -> &MsgDef {
|
||||||
@ -193,7 +194,7 @@ impl TryFrom<Obj> for Gc<MsgDefObj> {
|
|||||||
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
|
let this = unsafe { Gc::from_raw(value.as_ptr().cast()) };
|
||||||
Ok(this)
|
Ok(this)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::InvalidType)
|
Err(Error::TypeError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,7 +205,8 @@ unsafe extern "C" fn msg_def_obj_attr(self_in: Obj, attr: ffi::qstr, dest: *mut
|
|||||||
let attr = Qstr::from_u16(attr as _);
|
let attr = Qstr::from_u16(attr as _);
|
||||||
|
|
||||||
if unsafe { dest.read() } != Obj::const_null() {
|
if unsafe { dest.read() } != Obj::const_null() {
|
||||||
return Err(Error::InvalidOperation);
|
// this would be a setattr
|
||||||
|
return Err(Error::TypeError);
|
||||||
}
|
}
|
||||||
|
|
||||||
match attr {
|
match attr {
|
||||||
@ -235,7 +237,7 @@ unsafe extern "C" fn msg_def_obj_attr(self_in: Obj, attr: ffi::qstr, dest: *mut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Missing);
|
return Err(Error::AttributeError(attr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -5,17 +5,17 @@ use crate::{
|
|||||||
micropython::{
|
micropython::{
|
||||||
map::{Map, MapElem},
|
map::{Map, MapElem},
|
||||||
obj::Obj,
|
obj::Obj,
|
||||||
runtime::raise_value_error,
|
runtime::raise_exception,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn try_or_raise<T>(func: impl FnOnce() -> Result<T, Error>) -> T {
|
pub fn try_or_raise<T>(func: impl FnOnce() -> Result<T, Error>) -> T {
|
||||||
func().unwrap_or_else(|err| raise_value_error(err.as_cstr()))
|
func().unwrap_or_else(|err| raise_exception(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_with_kwargs(kwargs: *const Map, func: impl FnOnce(&Map) -> Result<Obj, Error>) -> Obj {
|
pub fn try_with_kwargs(kwargs: *const Map, func: impl FnOnce(&Map) -> Result<Obj, Error>) -> Obj {
|
||||||
try_or_raise(|| {
|
try_or_raise(|| {
|
||||||
let kwargs = unsafe { kwargs.as_ref() }.ok_or(Error::Missing)?;
|
let kwargs = unsafe { kwargs.as_ref() }.ok_or(Error::MissingKwargs)?;
|
||||||
|
|
||||||
func(kwargs)
|
func(kwargs)
|
||||||
})
|
})
|
||||||
@ -33,7 +33,7 @@ pub fn try_with_args_and_kwargs(
|
|||||||
} else {
|
} else {
|
||||||
unsafe { slice::from_raw_parts(args, n_args) }
|
unsafe { slice::from_raw_parts(args, n_args) }
|
||||||
};
|
};
|
||||||
let kwargs = unsafe { kwargs.as_ref() }.ok_or(Error::Missing)?;
|
let kwargs = unsafe { kwargs.as_ref() }.ok_or(Error::MissingKwargs)?;
|
||||||
|
|
||||||
func(args, kwargs)
|
func(args, kwargs)
|
||||||
})
|
})
|
||||||
|
@ -111,7 +111,7 @@ def _wrap_protobuf_load(
|
|||||||
if __debug__:
|
if __debug__:
|
||||||
log.exception(__name__, e)
|
log.exception(__name__, e)
|
||||||
if e.args:
|
if e.args:
|
||||||
raise DataError("Failed to decode message: {}".format(e.args[0]))
|
raise DataError("Failed to decode message: " + " ".join(e.args))
|
||||||
else:
|
else:
|
||||||
raise DataError("Failed to decode message")
|
raise DataError("Failed to decode message")
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ class TestProtobuf(unittest.TestCase):
|
|||||||
self.assertEqual(dump_uvarint(1), b"\x01")
|
self.assertEqual(dump_uvarint(1), b"\x01")
|
||||||
self.assertEqual(dump_uvarint(0xFF), b"\xff\x01")
|
self.assertEqual(dump_uvarint(0xFF), b"\xff\x01")
|
||||||
self.assertEqual(dump_uvarint(123456), b"\xc0\xc4\x07")
|
self.assertEqual(dump_uvarint(123456), b"\xc0\xc4\x07")
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(OverflowError):
|
||||||
dump_uvarint(-1)
|
dump_uvarint(-1)
|
||||||
|
|
||||||
def test_load_uvarint(self):
|
def test_load_uvarint(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user