Medium CVSS 6.5 webkit UAF 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected Safari crash
ComponentJSC
Sub-componentJSC Runtime
Bug ClassUAF
Tracker313473
Fix commita012babd4f16 (WebKit/WebKit) +129/-47
CWECWE-119, CWE-416 (Buffer bounds error, Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
CreditedMaher Azzouzi, Tuan and Duc from Calif.io, OpenAI Codex Security - Amy Burnett, Evan Lambert
Disclosed2026-06-29

Background

Resizable buffer over wasm memory
toResizableBuffer/useWasmMemoryToBufferAPIs exposes growable wasm memory as a resizable ArrayBuffer whose resize is routed to the memory.
GC tracing / write barrier
References between GC objects must be stored on GC-visible cells with a write barrier so the collector keeps the referent alive; a raw pointer on a non-GC object is invisible to GC.
Native ArrayBuffer vs JSArrayBuffer
ArrayBuffer is the native backing object; JSArrayBuffer is its GC-visible JS wrapper cell.
HostResizeArrayBuffer
The abstract operation resizing a buffer; for wasm-backed buffers it must delegate growth to the wasm memory.

Root Cause Analysis

When the useWasmMemoryToBufferAPIs feature exposes a WebAssembly.Memory as a resizable non-shared ArrayBuffer, the buffer and the wasm memory must stay associated so a JS-side resize is routed to the memory’s grow (which reallocates the backing store).

Pre-patch, the native ArrayBuffer held that association itself via m_associatedWasmMemory (set through ArrayBuffer::setAssociatedWasmMemory), and ArrayBuffer::resize, on a wasm-backed buffer, fetched RefPtrWasm::Memory from it and called memory->grow. Storing the buffer->memory link on the native ArrayBuffer (which is not a GC object and is not traced) meant the association was not GC-visible: after a garbage collection the link could go stale / the memory be reclaimed or left in an inconsistent state, and a later resize (the added test resizes a resizable buffer after GC) would operate on a dangling or wrong memory – a use-after-free / crash.

The fix moves the association onto the GC-visible JS wrapper: JSArrayBuffer gains m_associatedWasmMemoryWrapper with associatedWasmMemoryWrapper()/setAssociatedWasmMemoryWrapper(vm, wrapper) using a write barrier (.set(vm, this, wrapper)), so the JSWebAssemblyMemory is properly kept alive and traced by the collector; the native ArrayBuffer::setAssociatedWasmMemory is removed, and ArrayBuffer::resize now RELEASE_ASSERTs !isWasmMemory() (native resize must never touch a wasm memory – growth goes through the JS wrapper/memory path instead).

The restored invariant is that the buffer<->wasm-memory association is owned by a GC-traced wrapper so it cannot dangle across GC, and native ArrayBuffer resize is never applied to wasm memory.

Key insight
The ArrayBuffer<->WebAssembly.Memory association was stored on the non-GC native buffer and thus invisible to the collector, so it could dangle across GC; moving it to a write-barriered JS wrapper (and forbidding native resize of wasm memory) makes the link GC-safe.

Attack Path

  1. Expose wasm memory as a resizable buffer With useWasmMemoryToBufferAPIs, create a WebAssembly.Memory and obtain its resizable non-shared ArrayBuffer, associating buffer and memory.
  2. Drop strong references and GC Remove other references to the wasm memory / wrapper and force garbage collection, so the untraced native association can go stale.
  3. Resize after GC Call the resizable buffer’s resize/grow; pre-patch it dereferences the now-dangling associated Wasm::Memory.
  4. Use-after-free / crash The resize operates on freed or inconsistent memory state in the WebContent process.

Impact Assessment

A lifetime/GC bug: the buffer->wasm-memory association lived on an untraced native object, so after GC a resizable-buffer resize could touch a dangling memory – a use-after-free reachable from JS in the WebContent process. The attacker controls GC timing and the resize, making it a groomable primitive though the observed effect is a crash. Confined to WebContent; rated medium.

Changed Functions

FunctionChangeNotes
if
Source/JavaScriptCore/runtime/ArrayBuffer.cpp
modified
JSWebAssemblyMemory
Source/JavaScriptCore/runtime/JSArrayBuffer.h
modified
JSArrayBuffer
Source/JavaScriptCore/runtime/JSArrayBuffer.h
modified
if
Source/JavaScriptCore/runtime/JSArrayBufferPrototype.cpp
modified
LLIntOffsetsExtractor
Source/JavaScriptCore/wasm/WasmMemory.h
modified
Memory
Source/JavaScriptCore/wasm/WasmMemory.h
modified
for
JSTests/wasm/stress/wasm-resizable-buffer-resize-after-gc.js
modified

Files Changed

  • JSTests/wasm/stress/wasm-resizable-buffer-resize-after-gc.js
  • Source/JavaScriptCore/runtime/ArrayBuffer.cpp
  • Source/JavaScriptCore/runtime/ArrayBuffer.h
  • Source/JavaScriptCore/runtime/JSArrayBuffer.cpp
  • Source/JavaScriptCore/runtime/JSArrayBuffer.h
  • Source/JavaScriptCore/runtime/JSArrayBufferPrototype.cpp
  • Source/JavaScriptCore/wasm/WasmMemory.h
  • Source/JavaScriptCore/wasm/js/JSWebAssemblyMemory.cpp

Audit Directions

  • Untraced cross-object pointers
    grep JSC runtime for raw/RefPtr members on native (non-GC) objects that point at GC cells (wrappers, memories) and should instead live on the wrapper with a write barrier.
  • Wasm memory <-> buffer paths
    Audit resize/grow/detach/transfer for resizable and shared buffers backed by wasm memory to confirm associations survive GC and native fast paths are gated by isWasmMemory().
  • resize-after-GC
    Review other resizable-buffer operations for correctness when the last non-GC reference is dropped and a collection intervenes.
diff --git a/Source/JavaScriptCore/runtime/ArrayBuffer.cpp b/Source/JavaScriptCore/runtime/ArrayBuffer.cpp
index 84a7fd6b4476..2a4dc89d8557 100644
--- a/Source/JavaScriptCore/runtime/ArrayBuffer.cpp
+++ b/Source/JavaScriptCore/runtime/ArrayBuffer.cpp
@@ -443,19 +443,6 @@ void ArrayBuffer::makeWasmMemory()
     m_isWasmMemory = true;
 }
 
-void ArrayBuffer::setAssociatedWasmMemory(Wasm::Memory* memory)
-{
-    // The pointer from a buffer to a memory is only required when the buffer is resizable non-shared,
-    // to direct a grow request to the memory (see ArrayBuffer::resize). In other scenarios
-    // the pointer is not necessary and we should not be setting it to anything but a nullptr.
-    ASSERT(isWasmMemory() && (isResizableNonShared() || !memory));
-#if ENABLE(WEBASSEMBLY)
-    m_associatedWasmMemory = memory;
-#else
-    UNUSED_PARAM(memory);
-#endif
-}
-
 void ArrayBuffer::refreshAfterWasmMemoryGrow(Wasm::Memory* memory)
 {
     ASSERT(isWasmMemory());
@@ -553,10 +540,10 @@ Expected<int64_t, GrowFailReason> ArrayBuffer::grow(VM& vm, size_t newByteLength
     return result;
 }
 
-// Wasm JS API redefines the abstract operation HostResizeArrayBuffer as follows:
-// https://webassembly.github.io/threads/js-api/index.html#abstract-operation-hostresizearraybuffer
 Expected<int64_t, GrowFailReason> ArrayBuffer::resize(VM& vm, size_t newByteLength)
 {
+    RELEASE_ASSERT(!isWasmMemory());
+
     auto memoryHandle = m_contents.m_memoryHandle;
     if (!memoryHandle || m_contents.m_shared) [[unlikely]]
         return makeUnexpected(GrowFailReason::GrowSharedUnavailable);
@@ -570,12 +557,6 @@ Expected<int64_t, GrowFailReason> ArrayBuffer::resize(VM& vm, size_t newByteLeng
             return makeUnexpected(GrowFailReason::InvalidGrowSize);
 
         deltaByteLength = static_cast<int64_t>(newByteLength) - static_cast<int64_t>(m_contents.m_sizeInBytes);
-#if ENABLE(WEBASSEMBLY)
-        if (Options::useWasmMemoryToBufferAPIs()) {
-            if (isWasmMemory() && (deltaByteLength < 0 || deltaByteLength % PageCount::pageSize))
-                return makeUnexpected(GrowFailReason::InvalidGrowSize);
-        }
-#endif
         if (!deltaByteLength)
             return 0;
 
@@ -587,17 +568,6 @@ Expected<int64_t, GrowFailReason> ArrayBuffer::resize(VM& vm, size_t newByteLeng
         if (newPageCount != oldPageCount) {
             ASSERT(memoryHandle->maximum() >= newPageCount);
 
-#if ENABLE(WEBASSEMBLY)
-            if (Options::useWasmMemoryToBufferAPIs()) {
-                // If this is currently associated with a Wasm memory, let the memory do the growing.
-                // The memory will call back to our refreshAfterWasmMemoryGrow().
-                RefPtr<Wasm::Memory> memory = m_associatedWasmMemory.get();
-                if (memory) {
-                    std::ignore = memory->grow(vm, PageCount(newPageCount.pageCount() - oldPageCount.pageCount()));
-                    return deltaByteLength;
-                }
-            }
-#endif
             size_t desiredSize = newPageCount.bytes();
             RELEASE_ASSERT(desiredSize <= MAX_ARRAY_BUFFER_SIZE);
 
diff --git a/Source/JavaScriptCore/runtime/ArrayBuffer.h b/Source/JavaScriptCore/runtime/ArrayBuffer.h
index 19cdbde7d9c2..d74153fd0e39 100644
--- a/Source/JavaScriptCore/runtime/ArrayBuffer.h
+++ b/Source/JavaScriptCore/runtime/ArrayBuffer.h
@@ -287,7 +287,6 @@ public:
 
     void NODELETE makeWasmMemory();
     inline bool isWasmMemory();
-    void NODELETE setAssociatedWasmMemory(Wasm::Memory*);
     // When a resizable buffer is associated with a non-shared Wasm memory, this function is called by the memory's growthSuccessCallback.
     void refreshAfterWasmMemoryGrow(Wasm::Memory*);
 
@@ -328,7 +327,6 @@ private:
 public:
     Weak<JSArrayBuffer> m_wrapper;
 private:
-    WeakPtr<Wasm::Memory> m_associatedWasmMemory;
     Checked<unsigned> m_pinCount { 0 };
     bool m_isWasmMemory { false };
     // m_locked == true means that some API user fetched m_contents directly from a TypedArray object,
diff --git a/Source/JavaScriptCore/runtime/JSArrayBuffer.cpp b/Source/JavaScriptCore/runtime/JSArrayBuffer.cpp
index 03413453a597..3a05622c86ab 100644
--- a/Source/JavaScriptCore/runtime/JSArrayBuffer.cpp
+++ b/Source/JavaScriptCore/runtime/JSArrayBuffer.cpp
@@ -27,6 +27,9 @@
 #include "JSArrayBuffer.h"
 
 #include "JSCInlines.h"
+#if ENABLE(WEBASSEMBLY)
+#include "JSWebAssemblyMemory.h"
+#endif // ENABLE(WEBASSEMBLY)
 #include "TypedArrayController.h"
 
 namespace JSC {
@@ -82,5 +85,36 @@ size_t JSArrayBuffer::estimatedSize(JSCell* cell, VM& vm)
     return Base::estimatedSize(cell, vm) + bufferEstimatedSize;
 }
 
+#if ENABLE(WEBASSEMBLY)
+JSWebAssemblyMemory* JSArrayBuffer::associatedWasmMemoryWrapper() const
+{
+    return m_associatedWasmMemoryWrapper.get();
+}
+
+void JSArrayBuffer::setAssociatedWasmMemoryWrapper(VM& vm, JSWebAssemblyMemory* wrapper)
+{
+    ASSERT(impl()->isWasmMemory() && impl()->isResizableNonShared());
+    m_associatedWasmMemoryWrapper.set(vm, this, wrapper);
+}
+
+void JSArrayBuffer::clearAssociatedWasmMemoryWrapper()
+{
+    m_associatedWasmMemoryWrapper.clear();
+}
+#endif // ENABLE(WEBASSEMBLY)
+
+template<typename Visitor>
+void JSArrayBuffer::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+    auto* thisObject = uncheckedDowncast<JSArrayBuffer>(cell);
+    ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+    Base::visitChildren(thisObject, visitor);
+#if ENABLE(WEBASSEMBLY)
+    visitor.append(thisObject->m_associatedWasmMemoryWrapper);
+#endif // ENABLE(WEBASSEMBLY)
+}
+
+DEFINE_VISIT_CHILDREN(JSArrayBuffer);
+
 } // namespace JSC
 
diff --git a/Source/JavaScriptCore/runtime/JSArrayBuffer.h b/Source/JavaScriptCore/runtime/JSArrayBuffer.h
index 86ebc3c902a0..16aaee19929a 100644
--- a/Source/JavaScriptCore/runtime/JSArrayBuffer.h
+++ b/Source/JavaScriptCore/runtime/JSArrayBuffer.h
@@ -27,9 +27,14 @@
 
 #include <JavaScriptCore/ArrayBuffer.h>
 #include <JavaScriptCore/JSObject.h>
+#include <JavaScriptCore/WriteBarrier.h>
 
 namespace JSC {
 
+#if ENABLE(WEBASSEMBLY)
+class JSWebAssemblyMemory;
+#endif // ENABLE(WEBASSEMBLY)
+
 class JSArrayBuffer final : public JSNonFinalObject {
 public:
     using Base = JSNonFinalObject;
@@ -51,8 +56,16 @@ public:
     JS_EXPORT_PRIVATE bool NODELETE isShared() const;
     ArrayBufferSharingMode NODELETE sharingMode() const;
     bool isResizableOrGrowableShared() const { return m_impl->isResizableOrGrowableShared(); }
-    
+
+#if ENABLE(WEBASSEMBLY)
+    JSWebAssemblyMemory* associatedWasmMemoryWrapper() const;
+    void setAssociatedWasmMemoryWrapper(VM&, JSWebAssemblyMemory*);
+    void clearAssociatedWasmMemoryWrapper();
+#endif // ENABLE(WEBASSEMBLY)
+
     DECLARE_EXPORT_INFO;
+
+    DECLARE_VISIT_CHILDREN;
     
     // This is the default DOM unwrapping. It calls toUnsharedArrayBuffer().
     static ArrayBuffer* toWrapped(VM&, JSValue);
@@ -67,6 +80,11 @@ private:
     static size_t estimatedSize(JSCell*, VM&);
 
     ArrayBuffer* m_impl;
+    // For resizable non-shared Wasm buffers, this points back to the owning JSWebAssemblyMemory so
+    // that ArrayBuffer.prototype.resize can delegate to Wasm::Memory::grow.
+#if ENABLE(WEBASSEMBLY)
+    WriteBarrier<JSWebAssemblyMemory> m_associatedWasmMemoryWrapper;
+#endif // ENABLE(WEBASSEMBLY)
 };
 
 inline ArrayBuffer* toPossiblySharedArrayBuffer(VM&, JSValue value)
diff --git a/Source/JavaScriptCore/runtime/JSArrayBufferPrototype.cpp b/Source/JavaScriptCore/runtime/JSArrayBufferPrototype.cpp
index 9fa1f22bceb9..66e2a880bb35 100644
--- a/Source/JavaScriptCore/runtime/JSArrayBufferPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/JSArrayBufferPrototype.cpp
@@ -32,6 +32,11 @@
 #include "JSCInlines.h"
 #include <wtf/text/MakeString.h>
 
+#if ENABLE(WEBASSEMBLY)
+#include "JSWebAssemblyMemory.h"
+#include "WasmMemory.h"
+#endif
+
 WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
 
 namespace JSC {
@@ -292,6 +297,29 @@ JSC_DEFINE_HOST_FUNCTION(arrayBufferProtoFuncResize, (JSGlobalObject* globalObje
     if (!std::isfinite(newLength) || newLength < 0)
         return throwVMRangeError(globalObject, scope, "new length is out of range"_s);
     size_t newByteLength = static_cast<size_t>(newLength);
+
+#if ENABLE(WEBASSEMBLY)
+    // Wasm JS API redefines the abstract operation HostResizeArrayBuffer as follows:
+    // https://webassembly.github.io/threads/js-api/index.html#abstract-operation-hostresizearraybuffer
+    //
+    // Further, WebAssembly-originated resizable ArrayBuffers must defer resizing to the backing
+    // WebAssembly memory for correct handling of refreshing bounds-checking memories.
+    if (auto* jsMemory = thisObject->associatedWasmMemoryWrapper()) {
+        size_t oldByteLength = thisObject->impl()->byteLength();
+        if (newByteLength < oldByteLength)
+            return throwVMRangeError(globalObject, scope, "Cannot shrink WebAssembly memory"_s);
+        if (newByteLength % PageCount::pageSize)
+            return throwVMRangeError(globalObject, scope, makeString("WebAssembly memory cannot be resized to new byte length "_s, newByteLength, " because it is not a multiple of "_s, PageCount::pageSize));
+        size_t delta = newByteLength - oldByteLength;
+        if (delta) {
+            auto result = jsMemory->memory().grow(vm, PageCount::fromBytes(delta));
+            if (!result)
+                return throwVMRangeError(globalObject, scope, makeString("ArrayBuffer resize failed with new byte length "_s, newByteLength));
+        }
+        return JSValue::encode(jsUndefined());
+    }
+#endif
+
     if (!thisObject->impl()->resize(vm, newByteLength))
         return throwVMRangeError(globalObject, scope, makeString("ArrayBuffer resize failed with new byte length "_s, newByteLength));
 
diff --git a/Source/JavaScriptCore/wasm/WasmMemory.h b/Source/JavaScriptCore/wasm/WasmMemory.h
index 06fac07b2015..d23f2115eb79 100644
--- a/Source/JavaScriptCore/wasm/WasmMemory.h
+++ b/Source/JavaScriptCore/wasm/WasmMemory.h
@@ -38,10 +38,9 @@
 #include <wtf/CagedPtr.h>
 #include <wtf/Expected.h>
 #include <wtf/Function.h>
-#include <wtf/RefCountedAndCanMakeWeakPtr.h>
+#include <wtf/RefCounted.h>
 #include <wtf/RefPtr.h>
 #include <wtf/TZoneMalloc.h>
-#include <wtf/ThreadSafeWeakPtr.h>
 #include <wtf/Vector.h>
 
 namespace WTF {
@@ -53,7 +52,7 @@ namespace JSC {
 class LLIntOffsetsExtractor;
 
 namespace Wasm {
-class Memory final : public RefCountedAndCanMakeWeakPtr<Memory> {
+class Memory final : public RefCounted<Memory> {
     WTF_MAKE_NONCOPYABLE(Memory);
     WTF_MAKE_TZONE_ALLOCATED_EXPORT(Memory, JS_EXPORT_PRIVATE);
     friend LLIntOffsetsExtractor;
@@ -95,8 +94,6 @@ public:
 
     void registerInstance(JSWebAssemblyInstance&);
 
-    void checkLifetime() { ASSERT(!refCountDebugger().deletionHasBegun()); }
-
     static constexpr ptrdiff_t offsetOfHandle() { return OBJECT_OFFSETOF(Memory, m_handle); }
 
     SharedArrayBufferContents* shared() const { return m_shared.get(); }
diff --git a/Source/JavaScriptCore/wasm/js/JSWebAssemblyMemory.cpp b/Source/JavaScriptCore/wasm/js/JSWebAssemblyMemory.cpp
index 461b9fe19b03..9bdabe628539 100644
--- a/Source/JavaScriptCore/wasm/js/JSWebAssemblyMemory.cpp
+++ b/Source/JavaScriptCore/wasm/js/JSWebAssemblyMemory.cpp
@@ -51,7 +51,6 @@ void JSWebAssemblyMemory::adopt(Ref<Wasm::Memory>&& memory)
 {
     m_memory.swap(memory);
     ASSERT(m_memory->refCount() == 1);
-    m_memory->checkLifetime();
 }
 
 Structure* JSWebAssemblyMemory::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
@@ -104,10 +103,10 @@ void JSWebAssemblyMemory::associateArrayBuffer(JSGlobalObject* globalObject, boo
             m_buffer->makeShared();
     }
     m_buffer->makeWasmMemory();
-    if (m_buffer->isResizableNonShared())
-        m_buffer->setAssociatedWasmMemory(m_memory.ptr());
 
     auto* arrayBuffer = JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(m_buffer->sharingMode()), m_buffer.get());
+    if (m_buffer->isResizableNonShared())
+        arrayBuffer->setAssociatedWasmMemoryWrapper(vm, this);
     if (m_memory->sharingMode() == MemorySharingMode::Shared) {
         objectConstructorFreeze(globalObject, arrayBuffer);
         RETURN_IF_EXCEPTION(throwScope, void());
@@ -122,8 +121,9 @@ void JSWebAssemblyMemory::disassociateArrayBuffer(VM& vm)
     ASSERT(m_buffer);
     if (!m_buffer->isShared())
         m_buffer->detach(vm);
-    m_buffer->setAssociatedWasmMemory(nullptr);
     m_buffer = nullptr;
+    if (auto* wrapper = m_bufferWrapper.get())
+        wrapper->clearAssociatedWasmMemoryWrapper();
     m_bufferWrapper.clear();
 }
 
@@ -312,9 +312,6 @@ void JSWebAssemblyMemory::growSuccessCallback(VM& vm, PageCount oldPageCount, Pa
         }
     }
 
-    
-    memory().checkLifetime();
-    
     vm.heap.reportExtraMemoryAllocated(this, newPageCount.bytes() - oldPageCount.bytes());
 }
 
diff --git a/JSTests/wasm/stress/wasm-resizable-buffer-resize-after-gc.js b/JSTests/wasm/stress/wasm-resizable-buffer-resize-after-gc.js
new file mode 100644
index 000000000000..efa64566d316
--- /dev/null
+++ b/JSTests/wasm/stress/wasm-resizable-buffer-resize-after-gc.js
@@ -0,0 +1,40 @@
+// Resizing a Wasm-backed resizable ArrayBuffer should work
+// even after the originating WebAssembly.Memory is GC'd.
+
+function flushStackRoots(n) {
+    let a = [n, {}, n + 1, {}, {}];
+    if (n)
+        return flushStackRoots(n - 1);
+    return a;
+}
+
+for (let iter = 0; iter < 5; ++iter) {
+    let buf;
+    (function () {
+        let m = new WebAssembly.Memory({ initial: 1, maximum: 100 });
+        buf = m.toResizableBuffer();
+    })();
+    flushStackRoots(64);
+    fullGC();
+    flushStackRoots(64);
+    fullGC();
+
+    if (buf.byteLength !== 65536)
+        throw new Error("bad initial length: " + buf.byteLength);
+    if (!buf.resizable)
+        throw new Error("not resizable");
+
+    buf.resize(65536 * 100);
+
+    if (buf.byteLength !== 65536 * 100)
+        throw new Error("bad resized length: " + buf.byteLength);
+
+    let view = new Uint8Array(buf);
+    for (let i = 0; i < view.length; i += 4096) {
+        if (view[i] !== 0)
+            throw new Error("non-zero at " + i);
+    }
+    view[view.length - 1] = 0x42;
+    if (view[view.length - 1] !== 0x42)
+        throw new Error("write failed");
+}
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/JSTests/wasm/stress/wasm-resizable-buffer-resize-after-gc.js b/JSTests/wasm/stress/wasm-resizable-buffer-resize-after-gc.js
new file mode 100644
index 000000000000..efa64566d316
--- /dev/null
+++ b/JSTests/wasm/stress/wasm-resizable-buffer-resize-after-gc.js
@@ -0,0 +1,40 @@
+// Resizing a Wasm-backed resizable ArrayBuffer should work
+// even after the originating WebAssembly.Memory is GC'd.
+
+function flushStackRoots(n) {
+    let a = [n, {}, n + 1, {}, {}];
+    if (n)
+        return flushStackRoots(n - 1);
+    return a;
+}
+
+for (let iter = 0; iter < 5; ++iter) {
+    let buf;
+    (function () {
+        let m = new WebAssembly.Memory({ initial: 1, maximum: 100 });
+        buf = m.toResizableBuffer();
+    })();
+    flushStackRoots(64);
+    fullGC();
+    flushStackRoots(64);
+    fullGC();
+
+    if (buf.byteLength !== 65536)
+        throw new Error("bad initial length: " + buf.byteLength);
+    if (!buf.resizable)
+        throw new Error("not resizable");
+
+    buf.resize(65536 * 100);
+
+    if (buf.byteLength !== 65536 * 100)
+        throw new Error("bad resized length: " + buf.byteLength);
+
+    let view = new Uint8Array(buf);
+    for (let i = 0; i < view.length; i += 4096) {
+        if (view[i] !== 0)
+            throw new Error("non-zero at " + i);
+    }
+    view[view.length - 1] = 0x42;
+    if (view[view.length - 1] !== 0x42)
+        throw new Error("write failed");
+}
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker.