use backtrace::Backtrace;
use std::cmp;
use std::marker::PhantomData;
use std::os::raw::c_char;
use crate::util::permit_alloc;
pub(crate) mod buffer_management;
#[cfg(debug_assertions)]
pub(crate) mod context_checks;
#[cfg(all(not(miri), target_feature = "sse", feature = "unsafe_flush_denormals"))]
const SSE_FTZ_BIT: u32 = 1 << 15;
#[cfg(all(not(miri), target_arch = "aarch64", feature = "unsafe_flush_denormals"))]
const AARCH64_FTZ_BIT: u64 = 1 << 24;
#[cfg(all(
debug_assertions,
feature = "assert_process_allocs",
all(windows, target_env = "gnu")
))]
compile_error!(
"The 'assert_process_allocs' feature does not work correctly in combination with the 'x86_64-pc-windows-gnu' target, see https://github.com/Windfisch/rust-assert-no-alloc/issues/7"
);
#[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
#[global_allocator]
static A: nice_assert_no_alloc::AllocDisabler = nice_assert_no_alloc::AllocDisabler;
pub fn hash_param_id(id: &str) -> u32 {
let mut hash: u32 = 0;
for char in id.bytes() {
hash = hash.wrapping_mul(31).wrapping_add(char as u32);
}
hash &= !(1 << 31);
hash
}
pub fn strlcpy(dest: &mut [c_char], src: &str) {
if dest.is_empty() {
return;
}
let src_bytes: &[u8] = src.as_bytes();
let src_bytes_signed: &[c_char] = unsafe { &*(src_bytes as *const [u8] as *const [c_char]) };
let copy_len = cmp::min(dest.len() - 1, src.len());
dest[..copy_len].copy_from_slice(&src_bytes_signed[..copy_len]);
dest[copy_len] = 0;
}
#[inline]
pub fn clamp_input_event_timing(timing: u32, total_buffer_len: u32) -> u32 {
let last_valid_index = total_buffer_len.saturating_sub(1);
crate::nice_debug_assert!(
timing <= last_valid_index,
"Input event is out of bounds, will be clamped to the buffer's size"
);
timing.min(last_valid_index)
}
#[inline]
pub fn clamp_output_event_timing(timing: u32, total_buffer_len: u32) -> u32 {
let last_valid_index = total_buffer_len.saturating_sub(1);
crate::nice_debug_assert!(
timing <= last_valid_index,
"Output event is out of bounds, will be clamped to the buffer's size"
);
timing.min(last_valid_index)
}
pub fn setup_logger() {
let log_level = if cfg!(debug_assertions) {
log::LevelFilter::Trace
} else {
log::LevelFilter::Info
};
let logger_builder = nice_log::LoggerBuilder::new(log_level)
.filter_module("cosmic_text::buffer")
.filter_module("cosmic_text::shape")
.filter_module("selectors::matching");
#[cfg(debug_assertions)]
let logger_builder = logger_builder.always_show_module_path();
#[cfg(not(debug_assertions))]
let logger_builder = logger_builder.filter_module("cosmic_text::font::system::std");
let logger_set = logger_builder.build_global().is_ok();
if logger_set {
log_panics();
}
}
fn log_panics() {
std::panic::set_hook(Box::new(|info| {
permit_alloc(|| {
let backtrace = Backtrace::new();
let thread = std::thread::current();
let thread = thread.name().unwrap_or("unnamed");
let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s,
None => match info.payload().downcast_ref::<String>() {
Some(s) => &**s,
None => "Box<Any>",
},
};
match info.location() {
Some(location) => {
crate::nice_error!(
target: "panic", "thread '{}' panicked at '{}': {}:{}\n{:?}",
thread,
msg,
location.file(),
location.line(),
backtrace
);
}
None => {
crate::nice_error!(
target: "panic",
"thread '{}' panicked at '{}'\n{:?}",
thread,
msg,
backtrace
)
}
}
})
}));
}
pub fn process_wrapper<T, F: FnOnce() -> T>(f: F) -> T {
let _ftz_guard = ScopedFtz::enable();
#[cfg(all(debug_assertions, feature = "assert_process_allocs"))]
return nice_assert_no_alloc::assert_no_alloc(f);
#[cfg(not(all(debug_assertions, feature = "assert_process_allocs")))]
return f();
}
struct ScopedFtz {
#[allow(unused)]
should_disable_again: bool,
_send_sync_marker: PhantomData<*const ()>,
}
impl ScopedFtz {
fn enable() -> Self {
#[cfg(all(not(miri), feature = "unsafe_flush_denormals"))]
{
#[cfg(target_feature = "sse")]
{
let mut mxcsr: u32 = 0;
unsafe { std::arch::asm!("stmxcsr [{}]", in(reg) &mut mxcsr) };
let should_disable_again = mxcsr & SSE_FTZ_BIT == 0;
if should_disable_again {
unsafe { std::arch::asm!("ldmxcsr [{}]", in(reg) &(mxcsr | SSE_FTZ_BIT)) };
}
return Self {
should_disable_again,
_send_sync_marker: PhantomData,
};
}
#[cfg(target_arch = "aarch64")]
{
let mut fpcr: u64;
unsafe { std::arch::asm!("mrs {}, fpcr", out(reg) fpcr) };
let should_disable_again = fpcr & AARCH64_FTZ_BIT == 0;
if should_disable_again {
unsafe { std::arch::asm!("msr fpcr, {}", in(reg) fpcr | AARCH64_FTZ_BIT) };
}
return Self {
should_disable_again,
_send_sync_marker: PhantomData,
};
}
}
#[allow(unreachable_code)]
Self {
should_disable_again: false,
_send_sync_marker: PhantomData,
}
}
}
impl Drop for ScopedFtz {
fn drop(&mut self) {
#[cfg(all(not(miri), feature = "unsafe_flush_denormals"))]
if self.should_disable_again {
#[cfg(target_feature = "sse")]
{
let mut mxcsr: u32 = 0;
unsafe { std::arch::asm!("stmxcsr [{}]", in(reg) &mut mxcsr) };
unsafe { std::arch::asm!("ldmxcsr [{}]", in(reg) &(mxcsr & !SSE_FTZ_BIT)) };
}
#[cfg(target_arch = "aarch64")]
{
let mut fpcr: u64;
unsafe { std::arch::asm!("mrs {}, fpcr", out(reg) fpcr) };
unsafe { std::arch::asm!("msr fpcr, {}", in(reg) fpcr & !AARCH64_FTZ_BIT) };
}
}
}
}
#[cfg(test)]
mod miri {
use std::ffi::CStr;
use super::*;
#[test]
fn strlcpy_normal() {
let mut dest = [0; 256];
strlcpy(&mut dest, "Hello, world!");
assert_eq!(
unsafe { CStr::from_ptr(dest.as_ptr()) }.to_str(),
Ok("Hello, world!")
);
}
#[test]
fn strlcpy_overflow() {
let mut dest = [0; 6];
strlcpy(&mut dest, "Hello, world!");
assert_eq!(
unsafe { CStr::from_ptr(dest.as_ptr()) }.to_str(),
Ok("Hello")
);
}
}