1
0
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:
matejcik 2021-09-09 16:53:05 +02:00 committed by Martin Milata
parent 8abcb6f8cc
commit b666895303
24 changed files with 375 additions and 197 deletions

View File

@ -0,0 +1 @@
Errors from protobuf decoding are now more expressive.

View File

@ -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);
}

View File

@ -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

View File

@ -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");

View File

@ -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"

View File

@ -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`

View File

@ -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)
} }
} }

View File

@ -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)
} }
} }
} }

View File

@ -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))
} }
} }

View File

@ -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),
} }
} }
} }

View File

@ -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)
} }
} }
} }

View File

@ -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)
} }
} }

View File

@ -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()
} }
} }

View File

@ -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)
} }
} }
} }

View File

@ -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());
} }
} }

View File

@ -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)
} }

View File

@ -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> {

View File

@ -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)
} }
} }

View 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)
}

View File

@ -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;

View File

@ -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(())

View File

@ -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)
}) })

View File

@ -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")

View File

@ -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):