1+ /*
2+ * COPYRIGHT: See COPYING in the top level directory
3+ * PROJECT: MemoryManager.Tests
4+ * FILE: MemoryArenaChaosTests.cs
5+ * PURPOSE: Hardening and edge-case validation for the unmanaged memory arena.
6+ * PROGRAMMER: Peter Geinitz (Wayfarer)
7+ */
8+
9+ using MemoryManager . Core ;
10+ using MemoryManager . Lanes ;
11+
12+ namespace MemoryManager . Tests
13+ {
14+ [ TestClass ]
15+ public class MemoryArenaChaosTests
16+ {
17+ /// <summary>
18+ /// Ensures that attempting to free an already freed handle throws an exception
19+ /// instead of corrupting the free-list or leaking structural slot array trackers.
20+ /// </summary>
21+ [ TestMethod ]
22+ [ TestCategory ( "ChaosSafety" ) ]
23+ public void Arena_DoubleFree_ThrowsInvalidOperationException ( )
24+ {
25+ // Arrange
26+ const int capacity = 1024 * 1024 ;
27+ using var slowLane = new SlowLane ( capacity ) ;
28+ using var fastLane = new FastLane ( capacity , slowLane ) ;
29+
30+ var handle = fastLane . Allocate ( 64 ) ;
31+
32+ // First free is the valid happy path
33+ fastLane . Free ( handle ) ;
34+
35+ // Act & Assert
36+ Assert . ThrowsException < InvalidOperationException > ( ( ) =>
37+ {
38+ // Second free is an exploitation vector or developer bug
39+ fastLane . Free ( handle ) ;
40+ } , "The lane must block a double-free attempt to protect the structural integrity of the free-list." ) ;
41+
42+ fastLane . DebugVisualMap ( ) ;
43+ }
44+
45+ /// <summary>
46+ /// Verifies that when an ID is recycled, its generational version increments.
47+ /// An old handle pointing to the recycled ID must trigger a safe access violation,
48+ /// while the new handle resolves perfectly to the newly assigned block.
49+ /// </summary>
50+ [ TestMethod ]
51+ [ TestCategory ( "ChaosSafety" ) ]
52+ public unsafe void Arena_HandleRecycling_IncrementsVersion_BlocksStaleHandles ( )
53+ {
54+ // Arrange
55+ const int capacity = 1024 * 1024 ;
56+ using var slowLane = new SlowLane ( capacity ) ;
57+ using var fastLane = new FastLane ( capacity , slowLane ) ;
58+
59+ // 1. Allocate block A, populate it, and record the original handle signature
60+ var oldHandle = fastLane . Allocate ( 32 ) ;
61+ int * oldPtr = ( int * ) fastLane . Resolve ( oldHandle ) ;
62+ * oldPtr = 42 ;
63+
64+ // 2. Free block A to return its sequential integer ID back to the pool
65+ fastLane . Free ( oldHandle ) ;
66+
67+ // 3. Force a new allocation. This must recycle the old ID, but increment the generation version
68+ var newHandle = fastLane . Allocate ( 32 ) ;
69+
70+ // Assert that the identifier was recycled but the version is explicitly distinct
71+ Assert . AreEqual ( oldHandle . Id , newHandle . Id , "The allocator should recycle tracking IDs to prevent footprint bloat." ) ;
72+ Assert . AreNotEqual ( oldHandle . Version , newHandle . Version , "Recycled IDs must feature an incremented generation value." ) ;
73+
74+ int * newPtr = ( int * ) fastLane . Resolve ( newHandle ) ;
75+ * newPtr = 99 ; // Write new distinct data to the recycled slot
76+
77+ // Act & Assert
78+
79+ // Verifying the new handle operates perfectly
80+ int * verifyPtr = ( int * ) fastLane . Resolve ( newHandle ) ;
81+ Assert . AreEqual ( 99 , * verifyPtr ) ;
82+
83+ // Verifying the zombie check blocks the legacy handle signature
84+ Assert . ThrowsException < AccessViolationException > ( ( ) =>
85+ {
86+ // Attempting to resolve via the stale handle version must break deterministically
87+ fastLane . Resolve ( oldHandle ) ;
88+ } , "Resolving a stale generational handle version must trigger a structural AccessViolationException." ) ;
89+
90+ fastLane . DebugVisualMap ( ) ;
91+ }
92+
93+ /// <summary>
94+ /// Pushes the allocator exactly to its saturation limit down to the last byte.
95+ /// Verifies that compaction behaves perfectly at saturation thresholds and that
96+ /// allocating even one single byte past capacity constraints emits a clean OutOfMemoryException.
97+ /// </summary>
98+ [ TestMethod ]
99+ [ TestCategory ( "ChaosSafety" ) ]
100+ public void Arena_AbsoluteSaturation_HandlesCompactionAndThrowsOOM ( )
101+ {
102+ // Arrange
103+ const int elementSize = 64 ;
104+ int physicalBlockSize = MemoryCanary . GetPhysicalSize ( elementSize ) ;
105+
106+ // Establish a strict budget that fits exactly 5 aligned physical blocks
107+ int customCapacity = physicalBlockSize * 5 ;
108+
109+ using var slowLane = new SlowLane ( customCapacity ) ;
110+ using var fastLane = new FastLane ( customCapacity , slowLane ) ;
111+
112+ var handles = new List < MemoryHandle > ( ) ;
113+
114+ // 1. Fully saturate the lane down to the literal final byte line
115+ for ( int i = 0 ; i < 5 ; i ++ )
116+ {
117+ handles . Add ( fastLane . Allocate ( elementSize ) ) ;
118+ }
119+
120+ // Assert that the available free space tracking has hit zero
121+ Assert . AreEqual ( 0 , fastLane . FreeSpace ( ) , "Lane should have exactly 0 bytes remaining at absolute structural saturation." ) ;
122+
123+ // 2. CRITICAL BOUNDARY CHECK: Try to force an extra allocation while completely full
124+ Assert . ThrowsException < OutOfMemoryException > ( ( ) =>
125+ {
126+ // Because the arena is packed at 100% saturation, this must cleanly fail
127+ fastLane . Allocate ( 1 ) ;
128+ } , "Allocating past strict capacity parameters when full must cleanly emit an OutOfMemoryException." ) ;
129+
130+ // 3. Free alternating items to introduce calculated structural holes
131+ fastLane . Free ( handles [ 1 ] ) ;
132+ fastLane . Free ( handles [ 3 ] ) ;
133+
134+ // 4. Execute a complete compaction pass at high stress capacity limits
135+ fastLane . Compact ( ) ;
136+
137+ // 5. Verify surviving blocks are still completely safe post-compaction slide
138+ Assert . IsTrue ( fastLane . HasHandle ( handles [ 0 ] ) , "Survivor ID 0 must maintain valid entry properties post-compaction." ) ;
139+ Assert . IsTrue ( fastLane . HasHandle ( handles [ 2 ] ) , "Survivor ID 2 must maintain valid entry properties post-compaction." ) ;
140+ Assert . IsTrue ( fastLane . HasHandle ( handles [ 4 ] ) , "Survivor ID 4 must maintain valid entry properties post-compaction." ) ;
141+
142+ // 6. VERIFY RECLAIMED WORKSPACE: Request a new item now that compaction freed up consolidated space
143+ var hNew = fastLane . Allocate ( elementSize ) ;
144+ Assert . IsTrue ( fastLane . HasHandle ( hNew ) , "The allocator should be able to accept new allocations again after compaction consolidates holes." ) ;
145+ }
146+ }
147+ }
0 commit comments