test-allocation.cc 6.79 KB
Newer Older
1 2 3 4 5 6
// Copyright 2017 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 <stdlib.h>
#include <string.h>

7 8 9 10 11 12
#if V8_OS_POSIX
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>  // NOLINT
#endif

13
#include "src/init/v8.h"
14 15 16 17 18 19 20 21 22

#include "test/cctest/cctest.h"

using v8::internal::AccountingAllocator;

using v8::IdleTask;
using v8::Isolate;
using v8::Task;

23
#include "src/utils/allocation.h"
24 25
#include "src/zone/accounting-allocator.h"

26
// ASAN isn't configured to return nullptr, so skip all of these tests.
27 28 29
#if !defined(V8_USE_ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
    !defined(THREAD_SANITIZER)

30 31
namespace {

32 33
// Implementation of v8::Platform that can register OOM callbacks.
class AllocationPlatform : public TestPlatform {
34
 public:
35 36 37 38
  AllocationPlatform() {
    current_platform = this;
    // Now that it's completely constructed, make this the current platform.
    i::V8::SetPlatformForTesting(this);
39
  }
40
  ~AllocationPlatform() override = default;
41

42
  void OnCriticalMemoryPressure() override { oom_callback_called = true; }
43

44 45 46 47 48
  bool OnCriticalMemoryPressure(size_t length) override {
    oom_callback_called = true;
    return true;
  }

49
  static AllocationPlatform* current_platform;
50 51 52
  bool oom_callback_called = false;
};

53
AllocationPlatform* AllocationPlatform::current_platform = nullptr;
54 55

bool DidCallOnCriticalMemoryPressure() {
56 57
  return AllocationPlatform::current_platform &&
         AllocationPlatform::current_platform->oom_callback_called;
58 59
}

60 61 62 63 64 65 66 67
// No OS should be able to malloc/new this number of bytes. Generate enough
// random values in the address space to get a very large fraction of it. Using
// even larger values is that overflow from rounding or padding can cause the
// allocations to succeed somehow.
size_t GetHugeMemoryAmount() {
  static size_t huge_memory = 0;
  if (!huge_memory) {
    for (int i = 0; i < 100; i++) {
68
      huge_memory |= bit_cast<size_t>(v8::internal::GetRandomMmapAddr());
69 70 71 72 73 74 75 76
    }
    // Make it larger than the available address space.
    huge_memory *= 2;
    CHECK_NE(0, huge_memory);
  }
  return huge_memory;
}

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
void OnMallocedOperatorNewOOM(const char* location, const char* message) {
  // exit(0) if the OOM callback was called and location matches expectation.
  if (DidCallOnCriticalMemoryPressure())
    exit(strcmp(location, "Malloced operator new"));
  exit(1);
}

void OnNewArrayOOM(const char* location, const char* message) {
  // exit(0) if the OOM callback was called and location matches expectation.
  if (DidCallOnCriticalMemoryPressure()) exit(strcmp(location, "NewArray"));
  exit(1);
}

void OnAlignedAllocOOM(const char* location, const char* message) {
  // exit(0) if the OOM callback was called and location matches expectation.
  if (DidCallOnCriticalMemoryPressure()) exit(strcmp(location, "AlignedAlloc"));
  exit(1);
}

}  // namespace

TEST(AccountingAllocatorOOM) {
99
  AllocationPlatform platform;
100 101
  v8::internal::AccountingAllocator allocator;
  CHECK(!platform.oom_callback_called);
102 103
  v8::internal::Segment* result =
      allocator.AllocateSegment(GetHugeMemoryAmount());
104 105
  // On a few systems, allocation somehow succeeds.
  CHECK_EQ(result == nullptr, platform.oom_callback_called);
106 107
}

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
TEST(AccountingAllocatorCurrentAndMax) {
  AllocationPlatform platform;
  v8::internal::AccountingAllocator allocator;
  static constexpr size_t kAllocationSizes[] = {51, 231, 27};
  std::vector<v8::internal::Segment*> segments;
  CHECK_EQ(0, allocator.GetCurrentMemoryUsage());
  CHECK_EQ(0, allocator.GetMaxMemoryUsage());
  size_t expected_current = 0;
  size_t expected_max = 0;
  for (size_t size : kAllocationSizes) {
    segments.push_back(allocator.AllocateSegment(size));
    CHECK_NOT_NULL(segments.back());
    CHECK_EQ(size, segments.back()->total_size());
    expected_current += size;
    if (expected_current > expected_max) expected_max = expected_current;
    CHECK_EQ(expected_current, allocator.GetCurrentMemoryUsage());
    CHECK_EQ(expected_max, allocator.GetMaxMemoryUsage());
  }
  for (auto* segment : segments) {
    expected_current -= segment->total_size();
    allocator.ReturnSegment(segment);
    CHECK_EQ(expected_current, allocator.GetCurrentMemoryUsage());
  }
  CHECK_EQ(expected_max, allocator.GetMaxMemoryUsage());
  CHECK_EQ(0, allocator.GetCurrentMemoryUsage());
  CHECK(!platform.oom_callback_called);
}

136
TEST(MallocedOperatorNewOOM) {
137
  AllocationPlatform platform;
138 139
  CHECK(!platform.oom_callback_called);
  CcTest::isolate()->SetFatalErrorHandler(OnMallocedOperatorNewOOM);
140 141
  // On failure, this won't return, since a Malloced::New failure is fatal.
  // In that case, behavior is checked in OnMallocedOperatorNewOOM before exit.
142
  void* result = v8::internal::Malloced::operator new(GetHugeMemoryAmount());
143 144
  // On a few systems, allocation somehow succeeds.
  CHECK_EQ(result == nullptr, platform.oom_callback_called);
145 146 147
}

TEST(NewArrayOOM) {
148
  AllocationPlatform platform;
149 150 151
  CHECK(!platform.oom_callback_called);
  CcTest::isolate()->SetFatalErrorHandler(OnNewArrayOOM);
  // On failure, this won't return, since a NewArray failure is fatal.
152 153 154 155
  // In that case, behavior is checked in OnNewArrayOOM before exit.
  int8_t* result = v8::internal::NewArray<int8_t>(GetHugeMemoryAmount());
  // On a few systems, allocation somehow succeeds.
  CHECK_EQ(result == nullptr, platform.oom_callback_called);
156 157 158
}

TEST(AlignedAllocOOM) {
159
  AllocationPlatform platform;
160 161 162
  CHECK(!platform.oom_callback_called);
  CcTest::isolate()->SetFatalErrorHandler(OnAlignedAllocOOM);
  // On failure, this won't return, since an AlignedAlloc failure is fatal.
163 164
  // In that case, behavior is checked in OnAlignedAllocOOM before exit.
  void* result = v8::internal::AlignedAlloc(GetHugeMemoryAmount(),
165
                                            v8::internal::AllocatePageSize());
166 167
  // On a few systems, allocation somehow succeeds.
  CHECK_EQ(result == nullptr, platform.oom_callback_called);
168 169 170
}

TEST(AllocVirtualMemoryOOM) {
171
  AllocationPlatform platform;
172
  CHECK(!platform.oom_callback_called);
173 174
  v8::internal::VirtualMemory result(v8::internal::GetPlatformPageAllocator(),
                                     GetHugeMemoryAmount(), nullptr);
175
  // On a few systems, allocation somehow succeeds.
176
  CHECK_IMPLIES(!result.IsReserved(), platform.oom_callback_called);
177 178 179
}

TEST(AlignedAllocVirtualMemoryOOM) {
180
  AllocationPlatform platform;
181
  CHECK(!platform.oom_callback_called);
182 183 184
  v8::internal::VirtualMemory result(v8::internal::GetPlatformPageAllocator(),
                                     GetHugeMemoryAmount(), nullptr,
                                     v8::internal::AllocatePageSize());
185
  // On a few systems, allocation somehow succeeds.
186
  CHECK_IMPLIES(!result.IsReserved(), platform.oom_callback_called);
187 188 189 190
}

#endif  // !defined(V8_USE_ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) &&
        // !defined(THREAD_SANITIZER)