Skip to content

Commit 525f0ed

Browse files
authored
Merge branch 'main' into stdin-support
2 parents 08c98cc + 23886c0 commit 525f0ed

2 files changed

Lines changed: 52 additions & 2 deletions

File tree

src/memory/GarbageCollector_tests.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#include "GarbageCollector.hpp"
22
#include "Heap_test.hpp"
33

4+
#include <cstdint>
5+
#include <cstring>
6+
47
namespace {
58

69
static 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

5486
TEST_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);

src/testing/main.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@
55

66
#include <cxxopts.hpp>
77

8+
#if defined(__SANITIZE_ADDRESS__)
9+
#define PYTHON_ASAN_ENABLED
10+
#elif defined(__has_feature)
11+
#if __has_feature(address_sanitizer)
12+
#define PYTHON_ASAN_ENABLED
13+
#endif
14+
#endif
15+
16+
#ifdef PYTHON_ASAN_ENABLED
17+
// The garbage collector finds roots by conservatively scanning the machine
18+
// stack. ASan's use-after-return detection relocates locals to a heap-backed
19+
// fake stack that the scan never sees, so live GC pointers go unnoticed and
20+
// stale ones linger, breaking the collector's reachability tests.
21+
extern "C" const char *__asan_default_options() { return "detect_stack_use_after_return=0"; }
22+
#endif
23+
824
class PythonVMEnvironment : public ::testing::Environment
925
{
1026
char **m_argv;

0 commit comments

Comments
 (0)