chromium/v8/test/unittests/heap/pool-unittest.cc

// Copyright 2014 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 <map>
#include <optional>

#include "src/base/region-allocator.h"
#include "src/execution/isolate.h"
#include "src/heap/heap-inl.h"
#include "src/heap/memory-allocator.h"
#include "src/heap/spaces-inl.h"
#include "src/utils/ostreams.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace v8 {
namespace internal {

// This is a v8::PageAllocator implementation that decorates provided page
// allocator object with page tracking functionality.
class TrackingPageAllocator : public ::v8::PageAllocator {};

// This test is currently incompatible with the sandbox. Enable it
// once the VirtualAddressSpace interface is stable.
#if !V8_OS_FUCHSIA && !V8_ENABLE_SANDBOX

template <typename TMixin>
class PoolTestMixin : public TMixin {
 public:
  PoolTestMixin();
  ~PoolTestMixin() override;
};

class PoolTest : public                                     //
                 WithInternalIsolateMixin<                  //
                     WithIsolateScopeMixin<                 //
                         WithIsolateMixin<                  //
                             PoolTestMixin<                 //
                                 WithDefaultPlatformMixin<  //
                                     ::testing::Test>>>>> {
 public:
  PoolTest() = default;
  ~PoolTest() override = default;
  PoolTest(const PoolTest&) = delete;
  PoolTest& operator=(const PoolTest&) = delete;

  static void FreeProcessWidePtrComprCageForTesting() {
    IsolateGroup::ReleaseGlobal();
  }

  static void DoMixinSetUp() {
    CHECK_NULL(tracking_page_allocator_);
    old_page_allocator_ = GetPlatformPageAllocator();
    tracking_page_allocator_ = new TrackingPageAllocator(old_page_allocator_);
    CHECK(tracking_page_allocator_->IsEmpty());
    CHECK_EQ(old_page_allocator_,
             SetPlatformPageAllocatorForTesting(tracking_page_allocator_));
    old_sweeping_flag_ = i::v8_flags.concurrent_sweeping;
    i::v8_flags.concurrent_sweeping = false;
#ifndef V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
    // Reinitialize the process-wide pointer cage so it can pick up the
    // TrackingPageAllocator.
    // The pointer cage must be destroyed before the sandbox.
    FreeProcessWidePtrComprCageForTesting();
#ifdef V8_ENABLE_SANDBOX
    // Reinitialze the sandbox so it uses the TrackingPageAllocator.
    GetProcessWideSandbox()->TearDown();
    constexpr bool use_guard_regions = false;
    CHECK(GetProcessWideSandbox()->Initialize(
        tracking_page_allocator_, kSandboxMinimumSize, use_guard_regions));
#endif
    IsolateGroup::InitializeOncePerProcess();
#endif
  }

  static void DoMixinTearDown() {
#ifndef V8_COMPRESS_POINTERS_IN_MULTIPLE_CAGES
    // Free the process-wide cage reservation, otherwise the pages won't be
    // freed until process teardown.
    FreeProcessWidePtrComprCageForTesting();
#endif
#ifdef V8_ENABLE_SANDBOX
    GetProcessWideSandbox()->TearDown();
#endif
    i::v8_flags.concurrent_sweeping = old_sweeping_flag_;
    CHECK(tracking_page_allocator_->IsEmpty());

    // Restore the original v8::PageAllocator and delete the tracking one.
    CHECK_EQ(tracking_page_allocator_,
             SetPlatformPageAllocatorForTesting(old_page_allocator_));
    delete tracking_page_allocator_;
    tracking_page_allocator_ = nullptr;
  }

  Heap* heap() { return isolate()->heap(); }
  MemoryAllocator* allocator() { return heap()->memory_allocator(); }
  MemoryAllocator::Pool* pool() { return allocator()->pool(); }

  TrackingPageAllocator* tracking_page_allocator() {
    return tracking_page_allocator_;
  }

 private:
  static TrackingPageAllocator* tracking_page_allocator_;
  static v8::PageAllocator* old_page_allocator_;
  static bool old_sweeping_flag_;
};

TrackingPageAllocator* PoolTest::tracking_page_allocator_ = nullptr;
v8::PageAllocator* PoolTest::old_page_allocator_ = nullptr;
bool PoolTest::old_sweeping_flag_;

template <typename TMixin>
PoolTestMixin<TMixin>::PoolTestMixin() {
  PoolTest::DoMixinSetUp();
}
template <typename TMixin>
PoolTestMixin<TMixin>::~PoolTestMixin() {
  PoolTest::DoMixinTearDown();
}

// See v8:5945.
TEST_F(PoolTest, UnmapOnTeardown) {
  PageMetadata* page =
      allocator()->AllocatePage(MemoryAllocator::AllocationMode::kRegular,
                                static_cast<PagedSpace*>(heap()->old_space()),
                                Executability::NOT_EXECUTABLE);
  Address chunk_address = page->ChunkAddress();
  EXPECT_NE(nullptr, page);
  const size_t page_size = tracking_page_allocator()->AllocatePageSize();
  tracking_page_allocator()->CheckPagePermissions(chunk_address, page_size,
                                                  PageAllocator::kReadWrite);

  allocator()->Free(MemoryAllocator::FreeMode::kPool, page);
  tracking_page_allocator()->CheckPagePermissions(chunk_address, page_size,
                                                  PageAllocator::kReadWrite);
  pool()->ReleasePooledChunks();
#ifdef V8_COMPRESS_POINTERS
  // In this mode Isolate uses bounded page allocator which allocates pages
  // inside prereserved region. Thus these pages are kept reserved until
  // the Isolate dies.
  tracking_page_allocator()->CheckPagePermissions(
      chunk_address, page_size, PageAllocator::kNoAccess, false);
#else
  tracking_page_allocator()->CheckIsFree(chunk_address, page_size);
#endif  // V8_COMPRESS_POINTERS
}
#endif  // !V8_OS_FUCHSIA && !V8_ENABLE_SANDBOX

}  // namespace internal
}  // namespace v8