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