qld 0.1.0

A fast, parallel linker compatible with GNU ld, gold, lld and mold
Documentation
//! Fatal error type.
//!
//! Only errors that stop the link are returned as [`Error`]. Anything the link
//! can continue past is a [`crate::diag::Diagnostic`] instead.
//!
//! Parsing code must never panic on malformed input: bounds-check every
//! offset, use checked arithmetic, and return [`Error::Malformed`].

use std::fmt;
use std::path::PathBuf;

/// Result alias used throughout qld.
pub type Result<T, E = Error> = std::result::Result<T, E>;

/// A fatal linker error.
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
    /// An input file could not be parsed.
    Malformed {
        /// The file the problem was found in.
        file: PathBuf,
        /// Archive member name, when the file is an archive.
        member: Option<String>,
        /// Byte offset of the problem within the file.
        offset: u64,
        /// What was wrong, phrased as a noun ("section header table").
        what: String,
    },
    /// A linker script could not be parsed or evaluated. Renders as
    /// `file:line:column: message`, the form GNU tools use.
    Script(Box<crate::script::ScriptError>),
    /// An I/O operation failed.
    Io {
        /// The path being operated on, if any.
        path: Option<PathBuf>,
        /// The underlying error.
        source: std::io::Error,
    },
    /// A command-line option was not recognized, or its value was invalid.
    Option(String),
    /// A library or linker script named on the command line was not found in
    /// any search directory. The message is GNU-style: `cannot find -lfoo`.
    NotFound(String),
    /// A hard limit was exceeded (more than 2^32 input files, symbols, …).
    Limit(String),
    /// An LTO plugin could not be used, or an IR input has no plugin to
    /// compile it.
    Plugin(String),
    /// An internal invariant was violated: a bug in qld, such as a backend
    /// handing a pass an inconsistent layout. Never caused by input files.
    Internal(String),
    /// The link failed because of errors already reported to the diagnostic
    /// sink (for example undefined symbols).
    Reported {
        /// How many errors were reported.
        errors: usize,
    },
    /// A feature that qld intends to support is not implemented yet.
    ///
    /// Every use of this must name the roadmap milestone that will remove it.
    Unimplemented(String),
    /// The link was cancelled through
    /// [`LinkOptions::cancel`](crate::args::LinkOptions::cancel).
    Cancelled,
}

impl Error {
    /// Creates an [`Error::Io`] that names the path involved.
    pub fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
        Self::Io {
            path: Some(path.into()),
            source,
        }
    }

    /// Creates an [`Error::Malformed`] for a file with no archive member.
    pub fn malformed(path: impl Into<PathBuf>, offset: u64, what: impl Into<String>) -> Self {
        Self::Malformed {
            file: path.into(),
            member: None,
            offset,
            what: what.into(),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Malformed {
                file,
                member,
                offset,
                what,
            } => {
                write!(f, "{}", file.display())?;
                if let Some(member) = member {
                    write!(f, "({member})")?;
                }
                write!(f, ": malformed {what} at offset {offset:#x}")
            }
            Self::Io {
                path: Some(path),
                source,
            } => write!(f, "{}: {source}", path.display()),
            Self::Io { path: None, source } => write!(f, "{source}"),
            Self::Script(error) => write!(f, "{error}"),
            Self::Option(message) | Self::NotFound(message) => write!(f, "{message}"),
            Self::Limit(message) => write!(f, "limit exceeded: {message}"),
            Self::Plugin(message) => write!(f, "{message}"),
            Self::Internal(message) => write!(f, "internal error: {message}"),
            Self::Reported { errors } if *errors == 1 => write!(f, "1 error"),
            Self::Reported { errors } => write!(f, "{errors} errors"),
            Self::Unimplemented(what) => write!(f, "not implemented yet: {what}"),
            Self::Cancelled => f.write_str("link cancelled"),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Io { source, .. } => Some(source),
            Self::Script(error) => Some(error.as_ref()),
            _ => None,
        }
    }
}

impl From<std::io::Error> for Error {
    fn from(source: std::io::Error) -> Self {
        Self::Io { path: None, source }
    }
}