-
Notifications
You must be signed in to change notification settings - Fork 678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: don't abuse serialization for type erasure #5813
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,17 @@ | ||
#![doc = include_str!("../README.md")] | ||
|
||
use std::fmt::{self, Error, Formatter}; | ||
|
||
use borsh::{BorshDeserialize, BorshSerialize}; | ||
use near_account_id::AccountId; | ||
use near_rpc_error_macro::RpcError; | ||
use serde::{Deserialize, Serialize}; | ||
use std::any::Any; | ||
use std::fmt::{self, Error, Formatter}; | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
#[derive(Debug, PartialEq, Eq)] | ||
pub enum VMError { | ||
FunctionCallError(FunctionCallError), | ||
/// Serialized external error from External trait implementation. | ||
ExternalError(Vec<u8>), | ||
/// Type erased error from `External` trait implementation. | ||
ExternalError(AnyError), | ||
/// An error that is caused by an operation on an inconsistent state. | ||
/// E.g. an integer overflow by using a value from the given context. | ||
InconsistentStateError(InconsistentStateError), | ||
|
@@ -227,12 +227,12 @@ pub enum HostError { | |
AltBn128SerializationError { msg: String }, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq)] | ||
#[derive(Debug, PartialEq)] | ||
pub enum VMLogicError { | ||
/// Errors coming from native Wasm VM. | ||
HostError(HostError), | ||
/// Serialized external error from External trait implementation. | ||
ExternalError(Vec<u8>), | ||
/// Type erased error from `External` trait implementation. | ||
ExternalError(AnyError), | ||
/// An error that is caused by an operation on an inconsistent state. | ||
InconsistentStateError(InconsistentStateError), | ||
} | ||
|
@@ -268,14 +268,14 @@ impl From<PrepareError> for VMError { | |
} | ||
} | ||
|
||
impl From<&VMLogicError> for VMError { | ||
fn from(err: &VMLogicError) -> Self { | ||
impl From<VMLogicError> for VMError { | ||
fn from(err: VMLogicError) -> Self { | ||
match err { | ||
VMLogicError::HostError(h) => { | ||
VMError::FunctionCallError(FunctionCallError::HostError(h.clone())) | ||
VMError::FunctionCallError(FunctionCallError::HostError(h)) | ||
} | ||
VMLogicError::ExternalError(s) => VMError::ExternalError(s.clone()), | ||
VMLogicError::InconsistentStateError(e) => VMError::InconsistentStateError(e.clone()), | ||
VMLogicError::ExternalError(s) => VMError::ExternalError(s), | ||
VMLogicError::InconsistentStateError(e) => VMError::InconsistentStateError(e), | ||
} | ||
} | ||
} | ||
|
@@ -432,27 +432,59 @@ impl std::fmt::Display for HostError { | |
} | ||
} | ||
|
||
pub mod hex_format { | ||
use hex::{decode, encode}; | ||
/// Type-erased error used to shuttle some concrete error coming from `External` | ||
/// through vm-logic. | ||
/// | ||
/// The caller is supposed to downcast this to a concrete error type they should | ||
/// know. This would be just `Box<dyn Any + Eq>` if the latter actually worked. | ||
pub struct AnyError { | ||
any: Box<dyn AnyEq>, | ||
} | ||
|
||
use serde::de; | ||
use serde::{Deserialize, Deserializer, Serializer}; | ||
impl AnyError { | ||
pub fn new<E: Any + Eq + Send + Sync + 'static>(err: E) -> AnyError { | ||
AnyError { any: Box::new(err) } | ||
} | ||
pub fn downcast<E: Any + Eq + Send + Sync + 'static>(self) -> Result<E, ()> { | ||
match self.any.into_any().downcast::<E>() { | ||
Ok(it) => Ok(*it), | ||
Err(_) => Err(()), | ||
} | ||
} | ||
} | ||
|
||
pub fn serialize<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
T: AsRef<[u8]>, | ||
{ | ||
serializer.serialize_str(&encode(data)) | ||
impl PartialEq for AnyError { | ||
fn eq(&self, other: &Self) -> bool { | ||
self.any.any_eq(&*other.any) | ||
} | ||
} | ||
|
||
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
T: From<Vec<u8>>, | ||
{ | ||
let s = String::deserialize(deserializer)?; | ||
decode(&s).map_err(|err| de::Error::custom(err.to_string())).map(Into::into) | ||
impl Eq for AnyError {} | ||
|
||
impl fmt::Debug for AnyError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
fmt::Debug::fmt(self.any.as_any(), f) | ||
} | ||
} | ||
|
||
trait AnyEq: Any + Send + Sync { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this truly need to be using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question! On the one hand, yes, this could use Perhaps more important thing here is practicality -- if I use So, I'd say let's keep this as Any. But thanks for the sugestions -- using Error didn't occure to me! |
||
fn any_eq(&self, rhs: &dyn AnyEq) -> bool; | ||
fn as_any(&self) -> &dyn Any; | ||
fn into_any(self: Box<Self>) -> Box<dyn Any>; | ||
} | ||
|
||
impl<T: Any + Eq + Sized + Send + Sync> AnyEq for T { | ||
fn any_eq(&self, rhs: &dyn AnyEq) -> bool { | ||
match rhs.as_any().downcast_ref::<Self>() { | ||
Some(rhs) => self == rhs, | ||
None => false, | ||
} | ||
} | ||
fn as_any(&self) -> &dyn Any { | ||
&*self | ||
} | ||
fn into_any(self: Box<Self>) -> Box<dyn Any> { | ||
self | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
PartialEq
andEq
impls here don't appear to be used at all. Is there any reason why we need to keep these implementations in place?Comparing errors for equality (as opposed to matching their structure) in general seems like a pretty weird thing to need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If these are dropped, then the whole
AnyEq
thing could be replaced with justBox<dyn std::error::Error + Send + Sync>
fields. Which is pretty common way to do this kind of type erasure in Rust.EDIT: or
Box<dyn Any + Send + Sync>
I guess.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are used in our tests 😭 And yeah, this is also problematic, as it, eg, prevents us from using
io::Error
. I do want to fix that (by rewriting our test), but not in this PR.