1use crate::xdr::*;
21use core::fmt::{self, Debug};
22
23#[cfg(not(target_arch = "riscv32"))]
25use core::error::Error;
26#[cfg(not(target_arch = "riscv32"))]
27use core::fmt::Display;
28
29#[cfg(feature = "dev")]
32use thruput_native_exec::invoke_error::{PanicLocation, PanicRecord};
33
34#[cfg(not(target_arch = "riscv32"))]
36const CAP: usize = 500;
37
38#[cfg(not(target_arch = "riscv32"))]
41const NO_MESSAGE: &str = "<fault>";
42
43#[derive(Clone, Default)]
51pub struct FaultString {
52 #[cfg(not(target_arch = "riscv32"))]
53 msg: Option<heapless::String<CAP>>,
54}
55
56impl FaultString {
57 #[cfg(not(target_arch = "riscv32"))]
63 pub fn as_str(&self) -> &str {
64 self.msg.as_ref().map(|s| s.as_str()).unwrap_or(NO_MESSAGE)
65 }
66
67 #[inline]
69 pub fn empty() -> Self {
70 Self::default()
71 }
72
73 #[cfg(not(target_arch = "riscv32"))]
76 pub fn via_display<V>(v: &V) -> Self
77 where
78 V: Display + ?Sized,
79 {
80 use core::fmt::Write;
81
82 let mut s = heapless::String::new();
83 write!(s, "{}", v).unwrap_or_else(|_| {
84 s.clear();
85 let _ = write!(s, "<too-big>");
86 });
87
88 Self { msg: Some(s) }
89 }
90
91 #[cfg(target_arch = "riscv32")]
98 pub fn via_display<V: ?Sized>(_v: &V) -> Self {
99 Self {}
100 }
101
102 #[cfg(not(target_arch = "riscv32"))]
104 pub fn from_display(s: impl Display) -> Self {
105 Self::via_display(&s)
106 }
107
108 #[cfg(target_arch = "riscv32")]
110 pub fn from_display<S>(_s: S) -> Self {
111 Self {}
112 }
113}
114
115impl PartialEq for FaultString {
118 fn eq(&self, _other: &Self) -> bool {
119 true
120 }
121}
122
123impl Eq for FaultString {}
124
125#[cfg(not(target_arch = "riscv32"))]
129impl Debug for FaultString {
130 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131 f.debug_tuple("FaultString").field(&self.as_str()).finish()
132 }
133}
134
135#[cfg(target_arch = "riscv32")]
141impl Debug for FaultString {
142 fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
143 Ok(())
144 }
145}
146
147#[cfg(not(target_arch = "riscv32"))]
148impl Error for FaultString {}
149
150#[cfg(not(target_arch = "riscv32"))]
151impl Display for FaultString {
152 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153 f.write_str(self.as_str())
154 }
155}
156
157#[cfg(target_arch = "riscv32")]
162impl core::fmt::Display for FaultString {
163 fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
164 Ok(())
165 }
166}
167
168impl WriteXDR for FaultString {
171 fn write_xdr<F, W: xdrs::Writer<F>>(
172 &self,
173 _w: &mut W,
174 ) -> xdrs::WriteResult<()> {
175 Ok(())
176 }
177
178 unsafe fn write_xdr_unchecked<F, W: xdrs::WriterUnchecked<F>>(
179 &self,
180 _w: &mut W,
181 ) {
182 }
183}
184
185impl<'r> ReadXDR<'r> for FaultString {
186 fn read_xdr<R: xdrs::Reader<'r>>(_r: &mut R) -> xdrs::ReadResult<Self> {
187 Ok(Self::empty())
188 }
189}
190
191impl xdrs::cxs::ConstMaxSize for FaultString {
192 const MAX_SIZE: Option<usize> = Some(0);
193}
194
195impl xdrs::txs::ConstMaxSize for FaultString {
196 const MAX_SIZE: Option<usize> = Some(0);
197}
198
199#[cfg(target_arch = "riscv32")]
204const _: () = assert!(core::mem::size_of::<FaultString>() == 0);
205
206const _: () = assert!(matches!(
211 <FaultString as xdrs::cxs::ConstMaxSize>::MAX_SIZE,
212 Some(0)
213));
214
215impl crate::__contract_spec::ContractTypeName for FaultString {
216 const NAME: crate::__contract_spec::ContractSpecIdView<'static> =
217 crate::__contract_spec::ContractSpecIdView {
218 crate_name: ::core::env!("CARGO_PKG_NAME"),
219 major: crate::exn::EXN_ABI_MAJOR,
220 module_path: ::core::module_path!(),
221 declared: "FaultString",
222 };
223}
224
225impl crate::__contract_spec::ContractTypeSpec for FaultString {
226 const TYPE_DEF: crate::__contract_spec::ContractTypeDefView<'static> =
227 crate::__contract_spec::ContractTypeDefView::Struct(
228 crate::__contract_spec::ContractStructDefView {
229 name: <Self as crate::__contract_spec::ContractTypeName>::NAME,
230 fields: &[],
231 },
232 );
233}
234
235impl crate::__contract_spec::ContractTypeRefSpec for FaultString {
236 const INFO: crate::__contract_spec::ContractTypeInfo<'static> =
237 crate::__contract_spec::ContractTypeInfo {
238 ty: crate::__contract_spec::ContractTypeView::Named(
239 <Self as crate::__contract_spec::ContractTypeName>::NAME,
240 ),
241 };
242
243 const __THRUPUT_SPEC_ANCHOR_ROOT: fn() -> usize =
244 Self::__thruput_spec_anchor;
245
246 #[cfg_attr(target_arch = "riscv32", inline(never))]
247 #[cfg_attr(
248 target_arch = "riscv32",
249 unsafe(link_section = "thruputspecanchortextv0")
250 )]
251 fn __thruput_spec_anchor() -> usize {
252 0
253 }
254}
255
256#[doc(hidden)]
257#[allow(non_upper_case_globals)]
258pub const __thruput_spec_fragment_FaultString:
259 crate::__contract_spec::SpecFragmentView<'static> =
260 crate::__contract_spec::SpecFragmentView::TypeDef(
261 <FaultString as crate::__contract_spec::ContractTypeSpec>::TYPE_DEF,
262 );
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
271 fn encodes_to_nothing_whatever_the_message() {
272 for fs in [
273 FaultString::empty(),
274 FaultString::from_display("boom"),
275 FaultString::from_display(
276 core::str::from_utf8(&[b'x'; 500]).unwrap(),
277 ),
278 ] {
279 assert!(txs::encode(&fs).unwrap().is_empty());
280 }
281 }
282
283 #[test]
284 fn decodes_from_nothing() {
285 let s: FaultString = txs::decode(&[]).unwrap();
286 assert_eq!(s.as_str(), "<fault>");
287 }
288
289 #[test]
292 fn message_is_readable_host_side() {
293 assert_eq!(FaultString::from_display("boom").as_str(), "boom");
294 assert_eq!(FaultString::empty().as_str(), "<fault>");
295 }
296
297 #[test]
300 fn oversized_message_is_marked() {
301 let long = core::str::from_utf8(&[b'x'; CAP + 1]).unwrap();
302 assert_eq!(FaultString::from_display(long).as_str(), "<too-big>");
303 }
304}
305
306#[cfg_attr(not(target_arch = "riscv32"), derive(Debug))]
317#[derive(Clone, Default)]
318pub struct FaultInfo {
319 #[cfg(feature = "dev")]
320 record: PanicRecord,
321}
322
323impl FaultInfo {
324 #[inline]
326 pub fn empty() -> Self {
327 Self::default()
328 }
329
330 #[cfg(not(target_arch = "riscv32"))]
332 pub fn as_str(&self) -> &str {
333 #[cfg(feature = "dev")]
334 if let Some(message) = self.record.message.as_deref() {
335 return message;
336 }
337 NO_MESSAGE
338 }
339
340 #[cfg(feature = "dev")]
342 pub fn location(&self) -> Option<&PanicLocation> {
343 self.record.location.as_ref()
344 }
345}
346
347impl PartialEq for FaultInfo {
350 fn eq(&self, _other: &Self) -> bool {
351 true
352 }
353}
354
355impl Eq for FaultInfo {}
356
357#[cfg(not(target_arch = "riscv32"))]
360impl Display for FaultInfo {
361 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
362 f.write_str(self.as_str())?;
363 #[cfg(feature = "dev")]
364 if let Some(location) = self.location() {
365 write!(f, " at {location}")?;
366 }
367 Ok(())
368 }
369}
370
371#[cfg(target_arch = "riscv32")]
374impl Debug for FaultInfo {
375 fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
376 Ok(())
377 }
378}
379
380#[cfg(not(target_arch = "riscv32"))]
381impl Error for FaultInfo {}
382
383#[cfg(feature = "dev")]
384impl From<&PanicRecord> for FaultInfo {
385 fn from(record: &PanicRecord) -> Self {
386 Self {
387 record: record.clone(),
388 }
389 }
390}
391
392#[cfg(not(feature = "dev"))]
397const _: () = assert!(core::mem::size_of::<FaultInfo>() == 0);