cppgc: Add testing API structure

Adds testing API that can only be used after enabling it on a heap.
The call that enables testing is only provided via v8_for_testing or
cppgc_for_testing build targets which protects against misusing from
production code.

Change-Id: I24a8f5543a2bb479481384e2c555d231383e5d12
Bug: chromium:1056170
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2667513
Reviewed-by: Hannes Payer <hpayer@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Omer Katz <omerkatz@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#72503}
This commit is contained in:
Michael Lippautz 2021-02-03 15:11:03 +01:00 committed by Commit Bot
parent 8798b3ef4e
commit e963b636a5
11 changed files with 232 additions and 8 deletions

View File

@ -4419,6 +4419,7 @@ v8_component("v8_libbase") {
if (is_tsan && !build_with_chromium) {
data += [ "tools/sanitizers/tsan_suppressions.txt" ]
# llvm-symbolizer uses libstdc++ from the clang package.
data += [ "//third_party/llvm-build/Release+Asserts/lib/libstdc++.so.6" ]
}
@ -4730,6 +4731,22 @@ v8_source_set("cppgc_base") {
]
}
v8_source_set("cppgc_base_for_testing") {
visibility = [ ":*" ]
sources = [
"include/cppgc/testing.h",
"src/heap/cppgc/testing.cc",
]
configs = [
":internal_config",
":cppgc_base_config",
]
public_deps = [ ":cppgc_base" ]
}
###############################################################################
# Produce a single static library for embedders
#
@ -5066,6 +5083,7 @@ if (is_component_build) {
sources = [ "src/utils/v8dll-main.cc" ]
public_deps = [
":cppgc_base_for_testing",
":torque_base",
":torque_ls_base",
":v8_base",
@ -5082,12 +5100,11 @@ if (is_component_build) {
v8_component("cppgc") {
public_deps = [ ":cppgc_base" ]
configs = [ ":internal_config" ]
if (!cppgc_is_standalone) {
deps = [ ":v8" ]
}
configs = []
public_configs = [ ":external_config" ]
}
@ -5095,9 +5112,12 @@ if (is_component_build) {
v8_component("cppgc_for_testing") {
testonly = true
public_deps = [ ":cppgc_base" ]
public_deps = [
":cppgc_base",
":cppgc_base_for_testing",
]
configs = [ ":internal_config" ]
configs = []
public_configs = [ ":external_config" ]
}
}
@ -5107,7 +5127,7 @@ if (is_component_build) {
public_deps = [ ":v8_cppgc_shared" ]
configs = [ ":internal_config" ]
configs = []
public_configs = [ ":external_config" ]
}
} else {
@ -5124,6 +5144,7 @@ if (is_component_build) {
testonly = true
public_deps = [
":cppgc_base_for_testing",
":torque_base",
":torque_ls_base",
":v8_base",
@ -5148,7 +5169,10 @@ if (is_component_build) {
group("cppgc_for_testing") {
testonly = true
public_deps = [ ":cppgc_base" ]
public_deps = [
":cppgc_base",
":cppgc_base_for_testing",
]
public_configs = [ ":external_config" ]
}

View File

@ -64,6 +64,8 @@ _TEST_CODE_EXCLUDED_PATHS = (
r'src[\\\/]extensions[\\\/]gc-extension\.cc',
# Runtime functions used for testing.
r'src[\\\/]runtime[\\\/]runtime-test\.cc',
# Testing helpers.
r'src[\\\/]heap[\\\/]cppgc[\\\/]testing\.cc',
)

View File

@ -5,6 +5,8 @@
#ifndef INCLUDE_CPPGC_MACROS_H_
#define INCLUDE_CPPGC_MACROS_H_
#include <stddef.h>
#include "cppgc/internal/compiler-specific.h"
namespace cppgc {

69
include/cppgc/testing.h Normal file
View File

@ -0,0 +1,69 @@
// Copyright 2021 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.
#ifndef INCLUDE_CPPGC_TESTING_H_
#define INCLUDE_CPPGC_TESTING_H_
#include "cppgc/common.h"
#include "cppgc/macros.h"
#include "v8config.h" // NOLINT(build/include_directory)
namespace cppgc {
class HeapHandle;
/**
* Namespace contains testing helpers.
* Helpers are only enabled after invoking `Heap::EnableTestingAPIsForTesting()`
and will crash otherwise.
*/
namespace testing {
/**
* Testing helper used to acces heap internals.
* Only enabled after invoking `EnableTestingAPIsForTesting()`.
*/
class V8_EXPORT Heap final {
public:
/**
* Enables testing APIs that can be found in the corresponding `testing`
* namespace.
*
* \param heap_handle
*/
static void EnableTestingAPIsForTesting(HeapHandle& heap_handle);
};
/**
* Overrides the state of the stack with the provided value. Takes precedence
* over other parameters that set the stack state. Must no be nested.
*/
class V8_EXPORT V8_NODISCARD OverrideEmbedderStackStateScope final {
CPPGC_STACK_ALLOCATED();
public:
/**
* Constructs a scoped object that automatically enters and leaves the scope.
*
* \param heap_handle The corresponding heap.
*/
explicit OverrideEmbedderStackStateScope(HeapHandle& heap_handle,
EmbedderStackState state);
~OverrideEmbedderStackStateScope();
OverrideEmbedderStackStateScope(const OverrideEmbedderStackStateScope&) =
delete;
OverrideEmbedderStackStateScope& operator=(
const OverrideEmbedderStackStateScope&) = delete;
private:
HeapHandle& heap_handle_;
};
} // namespace testing
} // namespace cppgc
#endif // INCLUDE_CPPGC_TESTING_H_

View File

@ -261,6 +261,9 @@ void CppHeap::EnterFinalPause(EmbedderStackState stack_state) {
cppgc::internal::StatsCollector::EnabledScope stats_scope(
AsBase(), cppgc::internal::StatsCollector::kAtomicMark);
in_atomic_pause_ = true;
if (override_stack_state_) {
stack_state = *override_stack_state_;
}
marker_->EnterAtomicPause(stack_state);
if (compactor_.CancelIfShouldNotCompact(cppgc::Heap::MarkingType::kAtomic,
stack_state)) {

View File

@ -53,6 +53,20 @@ class ObjectSizeCounter : private HeapVisitor<ObjectSizeCounter> {
} // namespace
// static
HeapBase& HeapBase::ForTesting(cppgc::HeapHandle& heap_handle) {
auto& heap = From(heap_handle);
CHECK(heap.TestingEnabled());
return heap;
}
// static
const HeapBase& HeapBase::ForTesting(const cppgc::HeapHandle& heap_handle) {
const auto& heap = From(heap_handle);
CHECK(heap.TestingEnabled());
return heap;
}
HeapBase::HeapBase(
std::shared_ptr<cppgc::Platform> platform,
const std::vector<std::unique_ptr<CustomSpaceBase>>& custom_spaces,

View File

@ -36,6 +36,10 @@ class DisallowGarbageCollectionScope;
class NoGarbageCollectionScope;
} // namespace subtle
namespace testing {
class OverrideEmbedderStackStateScope;
} // namespace testing
class Platform;
class V8_EXPORT HeapHandle {
@ -66,6 +70,9 @@ class V8_EXPORT_PRIVATE HeapBase : public cppgc::HeapHandle {
return static_cast<const HeapBase&>(heap_handle);
}
static HeapBase& ForTesting(cppgc::HeapHandle& heap_handle);
static const HeapBase& ForTesting(const cppgc::HeapHandle& heap_handle);
HeapBase(std::shared_ptr<cppgc::Platform> platform,
const std::vector<std::unique_ptr<CustomSpaceBase>>& custom_spaces,
StackSupport stack_support,
@ -153,6 +160,9 @@ class V8_EXPORT_PRIVATE HeapBase : public cppgc::HeapHandle {
bool in_disallow_gc_scope() const { return disallow_gc_scope_ > 0; }
bool in_atomic_pause() const { return in_atomic_pause_; }
void EnableTestingAPIsForTesting() { testing_enabled_ = true; }
bool TestingEnabled() const { return testing_enabled_; }
protected:
virtual void FinalizeIncrementalGarbageCollectionIfNeeded(
cppgc::Heap::StackState) = 0;
@ -190,13 +200,16 @@ class V8_EXPORT_PRIVATE HeapBase : public cppgc::HeapHandle {
size_t disallow_gc_scope_ = 0;
const StackSupport stack_support_;
std::unique_ptr<EmbedderStackState> override_stack_state_;
bool in_atomic_pause_ = false;
bool testing_enabled_ = false;
friend class MarkerBase::IncrementalMarkingTask;
friend class testing::TestWithHeap;
friend class cppgc::subtle::DisallowGarbageCollectionScope;
friend class cppgc::subtle::NoGarbageCollectionScope;
friend class cppgc::testing::OverrideEmbedderStackStateScope;
};
} // namespace internal

View File

@ -173,12 +173,15 @@ void Heap::FinalizeGarbageCollection(Config::StackState stack_state) {
DCHECK(!in_no_gc_scope());
CHECK(!in_disallow_gc_scope());
config_.stack_state = stack_state;
if (override_stack_state_) {
config_.stack_state = *override_stack_state_;
}
in_atomic_pause_ = true;
{
// This guards atomic pause marking, meaning that no internal method or
// external callbacks are allowed to allocate new objects.
cppgc::subtle::DisallowGarbageCollectionScope no_gc_scope(*this);
marker_->FinishMarking(stack_state);
marker_->FinishMarking(config_.stack_state);
}
{
// Pre finalizers are forbidden from allocating objects.
@ -189,7 +192,7 @@ void Heap::FinalizeGarbageCollection(Config::StackState stack_state) {
// TODO(chromium:1056170): replace build flag with dedicated flag.
#if DEBUG
MarkingVerifier verifier(*this);
verifier.Run(stack_state);
verifier.Run(config_.stack_state);
#endif
subtle::NoGarbageCollectionScope no_gc(*this);

31
src/heap/cppgc/testing.cc Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2021 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 "include/cppgc/testing.h"
#include "src/base/logging.h"
#include "src/heap/cppgc/heap-base.h"
namespace cppgc {
namespace testing {
void Heap::EnableTestingAPIsForTesting(HeapHandle& heap_handle) {
internal::HeapBase::From(heap_handle).EnableTestingAPIsForTesting();
}
OverrideEmbedderStackStateScope::OverrideEmbedderStackStateScope(
HeapHandle& heap_handle, EmbedderStackState state)
: heap_handle_(heap_handle) {
auto& heap = internal::HeapBase::ForTesting(heap_handle_);
CHECK_NULL(heap.override_stack_state_.get());
heap.override_stack_state_ = std::make_unique<EmbedderStackState>(state);
}
OverrideEmbedderStackStateScope::~OverrideEmbedderStackStateScope() {
auto& heap = internal::HeapBase::ForTesting(heap_handle_);
heap.override_stack_state_.reset();
}
} // namespace testing
} // namespace cppgc

View File

@ -119,6 +119,7 @@ v8_source_set("cppgc_unittests_sources") {
"heap/cppgc/sweeper-unittest.cc",
"heap/cppgc/test-platform.cc",
"heap/cppgc/test-platform.h",
"heap/cppgc/testing-unittest.cc",
"heap/cppgc/tests.cc",
"heap/cppgc/tests.h",
"heap/cppgc/visitor-unittest.cc",

View File

@ -0,0 +1,62 @@
// Copyright 2020 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 "include/cppgc/testing.h"
#include "include/cppgc/allocation.h"
#include "include/cppgc/garbage-collected.h"
#include "include/cppgc/persistent.h"
#include "test/unittests/heap/cppgc/tests.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
class TestingTest : public testing::TestWithHeap {};
class GCed : public GarbageCollected<GCed> {
public:
void Trace(Visitor*) const {}
};
} // namespace
TEST_F(TestingTest, EnableTesting) {
EXPECT_FALSE(internal::Heap::From(GetHeap())->TestingEnabled());
cppgc::testing::Heap::EnableTestingAPIsForTesting(GetHeap()->GetHeapHandle());
EXPECT_TRUE(internal::Heap::From(GetHeap())->TestingEnabled());
}
TEST_F(TestingTest, OverrideEmbedderStackStateScope) {
{
auto* gced = MakeGarbageCollected<GCed>(GetHeap()->GetAllocationHandle());
WeakPersistent<GCed> weak{gced};
internal::Heap::From(GetHeap())->CollectGarbage(
Heap::Config::PreciseAtomicConfig());
EXPECT_FALSE(weak);
}
cppgc::testing::Heap::EnableTestingAPIsForTesting(GetHeap()->GetHeapHandle());
{
auto* gced = MakeGarbageCollected<GCed>(GetHeap()->GetAllocationHandle());
WeakPersistent<GCed> weak{gced};
cppgc::testing::OverrideEmbedderStackStateScope override_stack(
GetHeap()->GetHeapHandle(),
EmbedderStackState::kMayContainHeapPointers);
internal::Heap::From(GetHeap())->CollectGarbage(
Heap::Config::PreciseAtomicConfig());
EXPECT_TRUE(weak);
}
{
auto* gced = MakeGarbageCollected<GCed>(GetHeap()->GetAllocationHandle());
WeakPersistent<GCed> weak{gced};
cppgc::testing::OverrideEmbedderStackStateScope override_stack(
GetHeap()->GetHeapHandle(), EmbedderStackState::kNoHeapPointers);
internal::Heap::From(GetHeap())->CollectGarbage(
Heap::Config::ConservativeAtomicConfig());
EXPECT_FALSE(weak);
}
}
} // namespace internal
} // namespace cppgc