cargo/util/
errors.rs

1#![allow(unknown_lints)]
2
3use crate::core::{TargetKind, Workspace};
4use crate::ops::CompileOptions;
5use anyhow::Error;
6use std::fmt;
7use std::path::PathBuf;
8use std::process::{ExitStatus, Output};
9use std::str;
10
11pub type CargoResult<T> = anyhow::Result<T>;
12
13// TODO: should delete this trait and just use `with_context` instead
14pub trait CargoResultExt<T, E> {
15    fn chain_err<F, D>(self, f: F) -> CargoResult<T>
16    where
17        F: FnOnce() -> D,
18        D: fmt::Display + Send + Sync + 'static;
19}
20
21impl<T, E> CargoResultExt<T, E> for Result<T, E>
22where
23    E: Into<Error>,
24{
25    fn chain_err<F, D>(self, f: F) -> CargoResult<T>
26    where
27        F: FnOnce() -> D,
28        D: fmt::Display + Send + Sync + 'static,
29    {
30        self.map_err(|e| e.into().context(f()))
31    }
32}
33
34#[derive(Debug)]
35pub struct HttpNot200 {
36    pub code: u32,
37    pub url: String,
38}
39
40impl fmt::Display for HttpNot200 {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        write!(
43            f,
44            "failed to get 200 response from `{}`, got {}",
45            self.url, self.code
46        )
47    }
48}
49
50impl std::error::Error for HttpNot200 {}
51
52// =============================================================================
53// Verbose error
54
55/// An error wrapper for errors that should only be displayed with `--verbose`.
56///
57/// This should only be used in rare cases. When emitting this error, you
58/// should have a normal error higher up the error-cause chain (like "could
59/// not compile `foo`"), so at least *something* gets printed without
60/// `--verbose`.
61pub struct VerboseError {
62    inner: Error,
63}
64
65impl VerboseError {
66    pub fn new(inner: Error) -> VerboseError {
67        VerboseError { inner }
68    }
69}
70
71impl std::error::Error for VerboseError {
72    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
73        self.inner.source()
74    }
75}
76
77impl fmt::Debug for VerboseError {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        self.inner.fmt(f)
80    }
81}
82
83impl fmt::Display for VerboseError {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        self.inner.fmt(f)
86    }
87}
88
89// =============================================================================
90// Internal error
91
92/// An unexpected, internal error.
93///
94/// This should only be used for unexpected errors. It prints a message asking
95/// the user to file a bug report.
96pub struct InternalError {
97    inner: Error,
98}
99
100impl InternalError {
101    pub fn new(inner: Error) -> InternalError {
102        InternalError { inner }
103    }
104}
105
106impl std::error::Error for InternalError {
107    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
108        self.inner.source()
109    }
110}
111
112impl fmt::Debug for InternalError {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        self.inner.fmt(f)
115    }
116}
117
118impl fmt::Display for InternalError {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        self.inner.fmt(f)
121    }
122}
123
124// =============================================================================
125// Manifest error
126
127/// Error wrapper related to a particular manifest and providing it's path.
128///
129/// This error adds no displayable info of it's own.
130pub struct ManifestError {
131    cause: Error,
132    manifest: PathBuf,
133}
134
135impl ManifestError {
136    pub fn new<E: Into<Error>>(cause: E, manifest: PathBuf) -> Self {
137        Self {
138            cause: cause.into(),
139            manifest,
140        }
141    }
142
143    pub fn manifest_path(&self) -> &PathBuf {
144        &self.manifest
145    }
146
147    /// Returns an iterator over the `ManifestError` chain of causes.
148    ///
149    /// So if this error was not caused by another `ManifestError` this will be empty.
150    pub fn manifest_causes(&self) -> ManifestCauses<'_> {
151        ManifestCauses { current: self }
152    }
153}
154
155impl std::error::Error for ManifestError {
156    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
157        self.cause.source()
158    }
159}
160
161impl fmt::Debug for ManifestError {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        self.cause.fmt(f)
164    }
165}
166
167impl fmt::Display for ManifestError {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        self.cause.fmt(f)
170    }
171}
172
173/// An iterator over the `ManifestError` chain of causes.
174pub struct ManifestCauses<'a> {
175    current: &'a ManifestError,
176}
177
178impl<'a> Iterator for ManifestCauses<'a> {
179    type Item = &'a ManifestError;
180
181    fn next(&mut self) -> Option<Self::Item> {
182        self.current = self.current.cause.downcast_ref()?;
183        Some(self.current)
184    }
185}
186
187impl<'a> ::std::iter::FusedIterator for ManifestCauses<'a> {}
188
189// =============================================================================
190// Process errors
191#[derive(Debug)]
192pub struct ProcessError {
193    /// A detailed description to show to the user why the process failed.
194    pub desc: String,
195    /// The exit status of the process.
196    ///
197    /// This can be `None` if the process failed to launch (like process not found).
198    pub exit: Option<ExitStatus>,
199    /// The output from the process.
200    ///
201    /// This can be `None` if the process failed to launch, or the output was not captured.
202    pub output: Option<Output>,
203}
204
205impl fmt::Display for ProcessError {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        self.desc.fmt(f)
208    }
209}
210
211impl std::error::Error for ProcessError {}
212
213// =============================================================================
214// Cargo test errors.
215
216/// Error when testcases fail
217#[derive(Debug)]
218pub struct CargoTestError {
219    pub test: Test,
220    pub desc: String,
221    pub exit: Option<ExitStatus>,
222    pub causes: Vec<ProcessError>,
223}
224
225impl fmt::Display for CargoTestError {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        self.desc.fmt(f)
228    }
229}
230
231impl std::error::Error for CargoTestError {}
232
233#[derive(Debug)]
234pub enum Test {
235    Multiple,
236    Doc,
237    UnitTest {
238        kind: TargetKind,
239        name: String,
240        pkg_name: String,
241    },
242}
243
244impl CargoTestError {
245    pub fn new(test: Test, errors: Vec<ProcessError>) -> Self {
246        if errors.is_empty() {
247            panic!("Cannot create CargoTestError from empty Vec")
248        }
249        let desc = errors
250            .iter()
251            .map(|error| error.desc.clone())
252            .collect::<Vec<String>>()
253            .join("\n");
254        CargoTestError {
255            test,
256            desc,
257            exit: errors[0].exit,
258            causes: errors,
259        }
260    }
261
262    pub fn hint(&self, ws: &Workspace<'_>, opts: &CompileOptions) -> String {
263        match self.test {
264            Test::UnitTest {
265                ref kind,
266                ref name,
267                ref pkg_name,
268            } => {
269                let pkg_info = if opts.spec.needs_spec_flag(ws) {
270                    format!("-p {} ", pkg_name)
271                } else {
272                    String::new()
273                };
274
275                match *kind {
276                    TargetKind::Bench => {
277                        format!("test failed, to rerun pass '{}--bench {}'", pkg_info, name)
278                    }
279                    TargetKind::Bin => {
280                        format!("test failed, to rerun pass '{}--bin {}'", pkg_info, name)
281                    }
282                    TargetKind::Lib(_) => format!("test failed, to rerun pass '{}--lib'", pkg_info),
283                    TargetKind::Test => {
284                        format!("test failed, to rerun pass '{}--test {}'", pkg_info, name)
285                    }
286                    TargetKind::ExampleBin | TargetKind::ExampleLib(_) => {
287                        format!("test failed, to rerun pass '{}--example {}", pkg_info, name)
288                    }
289                    _ => "test failed.".into(),
290                }
291            }
292            Test::Doc => "test failed, to rerun pass '--doc'".into(),
293            _ => "test failed.".into(),
294        }
295    }
296}
297
298// =============================================================================
299// CLI errors
300
301pub type CliResult = Result<(), CliError>;
302
303#[derive(Debug)]
304/// The CLI error is the error type used at Cargo's CLI-layer.
305///
306/// All errors from the lib side of Cargo will get wrapped with this error.
307/// Other errors (such as command-line argument validation) will create this
308/// directly.
309pub struct CliError {
310    /// The error to display. This can be `None` in rare cases to exit with a
311    /// code without displaying a message. For example `cargo run -q` where
312    /// the resulting process exits with a nonzero code (on Windows), or an
313    /// external subcommand that exits nonzero (we assume it printed its own
314    /// message).
315    pub error: Option<anyhow::Error>,
316    /// The process exit code.
317    pub exit_code: i32,
318}
319
320impl CliError {
321    pub fn new(error: anyhow::Error, code: i32) -> CliError {
322        CliError {
323            error: Some(error),
324            exit_code: code,
325        }
326    }
327
328    pub fn code(code: i32) -> CliError {
329        CliError {
330            error: None,
331            exit_code: code,
332        }
333    }
334}
335
336impl From<anyhow::Error> for CliError {
337    fn from(err: anyhow::Error) -> CliError {
338        CliError::new(err, 101)
339    }
340}
341
342impl From<clap::Error> for CliError {
343    fn from(err: clap::Error) -> CliError {
344        let code = if err.use_stderr() { 1 } else { 0 };
345        CliError::new(err.into(), code)
346    }
347}
348
349// =============================================================================
350// Construction helpers
351
352/// Creates a new process error.
353///
354/// `status` can be `None` if the process did not launch.
355/// `output` can be `None` if the process did not launch, or output was not captured.
356pub fn process_error(
357    msg: &str,
358    status: Option<ExitStatus>,
359    output: Option<&Output>,
360) -> ProcessError {
361    let exit = match status {
362        Some(s) => status_to_string(s),
363        None => "never executed".to_string(),
364    };
365    let mut desc = format!("{} ({})", &msg, exit);
366
367    if let Some(out) = output {
368        match str::from_utf8(&out.stdout) {
369            Ok(s) if !s.trim().is_empty() => {
370                desc.push_str("\n--- stdout\n");
371                desc.push_str(s);
372            }
373            Ok(..) | Err(..) => {}
374        }
375        match str::from_utf8(&out.stderr) {
376            Ok(s) if !s.trim().is_empty() => {
377                desc.push_str("\n--- stderr\n");
378                desc.push_str(s);
379            }
380            Ok(..) | Err(..) => {}
381        }
382    }
383
384    return ProcessError {
385        desc,
386        exit: status,
387        output: output.cloned(),
388    };
389
390    #[cfg(unix)]
391    fn status_to_string(status: ExitStatus) -> String {
392        use std::os::unix::process::*;
393
394        if let Some(signal) = status.signal() {
395            let name = match signal as libc::c_int {
396                libc::SIGABRT => ", SIGABRT: process abort signal",
397                libc::SIGALRM => ", SIGALRM: alarm clock",
398                libc::SIGFPE => ", SIGFPE: erroneous arithmetic operation",
399                libc::SIGHUP => ", SIGHUP: hangup",
400                libc::SIGILL => ", SIGILL: illegal instruction",
401                libc::SIGINT => ", SIGINT: terminal interrupt signal",
402                libc::SIGKILL => ", SIGKILL: kill",
403                libc::SIGPIPE => ", SIGPIPE: write on a pipe with no one to read",
404                libc::SIGQUIT => ", SIGQUIT: terminal quite signal",
405                libc::SIGSEGV => ", SIGSEGV: invalid memory reference",
406                libc::SIGTERM => ", SIGTERM: termination signal",
407                libc::SIGBUS => ", SIGBUS: access to undefined memory",
408                #[cfg(not(target_os = "haiku"))]
409                libc::SIGSYS => ", SIGSYS: bad system call",
410                libc::SIGTRAP => ", SIGTRAP: trace/breakpoint trap",
411                _ => "",
412            };
413            format!("signal: {}{}", signal, name)
414        } else {
415            status.to_string()
416        }
417    }
418
419    #[cfg(windows)]
420    fn status_to_string(status: ExitStatus) -> String {
421        use winapi::shared::minwindef::DWORD;
422        use winapi::um::winnt::*;
423
424        let mut base = status.to_string();
425        let extra = match status.code().unwrap() as DWORD {
426            STATUS_ACCESS_VIOLATION => "STATUS_ACCESS_VIOLATION",
427            STATUS_IN_PAGE_ERROR => "STATUS_IN_PAGE_ERROR",
428            STATUS_INVALID_HANDLE => "STATUS_INVALID_HANDLE",
429            STATUS_INVALID_PARAMETER => "STATUS_INVALID_PARAMETER",
430            STATUS_NO_MEMORY => "STATUS_NO_MEMORY",
431            STATUS_ILLEGAL_INSTRUCTION => "STATUS_ILLEGAL_INSTRUCTION",
432            STATUS_NONCONTINUABLE_EXCEPTION => "STATUS_NONCONTINUABLE_EXCEPTION",
433            STATUS_INVALID_DISPOSITION => "STATUS_INVALID_DISPOSITION",
434            STATUS_ARRAY_BOUNDS_EXCEEDED => "STATUS_ARRAY_BOUNDS_EXCEEDED",
435            STATUS_FLOAT_DENORMAL_OPERAND => "STATUS_FLOAT_DENORMAL_OPERAND",
436            STATUS_FLOAT_DIVIDE_BY_ZERO => "STATUS_FLOAT_DIVIDE_BY_ZERO",
437            STATUS_FLOAT_INEXACT_RESULT => "STATUS_FLOAT_INEXACT_RESULT",
438            STATUS_FLOAT_INVALID_OPERATION => "STATUS_FLOAT_INVALID_OPERATION",
439            STATUS_FLOAT_OVERFLOW => "STATUS_FLOAT_OVERFLOW",
440            STATUS_FLOAT_STACK_CHECK => "STATUS_FLOAT_STACK_CHECK",
441            STATUS_FLOAT_UNDERFLOW => "STATUS_FLOAT_UNDERFLOW",
442            STATUS_INTEGER_DIVIDE_BY_ZERO => "STATUS_INTEGER_DIVIDE_BY_ZERO",
443            STATUS_INTEGER_OVERFLOW => "STATUS_INTEGER_OVERFLOW",
444            STATUS_PRIVILEGED_INSTRUCTION => "STATUS_PRIVILEGED_INSTRUCTION",
445            STATUS_STACK_OVERFLOW => "STATUS_STACK_OVERFLOW",
446            STATUS_DLL_NOT_FOUND => "STATUS_DLL_NOT_FOUND",
447            STATUS_ORDINAL_NOT_FOUND => "STATUS_ORDINAL_NOT_FOUND",
448            STATUS_ENTRYPOINT_NOT_FOUND => "STATUS_ENTRYPOINT_NOT_FOUND",
449            STATUS_CONTROL_C_EXIT => "STATUS_CONTROL_C_EXIT",
450            STATUS_DLL_INIT_FAILED => "STATUS_DLL_INIT_FAILED",
451            STATUS_FLOAT_MULTIPLE_FAULTS => "STATUS_FLOAT_MULTIPLE_FAULTS",
452            STATUS_FLOAT_MULTIPLE_TRAPS => "STATUS_FLOAT_MULTIPLE_TRAPS",
453            STATUS_REG_NAT_CONSUMPTION => "STATUS_REG_NAT_CONSUMPTION",
454            STATUS_HEAP_CORRUPTION => "STATUS_HEAP_CORRUPTION",
455            STATUS_STACK_BUFFER_OVERRUN => "STATUS_STACK_BUFFER_OVERRUN",
456            STATUS_ASSERTION_FAILURE => "STATUS_ASSERTION_FAILURE",
457            _ => return base,
458        };
459        base.push_str(", ");
460        base.push_str(extra);
461        base
462    }
463}
464
465pub fn is_simple_exit_code(code: i32) -> bool {
466    // Typical unix exit codes are 0 to 127.
467    // Windows doesn't have anything "typical", and is a
468    // 32-bit number (which appears signed here, but is really
469    // unsigned). However, most of the interesting NTSTATUS
470    // codes are very large. This is just a rough
471    // approximation of which codes are "normal" and which
472    // ones are abnormal termination.
473    code >= 0 && code <= 127
474}
475
476pub fn internal<S: fmt::Display>(error: S) -> anyhow::Error {
477    InternalError::new(anyhow::format_err!("{}", error)).into()
478}