1use crate::{
2 budget::AsBudget,
3 events::Events,
4 xdr::{self, LedgerKey, ScAddress, ScError, ScErrorCode, ScErrorType},
5 ConversionError, EnvBase, Error, Host, TryFromVal, U32Val, Val,
6};
7
8#[cfg(any(test, feature = "backtrace"))]
9use backtrace::{Backtrace, BacktraceFrame};
10use core::fmt::Debug;
11use std::{
12 cell::{Ref, RefCell, RefMut},
13 ops::DerefMut,
14};
15
16use super::metered_clone::MeteredClone;
17
18#[derive(Clone)]
19pub(crate) struct DebugInfo {
20 events: Events,
21 #[cfg(any(test, feature = "backtrace"))]
22 backtrace: Backtrace,
23}
24
25#[derive(Clone)]
26pub struct HostError {
27 pub error: Error,
28 pub(crate) info: Option<Box<DebugInfo>>,
29}
30
31impl std::error::Error for HostError {}
32
33impl Into<Error> for HostError {
34 fn into(self) -> Error {
35 self.error
36 }
37}
38
39impl DebugInfo {
40 fn write_events(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 let mut wrote_heading = false;
42 for (i, e) in self.events.0.iter().rev().enumerate() {
43 if !wrote_heading {
44 writeln!(f)?;
45 writeln!(f, "Event log (newest first):")?;
46 wrote_heading = true;
47 }
48 writeln!(f, " {}: {}", i, e)?;
49 }
50 Ok(())
51 }
52
53 #[cfg(not(any(test, feature = "backtrace")))]
54 fn write_backtrace(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 Ok(())
56 }
57
58 #[cfg(any(test, feature = "backtrace"))]
59 fn write_backtrace(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 fn frame_name_matches(frame: &BacktraceFrame, pat: &str) -> bool {
65 for sym in frame.symbols() {
66 match sym.name() {
67 Some(sn) if format!("{:}", sn).contains(pat) => {
68 return true;
69 }
70 _ => (),
71 }
72 }
73 false
74 }
75
76 fn frame_is_short_backtrace_start(frame: &BacktraceFrame) -> bool {
77 frame_name_matches(frame, "__rust_begin_short_backtrace")
78 }
79
80 fn frame_is_initial_error_plumbing(frame: &BacktraceFrame) -> bool {
81 frame_name_matches(frame, "::from")
82 || frame_name_matches(frame, "::into")
83 || frame_name_matches(frame, "host::err")
84 || frame_name_matches(frame, "Host::err")
85 || frame_name_matches(frame, "Host>::err")
86 || frame_name_matches(frame, "::augment_err_result")
87 || frame_name_matches(frame, "::with_shadow_mode")
88 || frame_name_matches(frame, "::with_debug_mode")
89 || frame_name_matches(frame, "::maybe_get_debug_info")
90 || frame_name_matches(frame, "::map_err")
91 }
92 let mut bt = self.backtrace.clone();
93 bt.resolve();
94 let frames: Vec<BacktraceFrame> = bt
95 .frames()
96 .iter()
97 .skip_while(|f| frame_is_initial_error_plumbing(f))
98 .take_while(|f| !frame_is_short_backtrace_start(f))
99 .cloned()
100 .collect();
101 let bt: Backtrace = frames.into();
102 writeln!(f)?;
103 writeln!(f, "Backtrace (newest first):")?;
104 writeln!(f, "{:?}", bt)
105 }
106}
107
108impl Debug for HostError {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 writeln!(f, "HostError: {:?}", self.error)?;
111 if let Some(info) = &self.info {
112 info.write_events(f)?;
113 info.write_backtrace(f)
114 } else {
115 writeln!(f, "DebugInfo not available")
116 }
117 }
118}
119
120impl HostError {
121 #[cfg(any(test, feature = "testutils"))]
122 pub fn result_matches_err<T, C>(res: Result<T, HostError>, code: C) -> bool
123 where
124 Error: From<C>,
125 {
126 match res {
127 Ok(_) => {
128 eprintln!("result is not an error");
129 false
130 }
131 Err(he) => {
132 let error: Error = code.into();
133 if he.error != error {
134 eprintln!(
135 "expected error != actual error: {:?} != {:?}",
136 error, he.error
137 );
138 }
139 he.error == error
140 }
141 }
142 }
143
144 pub fn is_recoverable(&self) -> bool {
149 if !self.error.is_type(ScErrorType::Contract)
153 && self.error.is_code(ScErrorCode::InternalError)
154 {
155 return false;
156 }
157 if self.error.is_code(ScErrorCode::ExceededLimit)
161 && (self.error.is_type(ScErrorType::Storage) || self.error.is_type(ScErrorType::Budget))
162 {
163 return false;
164 }
165
166 true
167 }
168}
169
170impl<T> From<T> for HostError
171where
172 Error: From<T>,
173{
174 fn from(error: T) -> Self {
175 let error = error.into();
176 Self { error, info: None }
177 }
178}
179
180impl std::fmt::Display for HostError {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 <HostError as Debug>::fmt(self, f)
183 }
184}
185
186impl TryFrom<&HostError> for ScError {
187 type Error = xdr::Error;
188 fn try_from(err: &HostError) -> Result<Self, Self::Error> {
189 err.error.try_into()
190 }
191}
192
193impl From<HostError> for std::io::Error {
194 fn from(e: HostError) -> Self {
195 std::io::Error::new(std::io::ErrorKind::Other, e)
196 }
197}
198
199pub(crate) trait TryBorrowOrErr<T> {
200 fn try_borrow_or_err(&self) -> Result<Ref<'_, T>, Error>;
201 fn try_borrow_mut_or_err(&self) -> Result<RefMut<'_, T>, Error>;
202 fn try_borrow_or_err_with(&self, host: &Host, msg: &str) -> Result<Ref<'_, T>, HostError> {
203 self.try_borrow_or_err()
204 .map_err(|e| host.error(e, msg, &[]))
205 }
206 fn try_borrow_mut_or_err_with(
207 &self,
208 host: &Host,
209 msg: &str,
210 ) -> Result<RefMut<'_, T>, HostError> {
211 self.try_borrow_mut_or_err()
212 .map_err(|e| host.error(e, msg, &[]))
213 }
214}
215
216impl<T> TryBorrowOrErr<T> for RefCell<T> {
217 fn try_borrow_or_err(&self) -> Result<Ref<'_, T>, Error> {
218 self.try_borrow().map_err(|_| {
219 Error::from_type_and_code(ScErrorType::Context, ScErrorCode::InternalError)
220 })
221 }
222
223 fn try_borrow_mut_or_err(&self) -> Result<RefMut<'_, T>, Error> {
224 self.try_borrow_mut().map_err(|_| {
225 Error::from_type_and_code(ScErrorType::Context, ScErrorCode::InternalError)
226 })
227 }
228}
229
230pub trait ErrorHandler {
233 fn map_err<T, E>(&self, res: Result<T, E>) -> Result<T, HostError>
234 where
235 Error: From<E>,
236 E: Debug;
237 fn error(&self, error: Error, msg: &str, args: &[Val]) -> HostError;
238}
239
240impl ErrorHandler for Host {
241 fn map_err<T, E>(&self, res: Result<T, E>) -> Result<T, HostError>
259 where
260 Error: From<E>,
261 E: Debug,
262 {
263 res.map_err(|e| {
264 use std::borrow::Cow;
265 let mut msg: Cow<'_, str> = Cow::Borrowed(&"");
266 self.with_debug_mode(|| {
270 msg = Cow::Owned(format!("{:?}", e));
271 Ok(())
272 });
273 self.error(e.into(), &msg, &[])
274 })
275 }
276
277 fn error(&self, error: Error, msg: &str, args: &[Val]) -> HostError {
283 let mut he = HostError::from(error);
284 self.with_debug_mode(|| {
285 if let Ok(mut events_refmut) = self.0.events.try_borrow_mut() {
293 self.record_err_diagnostics(events_refmut.deref_mut(), error, msg, args);
294 }
295 he = HostError {
296 error,
297 info: self.maybe_get_debug_info(),
298 };
299 Ok(())
300 });
301 he
302 }
303}
304
305impl Host {
306 pub(crate) fn err(
308 &self,
309 type_: ScErrorType,
310 code: ScErrorCode,
311 msg: &str,
312 args: &[Val],
313 ) -> HostError {
314 let error = Error::from_type_and_code(type_, code);
315 self.error(error, msg, args)
316 }
317
318 pub(crate) fn maybe_get_debug_info(&self) -> Option<Box<DebugInfo>> {
319 #[allow(unused_mut)]
320 let mut res = None;
321 #[cfg(any(test, feature = "testutils"))]
326 {
327 self.with_debug_mode(|| {
328 if let Ok(events_ref) = self.0.events.try_borrow() {
329 let events = events_ref.externalize(self)?;
330 res = Some(Box::new(DebugInfo {
331 #[cfg(any(test, feature = "backtrace"))]
332 backtrace: Backtrace::new_unresolved(),
333 events,
334 }));
335 }
336 Ok(())
337 });
338 }
339 res
340 }
341
342 pub(crate) fn err_arith_overflow(&self) -> HostError {
345 self.err(
346 ScErrorType::Value,
347 ScErrorCode::ArithDomain,
348 "arithmetic overflow",
349 &[],
350 )
351 }
352
353 pub(crate) fn err_oob_linear_memory(&self) -> HostError {
354 self.err(
355 ScErrorType::WasmVm,
356 ScErrorCode::IndexBounds,
357 "out-of-bounds access to WASM linear memory",
358 &[],
359 )
360 }
361
362 pub(crate) fn err_oob_object_index(&self, index: Option<u32>) -> HostError {
363 let type_ = ScErrorType::Object;
364 let code = ScErrorCode::IndexBounds;
365 let msg = "object index out of bounds";
366 match index {
367 None => self.err(type_, code, msg, &[]),
368 Some(index) => self.err(type_, code, msg, &[U32Val::from(index).to_val()]),
369 }
370 }
371
372 pub(crate) fn account_address_from_key(&self, lk: &LedgerKey) -> Result<Val, HostError> {
376 let account_id = match lk {
377 LedgerKey::Account(e) => &e.account_id,
378 LedgerKey::Trustline(e) => &e.account_id,
379 _ => {
380 return Ok(Val::VOID.into());
381 }
382 };
383 self.add_host_object(ScAddress::Account(
384 account_id.metered_clone(self.as_budget())?,
385 ))
386 .map(|a| a.to_val())
387 }
388}
389
390pub(crate) trait DebugArg {
391 fn debug_arg(host: &Host, arg: &Self) -> Val {
392 let mut val: Option<Val> = None;
396 if let Ok(_guard) = host.0.events.try_borrow_mut() {
397 host.with_debug_mode_allowing_new_objects(
398 || {
399 if let Ok(v) = Self::debug_arg_maybe_expensive_or_fallible(host, arg) {
400 val = Some(v);
401 }
402 Ok(())
403 },
404 true,
405 );
406 val.unwrap_or_else(|| {
407 Error::from_type_and_code(ScErrorType::Events, ScErrorCode::InternalError).into()
408 })
409 } else {
410 Error::from_type_and_code(ScErrorType::Events, ScErrorCode::InternalError).into()
411 }
412 }
413 fn debug_arg_maybe_expensive_or_fallible(host: &Host, arg: &Self) -> Result<Val, HostError>;
414}
415
416impl<T> DebugArg for T
417where
418 Val: TryFromVal<Host, T>,
419 HostError: From<<Val as TryFromVal<Host, T>>::Error>,
420{
421 fn debug_arg_maybe_expensive_or_fallible(host: &Host, arg: &Self) -> Result<Val, HostError> {
422 Val::try_from_val(host, arg).map_err(|e| HostError::from(e))
423 }
424}
425
426impl DebugArg for xdr::Hash {
427 fn debug_arg_maybe_expensive_or_fallible(host: &Host, arg: &Self) -> Result<Val, HostError> {
428 host.bytes_new_from_slice(arg.as_slice()).map(|b| b.into())
429 }
430}
431
432impl DebugArg for str {
433 fn debug_arg_maybe_expensive_or_fallible(host: &Host, arg: &Self) -> Result<Val, HostError> {
434 host.string_new_from_slice(arg.as_bytes()).map(|s| s.into())
435 }
436}
437
438impl DebugArg for usize {
439 fn debug_arg_maybe_expensive_or_fallible(_host: &Host, arg: &Self) -> Result<Val, HostError> {
440 u32::try_from(*arg)
441 .map(|x| U32Val::from(x).into())
442 .map_err(|_| HostError::from(ConversionError))
443 }
444}
445
446#[macro_export]
456macro_rules! err {
457 ($host:expr, $error:expr, $msg:literal, $($args:expr),*) => {
458 {
459 const fn voidarg(_: &'static str) -> $crate::Val {
460 $crate::Val::VOID.to_val()
461 }
462 let mut buf = [$(voidarg(stringify!($args))),*];
468 let mut i = 0;
469 $host.with_debug_mode_allowing_new_objects(||{
470 $(
471 buf[i] = <_ as $crate::host::error::DebugArg>::debug_arg($host, &$args);
473 i += 1;
475 )*
476 Ok(())
477 }, true);
478 <_ as $crate::ErrorHandler>::error($host, $error.into(), $msg, &buf[0..i])
479 }
480 };
481}