use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::OnceLock;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};
static USED: AtomicUsize = AtomicUsize::new(0);
static CAP: AtomicUsize = AtomicUsize::new(0);
static TIMEOUT: AtomicU64 = AtomicU64::new(0);
static STARTED: OnceLock<Instant> = OnceLock::new();
#[cfg(unix)]
fn stop(message: &[u8]) -> ! {
unsafe extern "C" {
fn write(fd: i32, buffer: *const std::ffi::c_void, count: usize) -> isize;
fn _exit(status: i32) -> !;
}
unsafe {
write(2, message.as_ptr().cast(), message.len());
_exit(1)
}
}
#[cfg(windows)]
fn stop(message: &[u8]) -> ! {
use std::ffi::c_void;
unsafe extern "system" {
fn GetStdHandle(which: u32) -> *mut c_void;
fn WriteFile(
file: *mut c_void,
buffer: *const u8,
count: u32,
written: *mut u32,
overlapped: *mut c_void,
) -> i32;
fn GetCurrentProcess() -> *mut c_void;
fn TerminateProcess(process: *mut c_void, status: u32) -> i32;
}
const STD_ERROR_HANDLE: u32 = -12i32 as u32;
let mut written = 0u32;
unsafe {
WriteFile(
GetStdHandle(STD_ERROR_HANDLE),
message.as_ptr(),
message.len() as u32,
&mut written,
std::ptr::null_mut(),
);
TerminateProcess(GetCurrentProcess(), 1);
}
loop {
std::hint::spin_loop();
}
}
#[cfg(not(any(unix, windows)))]
fn stop(_: &[u8]) -> ! {
std::process::abort()
}
pub struct Capped;
impl Capped {
fn charge(size: usize) {
let used = USED.fetch_add(size, Ordering::Relaxed) + size;
let cap = CAP.load(Ordering::Relaxed);
if cap != 0 && used > cap {
stop(
b"{\"error\":\"memory limit exceeded while processing this file; raise it with --max-memory if the file is trusted\"}\n",
);
}
}
}
unsafe impl GlobalAlloc for Capped {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
Self::charge(layout.size());
unsafe { System.alloc(layout) }
}
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
Self::charge(layout.size());
unsafe { System.alloc_zeroed(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
USED.fetch_sub(layout.size(), Ordering::Relaxed);
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if new_size > layout.size() {
Self::charge(new_size - layout.size());
} else {
USED.fetch_sub(layout.size() - new_size, Ordering::Relaxed);
}
unsafe { System.realloc(ptr, layout, new_size) }
}
}
pub fn set_max_memory(mebibytes: usize) {
CAP.store(mebibytes.saturating_mul(1024 * 1024), Ordering::Relaxed);
}
pub fn max_memory() -> usize {
CAP.load(Ordering::Relaxed) / (1024 * 1024)
}
pub fn seconds_left() -> Option<u64> {
let limit = TIMEOUT.load(Ordering::Relaxed);
let started = STARTED.get()?;
(limit > 0).then(|| limit.saturating_sub(started.elapsed().as_secs()))
}
pub fn set_timeout(seconds: u64) {
if seconds == 0 {
return;
}
TIMEOUT.store(seconds, Ordering::Relaxed);
STARTED.get_or_init(Instant::now);
let message = format!(
"{{\"error\":\"time limit of {seconds} s exceeded while processing this file; raise it with --timeout if the file is trusted\"}}\n"
);
std::thread::spawn(move || {
std::thread::sleep(Duration::from_secs(seconds));
stop(message.as_bytes());
});
}