//! Common code for printing backtraces.
#![forbid(unsafe_op_in_unsafe_fn)]

use crate::backtrace_rs::{self, BacktraceFmt, BytesOrWideString, PrintFmt};
use crate::borrow::Cow;
use crate::io::prelude::*;
use crate::path::{self, Path, PathBuf};
use crate::sync::{Mutex, MutexGuard, PoisonError};
use crate::{env, fmt, io};

/// Max number of frames to print.
const MAX_NB_FRAMES: usize = 100;

pub(crate) const FULL_BACKTRACE_DEFAULT: bool = cfg_select! {
    // Fuchsia components default to full backtrace.
    target_os = "fuchsia" => true,
    _ => false,
};

pub(crate) struct BacktraceLock<'a>(#[allow(dead_code)] MutexGuard<'a, ()>);

pub(crate) fn lock<'a>() -> BacktraceLock<'a> {
    static LOCK: Mutex<()> = Mutex::new(());
    BacktraceLock(LOCK.lock().unwrap_or_else(PoisonError::into_inner))
}

impl BacktraceLock<'_> {
    /// Prints the current backtrace.
    pub(crate) fn print(&mut self, w: &mut dyn Write, format: PrintFmt) -> io::Result<()> {
        // There are issues currently linking libbacktrace into tests, and in
        // general during std's own unit tests we're not testing this path. In
        // test mode immediately return here to optimize away any references to the
        // libbacktrace symbols
        if cfg!(test) {
            return Ok(());
        }

        struct DisplayBacktrace {
            format: PrintFmt,
        }
        impl fmt::Display for DisplayBacktrace {
            fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
                // SAFETY: the backtrace lock is held
                unsafe { _print_fmt(fmt, self.format) }
            }
        }
        write!(w, "{}", DisplayBacktrace { format })
    }
}

/// # Safety
///
/// This function is not Sync. The caller must hold a mutex lock, or there must be only one thread in the program.
unsafe fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt::Result {
    // Always 'fail' to get the cwd when running under Miri -
    // this allows Miri to display backtraces in isolation mode
    let cwd = if !cfg!(miri) { env::current_dir().ok() } else { None };

    let mut print_path = move |fmt: &mut fmt::Formatter<'_>, bows: BytesOrWideString<'_>| {
        output_filename(fmt, bows, print_fmt, cwd.as_ref())
    };
    writeln!(fmt, "stack backtrace:")?;
    let mut bt_fmt = BacktraceFmt::new(fmt, print_fmt, &mut print_path);
    bt_fmt.add_context()?;
    let mut idx = 0;
    let mut res = Ok(());
    let mut omitted_count: usize = 0;
    let mut first_omit = true;
    // If we're using a short backtrace, ignore all frames until we're told to start printing.
    let mut print = print_fmt != PrintFmt::Short;
    set_image_base();
    // SAFETY: we roll our own locking in this town
    unsafe {
        backtrace_rs::trace_unsynchronized(|frame| {
            if print_fmt == PrintFmt::Short && idx > MAX_NB_FRAMES {
                return false;
            }

            if cfg!(feature = "backtrace-trace-only") {
                const HEX_WIDTH: usize = 2 + 2 * size_of::<usize>();
                let frame_ip = frame.ip();
                res = writeln!(bt_fmt.formatter(), "{idx:4}: {frame_ip:HEX_WIDTH$?}");
            } else {
                let mut hit = false;
                backtrace_rs::resolve_frame_unsynchronized(frame, |symbol| {
                    hit = true;

                    // `__rust_end_short_backtrace` means we are done hiding symbols
                    // for now. Print until we see `__rust_begin_short_backtrace`.
                    if print_fmt == PrintFmt::Short {
                        if let Some(sym) = symbol.name().and_then(|s| s.as_str()) {
                            if sym.contains("__rust_end_short_backtrace") {
                                print = true;
                                return;
                            }
                            if print && sym.contains("__rust_begin_short_backtrace") {
                                print = false;
                                return;
                            }
                            if !print {
                                omitted_count += 1;
                            }
                        }
                    }

                    if print {
                        if omitted_count > 0 {
                            debug_assert!(print_fmt == PrintFmt::Short);
                            // only print the message between the middle of frames
                            if !first_omit {
                                let _ = writeln!(
                                    bt_fmt.formatter(),
                                    "      [... omitted {} frame{} ...]",
                                    omitted_count,
                                    if omitted_count > 1 { "s" } else { "" }
                                );
                            }
                            first_omit = false;
                            omitted_count = 0;
                        }
                        res = bt_fmt.frame().symbol(frame, symbol);
                    }
                });
                #[cfg(all(target_os = "nto", any(target_env = "nto70", target_env = "nto71")))]
                if libc::__my_thread_exit as *mut libc::c_void == frame.ip() {
                    if !hit && print {
                        use crate::backtrace_rs::SymbolName;
                        res = bt_fmt.frame().print_raw(
                            frame.ip(),
                            Some(SymbolName::new("__my_thread_exit".as_bytes())),
                            None,
                            None,
                        );
                    }
                    return false;
                }
                if !hit && print {
                    res = bt_fmt.frame().print_raw(frame.ip(), None, None, None);
                }
            }

            idx += 1;
            res.is_ok()
        })
    };
    res?;
    bt_fmt.finish()?;
    if print_fmt == PrintFmt::Short {
        writeln!(
            fmt,
            "note: Some details are omitted, \
             run with `RUST_BACKTRACE=full` for a verbose backtrace."
        )?;
    }
    Ok(())
}

/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`. Note that
/// this is only inline(never) when backtraces in std are enabled, otherwise
/// it's fine to optimize away.
#[cfg_attr(feature = "backtrace", inline(never))]
pub fn __rust_begin_short_backtrace<F, T>(f: F) -> T
where
    F: FnOnce() -> T,
{
    let result = f();

    // prevent this frame from being tail-call optimised away
    crate::hint::black_box(());

    result
}

/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`. Note that
/// this is only inline(never) when backtraces in std are enabled, otherwise
/// it's fine to optimize away.
#[cfg_attr(feature = "backtrace", inline(never))]
pub fn __rust_end_short_backtrace<F, T>(f: F) -> T
where
    F: FnOnce() -> T,
{
    let result = f();

    // prevent this frame from being tail-call optimised away
    crate::hint::black_box(());

    result
}

/// Prints the filename of the backtrace frame.
///
/// See also `output`.
pub fn output_filename(
    fmt: &mut fmt::Formatter<'_>,
    bows: BytesOrWideString<'_>,
    print_fmt: PrintFmt,
    cwd: Option<&PathBuf>,
) -> fmt::Result {
    let file: Cow<'_, Path> = match bows {
        #[cfg(unix)]
        BytesOrWideString::Bytes(bytes) => {
            use crate::os::unix::prelude::*;
            Path::new(crate::ffi::OsStr::from_bytes(bytes)).into()
        }
        #[cfg(not(unix))]
        BytesOrWideString::Bytes(bytes) => {
            Path::new(crate::str::from_utf8(bytes).unwrap_or("<unknown>")).into()
        }
        #[cfg(windows)]
        BytesOrWideString::Wide(wide) => {
            use crate::os::windows::prelude::*;
            Cow::Owned(crate::ffi::OsString::from_wide(wide).into())
        }
        #[cfg(not(windows))]
        BytesOrWideString::Wide(_wide) => Path::new("<unknown>").into(),
    };
    if print_fmt == PrintFmt::Short && file.is_absolute() {
        if let Some(cwd) = cwd {
            if let Ok(stripped) = file.strip_prefix(&cwd) {
                if let Some(s) = stripped.to_str() {
                    return write!(fmt, ".{}{s}", path::MAIN_SEPARATOR);
                }
            }
        }
    }
    fmt::Display::fmt(&file.display(), fmt)
}

#[cfg(all(target_vendor = "fortanix", target_env = "sgx"))]
pub fn set_image_base() {
    let image_base = crate::os::fortanix_sgx::mem::image_base();
    backtrace_rs::set_image_base(crate::ptr::without_provenance_mut(image_base as _));
}

#[cfg(not(all(target_vendor = "fortanix", target_env = "sgx")))]
pub fn set_image_base() {
    // nothing to do for platforms other than SGX
}
