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        // TODO: maybe make this something users can adjust?
42        // https://github.com/stellar/rs-soroban-env/issues/1288
43        const MAX_EVENTS: usize = 25;
44        let mut wrote_heading = false;
45        for (i, e) in self.events.0.iter().rev().take(MAX_EVENTS).enumerate() {
46            if !wrote_heading {
47                writeln!(f)?;
48                writeln!(f, "Event log (newest first):")?;
49                wrote_heading = true;
50            }
51            writeln!(f, "   {}: {}", i, e)?;
52        }
53        if self.events.0.len() > MAX_EVENTS {
54            writeln!(
55                f,
56                "   {}: ... {} events elided ...",
57                MAX_EVENTS,
58                self.events.0.len() - MAX_EVENTS
59            )?;
60        }
61        Ok(())
62    }
63
64    #[cfg(not(any(test, feature = "backtrace")))]
65    fn write_backtrace(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        Ok(())
67    }
68
69    #[cfg(any(test, feature = "backtrace"))]
70    fn write_backtrace(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        // We do a little trimming here, skipping the first two frames (which
72        // are always into, from, and one or more Host::err_foo calls) and all
73        // the frames _after_ the short-backtrace marker that rust compiles-in.
74
75        fn frame_name_matches(frame: &BacktraceFrame, pat: &str) -> bool {
76            for sym in frame.symbols() {
77                match sym.name() {
78                    Some(sn) if format!("{:}", sn).contains(pat) => {
79                        return true;
80                    }
81                    _ => (),
82                }
83            }
84            false
85        }
86
87        fn frame_is_short_backtrace_start(frame: &BacktraceFrame) -> bool {
88            frame_name_matches(frame, "__rust_begin_short_backtrace")
89        }
90
91        fn frame_is_initial_error_plumbing(frame: &BacktraceFrame) -> bool {
92            frame_name_matches(frame, "::from")
93                || frame_name_matches(frame, "::into")
94                || frame_name_matches(frame, "host::err")
95                || frame_name_matches(frame, "Host::err")
96                || frame_name_matches(frame, "Host>::err")
97                || frame_name_matches(frame, "::augment_err_result")
98                || frame_name_matches(frame, "::with_shadow_mode")
99                || frame_name_matches(frame, "::with_debug_mode")
100                || frame_name_matches(frame, "::maybe_get_debug_info")
101                || frame_name_matches(frame, "::map_err")
102        }
103        let mut bt = self.backtrace.clone();
104        bt.resolve();
105        let frames: Vec<BacktraceFrame> = bt
106            .frames()
107            .iter()
108            .skip_while(|f| frame_is_initial_error_plumbing(f))
109            .take_while(|f| !frame_is_short_backtrace_start(f))
110            .cloned()
111            .collect();
112        let bt: Backtrace = frames.into();
113        writeln!(f)?;
114        writeln!(f, "Backtrace (newest first):")?;
115        writeln!(f, "{:?}", bt)
116    }
117}
118
119impl Debug for HostError {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        writeln!(f, "HostError: {:?}", self.error)?;
122        if let Some(info) = &self.info {
123            info.write_events(f)?;
124            info.write_backtrace(f)
125        } else {
126            writeln!(f, "DebugInfo not available")
127        }
128    }
129}
130
131impl HostError {
132    #[cfg(any(test, feature = "testutils"))]
133    pub fn result_matches_err<T, C>(res: Result<T, HostError>, code: C) -> bool
134    where
135        Error: From<C>,
136    {
137        match res {
138            Ok(_) => {
139                eprintln!("result is not an error");
140                false
141            }
142            Err(he) => {
143                let error: Error = code.into();
144                if he.error != error {
145                    eprintln!(
146                        "expected error != actual error: {:?} != {:?}",
147                        error, he.error
148                    );
149                }
150                he.error == error
151            }
152        }
153    }
154
155    /// Identifies whether the error can be meaningfully recovered from.
156    ///
157    /// We consider errors that occur due to broken execution preconditions (
158    /// such as incorrect footprint) non-recoverable.
159    pub fn is_recoverable(&self) -> bool {
160        // All internal errors that originate from the host can be considered
161        // non-recoverable (they should only appear if there is some bug in the
162        // host implementation or setup).
163        if !self.error.is_type(ScErrorType::Contract)
164            && self.error.is_code(ScErrorCode::InternalError)
165        {
166            return false;
167        }
168        // Exceeding the budget or storage limit is non-recoverable. Exceeding
169        // storage 'limit' is basically accessing entries outside of the
170        // supplied footprint.
171        if self.error.is_code(ScErrorCode::ExceededLimit)
172            && (self.error.is_type(ScErrorType::Storage) || self.error.is_type(ScErrorType::Budget))
173        {
174            return false;
175        }
176
177        true
178    }
179}
180
181impl<T> From<T> for HostError
182where
183    Error: From<T>,
184{
185    fn from(error: T) -> Self {
186        let error = error.into();
187        Self { error, info: None }
188    }
189}
190
191impl std::fmt::Display for HostError {
192    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193        <HostError as Debug>::fmt(self, f)
194    }
195}
196
197impl TryFrom<&HostError> for ScError {
198    type Error = xdr::Error;
199    fn try_from(err: &HostError) -> Result<Self, Self::Error> {
200        err.error.try_into()
201    }
202}
203
204impl From<HostError> for std::io::Error {
205    fn from(e: HostError) -> Self {
206        std::io::Error::new(std::io::ErrorKind::Other, e)
207    }
208}
209
210pub(crate) trait TryBorrowOrErr<T> {
211    fn try_borrow_or_err(&self) -> Result<Ref<'_, T>, Error>;
212    fn try_borrow_mut_or_err(&self) -> Result<RefMut<'_, T>, Error>;
213    fn try_borrow_or_err_with(&self, host: &Host, msg: &str) -> Result<Ref<'_, T>, HostError> {
214        self.try_borrow_or_err()
215            .map_err(|e| host.error(e, msg, &[]))
216    }
217    fn try_borrow_mut_or_err_with(
218        &self,
219        host: &Host,
220        msg: &str,
221    ) -> Result<RefMut<'_, T>, HostError> {
222        self.try_borrow_mut_or_err()
223            .map_err(|e| host.error(e, msg, &[]))
224    }
225}
226
227impl<T> TryBorrowOrErr<T> for RefCell<T> {
228    fn try_borrow_or_err(&self) -> Result<Ref<'_, T>, Error> {
229        self.try_borrow().map_err(|_| {
230            Error::from_type_and_code(ScErrorType::Context, ScErrorCode::InternalError)
231        })
232    }
233
234    fn try_borrow_mut_or_err(&self) -> Result<RefMut<'_, T>, Error> {
235        self.try_borrow_mut().map_err(|_| {
236            Error::from_type_and_code(ScErrorType::Context, ScErrorCode::InternalError)
237        })
238    }
239}
240
241impl Host {
242    /// Convenience function to construct an [Error] and pass to [Host::error].
243    pub(crate) fn err(
244        &self,
245        type_: ScErrorType,
246        code: ScErrorCode,
247        msg: &str,
248        args: &[Val],
249    ) -> HostError {
250        let error = Error::from_type_and_code(type_, code);
251        self.error(error, msg, args)
252    }
253
254    /// At minimum constructs and returns a [HostError] built from the provided
255    /// [Error], and when running in [DiagnosticMode::Debug] additionally
256    /// records a diagnostic event with the provided `msg` and `args` and then
257    /// enriches the returned [Error] with [DebugInfo] in the form of a
258    /// [Backtrace] and snapshot of the [Events] buffer.
259    pub(crate) fn error(&self, error: Error, msg: &str, args: &[Val]) -> HostError {
260        let mut he = HostError::from(error);
261        self.with_debug_mode(|| {
262            // We _try_ to take a mutable borrow of the events buffer refcell
263            // while building up the event we're going to emit into the events
264            // log, failing gracefully (just emitting a no-debug-info
265            // `HostError` wrapping the supplied `Error`) if we cannot acquire
266            // the refcell. This is to handle the "double fault" case where we
267            // get an error _while performing_ any of the steps needed to record
268            // an error as an event, below.
269            if let Ok(mut events_refmut) = self.0.events.try_borrow_mut() {
270                self.record_err_diagnostics(events_refmut.deref_mut(), error, msg, args);
271            }
272            he = HostError {
273                error,
274                info: self.maybe_get_debug_info(),
275            };
276            Ok(())
277        });
278        he
279    }
280
281    pub(crate) fn maybe_get_debug_info(&self) -> Option<Box<DebugInfo>> {
282        #[allow(unused_mut)]
283        let mut res = None;
284        // DebugInfo should never even be _possible_ to turn on in a production
285        // environment. It does not contribute to the diagnostics emitted in the
286        // debug stream -- those happen elsewhere -- DebugInfo only exists for
287        // users doing local testing to get nice backtraces on their console.
288        #[cfg(any(test, feature = "testutils"))]
289        {
290            self.with_debug_mode(|| {
291                if let Ok(events_ref) = self.0.events.try_borrow() {
292                    let events = events_ref.externalize(self)?;
293                    res = Some(Box::new(DebugInfo {
294                        #[cfg(any(test, feature = "backtrace"))]
295                        backtrace: Backtrace::new_unresolved(),
296                        events,
297                    }));
298                }
299                Ok(())
300            });
301        }
302        res
303    }
304
305    // Some common error patterns here.
306
307    pub(crate) fn err_arith_overflow(&self) -> HostError {
308        self.err(
309            ScErrorType::Value,
310            ScErrorCode::ArithDomain,
311            "arithmetic overflow",
312            &[],
313        )
314    }
315
316    pub(crate) fn err_oob_linear_memory(&self) -> HostError {
317        self.err(
318            ScErrorType::WasmVm,
319            ScErrorCode::IndexBounds,
320            "out-of-bounds access to WASM linear memory",
321            &[],
322        )
323    }
324
325    pub(crate) fn err_oob_object_index(&self, index: Option<u32>) -> HostError {
326        let type_ = ScErrorType::Object;
327        let code = ScErrorCode::IndexBounds;
328        let msg = "object index out of bounds";
329        match index {
330            None => self.err(type_, code, msg, &[]),
331            Some(index) => self.err(type_, code, msg, &[U32Val::from(index).to_val()]),
332        }
333    }
334
335    /// Given a result carrying some error type that can be converted to an
336    /// [Error] and supports [core::fmt::Debug], calls [Host::error] with the
337    /// error when there's an error, also passing the result of
338    /// [core::fmt::Debug::fmt] when [Host::is_debug] is `true`. Returns a
339    /// [Result] over [HostError].
340    ///
341    /// If you have an error type `T` you want to record as a detailed debug
342    /// event and a less-detailed [Error] code embedded in a [HostError], add an
343    /// `impl From<T> for Error` over in `soroban_env_common::error`, or in the
344    /// module defining `T`, and call this where the error is generated.
345    ///
346    /// Note: we do _not_ want to `impl From<T> for HostError` for such types,
347    /// as doing so will avoid routing them through the host in order to record
348    /// their extended diagnostic information into the event log. This means you
349    /// will wind up writing `host.map_err(...)?` a bunch in code that you used
350    /// to be able to get away with just writing `...?`, there's no way around
351    /// this if we want to record the diagnostic information.
352    pub(crate) fn map_err<T, E>(&self, res: Result<T, E>) -> Result<T, HostError>
353    where
354        Error: From<E>,
355        E: Debug,
356    {
357        res.map_err(|e| {
358            use std::borrow::Cow;
359            let mut msg: Cow<'_, str> = Cow::Borrowed(&"");
360            // This observes the debug state, but it only causes a different
361            // (richer) string to be logged as a diagnostic event, which
362            // is itself not observable outside the debug state.
363            self.with_debug_mode(|| {
364                msg = Cow::Owned(format!("{:?}", e));
365                Ok(())
366            });
367            self.error(e.into(), &msg, &[])
368        })
369    }
370
371    // Extracts the account id from the given ledger key as address object `Val`.
372    // Returns Void for unsupported entries.
373    // Useful as a helper for error reporting.
374    pub(crate) fn account_address_from_key(&self, lk: &LedgerKey) -> Result<Val, HostError> {
375        let account_id = match lk {
376            LedgerKey::Account(e) => &e.account_id,
377            LedgerKey::Trustline(e) => &e.account_id,
378            _ => {
379                return Ok(Val::VOID.into());
380            }
381        };
382        self.add_host_object(ScAddress::Account(
383            account_id.metered_clone(self.as_budget())?,
384        ))
385        .map(|a| a.to_val())
386    }
387}
388
389pub(crate) trait DebugArg {
390    fn debug_arg(host: &Host, arg: &Self) -> Val {
391        // We similarly guard against double-faulting here by try-acquiring the
392        // event buffer, which will fail if we're re-entering error reporting
393        // _while_ forming a debug argument.
394        let mut val: Option<Val> = None;
395        if let Ok(_guard) = host.0.events.try_borrow_mut() {
396            host.with_debug_mode(|| {
397                if let Ok(v) = Self::debug_arg_maybe_expensive_or_fallible(host, arg) {
398                    val = Some(v);
399                }
400                Ok(())
401            });
402            val.unwrap_or_else(|| {
403                Error::from_type_and_code(ScErrorType::Events, ScErrorCode::InternalError).into()
404            })
405        } else {
406            Error::from_type_and_code(ScErrorType::Events, ScErrorCode::InternalError).into()
407        }
408    }
409    fn debug_arg_maybe_expensive_or_fallible(host: &Host, arg: &Self) -> Result<Val, HostError>;
410}
411
412impl<T> DebugArg for T
413where
414    Val: TryFromVal<Host, T>,
415    HostError: From<<Val as TryFromVal<Host, T>>::Error>,
416{
417    fn debug_arg_maybe_expensive_or_fallible(host: &Host, arg: &Self) -> Result<Val, HostError> {
418        Val::try_from_val(host, arg).map_err(|e| HostError::from(e))
419    }
420}
421
422impl DebugArg for xdr::Hash {
423    fn debug_arg_maybe_expensive_or_fallible(host: &Host, arg: &Self) -> Result<Val, HostError> {
424        host.bytes_new_from_slice(arg.as_slice()).map(|b| b.into())
425    }
426}
427
428impl DebugArg for str {
429    fn debug_arg_maybe_expensive_or_fallible(host: &Host, arg: &Self) -> Result<Val, HostError> {
430        host.string_new_from_slice(arg.as_bytes()).map(|s| s.into())
431    }
432}
433
434impl DebugArg for usize {
435    fn debug_arg_maybe_expensive_or_fallible(_host: &Host, arg: &Self) -> Result<Val, HostError> {
436        u32::try_from(*arg)
437            .map(|x| U32Val::from(x).into())
438            .map_err(|_| HostError::from(ConversionError))
439    }
440}
441
442/// Helper for building multi-argument errors.
443/// For example:
444/// ```ignore
445/// err!(host, error, "message", arg1, arg2);
446/// ```
447/// All arguments must be convertible to [Val] with [TryIntoVal]. This is
448/// expected to be called from within a function that returns
449/// `Result<_, HostError>`. If these requirements can't be fulfilled, use
450/// the [Host::error] function directly.
451#[macro_export]
452macro_rules! err {
453    ($host:expr, $error:expr, $msg:literal, $($args:expr),*) => {
454        {
455            const fn voidarg(_: &'static str) -> $crate::Val {
456                $crate::Val::VOID.to_val()
457            }
458            // The stringify and voidarg calls here exist just to cause the
459            // macro to stack-allocate a fixed-size local array with one VOID
460            // initializer per argument. The stringified-arguments themselves
461            // are not actually used at this point, they exist to have a macro
462            // expression that corresponds to the number of arguments.
463            let mut buf = [$(voidarg(stringify!($args))),*];
464            let mut i = 0;
465            $host.with_debug_mode(||{
466                $(
467                    // Args actually get used here, where we fill in array cells..
468                    buf[i] = <_ as $crate::host::error::DebugArg>::debug_arg($host, &$args);
469                    // .. and extend the end-index of the args-slice we'll pass.
470                    i += 1;
471                )*
472                Ok(())
473            });
474            $host.error($error.into(), $msg, &buf[0..i])
475        }
476    };
477}