// 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 #include #include "src/v8.h" #include "test/cctest/cctest.h" using v8::internal::AccountingAllocator; using v8::IdleTask; using v8::Isolate; using v8::Task; #include "src/allocation.h" #include "src/zone/accounting-allocator.h" // ASAN isn't configured to return NULL, so skip all of these tests. #if !defined(V8_USE_ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && \ !defined(THREAD_SANITIZER) namespace { // Implementation of v8::Platform that can register OOM callbacks. class AllocationPlatform : public TestPlatform { public: AllocationPlatform() { current_platform = this; // Now that it's completely constructed, make this the current platform. i::V8::SetPlatformForTesting(this); } virtual ~AllocationPlatform() = default; void OnCriticalMemoryPressure() override { oom_callback_called = true; } static AllocationPlatform* current_platform; bool oom_callback_called = false; }; AllocationPlatform* AllocationPlatform::current_platform = nullptr; bool DidCallOnCriticalMemoryPressure() { return AllocationPlatform::current_platform && AllocationPlatform::current_platform->oom_callback_called; } // 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++) { huge_memory |= bit_cast(v8::base::OS::GetRandomMmapAddr()); } // Make it larger than the available address space. huge_memory *= 2; CHECK_NE(0, huge_memory); } return huge_memory; } 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) { AllocationPlatform platform; v8::internal::AccountingAllocator allocator; CHECK(!platform.oom_callback_called); v8::internal::Segment* result = allocator.GetSegment(GetHugeMemoryAmount()); // On a few systems, allocation somehow succeeds. CHECK_EQ(result == nullptr, platform.oom_callback_called); } TEST(MallocedOperatorNewOOM) { AllocationPlatform platform; CHECK(!platform.oom_callback_called); CcTest::isolate()->SetFatalErrorHandler(OnMallocedOperatorNewOOM); // On failure, this won't return, since a Malloced::New failure is fatal. // In that case, behavior is checked in OnMallocedOperatorNewOOM before exit. void* result = v8::internal::Malloced::New(GetHugeMemoryAmount()); // On a few systems, allocation somehow succeeds. CHECK_EQ(result == nullptr, platform.oom_callback_called); } TEST(NewArrayOOM) { AllocationPlatform platform; CHECK(!platform.oom_callback_called); CcTest::isolate()->SetFatalErrorHandler(OnNewArrayOOM); // On failure, this won't return, since a NewArray failure is fatal. // In that case, behavior is checked in OnNewArrayOOM before exit. int8_t* result = v8::internal::NewArray(GetHugeMemoryAmount()); // On a few systems, allocation somehow succeeds. CHECK_EQ(result == nullptr, platform.oom_callback_called); } TEST(AlignedAllocOOM) { AllocationPlatform platform; CHECK(!platform.oom_callback_called); CcTest::isolate()->SetFatalErrorHandler(OnAlignedAllocOOM); // On failure, this won't return, since an AlignedAlloc failure is fatal. // In that case, behavior is checked in OnAlignedAllocOOM before exit. void* result = v8::internal::AlignedAlloc(GetHugeMemoryAmount(), v8::base::OS::AllocateAlignment()); // On a few systems, allocation somehow succeeds. CHECK_EQ(result == nullptr, platform.oom_callback_called); } TEST(AllocVirtualMemoryOOM) { AllocationPlatform platform; CHECK(!platform.oom_callback_called); v8::base::VirtualMemory result; bool success = v8::internal::AllocVirtualMemory(GetHugeMemoryAmount(), nullptr, &result); // On a few systems, allocation somehow succeeds. CHECK_IMPLIES(success, result.IsReserved()); CHECK_IMPLIES(!success, !result.IsReserved() && platform.oom_callback_called); } TEST(AlignedAllocVirtualMemoryOOM) { AllocationPlatform platform; CHECK(!platform.oom_callback_called); v8::base::VirtualMemory result; bool success = v8::internal::AlignedAllocVirtualMemory( GetHugeMemoryAmount(), v8::base::OS::AllocateAlignment(), nullptr, &result); // On a few systems, allocation somehow succeeds. CHECK_IMPLIES(success, result.IsReserved()); CHECK_IMPLIES(!success, !result.IsReserved() && platform.oom_callback_called); } #endif // !defined(V8_USE_ADDRESS_SANITIZER) && !defined(MEMORY_SANITIZER) && // !defined(THREAD_SANITIZER)