Skip to main content

tectonic/
errors.rs

1// Copyright 2016-2020 the Tectonic Project
2// Licensed under the MIT License.
3
4//! Tectonic error types and support code.
5
6// Silence warnings due to `error-chain` using `Error::cause` instead of `Error::source`. The
7// former was [deprecated in Rust 1.33](https://github.com/rust-lang/rust/pull/53533). The fix for
8// `error-chain` is in <https://github.com/rust-lang-nursery/error-chain/pull/255> and will
9// hopefully show up in a future version.
10#![allow(missing_docs)]
11
12use error_chain::error_chain;
13use std::{
14    ffi,
15    fmt::{self, Debug, Display},
16    io,
17    io::Write,
18    num,
19    result::Result as StdResult,
20    str,
21    sync::{Arc, Mutex, Weak},
22};
23use tectonic_errors::Error as NewError;
24use zip::result::ZipError;
25
26cfg_if::cfg_if! {
27    if #[cfg(feature = "toml")] {
28        pub type ReadError = toml::de::Error;
29        pub type WriteError = toml::ser::Error;
30    } else {
31        use std::error;
32
33        #[derive(Debug)]
34        pub enum ReadError {}
35
36        impl Display for ReadError {
37            fn fmt(&self, _fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
38                Ok(())
39            }
40        }
41
42        impl error::Error for ReadError { }
43
44        #[derive(Debug)]
45        pub enum WriteError {}
46
47        impl Display for WriteError {
48            fn fmt(&self, _fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
49                Ok(())
50            }
51        }
52
53        impl error::Error for WriteError { }
54    }
55}
56
57error_chain! {
58    types {
59        Error, ErrorKind, ResultExt, Result;
60    }
61
62    foreign_links {
63        Io(io::Error);
64        Fmt(fmt::Error);
65        Nul(ffi::NulError);
66        ParseInt(num::ParseIntError);
67        Persist(tempfile::PersistError);
68        ConfigRead(ReadError);
69        ConfigWrite(WriteError);
70        NewStyle(NewError);
71        QuickXml(quick_xml::Error);
72        Time(std::time::SystemTimeError);
73        Utf8(str::Utf8Error);
74        Xdv(tectonic_xdv::XdvError);
75        Zip(ZipError);
76    }
77
78    errors {
79        BadLength(expected: usize, observed: usize) {
80            description("the item is not the expected length")
81            display("expected length {}; found {}", expected, observed)
82        }
83
84        NotSeekable {
85            description("this stream is not seekable")
86            display("this stream is not seekable")
87        }
88
89        NotSizeable {
90            description("the size of this stream cannot be determined")
91            display("the size of this stream cannot be determined")
92        }
93
94        PathForbidden(path: String) {
95            description("access to this file path is forbidden")
96            display("access to the path {} is forbidden", path)
97        }
98
99        EngineError(engine: &'static str) {
100            description("some engine had an unrecoverable error")
101            display("the {} engine had an unrecoverable error", engine)
102        }
103    }
104}
105
106/// `chain_err` compatibility between our old and new error types
107pub trait ChainErrCompatExt<T> {
108    /// Chain this error with another
109    fn chain_err<F, K>(self, chainer: F) -> Result<T>
110    where
111        F: FnOnce() -> K,
112        K: Into<ErrorKind>;
113}
114
115impl<T> ChainErrCompatExt<T> for StdResult<T, NewError> {
116    fn chain_err<F, K>(self, chainer: F) -> Result<T>
117    where
118        F: FnOnce() -> K,
119        K: Into<ErrorKind>,
120    {
121        self.map_err(|e| {
122            let e: Error = e.into();
123            e.chain_err(chainer)
124        })
125    }
126}
127
128/// Use string formatting to create an `Error` of kind
129/// `errors::ErrorKind::Msg`.
130#[macro_export]
131macro_rules! errmsg {
132    ($( $fmt_args:expr ),*) => {
133        $crate::errors::ErrorKind::Msg(format!($( $fmt_args ),*)).into()
134    };
135}
136
137/// "Chained try" — like `try!`, but with the ability to add context to the error message.
138#[macro_export]
139macro_rules! ctry {
140    ($op:expr ; $( $chain_fmt_args:expr ),*) => {{
141        #[allow(unused_imports)]
142        use $crate::errors::{ChainErrCompatExt, ResultExt};
143        $op.chain_err(|| format!($( $chain_fmt_args ),*))?
144    }}
145}
146
147impl From<Error> for io::Error {
148    fn from(err: Error) -> io::Error {
149        io::Error::other(err.to_string())
150    }
151}
152
153impl Error {
154    /// Write the information contained in this object to standard error in a
155    /// somewhat user-friendly form.
156    ///
157    /// The `error_chain` crate provides a Display impl for its Error objects
158    /// that ought to provide this functionality, but I have had enormous
159    /// trouble being able to use it. So instead we emulate their code. This
160    /// function is also paralleled by the implementation in
161    /// `status::termcolor::TermcolorStatusBackend`, which adds the sugar of
162    /// providing nice colorization if possible. This function should only be
163    /// used if a `StatusBackend` is not yet available in the running program.
164    pub fn dump_uncolorized(&self) {
165        let mut prefix = "error:";
166        let mut s = io::stderr();
167
168        for item in self.iter() {
169            writeln!(s, "{prefix} {item}").expect("write to stderr failed");
170            prefix = "caused by:";
171        }
172
173        if let Some(backtrace) = self.backtrace() {
174            writeln!(s, "debugging: backtrace follows:").expect("write to stderr failed");
175            writeln!(s, "{backtrace:?}").expect("write to stderr failed");
176        }
177    }
178}
179
180/// The DefinitelySame trait is a helper trait implemented because Errors do
181/// not generically implement PartialEq. This is a bit of a drag for testing
182/// since it's nice to be able to check if an error matches the one that's
183/// expected. DefinitelySame addresses this by providing a weak equivalence
184/// test: definitely_same() returns true if the two values definitely are
185/// equivalent, and false otherwise. This can happen if the value are known to
186/// be different, but also if we can't tell. It doesn't cover all cases, but
187/// it does cover the ones that come up in our test suite.
188pub trait DefinitelySame {
189    fn definitely_same(&self, other: &Self) -> bool;
190}
191
192// Rust currently thinks that this impl conflicts with the one that we
193// provide for Result ... I am pretty sure that's not the case since the
194// Result PartialEq impl requires that T and E be PartialEq too, whereas
195// our definition works for subtypes that are DefinitelySame but
196// not PartialEq too.
197//
198//impl<T: PartialEq> DefinitelySame for T {
199//    fn definitely_same(&self, other: &T) -> bool {
200//        self == other
201//    }
202//}
203
204impl DefinitelySame for Error {
205    fn definitely_same(&self, other: &Self) -> bool {
206        if !self.0.definitely_same(&other.0) {
207            return false;
208        }
209        self.1.definitely_same(&other.1)
210    }
211}
212
213impl DefinitelySame for error_chain::State {
214    fn definitely_same(&self, other: &Self) -> bool {
215        // We ignore backtraces
216        // We have to remove the Send bounds, which current Rust makes a bit annoying
217        self.next_error.definitely_same(&other.next_error)
218    }
219}
220
221impl DefinitelySame for ErrorKind {
222    fn definitely_same(&self, other: &Self) -> bool {
223        match self {
224            ErrorKind::Msg(ref s) => {
225                if let ErrorKind::Msg(ref o) = *other {
226                    s == o
227                } else {
228                    false
229                }
230            }
231
232            // Hacky for tex-outputs test
233            ErrorKind::NewStyle(ref s) => {
234                if let ErrorKind::NewStyle(ref o) = *other {
235                    s.to_string() == o.to_string()
236                } else {
237                    false
238                }
239            }
240
241            _ => false,
242        }
243    }
244}
245
246impl DefinitelySame for NewError {
247    /// Hack alert! We only compare stringifications.
248    fn definitely_same(&self, other: &Self) -> bool {
249        self.to_string() == other.to_string()
250    }
251}
252
253impl DefinitelySame for Box<dyn std::error::Error + Send> {
254    /// Hack alert! We only compare stringifications.
255    fn definitely_same(&self, other: &Self) -> bool {
256        self.to_string() == other.to_string()
257    }
258}
259
260impl<T: DefinitelySame> DefinitelySame for Option<T> {
261    fn definitely_same(&self, other: &Self) -> bool {
262        match (self, other) {
263            (None, None) => true,
264            (Some(a), Some(b)) => a.definitely_same(b),
265            _ => false,
266        }
267    }
268}
269
270impl<T: DefinitelySame, E: DefinitelySame> DefinitelySame for StdResult<T, E> {
271    fn definitely_same(&self, other: &Self) -> bool {
272        match *self {
273            Ok(ref st) => {
274                if let Ok(ref ot) = *other {
275                    st.definitely_same(ot)
276                } else {
277                    false
278                }
279            }
280            Err(ref se) => {
281                if let Err(ref oe) = *other {
282                    se.definitely_same(oe)
283                } else {
284                    false
285                }
286            }
287        }
288    }
289}
290
291// SyncError copied from:
292// https://github.com/rust-lang-nursery/error-chain/issues/240
293//
294// This is needed to be able to turn error_chain errors into anyhow errors,
295// since the latter requires that they are sync.
296
297pub struct SyncError<T: 'static> {
298    inner: Arc<Mutex<T>>,
299    proxy: Option<CauseProxy<T>>,
300}
301
302impl<T: std::error::Error + 'static> SyncError<T> {
303    pub fn new(err: T) -> Self {
304        let arc = Arc::new(Mutex::new(err));
305        let proxy = arc
306            .lock()
307            .unwrap()
308            .source()
309            .map(|s| CauseProxy::new(s, Arc::downgrade(&arc), 0));
310
311        SyncError { inner: arc, proxy }
312    }
313
314    pub fn maybe<R>(r: std::result::Result<R, T>) -> std::result::Result<R, Self> {
315        match r {
316            Ok(v) => Ok(v),
317            Err(e) => Err(SyncError::new(e)),
318        }
319    }
320
321    pub fn unwrap(self) -> T {
322        Arc::try_unwrap(self.inner).unwrap().into_inner().unwrap()
323    }
324}
325
326impl<T: std::error::Error + 'static> std::error::Error for SyncError<T> {
327    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
328        self.proxy.as_ref().map(|e| e as _)
329    }
330}
331
332impl<T> Display for SyncError<T>
333where
334    T: Display,
335{
336    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337        self.inner.lock().unwrap().fmt(f)
338    }
339}
340
341impl<T> Debug for SyncError<T>
342where
343    T: Debug,
344{
345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346        self.inner.lock().unwrap().fmt(f)
347    }
348}
349
350struct CauseProxy<T: 'static> {
351    inner: Weak<Mutex<T>>,
352    next: Option<Box<CauseProxy<T>>>,
353    depth: u32,
354}
355
356impl<T: std::error::Error> CauseProxy<T> {
357    fn new(err: &dyn std::error::Error, weak: Weak<Mutex<T>>, depth: u32) -> Self {
358        // Can't allocate an object, or mutate the proxy safely during source(),
359        // so we just take the hit at construction, recursively. We can't hold
360        // references outside the mutex at all, so instead we remember how many
361        // steps to get to this proxy. And if some error chain plays tricks, the
362        // user gets both pieces.
363        CauseProxy {
364            inner: weak.clone(),
365            depth,
366            next: err
367                .source()
368                .map(|s| Box::new(CauseProxy::new(s, weak, depth + 1))),
369        }
370    }
371
372    fn with_instance<R, F>(&self, f: F) -> R
373    where
374        F: FnOnce(&(dyn std::error::Error + 'static)) -> R,
375    {
376        let arc = self.inner.upgrade().unwrap();
377        {
378            let e = arc.lock().unwrap();
379            let mut source = e.source().unwrap();
380            for _ in 0..self.depth {
381                source = source.source().unwrap();
382            }
383            f(source)
384        }
385    }
386}
387
388impl<T: std::error::Error + 'static> std::error::Error for CauseProxy<T> {
389    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
390        self.next.as_ref().map(|e| e as _)
391    }
392}
393
394impl<T> Display for CauseProxy<T>
395where
396    T: Display + std::error::Error,
397{
398    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399        self.with_instance(|i| std::fmt::Display::fmt(&i, f))
400    }
401}
402
403impl<T> Debug for CauseProxy<T>
404where
405    T: Debug + std::error::Error,
406{
407    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
408        self.with_instance(|i| std::fmt::Debug::fmt(&i, f))
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use super::*;
415    use error_chain::{ChainedError, State};
416    use std::error::Error as StdError;
417
418    #[test]
419    fn test_def_same_option() {
420        let a = Some(Box::from(NewError::msg("A")));
421        let b = Some(Box::from(NewError::msg("A")));
422
423        assert!(a.definitely_same(&b));
424        assert!(b.definitely_same(&a));
425
426        let b = Some(Box::from(NewError::msg("B")));
427        assert!(!a.definitely_same(&b));
428
429        let b = None;
430        let c = None;
431        assert!(!a.definitely_same(&b));
432        assert!(b.definitely_same(&c));
433    }
434
435    #[test]
436    fn test_def_same_err() {
437        let a = Error::new(ErrorKind::Msg(String::from("A")), State::default());
438        let b = Error::new(ErrorKind::Msg(String::from("A")), State::default());
439
440        // Different backtraces but should be same
441        assert!(a.definitely_same(&b));
442
443        let b = Error::new(ErrorKind::BadLength(0, 0), State::default());
444        assert!(!a.definitely_same(&b));
445
446        let a = Error::new(ErrorKind::NewStyle(NewError::msg("A")), State::default());
447        assert!(!a.definitely_same(&b));
448
449        let b = Error::new(ErrorKind::NewStyle(NewError::msg("A")), State::default());
450        assert!(a.definitely_same(&b));
451    }
452
453    #[test]
454    fn test_def_same_box_err() {
455        let a: Box<dyn StdError + Send> = Box::from(NewError::msg("A"));
456        let b: Box<dyn StdError + Send> = Box::from(NewError::msg("B"));
457
458        assert!(a.definitely_same(&a));
459        assert!(!a.definitely_same(&b));
460    }
461}