1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT
use std::fmt::Display;

use fvm_shared::address::Address;
use fvm_shared::error::{ErrorNumber, ExitCode};
use fvm_shared::{ActorID, MethodNum};

use crate::kernel::SyscallError;

/// A call backtrace records the actors an error was propagated through, from
/// the moment it was emitted. The original error is the _cause_. Backtraces are
/// useful for identifying the root cause of an error.
#[derive(Debug, Default, Clone)]
pub struct Backtrace {
    /// The actors through which this error was propagated from bottom (source) to top.
    pub frames: Vec<Frame>,
    /// The last syscall error before the first actor in `frames` aborted.
    pub cause: Option<Cause>,
}

impl Display for Backtrace {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (i, frame) in self.frames.iter().rev().enumerate() {
            writeln!(f, "{:02}: {}", i, frame)?;
        }
        if let Some(cause) = &self.cause {
            writeln!(f, "--> caused by: {}", cause)?;
        }
        Ok(())
    }
}

impl Backtrace {
    /// Returns true if the backtrace is completely empty.
    pub fn is_empty(&self) -> bool {
        self.frames.is_empty() && self.cause.is_none()
    }

    /// Clear the backtrace.
    pub fn clear(&mut self) {
        self.cause = None;
        self.frames.clear();
    }

    /// Begins a new backtrace. If there is an existing backtrace, this will clear it.
    ///
    /// Note: Backtraces are populated _backwards_. That is, a frame is inserted
    /// every time an actor returns. That's why `begin()` resets any currently
    /// accumulated state, as once an error occurs, we want to track its
    /// propagation all the way up.
    pub fn begin(&mut self, cause: Cause) {
        self.cause = Some(cause);
        self.frames.clear();
    }

    /// Sets the cause of a backtrace.
    ///
    /// This is useful to stamp a backtrace with its cause after the frames
    /// have been collected, such as when we ultimately handle a fatal error at
    /// the top of its propagation chain.
    pub fn set_cause(&mut self, cause: Cause) {
        self.cause = Some(cause);
    }

    /// Push a "frame" (actor exit) onto the backtrace.
    ///
    /// This should be called every time an actor exits with an error.
    pub fn push_frame(&mut self, frame: Frame) {
        self.frames.push(frame)
    }
}

/// A "frame" in a call backtrace.
#[derive(Clone, Debug)]
pub struct Frame {
    /// The actor that exited with this code.
    pub source: ActorID,
    /// The method that was invoked.
    pub method: MethodNum,
    /// The exit code.
    pub code: ExitCode,
    /// The abort message.
    pub message: String,
}

impl Display for Frame {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{} (method {}) -- {} ({})",
            Address::new_id(self.source),
            self.method,
            &self.message,
            self.code,
        )
    }
}

/// The ultimate "cause" of a failed message.
#[derive(Clone, Debug)]
pub enum Cause {
    /// The original cause was a syscall error.
    Syscall {
        /// The syscall "module".
        module: &'static str,
        /// The syscall function name.
        function: &'static str,
        /// The exact syscall error.
        error: ErrorNumber,
        /// The informational syscall message.
        message: String,
    },
    /// The original cause was a fatal error.
    Fatal {
        /// The alternate-formatted message from the anyhow error.
        error_msg: String,
        /// The backtrace, captured if the relevant
        /// [environment variables](https://doc.rust-lang.org/std/backtrace/index.html#environment-variables) are enabled.
        backtrace: String,
    },
}

impl Cause {
    /// Records a failing syscall as the cause of a backtrace.
    pub fn from_syscall(module: &'static str, function: &'static str, err: SyscallError) -> Self {
        Self::Syscall {
            module,
            function,
            error: err.1,
            message: err.0,
        }
    }

    /// Records a fatal error as the cause of a backtrace.
    pub fn from_fatal(err: anyhow::Error) -> Self {
        Self::Fatal {
            error_msg: format!("{:#}", err),
            backtrace: err.backtrace().to_string(),
        }
    }
}

impl Display for Cause {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Cause::Syscall {
                module,
                function,
                error,
                message,
            } => {
                write!(
                    f,
                    "{}::{} -- {} ({}: {})",
                    module, function, &message, *error as u32, error,
                )
            }
            Cause::Fatal {
                error_msg,
                backtrace,
            } => {
                write!(f, "[FATAL] Error: {}, Backtrace:\n{}", error_msg, backtrace)
            }
        }
    }
}