soroban_env_host/host/
error.rs

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        // We do a little trimming here, skipping the first two frames (which
61        // are always into, from, and one or more Host::err_foo calls) and all
62        // the frames _after_ the short-backtrace marker that rust compiles-in.
63
64        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    /// Identifies whether the error can be meaningfully recovered from.
145    ///
146    /// We consider errors that occur due to broken execution preconditions (
147    /// such as incorrect footprint) non-recoverable.
148    pub fn is_recoverable(&self) -> bool {
149        // All internal errors that originate from the host can be considered
150        // non-recoverable (they should only appear if there is some bug in the
151        // host implementation or setup).
152        if !self.error.is_type(ScErrorType::Contract)
153            && self.error.is_code(ScErrorCode::InternalError)
154        {
155            return false;
156        }
157        // Exceeding the budget or storage limit is non-recoverable. Exceeding
158        // storage 'limit' is basically accessing entries outside of the
159        // supplied footprint.
160        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
230/// This is a trait for mapping Results carrying various error types into `HostError`,
231/// while potentially recording the existence of the error to diagnostic logs.
232pub 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    /// Given a result carrying some error type that can be converted to an
242    /// [Error] and supports [core::fmt::Debug], calls [Host::error] with the
243    /// error when there's an error, also passing the result of
244    /// [core::fmt::Debug::fmt] when [Host::is_debug] is `true`. Returns a
245    /// [Result] over [HostError].
246    ///
247    /// If you have an error type `T` you want to record as a detailed debug
248    /// event and a less-detailed [Error] code embedded in a [HostError], add an
249    /// `impl From<T> for Error` over in `soroban_env_common::error`, or in the
250    /// module defining `T`, and call this where the error is generated.
251    ///
252    /// Note: we do _not_ want to `impl From<T> for HostError` for such types,
253    /// as doing so will avoid routing them through the host in order to record
254    /// their extended diagnostic information into the event log. This means you
255    /// will wind up writing `host.map_err(...)?` a bunch in code that you used
256    /// to be able to get away with just writing `...?`, there's no way around
257    /// this if we want to record the diagnostic information.
258    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            // This observes the debug state, but it only causes a different
267            // (richer) string to be logged as a diagnostic event, which
268            // is itself not observable outside the debug state.
269            self.with_debug_mode(|| {
270                msg = Cow::Owned(format!("{:?}", e));
271                Ok(())
272            });
273            self.error(e.into(), &msg, &[])
274        })
275    }
276
277    /// At minimum constructs and returns a [HostError] built from the provided
278    /// [Error], and when running in [DiagnosticMode::Debug] additionally
279    /// records a diagnostic event with the provided `msg` and `args` and then
280    /// enriches the returned [Error] with [DebugInfo] in the form of a
281    /// [Backtrace] and snapshot of the [Events] buffer.
282    fn error(&self, error: Error, msg: &str, args: &[Val]) -> HostError {
283        let mut he = HostError::from(error);
284        self.with_debug_mode(|| {
285            // We _try_ to take a mutable borrow of the events buffer refcell
286            // while building up the event we're going to emit into the events
287            // log, failing gracefully (just emitting a no-debug-info
288            // `HostError` wrapping the supplied `Error`) if we cannot acquire
289            // the refcell. This is to handle the "double fault" case where we
290            // get an error _while performing_ any of the steps needed to record
291            // an error as an event, below.
292            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    /// Convenience function to construct an [Error] and pass to [Host::error].
307    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        // DebugInfo should never even be _possible_ to turn on in a production
322        // environment. It does not contribute to the diagnostics emitted in the
323        // debug stream -- those happen elsewhere -- DebugInfo only exists for
324        // users doing local testing to get nice backtraces on their console.
325        #[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    // Some common error patterns here.
343
344    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    // Extracts the account id from the given ledger key as address object `Val`.
373    // Returns Void for unsupported entries.
374    // Useful as a helper for error reporting.
375    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        // We similarly guard against double-faulting here by try-acquiring the
393        // event buffer, which will fail if we're re-entering error reporting
394        // _while_ forming a debug argument.
395        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/// Helper for building multi-argument errors.
447/// For example:
448/// ```ignore
449/// err!(host, error, "message", arg1, arg2);
450/// ```
451/// All arguments must be convertible to [Val] with [TryIntoVal]. This is
452/// expected to be called from within a function that returns
453/// `Result<_, HostError>`. If these requirements can't be fulfilled, use
454/// the [Host::error] function directly.
455#[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            // The stringify and voidarg calls here exist just to cause the
463            // macro to stack-allocate a fixed-size local array with one VOID
464            // initializer per argument. The stringified-arguments themselves
465            // are not actually used at this point, they exist to have a macro
466            // expression that corresponds to the number of arguments.
467            let mut buf = [$(voidarg(stringify!($args))),*];
468            let mut i = 0;
469            $host.with_debug_mode_allowing_new_objects(||{
470                $(
471                    // Args actually get used here, where we fill in array cells..
472                    buf[i] = <_ as $crate::host::error::DebugArg>::debug_arg($host, &$args);
473                    // .. and extend the end-index of the args-slice we'll pass.
474                    i += 1;
475                )*
476                Ok(())
477            }, true);
478            <_ as $crate::ErrorHandler>::error($host, $error.into(), $msg, &buf[0..i])
479        }
480    };
481}