current commit
261018.1 · chore: update swc to v1.16.18
//! Self-loading target carrier. All native image bytes are verified before the
//! OS loader sees them; N-API registration is forwarded without wrapping
//! exports.

mod legacy;
mod napi;

use std::sync::Mutex;

use libloading::Library;
use swc_native_addon::{
    cache::{self, CacheMode, Materialized},
    format::Payload,
    integrity::RuntimeIntegrity,
    platform, replacement, Error, ErrorKind, Result,
};

include!(concat!(env!("OUT_DIR"), "/payload.rs"));

struct Loaded {
    register: napi::Register,
    _library: Library,
    _materialized: Materialized,
}

// Statics are deliberately never dropped. Addon callbacks can survive the
// original registration call and other Node environments in the same process.
// A Windows temporary's cleanup pipe is closed by process teardown.
static LOADED: Mutex<Option<Loaded>> = Mutex::new(None);
static IMAGE_ANCHOR: u8 = 0;

fn replacement_carrier(
    mode: &CacheMode,
    resolve: impl FnOnce() -> Result<std::path::PathBuf>,
) -> Option<std::path::PathBuf> {
    // APFS self-replacement recompresses the decoded image with ditto and
    // decodes it again for this process. Keep macOS first loads on the verified
    // cache path, even for writable installations, before resolving the carrier.
    if cfg!(target_os = "macos") || *mode == CacheMode::Temporary {
        return None;
    }
    match resolve() {
        Ok(path) => Some(path),
        Err(error) => {
            tracing::debug!(%error, "native carrier path unavailable; using cache");
            None
        }
    }
}

fn initialize() -> Result<napi::Register> {
    let mut loaded = LOADED.lock().map_err(|_| {
        Error::new(
            ErrorKind::Load,
            "native loader initialization lock was poisoned",
        )
    })?;
    if let Some(loaded) = loaded.as_ref() {
        return Ok(loaded.register);
    }
    let payload = Payload::parse(PAYLOAD)?.with_integrity(RuntimeIntegrity::parse(INTEGRITY)?)?;
    let mode = CacheMode::from_env()?;
    let replacement = if let Some(carrier) = replacement_carrier(&mode, || unsafe {
        platform::carrier_path((&IMAGE_ANCHOR as *const u8).cast())
    }) {
        // Compression and replacement are optional. Integrity is rechecked in
        // the cache path; no fallback ever loads an unverified original buffer.
        match replacement::try_replace(&payload, &carrier) {
            Ok(replacement) => replacement,
            Err(error) => {
                tracing::debug!(path = %carrier.display(), %error, "native self-replacement unavailable; using cache");
                None
            }
        }
    } else {
        None
    };
    let mut materialized = match replacement {
        Some(file) => file,
        None => cache::materialize(&payload, &mode)?,
    };
    #[cfg(unix)]
    let library = unsafe {
        libloading::os::unix::Library::open(
            Some(materialized.path()),
            libloading::os::unix::RTLD_NOW | libloading::os::unix::RTLD_LOCAL,
        )
        .map(Library::from)
    };
    #[cfg(windows)]
    let library = unsafe { Library::new(materialized.path()) };
    let library = library.map_err(|e| {
        Error::new(
            ErrorKind::Load,
            format!("load verified addon {}: {e}", materialized.path().display()),
        )
    })?;
    let register = *unsafe { library.get::<napi::Register>(b"napi_register_module_v1\0") }
        .map_err(|e| {
            Error::new(
                ErrorKind::Load,
                format!("verified addon has no napi_register_module_v1: {e}"),
            )
        })?;
    if register as usize == napi_register_module_v1 as *const () as usize {
        return Err(Error::new(
            ErrorKind::Load,
            "raw addon registration resolved back to the carrier",
        ));
    }
    materialized.loaded()?;
    *loaded = Some(Loaded {
        register,
        _library: library,
        _materialized: materialized,
    });
    Ok(register)
}

/// The raw packages already target N-API 3, supported by their Node minimums.
#[no_mangle]
pub extern "C" fn node_api_module_get_api_version_v1() -> i32 {
    3
}

/// Forward each environment independently, including a raw initializer
/// returning a different exports value or null with a pending JavaScript
/// exception.
///
/// # Safety
/// Node must pass a live `napi_env` and its associated initial exports value.
#[no_mangle]
pub unsafe extern "C" fn napi_register_module_v1(
    env: napi::Env,
    exports: napi::Value,
) -> napi::Value {
    let error = match std::panic::catch_unwind(initialize) {
        Ok(Ok(register)) => return register(env, exports),
        Ok(Err(error)) => error,
        Err(_) => Error::new(ErrorKind::Load, "native loader initialization panicked"),
    };
    // Successful registration forwards the raw initializer directly. Resolving
    // exception helpers would open and close the process image unnecessarily on
    // every environment; only the failure path needs those symbols.
    let api = match napi::Api::load() {
        Ok(api) => api,
        Err(error) => {
            // This can only occur outside the supported Node ABI: without even
            // napi_throw_error there is no legal way to create a JS exception.
            eprintln!("SWC native addon cannot initialize its N-API host: {error}");
            std::process::abort();
        }
    };
    api.throw(env, &error)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn temporary_mode_skips_carrier_resolution() {
        assert!(replacement_carrier(&CacheMode::Temporary, || -> Result<_> {
            panic!("temporary mode must not resolve the carrier")
        })
        .is_none());
    }

    #[test]
    #[cfg(not(target_os = "macos"))]
    fn unavailable_carrier_disables_replacement() {
        assert!(replacement_carrier(&CacheMode::Default, || {
            Err(Error::new(ErrorKind::Load, "carrier is unavailable"))
        })
        .is_none());
    }

    #[test]
    #[cfg(target_os = "macos")]
    fn macos_persistent_modes_skip_carrier_resolution() {
        for mode in [
            CacheMode::Default,
            CacheMode::Custom("/custom/cache".into()),
        ] {
            assert!(replacement_carrier(&mode, || -> Result<_> {
                panic!("macOS must not resolve the carrier for self-replacement")
            })
            .is_none());
        }
    }
}