CVE-2025-24158
Overview
Background
- BBQ JIT
- JavaScriptCore’s baseline optimizing wasm compiler with its own register allocator.
- allocate vs loadIfNecessary
- allocate() forces a fresh register for a value; loadIfNecessary() reuses the value’s existing location if it already has one.
- shouldThrow value
- A computed flag operand for bulk table/memory ops that must reside in a register for the trap check.
- Register pressure
- When few registers are free, forcing an allocation on an already-located value breaks allocator invariants.
Root Cause Analysis
JavaScriptCore’s Wasm BBQ JIT emits table and memory bulk operations (addTableSet, addTableInit, addTableFill, addTableCopy, addMemoryFill, addMemoryCopy, addMemoryInit) and some atomics/array-segment ops that materialize a ‘shouldThrow’/result value into a register. These call sites used allocate(shouldThrow) / allocate(result), which unconditionally allocates a fresh register location for the value. When the value is already materialized in a location (or when register pressure means allocate cannot proceed as assumed), forcing a new allocation drives the BBQ register allocator into an inconsistent/invalid state, hitting a release assertion or otherwise aborting – a reliably reachable crash (denial-of-service).
The fix replaces allocate(...) with loadIfNecessary(...) at these sites, which brings the value into a register only if it is not already in one, matching how the value is actually used.
The restored invariant is that these bulk table/memory operation operands are materialized with loadIfNecessary rather than a forced allocate, keeping the register allocator consistent and avoiding the crash.
Attack Path
- Craft wasm with bulk table/memory ops Build a module whose functions use table.set/init/fill/copy or memory.fill/copy/init under register pressure.
- Compile via BBQ JIT Instantiation compiles the ops, which call allocate() on a value that is already located.
- Corrupt allocator state The forced allocate drives the register allocator into an invalid state.
- Denial-of-service A release assertion/abort crashes the WebContent process (reliable DoS).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
forSource/JavaScriptCore/wasm/WasmBBQJIT.cpp |
modified |
Files Changed
Source/JavaScriptCore/wasm/WasmBBQJIT.cppSource/JavaScriptCore/wasm/WasmBBQJIT.hSource/JavaScriptCore/wasm/WasmBBQJIT32_64.cpp
Audit Directions
- Other allocate() misusegrep WasmBBQJIT for allocate(value) where the value may already be materialized and loadIfNecessary is the correct call.
- Bulk op operand handlingAudit table/memory/array bulk-op operand materialization for consistent load/allocate/consume discipline.
- Register-exhaustion assertsReview RELEASE_ASSERTs in the BBQ allocator that a crafted module could deterministically trip.
Patch
diff --git a/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp b/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp
index a583c0fc29fe..7eaa5e9f9bfe 100644
--- a/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp
+++ b/Source/JavaScriptCore/wasm/WasmBBQJIT.cpp
@@ -830,7 +830,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addTableSet(unsigned tableIndex, Value
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationSetWasmTableElement, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
LOG_INSTRUCTION("TableSet", tableIndex, index, value);
@@ -857,7 +857,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addTableInit(unsigned elementIndex, uns
};
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationWasmTableInit, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
LOG_INSTRUCTION("TableInit", tableIndex, dstOffset, srcOffset, length);
@@ -921,7 +921,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addTableFill(unsigned tableIndex, Value
};
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationWasmTableFill, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
LOG_INSTRUCTION("TableFill", tableIndex, fill, offset, count);
@@ -946,7 +946,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addTableCopy(unsigned dstTableIndex, un
};
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationWasmTableCopy, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
LOG_INSTRUCTION("TableCopy", dstTableIndex, srcTableIndex, dstOffset, srcOffset, length);
@@ -1110,7 +1110,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addMemoryFill(Value dstAddress, Value t
};
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationWasmMemoryFill, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR()));
@@ -1133,7 +1133,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addMemoryCopy(Value dstAddress, Value s
};
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationWasmMemoryCopy, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR()));
@@ -1157,7 +1157,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addMemoryInit(unsigned dataSegmentIndex
};
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationWasmMemoryInit, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
throwExceptionIf(ExceptionType::OutOfBoundsMemoryAccess, m_jit.branchTest32(ResultCondition::Zero, shouldThrowLocation.asGPR()));
@@ -1262,7 +1262,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::atomicWait(ExtAtomicOpType op, Expressi
emitCCall(&operationMemoryAtomicWait32, arguments, result);
else
emitCCall(&operationMemoryAtomicWait64, arguments, result);
- Location resultLocation = allocate(result);
+ Location resultLocation = loadIfNecessary(result);
LOG_INSTRUCTION(makeString(op), pointer, value, timeout, uoffset, RESULT(result));
@@ -1280,7 +1280,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::atomicNotify(ExtAtomicOpType op, Expres
};
result = topValue(TypeKind::I32);
emitCCall(&operationMemoryAtomicNotify, arguments, result);
- Location resultLocation = allocate(result);
+ Location resultLocation = loadIfNecessary(result);
LOG_INSTRUCTION(makeString(op), pointer, count, uoffset, RESULT(result));
@@ -1439,7 +1439,7 @@ void BBQJIT::pushArrayNewFromSegment(ArraySegmentOperation operation, uint32_t t
};
result = topValue(TypeKind::I64);
emitCCall(operation, arguments, result);
- Location resultLocation = allocate(result);
+ Location resultLocation = loadIfNecessary(result);
emitThrowOnNullReference(exceptionType, resultLocation);
}
@@ -1463,6 +1463,14 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayCopy(uint32_t dstTypeIndex, Exp
if (dst.isConst() || src.isConst()) {
ASSERT_IMPLIES(dst.isConst(), dst.asI64() == JSValue::encode(jsNull()));
ASSERT_IMPLIES(src.isConst(), src.asI64() == JSValue::encode(jsNull()));
+
+ LOG_INSTRUCTION("ArrayCopy", dstTypeIndex, dst, dstOffset, srcTypeIndex, src, srcOffset, size);
+
+ consume(dst);
+ consume(dstOffset);
+ consume(src);
+ consume(srcOffset);
+ consume(size);
emitThrowException(ExceptionType::NullArrayCopy);
return { };
}
@@ -1480,7 +1488,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayCopy(uint32_t dstTypeIndex, Exp
};
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationWasmArrayCopy, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
LOG_INSTRUCTION("ArrayCopy", dstTypeIndex, dst, dstOffset, srcTypeIndex, src, srcOffset, size);
@@ -1495,6 +1503,12 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayInitElem(uint32_t dstTypeIndex,
{
if (dst.isConst()) {
ASSERT(dst.asI64() == JSValue::encode(jsNull()));
+
+ LOG_INSTRUCTION("ArrayInitElem", dstTypeIndex, dst, dstOffset, srcElementIndex, srcOffset, size);
+
+ consume(dstOffset);
+ consume(srcOffset);
+ consume(size);
emitThrowException(ExceptionType::NullArrayInitElem);
return { };
}
@@ -1511,7 +1525,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayInitElem(uint32_t dstTypeIndex,
};
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationWasmArrayInitElem, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
LOG_INSTRUCTION("ArrayInitElem", dstTypeIndex, dst, dstOffset, srcElementIndex, srcOffset, size);
@@ -1526,6 +1540,12 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayInitData(uint32_t dstTypeIndex,
{
if (dst.isConst()) {
ASSERT(dst.asI64() == JSValue::encode(jsNull()));
+
+ LOG_INSTRUCTION("ArrayInitData", dstTypeIndex, dst, dstOffset, srcDataIndex, srcOffset, size);
+
+ consume(dstOffset);
+ consume(srcOffset);
+ consume(size);
emitThrowException(ExceptionType::NullArrayInitData);
return { };
}
@@ -1542,7 +1562,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayInitData(uint32_t dstTypeIndex,
};
Value shouldThrow = topValue(TypeKind::I32);
emitCCall(&operationWasmArrayInitData, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
LOG_INSTRUCTION("ArrayInitData", dstTypeIndex, dst, dstOffset, srcDataIndex, srcOffset, size);
@@ -1585,12 +1605,24 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addAnyConvertExtern(ExpressionType refe
};
result = topValue(TypeKind::Anyref);
emitCCall(&operationWasmAnyConvertExtern, arguments, result);
+
+ LOG_INSTRUCTION("AnyConvertExtern", reference, RESULT(result));
return { };
}
PartialResult WARN_UNUSED_RETURN BBQJIT::addExternConvertAny(ExpressionType reference, ExpressionType& result)
{
- result = reference;
+ auto referenceLocation = reference.isConst() ? Location::none() : loadIfNecessary(reference);
+ consume(reference);
+
+ result = topValue(TypeKind::Externref);
+ auto resultLocation = allocate(result);
+ if (reference.isConst())
+ emitMoveConst(reference, resultLocation);
+ else
+ emitMove(reference.type(), referenceLocation, resultLocation);
+
+ LOG_INSTRUCTION("ExternConvertAny", reference, RESULT(result));
return { };
}
@@ -4500,6 +4532,22 @@ ALWAYS_INLINE void BBQJIT::willParseOpcode()
}
m_disassembler->setOpcode(m_jit.label(), PrefixedOpcode(m_parser->currentOpcode()), m_parser->currentOpcodeStartingOffset());
}
+
+#if ASSERT_ENABLED
+ if (shouldFuseBranchCompare && isCompareOpType(m_prevOpcode)
+ && (m_parser->currentOpcode() == OpType::BrIf || m_parser->currentOpcode() == OpType::If)) {
+ m_prevOpcode = m_parser->currentOpcode();
+ return;
+ }
+ for (Value value : m_justPoppedStack) {
+ // Temps should have been consumed, we should have removed them from this list.
+ ASSERT_WITH_MESSAGE(!value.isTemp(), "Temp(%u) was not consumed by the instruction that popped it!", value.asTemp());
+
+ ASSERT(!value.isLocal()); // Change this if/when we start register-allocating locals.
+ }
+ m_prevOpcode = m_parser->currentOpcode();
+ m_justPoppedStack.clear();
+#endif
}
ALWAYS_INLINE void BBQJIT::willParseExtendedOpcode()
@@ -4524,7 +4572,14 @@ bool BBQJIT::usesSIMD()
void BBQJIT::dump(const ControlStack&, const Stack*) { }
void BBQJIT::didFinishParsingLocals() { }
-void BBQJIT::didPopValueFromStack(ExpressionType, ASCIILiteral) { }
+
+void BBQJIT::didPopValueFromStack(Value value, ASCIILiteral)
+{
+ UNUSED_PARAM(value);
+#if ASSERT_ENABLED
+ m_justPoppedStack.append(value);
+#endif
+}
void BBQJIT::finalize()
{
@@ -4694,15 +4749,6 @@ Location BBQJIT::allocateWithHint(Value value, Location hint)
if (value.isPinned())
return value.asPinned();
- // It's possible, in some circumstances, that the value in question was already assigned to a register.
- // The most common example of this is returned values (that use registers from the calling convention) or
- // values passed between control blocks. In these cases, we just leave it in its register unmoved.
- Location existingLocation = locationOf(value);
- if (existingLocation.isRegister()) {
- ASSERT(value.isFloat() == existingLocation.isFPR());
- return existingLocation;
- }
-
Location reg = hint;
if (reg.kind() == Location::None
|| value.isFloat() != reg.isFPR()
@@ -4775,6 +4821,12 @@ void BBQJIT::consume(Value value)
Location location = locationOf(value);
if (value.isTemp() && location != canonicalSlot(value))
unbind(value, location);
+
+#if ASSERT_ENABLED
+ m_justPoppedStack.removeFirstMatching([&](Value& popped) -> bool {
+ return popped.isTemp() && value.isTemp() && popped.asTemp() == value.asTemp();
+ });
+#endif
}
Location BBQJIT::allocateRegister(TypeKind type)
diff --git a/Source/JavaScriptCore/wasm/WasmBBQJIT.h b/Source/JavaScriptCore/wasm/WasmBBQJIT.h
index ad1ced3e5c1d..3debbe62957b 100644
--- a/Source/JavaScriptCore/wasm/WasmBBQJIT.h
+++ b/Source/JavaScriptCore/wasm/WasmBBQJIT.h
@@ -2294,6 +2294,11 @@ private:
PCToCodeOriginMapBuilder m_pcToCodeOriginMapBuilder;
std::unique_ptr<BBQDisassembler> m_disassembler;
+
+#if ASSERT_ENABLED
+ Vector<Value, 8> m_justPoppedStack;
+ OpType m_prevOpcode;
+#endif
};
using LocalOrTempIndex = BBQJIT::LocalOrTempIndex;
diff --git a/Source/JavaScriptCore/wasm/WasmBBQJIT32_64.cpp b/Source/JavaScriptCore/wasm/WasmBBQJIT32_64.cpp
index 03676a366152..7f82551ca8ee 100644
--- a/Source/JavaScriptCore/wasm/WasmBBQJIT32_64.cpp
+++ b/Source/JavaScriptCore/wasm/WasmBBQJIT32_64.cpp
@@ -221,7 +221,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addTableGet(unsigned tableIndex, Value
};
result = topValue(returnType);
emitCCall(&operationGetWasmTableElement, arguments, result);
- Location resultLocation = allocate(result);
+ Location resultLocation = loadIfNecessary(result);
LOG_INSTRUCTION("TableGet", tableIndex, index, RESULT(result));
@@ -1505,7 +1505,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayNewFixed(uint32_t typeIndex, Ar
Value allocationResult = Value::fromTemp(TypeKind::Arrayref, currentControlData().enclosedHeight() + currentControlData().implicitSlots() + m_parser->expressionStack().size() + args.size());
emitCCall(operationWasmArrayNewEmpty, arguments, allocationResult);
- Location allocationResultLocation = allocate(allocationResult);
+ Location allocationResultLocation = loadIfNecessary(allocationResult);
emitThrowOnNullReference(ExceptionType::BadArrayNew, allocationResultLocation);
for (uint32_t i = 0; i < args.size(); ++i) {
@@ -1518,14 +1518,18 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayNewFixed(uint32_t typeIndex, Ar
}
result = topValue(TypeKind::Arrayref);
- Location resultLocation = allocate(result);
- emitMove(allocationResult, resultLocation);
+ Location resultLocation;
// If args.isEmpty() then allocationResult.asTemp() == result.asTemp() so we will consume our result.
if (args.size()) {
consume(allocationResult);
+ resultLocation = allocate(result);
+ emitMove(allocationResult.type(), allocationResultLocation, resultLocation);
if (isRefType(getArrayElementType(typeIndex).unpacked()))
emitWriteBarrier(resultLocation.asGPRlo());
+ } else {
+ RELEASE_ASSERT(result.asTemp() == allocationResult.asTemp());
+ resultLocation = allocationResultLocation;
}
LOG_INSTRUCTION("ArrayNewFixed", typeIndex, args.size(), RESULT(result));
@@ -1789,6 +1793,9 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArraySet(uint32_t typeIndex, Express
{
if (arrayref.isConst()) {
ASSERT(arrayref.asI64() == JSValue::encode(jsNull()));
+
+ LOG_INSTRUCTION("ArraySet", typeIndex, arrayref, index, value);
+ consume(value);
emitThrowException(ExceptionType::NullArraySet);
return { };
}
@@ -1842,6 +1849,12 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayFill(uint32_t typeIndex, Expres
{
if (arrayref.isConst()) {
ASSERT(arrayref.asI64() == JSValue::encode(jsNull()));
+
+ LOG_INSTRUCTION("ArrayFill", typeIndex, arrayref, offset, value, size);
+
+ consume(offset);
+ consume(value);
+ consume(size);
emitThrowException(ExceptionType::NullArrayFill);
return { };
}
@@ -1858,7 +1871,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addArrayFill(uint32_t typeIndex, Expres
size
};
emitCCall(&operationWasmArrayFill, arguments, shouldThrow);
- Location shouldThrowLocation = allocate(shouldThrow);
+ Location shouldThrowLocation = loadIfNecessary(shouldThrow);
LOG_INSTRUCTION("ArrayFill", typeIndex, arrayref, offset, value, size);
@@ -1951,7 +1964,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addStructNewDefault(uint32_t typeIndex,
emitCCall(operationWasmStructNewEmpty, arguments, result);
const auto& structType = *m_info.typeSignatures[typeIndex]->expand().template as<StructType>();
- Location structLocation = allocate(result);
+ Location structLocation = loadIfNecessary(result);
emitThrowOnNullReference(ExceptionType::BadStructNew, structLocation);
m_jit.loadPtr(MacroAssembler::Address(structLocation.asGPRlo(), JSWebAssemblyStruct::offsetOfPayload()), wasmScratchGPR);
for (StructFieldCount i = 0; i < structType.fieldCount(); ++i) {
@@ -1979,7 +1992,7 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addStructNew(uint32_t typeIndex, Argume
emitCCall(operationWasmStructNewEmpty, arguments, allocationResult);
const auto& structType = *m_info.typeSignatures[typeIndex]->expand().template as<StructType>();
- Location structLocation = allocate(allocationResult);
+ Location structLocation = loadIfNecessary(allocationResult);
emitThrowOnNullReference(ExceptionType::BadStructNew, structLocation);
m_jit.loadPtr(MacroAssembler::Address(structLocation.asGPRlo(), JSWebAssemblyStruct::offsetOfPayload()), wasmScratchGPR);
bool hasRefTypeField = false;
@@ -1993,11 +2006,16 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addStructNew(uint32_t typeIndex, Argume
emitWriteBarrier(structLocation.asGPRlo());
result = topValue(TypeKind::Structref);
- Location resultLocation = allocate(result);
- emitMove(allocationResult, resultLocation);
+ Location resultLocation;
// If args.isEmpty() then allocationResult.asTemp() == result.asTemp() so we will consume our result.
- if (args.size())
+ if (args.size()) {
consume(allocationResult);
+ resultLocation = allocate(result);
+ emitMove(allocationResult.type(), structLocation, resultLocation);
+ } else {
+ RELEASE_ASSERT(result.asTemp() == allocationResult.asTemp());
+ resultLocation = structLocation;
+ }
LOG_INSTRUCTION("StructNew", typeIndex, args, RESULT(result));
@@ -2079,8 +2097,10 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addStructSet(Value structValue, const S
if (structValue.isConst()) {
// This is the only constant struct currently possible.
ASSERT(JSValue::decode(structValue.asRef()).isNull());
- emitThrowException(ExceptionType::NullStructSet);
+
LOG_INSTRUCTION("StructSet", structValue, fieldIndex, value, "Exception");
+ consume(value);
+ emitThrowException(ExceptionType::NullStructSet);
return { };
}
@@ -2105,7 +2125,8 @@ PartialResult WARN_UNUSED_RETURN BBQJIT::addRefCast(ExpressionType reference, bo
};
result = topValue(TypeKind::Ref);
... (file diff truncated)
Regression Test / PoC
diff --git a/JSTests/wasm/stress/array-copy-with-constant-null.js b/JSTests/wasm/stress/array-copy-with-constant-null.js
new file mode 100644
index 000000000000..dded10f50c95
--- /dev/null
+++ b/JSTests/wasm/stress/array-copy-with-constant-null.js
@@ -0,0 +1,57 @@
+import * as assert from '../assert.js'
+
+/*
+(module
+ (type $A (array (mut i32)))
+
+ (func (export "null_src") (result i32) (local (ref $A))
+ i32.const 4
+ i32.const 42
+ array.new $A
+ local.tee 0
+ i32.const 0
+ ref.null $A
+ i32.const 0
+ i32.const 4
+ array.copy $A $A
+
+ local.get 0
+ i32.const 0
+ array.get $A
+ )
+
+ (func (export "null_dst") (result i32) (local (ref $A))
+ ref.null $A
+ i32.const 0
+ i32.const 4
+ i32.const 42
+ array.new $A
+ local.tee 0
+ i32.const 0
+ i32.const 4
+ array.copy $A $A
+
+ local.get 0
+ i32.const 0
+ array.get $A
+ )
+)
+*/
+
+const wasm = new Uint8Array([
+ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x88, 0x80, 0x80, 0x80, 0x00, 0x02, 0x5e, 0x7f,
+ 0x01, 0x60, 0x00, 0x01, 0x7f, 0x03, 0x83, 0x80, 0x80, 0x80, 0x00, 0x02, 0x01, 0x01, 0x07, 0x97, 0x80,
+ 0x80, 0x80, 0x00, 0x02, 0x08, 0x6e, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x72, 0x63, 0x00, 0x00, 0x08, 0x6e,
+ 0x75, 0x6c, 0x6c, 0x5f, 0x64, 0x73, 0x74, 0x00, 0x01, 0x0a, 0xcd, 0x80, 0x80, 0x80, 0x00, 0x02, 0xa1,
+ 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x64, 0x00, 0x41, 0x04, 0x41, 0x2a, 0xfb, 0x06, 0x00, 0x22, 0x00,
+ 0x41, 0x00, 0xd0, 0x00, 0x41, 0x00, 0x41, 0x04, 0xfb, 0x11, 0x00, 0x00, 0x20, 0x00, 0x41, 0x00, 0xfb,
+ 0x0b, 0x00, 0x0b, 0xa1, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x64, 0x00, 0xd0, 0x00, 0x41, 0x00, 0x41,
+ 0x04, 0x41, 0x2a, 0xfb, 0x06, 0x00, 0x22, 0x00, 0x41, 0x00, 0x41, 0x04, 0xfb, 0x11, 0x00, 0x00, 0x20,
+ 0x00, 0x41, 0x00, 0xfb, 0x0b, 0x00, 0x0b
+]);
+
+let instance = WebAssembly.instantiate(wasm);
+for (let i = 0; i < 50000; i ++) {
+ assert.throws(() => instance.exports.null_src(), TypeError);
+ assert.throws(() => instance.exports.null_dst(), TypeError);
+}