mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-04 05:58:12 +08:00
Guard the shared generation counter in tests and reject oversized batch ranges
This commit is contained in:
@@ -54,13 +54,15 @@ fn next_generation() -> Option<u64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Rewinds the shared generation counter so previously issued values are reused.
|
/// Rewinds the shared generation counter so previously issued values are reused.
|
||||||
/// Returns `false` without rewinding while any [`Arena`] is still live.
|
/// Returns `false` without rewinding when an [`Arena`] is live at the time of the
|
||||||
|
/// check, which is a debugging aid rather than a guarantee.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// No [`ArenaWeak`] minted before this call may be upgraded afterwards. Dropping
|
/// No [`ArenaWeak`] minted before this call may be upgraded afterwards. Dropping
|
||||||
/// every [`Arena`] is not sufficient, since nodes also hold handles in
|
/// every [`Arena`] is not sufficient, since nodes also hold handles in
|
||||||
/// [`ArenaCell`]s; those nodes must be dropped too.
|
/// [`ArenaCell`]s; those nodes must be dropped too. No arena may be constructed
|
||||||
|
/// concurrently either, since the live check and the rewind are separate steps.
|
||||||
pub unsafe fn reset_generation_counter() -> bool {
|
pub unsafe fn reset_generation_counter() -> bool {
|
||||||
if LIVE_ARENAS.load(Ordering::Acquire) != 0 {
|
if LIVE_ARENAS.load(Ordering::Acquire) != 0 {
|
||||||
return false;
|
return false;
|
||||||
@@ -261,10 +263,12 @@ impl<T> ArenaCell<T> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::sync::PoisonError;
|
||||||
use std::sync::atomic::AtomicU32;
|
use std::sync::atomic::AtomicU32;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alloc_upgrade_reset_miss() {
|
fn alloc_upgrade_reset_miss() {
|
||||||
|
let _guard = COUNTER_GUARD.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
let mut arena = Arena::new(1024).unwrap();
|
let mut arena = Arena::new(1024).unwrap();
|
||||||
let cell = ArenaCell::new();
|
let cell = ArenaCell::new();
|
||||||
let (value, weak) = arena.alloc(41u32).unwrap();
|
let (value, weak) = arena.alloc(41u32).unwrap();
|
||||||
@@ -277,6 +281,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn capacity_survives_reset() {
|
fn capacity_survives_reset() {
|
||||||
|
let _guard = COUNTER_GUARD.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
let mut arena = Arena::new(64 + align_of::<u32>() - 1).unwrap();
|
let mut arena = Arena::new(64 + align_of::<u32>() - 1).unwrap();
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
for _ in 0..16 {
|
for _ in 0..16 {
|
||||||
@@ -289,6 +294,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn panics_leave_the_arena_coherent() {
|
fn panics_leave_the_arena_coherent() {
|
||||||
|
let _guard = COUNTER_GUARD.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
fn assert_ref_unwind_safe<T: std::panic::RefUnwindSafe>() {}
|
fn assert_ref_unwind_safe<T: std::panic::RefUnwindSafe>() {}
|
||||||
assert_ref_unwind_safe::<Arena>();
|
assert_ref_unwind_safe::<Arena>();
|
||||||
|
|
||||||
@@ -314,8 +320,13 @@ mod tests {
|
|||||||
assert!(arena.alloc(0u32).is_some(), "the arena stays usable");
|
assert!(arena.alloc(0u32).is_some(), "the arena stays usable");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Held by every test that perturbs [`NEXT_GENERATION`], so a swapped-out counter
|
||||||
|
/// is never observed by a concurrently constructing test.
|
||||||
|
static COUNTER_GUARD: Mutex<()> = Mutex::new(());
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn an_exhausted_reset_parks_the_arena_and_refuses_handles() {
|
fn an_exhausted_reset_parks_the_arena_and_refuses_handles() {
|
||||||
|
let _guard = COUNTER_GUARD.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
let mut arena = Arena::new(1024).unwrap();
|
let mut arena = Arena::new(1024).unwrap();
|
||||||
let (_, weak) = arena.alloc(41u32).unwrap();
|
let (_, weak) = arena.alloc(41u32).unwrap();
|
||||||
|
|
||||||
@@ -330,6 +341,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_generation_counter_rewinds_only_without_live_arenas() {
|
fn the_generation_counter_rewinds_only_without_live_arenas() {
|
||||||
|
let _guard = COUNTER_GUARD.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
let arena = Arena::new(64).unwrap();
|
let arena = Arena::new(64).unwrap();
|
||||||
assert!(!unsafe { reset_generation_counter() }, "a live arena must block the rewind");
|
assert!(!unsafe { reset_generation_counter() }, "a live arena must block the rewind");
|
||||||
drop(arena);
|
drop(arena);
|
||||||
@@ -337,6 +349,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn handles_do_not_upgrade_against_a_foreign_arena() {
|
fn handles_do_not_upgrade_against_a_foreign_arena() {
|
||||||
|
let _guard = COUNTER_GUARD.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
let first = Arena::new(1024).unwrap();
|
let first = Arena::new(1024).unwrap();
|
||||||
let second = Arena::new(1024).unwrap();
|
let second = Arena::new(1024).unwrap();
|
||||||
let (_, weak) = first.alloc(41u32).unwrap();
|
let (_, weak) = first.alloc(41u32).unwrap();
|
||||||
@@ -347,6 +360,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reset_drops_dependents_before_their_dependencies() {
|
fn reset_drops_dependents_before_their_dependencies() {
|
||||||
|
let _guard = COUNTER_GUARD.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
static ORDER: Mutex<Vec<u32>> = Mutex::new(Vec::new());
|
static ORDER: Mutex<Vec<u32>> = Mutex::new(Vec::new());
|
||||||
struct Probe(u32);
|
struct Probe(u32);
|
||||||
impl Drop for Probe {
|
impl Drop for Probe {
|
||||||
@@ -364,6 +378,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn drop_glue_runs_on_reset() {
|
fn drop_glue_runs_on_reset() {
|
||||||
|
let _guard = COUNTER_GUARD.lock().unwrap_or_else(PoisonError::into_inner);
|
||||||
static DROPS: AtomicU32 = AtomicU32::new(0);
|
static DROPS: AtomicU32 = AtomicU32::new(0);
|
||||||
struct Probe(#[allow(dead_code)] String);
|
struct Probe(#[allow(dead_code)] String);
|
||||||
impl Drop for Probe {
|
impl Drop for Probe {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub trait Node<Input> {
|
|||||||
let Some(scratch) = scratch else {
|
let Some(scratch) = scratch else {
|
||||||
return BatchStatus::NeedBuffer;
|
return BatchStatus::NeedBuffer;
|
||||||
};
|
};
|
||||||
let Some(len) = range.end.checked_sub(range.start).map(|len| len as usize) else {
|
let Some(len) = range.end.checked_sub(range.start).and_then(|len| usize::try_from(len).ok()) else {
|
||||||
return BatchStatus::InvalidRange;
|
return BatchStatus::InvalidRange;
|
||||||
};
|
};
|
||||||
if scratch.len() < len {
|
if scratch.len() < len {
|
||||||
|
|||||||
Reference in New Issue
Block a user