[heap] Add StackStartMarker for cppgc

For conservative stack scanning we currently scan for references up
until the stack top. But this includes stack frames like that of
_start which definitely do not have pointers into the cppgc heap.

This CL adds a StackStartMarker class which embedders can optionally
use to decrease the stack height considered during conservative
stack scanning. This reduces the chance of false positives.

Bug: 492198560
Change-Id: Ic9a7d428684bb835d372873beb821e412e131246
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7666257
Commit-Queue: Dominik Inführ <dinfuehr@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#105816}
This commit is contained in:
Dominik Inführ authored and V8 LUCI CQ committed 2026-03-16 05:40:16 -07:00
1 parent 17898c8b37
commit b3cb54c7e6
13 files changed
+151 -16

No files matched your search

+18 -1
View File
@@ -32,6 +32,18 @@ namespace internal {
class Heap;
} // namespace internal
/**
* A marker that captures the current stack start address.
*/
class V8_EXPORT StackStartMarker {
public:
StackStartMarker() : stack_start_(__builtin_frame_address(0)) {}
void* stack_start() const { return stack_start_; }
private:
void* stack_start_;
};
class V8_EXPORT Heap {
public:
/**
@@ -151,8 +163,13 @@ class V8_EXPORT Heap {
* GC scheduler follows.
*/
ResourceConstraints resource_constraints;
};
/**
* Optional marker representing the stack start of the thread creating the
* heap.
*/
std::optional<StackStartMarker> stack_start_marker = std::nullopt;
};
/**
* Creates a new heap that can be used for object allocation.
*
+5
View File
@@ -53,6 +53,11 @@ struct V8_EXPORT CppHeapCreateParams {
*/
cppgc::Heap::SweepingType sweeping_support =
cppgc::Heap::SweepingType::kIncrementalAndConcurrent;
/**
* Optional marker representing the stack start of the thread creating the
* heap.
*/
std::optional<cppgc::StackStartMarker> stack_start_marker = std::nullopt;
};
/**
+5 -3
View File
@@ -34,9 +34,9 @@ using StackVisitorCallback = void (*)(StackVisitor*);
// of relevant GC stack regions where interesting pointers can be found.
class V8_EXPORT_PRIVATE Stack final {
public:
// Sets the start of the stack to the current stack start.
void SetStackStart() {
current_segment_.start = v8::base::Stack::GetStackStart();
// Sets the start of the stack to the provided stack start.
void SetStackStart(void* stack_start) {
current_segment_.start = stack_start;
#ifdef V8_USE_ADDRESS_SANITIZER
current_segment_.asan_fake_stack = __asan_get_current_fake_stack();
#endif // V8_USE_ADDRESS_SANITIZER
@@ -45,6 +45,8 @@ class V8_EXPORT_PRIVATE Stack final {
#endif // V8_USE_SAFE_STACK
}
const void* stack_start() const { return current_segment_.start; }
// Returns true if |slot| is part of the stack and false otherwise.
// It is a static method, ignoring the set stack start and marker, but it
// considers the ASAN stack and SafeStack.
+6 -5
View File
@@ -103,9 +103,9 @@ class MinorGCHeapGrowing
// static
std::unique_ptr<CppHeap> CppHeap::Create(v8::Platform* platform,
const CppHeapCreateParams& params) {
return std::make_unique<internal::CppHeap>(platform, params.custom_spaces,
params.marking_support,
params.sweeping_support);
return std::make_unique<internal::CppHeap>(
platform, params.custom_spaces, params.marking_support,
params.sweeping_support, params.stack_start_marker);
}
cppgc::AllocationHandle& CppHeap::GetAllocationHandle() {
@@ -517,12 +517,13 @@ CppHeap::CppHeap(
v8::Platform* platform,
const std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>& custom_spaces,
cppgc::Heap::MarkingType marking_support,
cppgc::Heap::SweepingType sweeping_support)
cppgc::Heap::SweepingType sweeping_support,
std::optional<cppgc::StackStartMarker> stack_start_marker)
: cppgc::internal::HeapBase(
std::make_shared<CppgcPlatformAdapter>(platform), custom_spaces,
cppgc::internal::HeapBase::StackSupport::
kSupportsConservativeStackScan,
marking_support, sweeping_support, *this),
marking_support, sweeping_support, *this, stack_start_marker),
minor_gc_heap_growing_(
std::make_unique<MinorGCHeapGrowing>(*stats_collector())),
cross_heap_remembered_set_(*this) {
+2 -1
View File
@@ -110,7 +110,8 @@ class V8_EXPORT_PRIVATE CppHeap final
CppHeap(v8::Platform*,
const std::vector<std::unique_ptr<cppgc::CustomSpaceBase>>&,
cppgc::Heap::MarkingType, cppgc::Heap::SweepingType);
cppgc::Heap::MarkingType, cppgc::Heap::SweepingType,
std::optional<cppgc::StackStartMarker>);
~CppHeap() final;
CppHeap(const CppHeap&) = delete;
+7 -2
View File
@@ -100,7 +100,8 @@ HeapBase::HeapBase(
std::shared_ptr<cppgc::Platform> platform,
const std::vector<std::unique_ptr<CustomSpaceBase>>& custom_spaces,
StackSupport stack_support, MarkingType marking_support,
SweepingType sweeping_support, GarbageCollector& garbage_collector)
SweepingType sweeping_support, GarbageCollector& garbage_collector,
std::optional<cppgc::StackStartMarker> stack_start_marker)
: raw_heap_(this, custom_spaces),
platform_(std::move(platform)),
oom_handler_(std::make_unique<FatalOutOfMemoryHandler>(this)),
@@ -129,7 +130,11 @@ HeapBase::HeapBase(
sweeping_support_(sweeping_support) {
stats_collector_->RegisterObserver(
&allocation_observer_for_PROCESS_HEAP_STATISTICS_);
stack_->SetStackStart();
if (stack_start_marker) {
stack_->SetStackStart(stack_start_marker->stack_start());
} else {
stack_->SetStackStart(v8::base::Stack::GetStackStart());
}
}
HeapBase::~HeapBase() = default;
+2 -1
View File
@@ -95,7 +95,8 @@ class V8_EXPORT_PRIVATE HeapBase : public cppgc::HeapHandle {
HeapBase(std::shared_ptr<cppgc::Platform> platform,
const std::vector<std::unique_ptr<CustomSpaceBase>>& custom_spaces,
StackSupport stack_support, MarkingType marking_support,
SweepingType sweeping_support, GarbageCollector& garbage_collector);
SweepingType sweeping_support, GarbageCollector& garbage_collector,
std::optional<cppgc::StackStartMarker> stack_start_marker);
virtual ~HeapBase();
HeapBase(const HeapBase&) = delete;
+2 -1
View File
@@ -75,7 +75,8 @@ void CheckConfig(GCConfig config, HeapBase::MarkingType marking_support,
Heap::Heap(std::shared_ptr<cppgc::Platform> platform,
cppgc::Heap::HeapOptions options)
: HeapBase(platform, options.custom_spaces, options.stack_support,
options.marking_support, options.sweeping_support, gc_invoker_),
options.marking_support, options.sweeping_support, gc_invoker_,
options.stack_start_marker),
gc_invoker_(this, platform_.get(), options.stack_support),
growing_(&gc_invoker_, stats_collector_.get(),
options.resource_constraints, options.marking_support,
+2 -1
View File
@@ -25,6 +25,7 @@
#include "src/base/once.h"
#include "src/base/platform/memory.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/platform.h"
#include "src/base/platform/time.h"
#include "src/base/utils/random-number-generator.h"
#include "src/builtins/accessors.h"
@@ -6395,7 +6396,7 @@ void Heap::SetStackStart() {
// If no main thread local heap has been set up (we're still in the
// deserialization process), we don't need to set the stack start.
if (main_thread_local_heap_ == nullptr) return;
stack().SetStackStart();
stack().SetStackStart(v8::base::Stack::GetStackStart());
}
::heap::base::Stack& Heap::stack() {
+3
View File
@@ -155,6 +155,7 @@ v8_cluster_source_set("cppgc_unittests_sources") {
"heap/cppgc/prefinalizer-unittest.cc",
"heap/cppgc/sanitizer-unittest.cc",
"heap/cppgc/source-location-unittest.cc",
"heap/cppgc/stack-start-marker-unittest.cc",
"heap/cppgc/stack-unittest.cc",
"heap/cppgc/stats-collector-scopes-unittest.cc",
"heap/cppgc/stats-collector-unittest.cc",
@@ -370,6 +371,7 @@ v8_cluster_source_set("v8_unittests_sources") {
"heap/code-range-unittest.cc",
"heap/collection-barrier-unittest.cc",
"heap/conservative-stack-visitor-unittest.cc",
"heap/cppgc-js/cpp-heap-stack-start-marker-unittest.cc",
"heap/cppgc-js/embedder-roots-handler-unittest.cc",
"heap/cppgc-js/traced-reference-unittest.cc",
"heap/cppgc-js/unified-heap-snapshot-unittest.cc",
@@ -543,6 +545,7 @@ v8_cluster_source_set("v8_unittests_sources") {
"compiler/run-variables-unittest.cc",
"compiler/linear-scheduler-unittest.cc",
"compiler/linkage-unittest.cc",
"heap/cppgc-js/cpp-heap-stack-start-marker-unittest.cc",
"heap/cppgc-js/embedder-roots-handler-unittest.cc",
"heap/cppgc-js/unified-heap-snapshot-unittest.cc",
"heap/cppgc-js/unified-heap-unittest.cc",
@@ -0,0 +1,48 @@
// Copyright 2026 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "include/libplatform/libplatform.h"
#include "include/v8-cppgc.h"
#include "src/heap/cppgc-js/cpp-heap.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8 {
namespace internal {
namespace {
class CppHeapStackStartMarkerTest : public TestWithPlatform {};
} // namespace
TEST_F(CppHeapStackStartMarkerTest, StackStartMarkerIsUsed) {
cppgc::StackStartMarker marker;
CppHeapCreateParams params({});
params.stack_start_marker = marker;
auto cpp_heap = CppHeap::Create(platform(), params);
CppHeap* internal_heap = CppHeap::From(cpp_heap.get());
EXPECT_EQ(marker.stack_start(), internal_heap->stack()->stack_start());
EXPECT_LT(internal_heap->stack()->stack_start(),
v8::base::Stack::GetStackStart());
}
TEST_F(CppHeapStackStartMarkerTest, DefaultWithoutStackStartMarker) {
CppHeapCreateParams params({});
EXPECT_FALSE(params.stack_start_marker.has_value());
auto cpp_heap = CppHeap::Create(platform(), params);
CppHeap* internal_heap = CppHeap::From(cpp_heap.get());
// Without a marker, it should use the default stack start.
EXPECT_EQ(v8::base::Stack::GetStackStart(),
internal_heap->stack()->stack_start());
}
} // namespace internal
} // namespace v8
@@ -0,0 +1,48 @@
// Copyright 2026 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "include/cppgc/heap.h"
#include "src/heap/base/stack.h"
#include "src/heap/cppgc/heap.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class StackStartMarkerTest : public testing::TestWithPlatform {};
} // namespace
TEST_F(StackStartMarkerTest, StackStartMarkerIsUsed) {
StackStartMarker marker;
Heap::HeapOptions options;
options.stack_start_marker = marker;
auto heap = Heap::Create(GetPlatformHandle(), std::move(options));
Heap* internal_heap = Heap::From(heap.get());
EXPECT_EQ(marker.stack_start(), internal_heap->stack()->stack_start());
EXPECT_LT(internal_heap->stack()->stack_start(),
v8::base::Stack::GetStackStart());
}
TEST_F(StackStartMarkerTest, DefaultWithoutStackStartMarker) {
Heap::HeapOptions options;
EXPECT_FALSE(options.stack_start_marker.has_value());
auto heap = Heap::Create(GetPlatformHandle(), std::move(options));
Heap* internal_heap = Heap::From(heap.get());
// Without a marker, it should use the default stack start.
EXPECT_EQ(v8::base::Stack::GetStackStart(),
internal_heap->stack()->stack_start());
}
} // namespace internal
} // namespace cppgc
+3 -1
View File
@@ -24,7 +24,9 @@ namespace {
class GCStackTest : public ::testing::Test {
public:
GCStackTest() : stack_(std::make_unique<Stack>()) { stack_->SetStackStart(); }
GCStackTest() : stack_(std::make_unique<Stack>()) {
stack_->SetStackStart(v8::base::Stack::GetStackStart());
}
Stack* GetStack() const { return stack_.get(); }