diff --git a/Cargo.lock b/Cargo.lock index 3e9c4bf4..d0334fe3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,7 +257,7 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "candid" -version = "0.9.8" +version = "0.9.9" dependencies = [ "anyhow", "arbitrary", diff --git a/Changelog.md b/Changelog.md index a7b4fbf7..51a20b77 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,10 @@ # Changelog +## Rust 0.9.9 + +* Set different config values for `full_error_message` and `zero_sized_values` for Wasm and non-Wasm target. + ## 2023-09-27 ### Rust 0.9.8 diff --git a/rust/candid/Cargo.toml b/rust/candid/Cargo.toml index 41947962..cd1aa777 100644 --- a/rust/candid/Cargo.toml +++ b/rust/candid/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candid" -version = "0.9.8" +version = "0.9.9" edition = "2021" authors = ["DFINITY Team"] description = "Candid is an interface description language (IDL) for interacting with canisters running on the Internet Computer." diff --git a/rust/candid/src/de.rs b/rust/candid/src/de.rs index 4d40df92..3e8404fe 100644 --- a/rust/candid/src/de.rs +++ b/rust/candid/src/de.rs @@ -2,7 +2,7 @@ use super::{ error::{Error, Result}, - types::internal::{type_of, TypeId}, + types::internal::{text_size, type_of, TypeId}, types::{Field, Label, SharedLabel, Type, TypeEnv, TypeInner}, CandidType, Int, Nat, }; @@ -17,6 +17,8 @@ use serde::de::{self, Visitor}; use std::fmt::Write; use std::{collections::VecDeque, io::Cursor, mem::replace}; +const MAX_TYPE_LEN: i32 = 500; + /// Use this struct to deserialize a sequence of Rust values (heterogeneous) from IDL binary message. pub struct IDLDeserialize<'de> { de: Deserializer<'de>, @@ -24,20 +26,26 @@ pub struct IDLDeserialize<'de> { impl<'de> IDLDeserialize<'de> { /// Create a new deserializer with IDL binary message. pub fn new(bytes: &'de [u8]) -> Result { - let de = Deserializer::from_bytes(bytes) - .with_context(|| format!("Cannot parse header {}", &hex::encode(bytes)))?; + let de = Deserializer::from_bytes(bytes).with_context(|| { + if bytes.len() <= 500 { + format!("Cannot parse header {}", &hex::encode(bytes)) + } else { + "Cannot parse header".to_string() + } + })?; Ok(IDLDeserialize { de }) } /// Create a new deserializer with IDL binary message. The config is used to adjust some parameters in the deserializer. pub fn new_with_config(bytes: &'de [u8], config: Config) -> Result { - let mut de = if config.minize_error_message { - Deserializer::from_bytes(bytes)? - } else { - Deserializer::from_bytes(bytes) - .with_context(|| format!("Cannot parse header {}", &hex::encode(bytes)))? - }; + let mut de = Deserializer::from_bytes(bytes).with_context(|| { + if config.full_error_message || bytes.len() <= 500 { + format!("Cannot parse header {}", &hex::encode(bytes)) + } else { + "Cannot parse header".to_string() + } + })?; de.zero_sized_values = config.zero_sized_values; - de.minize_error_message = config.minize_error_message; + de.full_error_message = config.full_error_message; Ok(IDLDeserialize { de }) } /// Deserialize one value from deserializer. @@ -70,12 +78,13 @@ impl<'de> IDLDeserialize<'de> { self.de.expect_type = expected_type; self.de.wire_type = TypeInner::Reserved.into(); return T::deserialize(&mut self.de); - } else if self.de.minize_error_message { - return Err(Error::msg("No more values on the wire")); - } else { + } else if self.de.full_error_message || text_size(&expected_type, MAX_TYPE_LEN).is_ok() + { return Err(Error::msg(format!( "No more values on the wire, the expected type {expected_type} is not opt, null, or reserved" ))); + } else { + return Err(Error::msg("No more values on the wire")); } } @@ -88,16 +97,20 @@ impl<'de> IDLDeserialize<'de> { }; self.de.wire_type = ty.clone(); - let v = if self.de.minize_error_message { - T::deserialize(&mut self.de)? - } else { - T::deserialize(&mut self.de) - .with_context(|| self.de.dump_state()) - .with_context(|| { - format!("Fail to decode argument {ind} from {ty} to {expected_type}") - })? - }; - Ok(v) + let mut v = T::deserialize(&mut self.de).with_context(|| { + if self.de.full_error_message + || (text_size(&ty, MAX_TYPE_LEN).is_ok() + && text_size(&expected_type, MAX_TYPE_LEN).is_ok()) + { + format!("Fail to decode argument {ind} from {ty} to {expected_type}") + } else { + format!("Fail to decode argument {ind}") + } + }); + if self.de.full_error_message { + v = v.with_context(|| self.de.dump_state()); + } + Ok(v?) } /// Check if we finish deserializing all values. pub fn is_done(&self) -> bool { @@ -111,7 +124,7 @@ impl<'de> IDLDeserialize<'de> { let ind = self.de.input.position() as usize; let rest = &self.de.input.get_ref()[ind..]; if !rest.is_empty() { - if self.de.minize_error_message { + if !self.de.full_error_message { return Err(Error::msg("Trailing value after finishing deserialization")); } else { return Err(anyhow!(self.de.dump_state())) @@ -124,7 +137,7 @@ impl<'de> IDLDeserialize<'de> { pub struct Config { pub zero_sized_values: usize, - pub minize_error_message: bool, + pub full_error_message: bool, } macro_rules! assert { @@ -198,7 +211,7 @@ struct Deserializer<'de> { // It only affects the field id generation in enum type. is_untyped: bool, zero_sized_values: usize, - minize_error_message: bool, + full_error_message: bool, #[cfg(not(target_arch = "wasm32"))] recursion_depth: u16, } @@ -217,8 +230,14 @@ impl<'de> Deserializer<'de> { gamma: Gamma::default(), field_name: None, is_untyped: false, + #[cfg(not(target_arch = "wasm32"))] zero_sized_values: 2_000_000, - minize_error_message: false, + #[cfg(target_arch = "wasm32")] + zero_sized_values: 0, + #[cfg(not(target_arch = "wasm32"))] + full_error_message: true, + #[cfg(target_arch = "wasm32")] + full_error_message: false, #[cfg(not(target_arch = "wasm32"))] recursion_depth: 0, }) @@ -254,25 +273,26 @@ impl<'de> Deserializer<'de> { Ok(res) } fn check_subtype(&mut self) -> Result<()> { - let res = subtype( + subtype( &mut self.gamma, &self.table, &self.wire_type, &self.expect_type, - ); - if res.is_err() { - if self.minize_error_message { - return Err(Error::subtype(format!("{}", self.wire_type))); + ) + .with_context(|| { + if self.full_error_message + || (text_size(&self.wire_type, MAX_TYPE_LEN).is_ok() + && text_size(&self.expect_type, MAX_TYPE_LEN).is_ok()) + { + format!( + "{} is not a subtype of {}", + self.wire_type, self.expect_type, + ) } else { - res.with_context(|| { - format!( - "{} is not a subtype of {}", - self.wire_type, self.expect_type, - ) - }) - .map_err(Error::subtype)?; + "subtype mismatch".to_string() } - } + }) + .map_err(Error::subtype)?; Ok(()) } fn unroll_type(&mut self) -> Result<()> { diff --git a/rust/candid/src/types/internal.rs b/rust/candid/src/types/internal.rs index f731bb9d..47676e07 100644 --- a/rust/candid/src/types/internal.rs +++ b/rust/candid/src/types/internal.rs @@ -295,6 +295,68 @@ impl fmt::Display for TypeInner { ) } } +pub(crate) fn text_size(t: &Type, limit: i32) -> Result { + use TypeInner::*; + if limit <= 1 { + return Err(()); + } + let cost = match t.as_ref() { + Null | Bool | Text | Nat8 | Int8 => 4, + Nat | Int => 3, + Nat16 | Nat32 | Nat64 | Int16 | Int32 | Int64 | Empty => 5, + Float32 | Float64 => 7, + Reserved => 8, + Principal => 9, + Knot(_) => 5, + Var(id) => id.len() as i32, + Opt(t) => 4 + text_size(t, limit - 4)?, + Vec(t) => 4 + text_size(t, limit - 4)?, + Record(fs) | Variant(fs) => { + let mut cnt = 0; + let mut limit = limit; + for f in fs.iter() { + let id_size = match f.id.as_ref() { + Label::Named(n) => n.len() as i32, + Label::Id(_) => 4, + _ => 0, + }; + cnt += id_size + text_size(&f.ty, limit - id_size - 3)? + 3; + limit -= cnt; + } + 9 + cnt + } + Func(func) => { + let mode = if func.modes.is_empty() { 0 } else { 6 }; + let mut cnt = mode + 6; + let mut limit = limit - cnt; + for t in func.args.iter() { + cnt += text_size(t, limit)?; + limit -= cnt; + } + for t in func.rets.iter() { + cnt += text_size(t, limit)?; + limit -= cnt; + } + cnt + } + Service(ms) => { + let mut cnt = 0; + let mut limit = limit; + for (name, f) in ms.iter() { + let len = name.len() as i32; + cnt += len + text_size(f, limit - len - 3)? + 3; + limit -= cnt; + } + 10 + cnt + } + Class(_, _) | Future | Unknown => unimplemented!(), + }; + if cost > limit { + Err(()) + } else { + Ok(cost) + } +} #[derive(Debug, Eq, Clone)] pub enum Label { diff --git a/tools/ui/Cargo.lock b/tools/ui/Cargo.lock index e57fe4b4..411743aa 100644 --- a/tools/ui/Cargo.lock +++ b/tools/ui/Cargo.lock @@ -111,7 +111,7 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "candid" -version = "0.9.7" +version = "0.9.8" dependencies = [ "anyhow", "binread",