Line data Source code
1 : // Copyright 2017 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 : #include <stdlib.h>
5 : #include <string.h>
6 :
7 : #if V8_OS_POSIX
8 : #include <setjmp.h>
9 : #include <signal.h>
10 : #include <unistd.h> // NOLINT
11 : #endif
12 :
13 : #include "src/v8.h"
14 :
15 : #include "test/cctest/cctest.h"
16 :
17 : using v8::internal::AccountingAllocator;
18 :
19 : using v8::IdleTask;
20 : using v8::Isolate;
21 : using v8::Task;
22 :
23 : #include "src/allocation.h"
24 : #include "src/zone/accounting-allocator.h"
25 :
26 : // ASAN isn't configured to return nullptr, so skip all of these tests.
27 : #if !defined(V8_USE_ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \
28 : !defined(THREAD_SANITIZER)
29 :
30 : namespace {
31 :
32 : // Implementation of v8::Platform that can register OOM callbacks.
33 : class AllocationPlatform : public TestPlatform {
34 : public:
35 70 : AllocationPlatform() {
36 35 : current_platform = this;
37 : // Now that it's completely constructed, make this the current platform.
38 35 : i::V8::SetPlatformForTesting(this);
39 35 : }
40 40 : ~AllocationPlatform() override = default;
41 :
42 5 : void OnCriticalMemoryPressure() override { oom_callback_called = true; }
43 :
44 50 : bool OnCriticalMemoryPressure(size_t length) override {
45 50 : oom_callback_called = true;
46 50 : return true;
47 : }
48 :
49 : static AllocationPlatform* current_platform;
50 : bool oom_callback_called = false;
51 : };
52 :
53 : AllocationPlatform* AllocationPlatform::current_platform = nullptr;
54 :
55 : bool DidCallOnCriticalMemoryPressure() {
56 30 : return AllocationPlatform::current_platform &&
57 15 : AllocationPlatform::current_platform->oom_callback_called;
58 : }
59 :
60 : // No OS should be able to malloc/new this number of bytes. Generate enough
61 : // random values in the address space to get a very large fraction of it. Using
62 : // even larger values is that overflow from rounding or padding can cause the
63 : // allocations to succeed somehow.
64 30 : size_t GetHugeMemoryAmount() {
65 : static size_t huge_memory = 0;
66 30 : if (!huge_memory) {
67 6030 : for (int i = 0; i < 100; i++) {
68 3000 : huge_memory |= bit_cast<size_t>(v8::internal::GetRandomMmapAddr());
69 : }
70 : // Make it larger than the available address space.
71 30 : huge_memory *= 2;
72 30 : CHECK_NE(0, huge_memory);
73 : }
74 30 : return huge_memory;
75 : }
76 :
77 5 : void OnMallocedOperatorNewOOM(const char* location, const char* message) {
78 : // exit(0) if the OOM callback was called and location matches expectation.
79 5 : if (DidCallOnCriticalMemoryPressure())
80 5 : exit(strcmp(location, "Malloced operator new"));
81 0 : exit(1);
82 : }
83 :
84 5 : void OnNewArrayOOM(const char* location, const char* message) {
85 : // exit(0) if the OOM callback was called and location matches expectation.
86 5 : if (DidCallOnCriticalMemoryPressure()) exit(strcmp(location, "NewArray"));
87 0 : exit(1);
88 : }
89 :
90 5 : void OnAlignedAllocOOM(const char* location, const char* message) {
91 : // exit(0) if the OOM callback was called and location matches expectation.
92 5 : if (DidCallOnCriticalMemoryPressure()) exit(strcmp(location, "AlignedAlloc"));
93 0 : exit(1);
94 : }
95 :
96 : } // namespace
97 :
98 26644 : TEST(AccountingAllocatorOOM) {
99 5 : AllocationPlatform platform;
100 10 : v8::internal::AccountingAllocator allocator;
101 5 : CHECK(!platform.oom_callback_called);
102 : v8::internal::Segment* result =
103 5 : allocator.AllocateSegment(GetHugeMemoryAmount());
104 : // On a few systems, allocation somehow succeeds.
105 5 : CHECK_EQ(result == nullptr, platform.oom_callback_called);
106 5 : }
107 :
108 26644 : TEST(AccountingAllocatorCurrentAndMax) {
109 5 : AllocationPlatform platform;
110 10 : v8::internal::AccountingAllocator allocator;
111 : static constexpr size_t kAllocationSizes[] = {51, 231, 27};
112 : std::vector<v8::internal::Segment*> segments;
113 5 : CHECK_EQ(0, allocator.GetCurrentMemoryUsage());
114 5 : CHECK_EQ(0, allocator.GetMaxMemoryUsage());
115 : size_t expected_current = 0;
116 : size_t expected_max = 0;
117 35 : for (size_t size : kAllocationSizes) {
118 30 : segments.push_back(allocator.AllocateSegment(size));
119 15 : CHECK_NOT_NULL(segments.back());
120 15 : CHECK_EQ(size, segments.back()->total_size());
121 15 : expected_current += size;
122 15 : if (expected_current > expected_max) expected_max = expected_current;
123 15 : CHECK_EQ(expected_current, allocator.GetCurrentMemoryUsage());
124 15 : CHECK_EQ(expected_max, allocator.GetMaxMemoryUsage());
125 : }
126 20 : for (auto* segment : segments) {
127 15 : expected_current -= segment->total_size();
128 15 : allocator.ReturnSegment(segment);
129 15 : CHECK_EQ(expected_current, allocator.GetCurrentMemoryUsage());
130 : }
131 5 : CHECK_EQ(expected_max, allocator.GetMaxMemoryUsage());
132 5 : CHECK_EQ(0, allocator.GetCurrentMemoryUsage());
133 5 : CHECK(!platform.oom_callback_called);
134 5 : }
135 :
136 26644 : TEST(MallocedOperatorNewOOM) {
137 5 : AllocationPlatform platform;
138 5 : CHECK(!platform.oom_callback_called);
139 5 : CcTest::isolate()->SetFatalErrorHandler(OnMallocedOperatorNewOOM);
140 : // On failure, this won't return, since a Malloced::New failure is fatal.
141 : // In that case, behavior is checked in OnMallocedOperatorNewOOM before exit.
142 5 : void* result = v8::internal::Malloced::New(GetHugeMemoryAmount());
143 : // On a few systems, allocation somehow succeeds.
144 0 : CHECK_EQ(result == nullptr, platform.oom_callback_called);
145 0 : }
146 :
147 26644 : TEST(NewArrayOOM) {
148 5 : AllocationPlatform platform;
149 5 : CHECK(!platform.oom_callback_called);
150 5 : CcTest::isolate()->SetFatalErrorHandler(OnNewArrayOOM);
151 : // On failure, this won't return, since a NewArray failure is fatal.
152 : // In that case, behavior is checked in OnNewArrayOOM before exit.
153 5 : int8_t* result = v8::internal::NewArray<int8_t>(GetHugeMemoryAmount());
154 : // On a few systems, allocation somehow succeeds.
155 0 : CHECK_EQ(result == nullptr, platform.oom_callback_called);
156 0 : }
157 :
158 26644 : TEST(AlignedAllocOOM) {
159 5 : AllocationPlatform platform;
160 5 : CHECK(!platform.oom_callback_called);
161 5 : CcTest::isolate()->SetFatalErrorHandler(OnAlignedAllocOOM);
162 : // On failure, this won't return, since an AlignedAlloc failure is fatal.
163 : // In that case, behavior is checked in OnAlignedAllocOOM before exit.
164 5 : void* result = v8::internal::AlignedAlloc(GetHugeMemoryAmount(),
165 5 : v8::internal::AllocatePageSize());
166 : // On a few systems, allocation somehow succeeds.
167 0 : CHECK_EQ(result == nullptr, platform.oom_callback_called);
168 0 : }
169 :
170 26644 : TEST(AllocVirtualMemoryOOM) {
171 5 : AllocationPlatform platform;
172 5 : CHECK(!platform.oom_callback_called);
173 : v8::internal::VirtualMemory result(v8::internal::GetPlatformPageAllocator(),
174 10 : GetHugeMemoryAmount(), nullptr);
175 : // On a few systems, allocation somehow succeeds.
176 5 : CHECK_IMPLIES(!result.IsReserved(), platform.oom_callback_called);
177 5 : }
178 :
179 26644 : TEST(AlignedAllocVirtualMemoryOOM) {
180 5 : AllocationPlatform platform;
181 5 : CHECK(!platform.oom_callback_called);
182 : v8::internal::VirtualMemory result(v8::internal::GetPlatformPageAllocator(),
183 : GetHugeMemoryAmount(), nullptr,
184 10 : v8::internal::AllocatePageSize());
185 : // On a few systems, allocation somehow succeeds.
186 5 : CHECK_IMPLIES(!result.IsReserved(), platform.oom_callback_called);
187 79922 : }
188 :
189 : #endif // !defined(V8_USE_ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) &&
190 : // !defined(THREAD_SANITIZER)
|