11#include " GarbageCollector.hpp"
22#include " Heap_test.hpp"
33
4+ #include < cstdint>
5+ #include < cstring>
6+
47namespace {
58
69static int64_t g_counter = 0 ;
@@ -49,6 +52,35 @@ static_assert(false, "compiler not supported");
4952 ASSERT_EQ (ptr4->foo , 4 );
5053 ASSERT_EQ (ptr5->foo , 5 );
5154}
55+
56+ // Overwrites the stack region just vacated by a popped frame. Unoptimized
57+ // builds give every temporary its own slot and the collector's call chain
58+ // does not reliably overwrite all of them before the conservative scan runs,
59+ // so a stale copy of a dead GC pointer can be picked up as a root. Zeroing
60+ // the dead region makes collection of unreachable objects deterministic.
61+ // The scrub stays within `buffer`, which cannot reach the last few words
62+ // below this function's frame header (alignment padding and compiler-placed
63+ // slots sit there); callers must run the allocating helper through
64+ // call_in_padded_frame so its residue lands below that blind spot.
65+ __attribute__ ((noinline)) void scrub_dead_stack ()
66+ {
67+ uint8_t buffer[16 * 1024 ];
68+ std::memset (buffer, 0 , sizeof (buffer));
69+ // keep the memset from being eliminated as a dead store
70+ asm volatile (" " ::" r" (buffer) : " memory" );
71+ }
72+
73+ // Runs fn with its stack frame pushed at least sizeof(pad) bytes deeper than
74+ // the caller's, so every slot fn writes lies inside the span that
75+ // scrub_dead_stack can zero without stepping past its buffer's bounds.
76+ template <typename Fn> __attribute__ ((noinline)) void call_in_padded_frame(Fn &&fn)
77+ {
78+ volatile uint8_t pad[256 ] = {};
79+ fn ();
80+ // volatile read keeps pad live across the call, preventing a tail call
81+ // that would collapse this frame into fn's
82+ (void )pad[0 ];
83+ }
5284}// namespace
5385
5486TEST_F (TestHeap, GarbageCollectorDoesNotDeallocateGCPointersOnTheStack)
@@ -72,8 +104,9 @@ TEST_F(TestHeap, GarbageCollectorDeallocatesGCPointersWhenStackFrameIsPopped)
72104 ASSERT_EQ (g_counter, 0 );
73105 m_heap->collect_garbage ();
74106
75- new_stack_frame_function (*m_heap);
107+ call_in_padded_frame ([ this ] { new_stack_frame_function (*m_heap); } );
76108
109+ scrub_dead_stack ();
77110 m_heap->collect_garbage ();
78111
79112 ASSERT_EQ (g_counter, 5 );
@@ -120,8 +153,9 @@ TEST_F(TestHeap, MutuallyReferencingObjectsAreCollected)
120153 int64_t counter = 0 ;
121154 m_heap->garbage_collector ().set_frequency (1 );
122155
123- allocate_cycle_in_popped_frame (*m_heap, counter);
156+ call_in_padded_frame ([&] { allocate_cycle_in_popped_frame (*m_heap, counter); } );
124157
158+ scrub_dead_stack ();
125159 m_heap->collect_garbage ();
126160
127161 ASSERT_EQ (counter, 2 );
0 commit comments