Files
photocraft/crates/engine/src/allocation.rs
T
28ed1c1147 Handle allocation failures in large pixel operations (#2530)
* Make large pixel operations allocation-fallible

* Warp: drop the fixed 400 MP cap; fallible reservations already cover large transforms

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Adapt #2530 to main: fallible steep-Distort warp, Result from mask paste in float moves; regenerate scorecard

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:26:07 -04:00

71 lines
2.4 KiB
Rust

//! Fallible buffers used by memory-intensive document operations.
use crate::{EngineError, Result};
#[cfg(test)]
thread_local! {
static FAIL_NEXT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
/// Reserve and initialize a buffer without invoking the allocator's abort-on-OOM path.
pub(crate) fn filled<T: Clone>(len: usize, value: T, purpose: &str) -> Result<Vec<T>> {
let mut out = capacity(len, purpose)?;
out.resize(len, value);
Ok(out)
}
/// Reserve a vector for a known number of operation results.
pub(crate) fn capacity<T>(len: usize, purpose: &str) -> Result<Vec<T>> {
let bytes = len.checked_mul(std::mem::size_of::<T>()).ok_or_else(|| error(purpose, "requested size overflowed"))?;
#[cfg(test)]
if FAIL_NEXT.with(|flag| flag.replace(false)) {
return Err(error(purpose, "not enough memory"));
}
let mut out = Vec::new();
out.try_reserve_exact(len).map_err(|_| error(purpose, &format!("not enough memory for {bytes} bytes")))?;
Ok(out)
}
/// Check an operation boundary where downstream encoders/decoders allocate internally.
/// The test-only failpoint lets command tests prove their transaction boundary.
pub(crate) fn checkpoint(purpose: &str) -> Result<()> {
#[cfg(test)]
if FAIL_NEXT.with(|flag| flag.replace(false)) {
return Err(error(purpose, "not enough memory"));
}
let _ = purpose;
Ok(())
}
/// Copy a region into an explicitly reserved buffer.
pub(crate) fn read_region(surface: &photocraft_raster::Surface, area: photocraft_geom::Rect, purpose: &str) -> Result<Vec<f32>> {
let len = (area.width() as usize)
.checked_mul(area.height() as usize)
.and_then(|n| n.checked_mul(surface.channels()))
.ok_or_else(|| error(purpose, "requested size overflowed"))?;
let mut out = filled(len, 0.0, purpose)?;
surface.read_region_into(area, &mut out);
Ok(out)
}
fn error(purpose: &str, detail: &str) -> EngineError {
EngineError::Other(format!("not enough memory for {purpose} ({detail}); the document was not changed"))
}
#[cfg(test)]
pub(crate) fn fail_next_for_test() {
FAIL_NEXT.with(|flag| flag.set(true));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allocation_failure_is_reported_without_panicking() {
fail_next_for_test();
let result = filled::<u8>(16, 0, "test image");
assert!(result.as_ref().is_err_and(|e| e.to_string().contains("not enough memory for test image")));
}
}