Critical CVSS 8.8 webkit OOB 🔧 Commit mapped

Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to arbitrary code execution
ComponentWebCore
Sub-componentJSC Wasm
Bug ClassOOB
Tracker272106
Fix commit1e58c9386ed9 (WebKit/WebKit)
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedNan Wang (@eternalsakura13) of 360 Vulnerability Research Institute
Disclosed2024-05-13

Background

OMG / B3IRGenerator
JSC’s optimizing wasm tier and its B3 IR generator, which can inline direct calls between wasm functions.
Call-site index
An index identifying a call site used to look up exception-handling metadata during unwinding.
hasExceptionHandlers
Per-function state indicating whether a wasm function contains try/catch handlers, needed to emit correct EH metadata when inlined.
CalleeGroup
The per-module registry of compiled wasm callees, used here to look up an inlinee’s real exception-handler state.

Root Cause Analysis

When JavaScriptCore’s optimizing wasm compiler (OMG/B3) inlines a direct call, the inlinee’s B3IRGenerator must know whether the inlined function contains exception handlers, because that determines the call-site index metadata emitted for exception dispatch.

Pre-patch, the inlining constructor B3IRGenerator(parentCaller, rootCaller, functionIndex, returnContinuation, args) hardcoded m_hasExceptionHandlers(false) and never consulted the actual inlinee, and emitInlineDirectCall used a shortcut bool mayHaveExceptionHandlers = !m_hasExceptionHandlers || m_hasExceptionHandlers.value(); to decide whether to emit a real call-site index or PatchpointExceptionHandle::s_invalidCallSiteIndex. So when a function that does have exception handlers was inlined, the generator believed it had none and emitted invalid/placeholder call-site indices for the inlined region. If an exception was then thrown across that inlined frame, the unwinder resolved the wrong (invalid) call-site index and used incorrect exception-handling metadata — an out-of-bounds / type-confused read of handler tables that WebKit’s advisory describes as leading to arbitrary code execution.

The fix threads the module’s CalleeGroup& into the generators and, in emitInlineDirectCall, looks up the real inlinee (wasmEntrypointCalleeFromFunctionIndexSpace(...).hasExceptionHandlers()) to pass a correct std::optional<bool> hasExceptionHandlers into the inlinee generator; it also propagates the parent’s exception-handler state (if (parentCaller.m_hasExceptionHandlers && *parentCaller.m_hasExceptionHandlers) m_hasExceptionHandlers = { true }), removes the invalid-call-site-index shortcut (always emitting proper indices), and extends PatchpointExceptionHandle to carry a callSiteIndex.

The restored invariant is that inlined wasm frames always carry accurate has-exception-handlers state and valid call-site indices, so exception unwinding indexes the correct handler metadata.

Key insight
Wasm call inlining hardcoded the inlinee as having no exception handlers and emitted invalid call-site indices, so an exception thrown through an inlined EH-bearing function dispatched on wrong metadata; propagating the real hasExceptionHandlers state and valid call-site indices fixes it.

Attack Path

  1. Craft a wasm module with EH + inlining Build a module where a small function containing exception handlers is a hot inlining target of a caller compiled by OMG/B3.
  2. Force optimizing compilation + inlining Warm the functions so OMG inlines the EH-bearing callee via emitInlineDirectCall, which pre-patch records the inlinee as having no exception handlers.
  3. Emit invalid call-site metadata The inlined region is compiled with placeholder/invalid call-site indices because m_hasExceptionHandlers was false.
  4. Throw across the inlined frame Trigger an exception in the inlined callee so the unwinder resolves the wrong call-site index and reads incorrect exception-handler metadata.
  5. Corrupt / hijack control flow The mismatched handler metadata yields an out-of-bounds/type-confused dispatch that the advisory rates as arbitrary code execution in WebContent.

Impact Assessment

A JIT exception-metadata correctness bug: inlining an exception-handler-bearing wasm function while recording it as having none produced invalid call-site indices, so throwing across the inlined frame dispatches on wrong handler metadata. This is a powerful primitive — the advisory rates it arbitrary code execution — reachable from a crafted wasm module, though it requires shaping inlining and an exception. Confined to the WebContent process. Rated critical.

Changed Functions

FunctionChangeNotes
BBQDisassembler
Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
modified
CalleeGroup
Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
modified
MemoryInformation
Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
modified
OptimizingJITCallee
Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
modified
TierUpCount
Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
modified
if
Source/JavaScriptCore/wasm/WasmIRGeneratorHelpers.h
modified
for
JSTests/wasm/stress/inlinee-may-have-exception-handlers.js
modified
for
JSTests/wasm/stress/rethrow-should-set-callsite-index.js
modified

Files Changed

  • Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
  • Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
  • Source/JavaScriptCore/wasm/WasmIRGeneratorHelpers.h
  • Source/JavaScriptCore/wasm/WasmOMGPlan.cpp
  • Source/JavaScriptCore/wasm/WasmOSREntryPlan.cpp
  • JSTests/wasm/stress/inlinee-may-have-exception-handlers.js
  • JSTests/wasm/stress/rethrow-should-set-callsite-index.js

Audit Directions

  • Other inlining-propagated state
    Audit B3IRGenerator’s inlining constructor for other fields defaulted/hardcoded rather than derived from the inlinee (stack checks, memory mode, tier-up).
  • Call-site index emission
    grep wasm/ for s_invalidCallSiteIndex and mayHaveExceptionHandlers to find other places that substitute placeholder call-site indices based on possibly-wrong EH state.
  • hasExceptionHandlers plumbing
    Trace hasExceptionHandlers/CalleeGroup::hasExceptionHandlers through OMG/OSR/BBQ compilation to ensure every inlined or tiered path uses the callee’s true value.
diff --git a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
index 7f1d5feadd84..9ceede266fb0 100644
--- a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
+++ b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp
@@ -345,8 +345,8 @@ public:
         return m_callSiteIndex;
     }
 
-    B3IRGenerator(const ModuleInformation&, OptimizingJITCallee&, Procedure&, Vector<UnlinkedWasmToWasmCall>&, unsigned& osrEntryScratchBufferSize, MemoryMode, CompilationMode, unsigned functionIndex, std::optional<bool> hasExceptionHandlers, unsigned loopIndexForOSREntry, TierUpCount*);
-    B3IRGenerator(B3IRGenerator& inlineCaller, B3IRGenerator& inlineRoot, unsigned functionIndex, BasicBlock* returnContinuation, Vector<Value*> args);
+    B3IRGenerator(CalleeGroup&, const ModuleInformation&, OptimizingJITCallee&, Procedure&, Vector<UnlinkedWasmToWasmCall>&, unsigned& osrEntryScratchBufferSize, MemoryMode, CompilationMode, unsigned functionIndex, std::optional<bool> hasExceptionHandlers, unsigned loopIndexForOSREntry, TierUpCount*);
+    B3IRGenerator(B3IRGenerator& inlineCaller, B3IRGenerator& inlineRoot, CalleeGroup&, unsigned functionIndex, std::optional<bool> hasExceptionHandlers, BasicBlock* returnContinuation, Vector<Value*> args);
 
     void computeStackCheckSize(bool& needsOverflowCheck, int32_t& checkSize);
 
@@ -899,6 +899,7 @@ private:
     void traceCF(Args&&... info);
 
     FunctionParser<B3IRGenerator>* m_parser { nullptr };
+    CalleeGroup& m_calleeGroup;
     const ModuleInformation& m_info;
     OptimizingJITCallee* m_callee;
     const MemoryMode m_mode { MemoryMode::BoundsChecking };
@@ -1005,8 +1006,9 @@ void B3IRGenerator::restoreWasmContextInstance(BasicBlock* block, Value* arg)
     });
 }
 
-B3IRGenerator::B3IRGenerator(B3IRGenerator& parentCaller, B3IRGenerator& rootCaller, unsigned functionIndex, BasicBlock* returnContinuation, Vector<Value*> args)
-    : m_info(rootCaller.m_info)
+B3IRGenerator::B3IRGenerator(B3IRGenerator& parentCaller, B3IRGenerator& rootCaller, CalleeGroup& calleeGroup, unsigned functionIndex, std::optional<bool> hasExceptionHandlers, BasicBlock* returnContinuation, Vector<Value*> args)
+    : m_calleeGroup(calleeGroup)
+    , m_info(rootCaller.m_info)
     , m_callee(parentCaller.m_callee)
     , m_mode(rootCaller.m_mode)
     , m_compilationMode(CompilationMode::OMGMode)
@@ -1022,7 +1024,7 @@ B3IRGenerator::B3IRGenerator(B3IRGenerator& parentCaller, B3IRGenerator& rootCal
     , m_unlinkedWasmToWasmCalls(rootCaller.m_unlinkedWasmToWasmCalls)
     , m_osrEntryScratchBufferSize(nullptr)
     , m_constantInsertionValues(m_proc)
-    , m_hasExceptionHandlers(false)
+    , m_hasExceptionHandlers(hasExceptionHandlers)
     , m_numImportFunctions(m_info.importFunctionCount())
     , m_tryCatchDepth(parentCaller.m_tryCatchDepth)
     , m_callSiteIndex(0)
@@ -1033,6 +1035,8 @@ B3IRGenerator::B3IRGenerator(B3IRGenerator& parentCaller, B3IRGenerator& rootCal
     m_instanceValue = rootCaller.m_instanceValue;
     m_baseMemoryValue = rootCaller.m_baseMemoryValue;
     m_boundsCheckingSizeValue = rootCaller.m_boundsCheckingSizeValue;
+    if (parentCaller.m_hasExceptionHandlers && *parentCaller.m_hasExceptionHandlers)
+        m_hasExceptionHandlers = { true };
 }
 
 void B3IRGenerator::computeStackCheckSize(bool& needsOverflowCheck, int32_t& checkSize)
@@ -1071,8 +1075,9 @@ void B3IRGenerator::computeStackCheckSize(bool& needsOverflowCheck, int32_t& che
     needsOverflowCheck = needsOverflowCheck || needUnderflowCheck;
 }
 
-B3IRGenerator::B3IRGenerator(const ModuleInformation& info, OptimizingJITCallee& callee, Procedure& procedure, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, unsigned& osrEntryScratchBufferSize, MemoryMode mode, CompilationMode compilationMode, unsigned functionIndex, std::optional<bool> hasExceptionHandlers, unsigned loopIndexForOSREntry, TierUpCount* tierUp)
-    : m_info(info)
+B3IRGenerator::B3IRGenerator(CalleeGroup& calleeGroup, const ModuleInformation& info, OptimizingJITCallee& callee, Procedure& procedure, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, unsigned& osrEntryScratchBufferSize, MemoryMode mode, CompilationMode compilationMode, unsigned functionIndex, std::optional<bool> hasExceptionHandlers, unsigned loopIndexForOSREntry, TierUpCount* tierUp)
+    : m_calleeGroup(calleeGroup)
+    , m_info(info)
     , m_callee(&callee)
     , m_mode(mode)
     , m_compilationMode(compilationMode)
@@ -4172,7 +4177,7 @@ PatchpointExceptionHandle B3IRGenerator::preparePatchpointForExceptions(BasicBlo
     bool mustSaveState = m_tryCatchDepth;
 
     if (!mustSaveState)
-        return { m_hasExceptionHandlers };
+        return { m_hasExceptionHandlers, callSiteIndex() };
 
     Vector<Value*> liveValues;
     Origin origin = this->origin();
@@ -4307,6 +4312,7 @@ auto B3IRGenerator::addDelegateToUnreachable(ControlType& target, ControlType& d
 auto B3IRGenerator::addThrow(unsigned exceptionIndex, Vector<ExpressionType>& args, Stack&) -> PartialResult
 {
     TRACE_CF("THROW");
+
     PatchpointValue* patch = m_proc.add<PatchpointValue>(B3::Void, origin(), cloningForbidden(Patchpoint));
     patch->effects.terminal = true;
     patch->append(instanceValue(), ValueRep::reg(GPRInfo::argumentGPR0));
@@ -4333,6 +4339,7 @@ auto B3IRGenerator::addThrow(unsigned exceptionIndex, Vector<ExpressionType>& ar
 auto B3IRGenerator::addRethrow(unsigned, ControlType& data) -> PartialResult
 {
     TRACE_CF("RETHROW");
+
     PatchpointValue* patch = m_proc.add<PatchpointValue>(B3::Void, origin(), cloningForbidden(Patchpoint));
     patch->clobber(RegisterSetBuilder::registersToSaveForJSCall(m_proc.usesSIMD() ? RegisterSetBuilder::allRegisters() : RegisterSetBuilder::allScalarRegisters()));
     patch->effects.terminal = true;
@@ -4550,7 +4557,7 @@ B3::PatchpointValue* B3IRGenerator::createCallPatchpoint(BasicBlock* block, Valu
     if (jsCalleeAnchor)
         constrainedPatchArgs.append(B3::ConstrainedValue(jsCalleeAnchor, wasmCalleeInfo.thisArgument));
 
-    Box<PatchpointExceptionHandle> exceptionHandle = Box<PatchpointExceptionHandle>::create(m_hasExceptionHandlers);
+    Box<PatchpointExceptionHandle> exceptionHandle = Box<PatchpointExceptionHandle>::create(m_hasExceptionHandlers, callSiteIndex());
 
     PatchpointValue* patchpoint = m_proc.add<PatchpointValue>(returnType, origin());
     patchpoint->effects.writesPinned = true;
@@ -4684,7 +4691,14 @@ auto B3IRGenerator::emitInlineDirectCall(uint32_t calleeFunctionIndex, const Typ
     auto firstInlineCSI = advanceCallSiteIndex();
 
     const FunctionData& function = m_info.functions[calleeFunctionIndex];
-    m_protectedInlineeGenerators.append(makeUnique<B3IRGenerator>(*this, *m_inlineRoot, calleeFunctionIndex, continuation, WTFMove(getArgs)));
+    std::optional<bool> inlineeHasExceptionHandlers;
+    {
+        Locker locker { m_calleeGroup.m_lock };
+        unsigned calleeFunctionIndexSpace = calleeFunctionIndex + m_numImportFunctions;
+        auto& inlineCallee = m_calleeGroup.wasmEntrypointCalleeFromFunctionIndexSpace(locker, calleeFunctionIndexSpace);
+        inlineeHasExceptionHandlers = inlineCallee.hasExceptionHandlers();
+    }
+    m_protectedInlineeGenerators.append(makeUnique<B3IRGenerator>(*this, *m_inlineRoot, m_calleeGroup, calleeFunctionIndex, inlineeHasExceptionHandlers, continuation, WTFMove(getArgs)));
     auto& irGenerator = *m_protectedInlineeGenerators.last();
     m_protectedInlineeParsers.append(makeUnique<FunctionParser<B3IRGenerator>>(irGenerator, function.data.data(), function.data.size(), calleeSignature, m_info));
     auto& parser = *m_protectedInlineeParsers.last();
@@ -4709,9 +4723,8 @@ auto B3IRGenerator::emitInlineDirectCall(uint32_t calleeFunctionIndex, const Typ
 
     dataLogLnIf(WasmB3IRGeneratorInternal::verboseInlining, "Block ", *m_currentBlock, " is going to do an inline call to block ", *irGenerator.m_topLevelBlock, " then continue at ", *continuation);
 
-    bool mayHaveExceptionHandlers = !m_hasExceptionHandlers || m_hasExceptionHandlers.value();
     m_currentBlock->appendNew<B3::MemoryValue>(m_proc, B3::Store, origin(),
-        m_currentBlock->appendIntConstant(m_proc, origin(), Int32, mayHaveExceptionHandlers ? PatchpointExceptionHandle::s_invalidCallSiteIndex : firstInlineCSI),
+        m_currentBlock->appendIntConstant(m_proc, origin(), Int32, firstInlineCSI),
         framePointer(), safeCast<int32_t>(CallFrameSlot::argumentCountIncludingThis * sizeof(Register) + TagOffset));
 
     m_currentBlock->appendNewControlValue(m_proc, B3::Jump, origin(), FrequentedBlock(irGenerator.m_topLevelBlock));
@@ -4723,7 +4736,7 @@ auto B3IRGenerator::emitInlineDirectCall(uint32_t calleeFunctionIndex, const Typ
     auto lastInlineCSI = advanceCallSiteIndex();
 
     m_currentBlock->appendNew<B3::MemoryValue>(m_proc, B3::Store, origin(),
-        m_currentBlock->appendIntConstant(m_proc, origin(), Int32, mayHaveExceptionHandlers ? PatchpointExceptionHandle::s_invalidCallSiteIndex : advanceCallSiteIndex()),
+        m_currentBlock->appendIntConstant(m_proc, origin(), Int32, advanceCallSiteIndex()),
         framePointer(), safeCast<int32_t>(CallFrameSlot::argumentCountIncludingThis * sizeof(Register) + TagOffset));
 
     m_callee->addCodeOrigin(firstInlineCSI, lastInlineCSI, m_info, calleeFunctionIndex + m_numImportFunctions);
@@ -5065,7 +5078,7 @@ static bool shouldDumpIRFor(uint32_t functionIndex)
     return dumpAllowlist->shouldDumpWasmFunction(functionIndex);
 }
 
-Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileB3(CompilationContext& compilationContext, OptimizingJITCallee& callee, const FunctionData& function, const TypeDefinition& signature, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, const ModuleInformation& info, MemoryMode mode, CompilationMode compilationMode, uint32_t functionIndex, std::optional<bool> hasExceptionHandlers, uint32_t loopIndexForOSREntry, TierUpCount* tierUp)
+Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileB3(CompilationContext& compilationContext, OptimizingJITCallee& callee, const FunctionData& function, const TypeDefinition& signature, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, CalleeGroup& calleeGroup, const ModuleInformation& info, MemoryMode mode, CompilationMode compilationMode, uint32_t functionIndex, std::optional<bool> hasExceptionHandlers, uint32_t loopIndexForOSREntry, TierUpCount* tierUp)
 {
     CompilerTimingScope totalScope("B3", "Total WASM compilation");
 
@@ -5106,7 +5119,7 @@ Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileB3(Compilatio
 
     procedure.code().setForceIRCRegisterAllocation();
 
-    B3IRGenerator irGenerator(info, callee, procedure, unlinkedWasmToWasmCalls, result->osrEntryScratchBufferSize, mode, compilationMode, functionIndex, hasExceptionHandlers, loopIndexForOSREntry, tierUp);
+    B3IRGenerator irGenerator(calleeGroup, info, callee, procedure, unlinkedWasmToWasmCalls, result->osrEntryScratchBufferSize, mode, compilationMode, functionIndex, hasExceptionHandlers, loopIndexForOSREntry, tierUp);
     FunctionParser<B3IRGenerator> parser(irGenerator, function.data.data(), function.data.size(), signature, info);
     WASM_FAIL_IF_HELPER_FAILS(parser.parse());
 
@@ -5670,7 +5683,7 @@ using namespace B3;
 #if !USE(JSVALUE64)
 // On 32-bit platforms, we stub out the entire B3 generator
 
-Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileB3(CompilationContext&, OptimizingJITCallee&, const FunctionData&, const TypeDefinition&, Vector<UnlinkedWasmToWasmCall>&, const ModuleInformation&, MemoryMode, CompilationMode, uint32_t, std::optional<bool>, uint32_t, TierUpCount*)
+Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileB3(CompilationContext&, OptimizingJITCallee&, const FunctionData&, const TypeDefinition&, Vector<UnlinkedWasmToWasmCall>&, CalleeGroup&, const ModuleInformation&, MemoryMode, CompilationMode, uint32_t, std::optional<bool>, uint32_t, TierUpCount*)
 {
     UNREACHABLE_FOR_PLATFORM();
 }
diff --git a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.h b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
index 43b13b4852ba..9b4bff76b524 100644
--- a/Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
+++ b/Source/JavaScriptCore/wasm/WasmB3IRGenerator.h
@@ -49,6 +49,7 @@ namespace JSC {
 namespace Wasm {
 
 class BBQDisassembler;
+class CalleeGroup;
 class MemoryInformation;
 class OptimizingJITCallee;
 class TierUpCount;
@@ -64,7 +65,7 @@ struct CompilationContext {
     Vector<CCallHelpers::Label> catchEntrypoints;
 };
 
-Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileB3(CompilationContext&, OptimizingJITCallee&, const FunctionData&, const TypeDefinition&, Vector<UnlinkedWasmToWasmCall>&, const ModuleInformation&, MemoryMode, CompilationMode, uint32_t functionIndex, std::optional<bool> hasExceptionHandlers, uint32_t loopIndexForOSREntry, TierUpCount* = nullptr);
+Expected<std::unique_ptr<InternalFunction>, String> parseAndCompileB3(CompilationContext&, OptimizingJITCallee&, const FunctionData&, const TypeDefinition&, Vector<UnlinkedWasmToWasmCall>&, CalleeGroup&, const ModuleInformation&, MemoryMode, CompilationMode, uint32_t functionIndex, std::optional<bool> hasExceptionHandlers, uint32_t loopIndexForOSREntry, TierUpCount* = nullptr);
 
 void computePCToCodeOriginMap(CompilationContext&, LinkBuffer&);
 
diff --git a/Source/JavaScriptCore/wasm/WasmIRGeneratorHelpers.h b/Source/JavaScriptCore/wasm/WasmIRGeneratorHelpers.h
index 25eeb6c06333..25d79bb58f02 100644
--- a/Source/JavaScriptCore/wasm/WasmIRGeneratorHelpers.h
+++ b/Source/JavaScriptCore/wasm/WasmIRGeneratorHelpers.h
@@ -41,8 +41,9 @@
 namespace JSC { namespace Wasm {
 
 struct PatchpointExceptionHandle {
-    PatchpointExceptionHandle(std::optional<bool> hasExceptionHandlers)
+    PatchpointExceptionHandle(std::optional<bool> hasExceptionHandlers, unsigned callSiteIndex)
         : m_hasExceptionHandlers(hasExceptionHandlers)
+        , m_callSiteIndex(callSiteIndex)
     { }
 
     PatchpointExceptionHandle(std::optional<bool> hasExceptionHandlers, unsigned callSiteIndex, unsigned numLiveValues)
@@ -54,28 +55,28 @@ struct PatchpointExceptionHandle {
     template <typename Generator>
     void generate(CCallHelpers& jit, const B3::StackmapGenerationParams& params, Generator* generator) const
     {
-        if (m_callSiteIndex == s_invalidCallSiteIndex) {
-            if (!m_hasExceptionHandlers || m_hasExceptionHandlers.value())
-                jit.store32(CCallHelpers::TrustedImm32(m_callSiteIndex), CCallHelpers::tagFor(CallFrameSlot::argumentCountIncludingThis));
+        JIT_COMMENT(jit, "Store call site index ", m_callSiteIndex, " at throw or call site.");
+        jit.store32(CCallHelpers::TrustedImm32(m_callSiteIndex), CCallHelpers::tagFor(CallFrameSlot::argumentCountIncludingThis));
+
+        if (m_hasExceptionHandlers && !*m_hasExceptionHandlers)
+            return;
+        if (!m_numLiveValues)
             return;
-        }
 
-        StackMap values(m_numLiveValues);
-        unsigned paramsOffset = params.size() - m_numLiveValues;
-        unsigned childrenOffset = params.value()->numChildren() - m_numLiveValues;
-        for (unsigned i = 0; i < m_numLiveValues; ++i)
+        StackMap values(*m_numLiveValues);
+        unsigned paramsOffset = params.size() - *m_numLiveValues;
+        unsigned childrenOffset = params.value()->numChildren() - *m_numLiveValues;
+        for (unsigned i = 0; i < *m_numLiveValues; ++i)
             values[i] = OSREntryValue(params[i + paramsOffset], params.value()->child(i + childrenOffset)->type());
 
         generator->addStackMap(m_callSiteIndex, WTFMove(values));
-        JIT_COMMENT(jit, "Store call site index ", m_callSiteIndex, " at throw or call site.");
-        jit.store32(CCallHelpers::TrustedImm32(m_callSiteIndex), CCallHelpers::tagFor(CallFrameSlot::argumentCountIncludingThis));
     }
 
     static constexpr unsigned s_invalidCallSiteIndex = std::numeric_limits<unsigned>::max();
 
     std::optional<bool> m_hasExceptionHandlers;
     unsigned m_callSiteIndex { s_invalidCallSiteIndex };
-    unsigned m_numLiveValues;
+    std::optional<unsigned> m_numLiveValues { };
 };
 
 
diff --git a/Source/JavaScriptCore/wasm/WasmOMGPlan.cpp b/Source/JavaScriptCore/wasm/WasmOMGPlan.cpp
index 4907a65ed92a..e16a7d0e20b8 100644
--- a/Source/JavaScriptCore/wasm/WasmOMGPlan.cpp
+++ b/Source/JavaScriptCore/wasm/WasmOMGPlan.cpp
@@ -114,7 +114,7 @@ void OMGPlan::work(CompilationEffort)
 
     Vector<UnlinkedWasmToWasmCall> unlinkedCalls;
     CompilationContext context;
-    auto parseAndCompileResult = parseAndCompileB3(context, callee.get(), function, signature, unlinkedCalls, m_moduleInformation.get(), m_mode, CompilationMode::OMGMode, m_functionIndex, m_hasExceptionHandlers, UINT32_MAX);
+    auto parseAndCompileResult = parseAndCompileB3(context, callee.get(), function, signature, unlinkedCalls, m_calleeGroup.get(), m_moduleInformation.get(), m_mode, CompilationMode::OMGMode, m_functionIndex, m_hasExceptionHandlers, UINT32_MAX);
 
     if (UNLIKELY(!parseAndCompileResult)) {
         Locker locker { m_lock };
diff --git a/Source/JavaScriptCore/wasm/WasmOSREntryPlan.cpp b/Source/JavaScriptCore/wasm/WasmOSREntryPlan.cpp
index 6563fe9a9dda..2ed7c11ddb98 100644
--- a/Source/JavaScriptCore/wasm/WasmOSREntryPlan.cpp
+++ b/Source/JavaScriptCore/wasm/WasmOSREntryPlan.cpp
@@ -107,7 +107,7 @@ void OSREntryPlan::work(CompilationEffort)
 
     Vector<UnlinkedWasmToWasmCall> unlinkedCalls;
     CompilationContext context;
-    auto parseAndCompileResult = parseAndCompileB3(context, callee.get(), function, signature, unlinkedCalls, m_moduleInformation.get(), m_mode, targetCompilationMode, m_functionIndex, m_hasExceptionHandlers, m_loopIndex);
+    auto parseAndCompileResult = parseAndCompileB3(context, callee.get(), function, signature, unlinkedCalls, m_calleeGroup.get(), m_moduleInformation.get(), m_mode, targetCompilationMode, m_functionIndex, m_hasExceptionHandlers, m_loopIndex);
 
     if (UNLIKELY(!parseAndCompileResult)) {
         Locker locker { m_lock };
diff --git a/JSTests/wasm/stress/inlinee-may-have-exception-handlers.js b/JSTests/wasm/stress/inlinee-may-have-exception-handlers.js
new file mode 100644
index 000000000000..686066ec9274
--- /dev/null
+++ b/JSTests/wasm/stress/inlinee-may-have-exception-handlers.js
@@ -0,0 +1,84 @@
+import * as assert from '../assert.js';
+import { instantiate } from "../wabt-wrapper.js";
+
+let wat = `
+(module
+    (func $main (export "main")
+        call $catcher
+        i64.const 3127057505886423800
+        i64.const -5049743701649469475
+        i32.const 1279394412
+        i32.const 1249280136
+        i32.const -851957055
+        i32.div_s
+        i32.div_s
+        select (result i64)
+        loop (result i64)
+        i64.const 7052281334997434446
+        f64.const 0x1.f0094d7063744p+967
+        i64.trunc_sat_f64_u
+        i64.rem_s
+        end
+        i64.add
+        f64.convert_i64_s
+        i32.const 993640798
+        i32.const -291103156
+        i32.atomic.rmw.xor offset=36014
+        f32.const 0x1.2e5784p-38
+        i64.const 8981315711995315489
+        i64.const 5932917051412947299
+        i64.const 6207621187208520631
+        i32.const 1636976966
+        select (result i64)
+        i64.ne
+        br 0
+        i32.trunc_sat_f32_u
+        i32.div_s
+        f64.const 0x1.7c35ecf56d865p-116
+        i32.const 1987656988
+        i64.const -1947434429939530388
+        f64.const -0x1.ca05a27b00c85p+372
+        i64.const 5169929820610505455
+        block (param f64 i32 f64 i32 i64 f64 i64)
+        drop
+        drop
+        drop
+        drop
+        drop
+        drop
+        drop
+        end
+    )
+    (func $empty (param i32))
+    (func $catcher (type 0)
+        try
+        i32.const 1
+        call $empty
+        catch_all
+        throw $exc
+        end
+        call $main
+    )
+    (memory 32 64)
+    (tag $exc)
+)
+`;
+
+async function test() {
+    let instance = await instantiate(wat, {}, {threads: true, exceptions: true});
+
+    let caughtCount = 0;
+    for (let i = 0; i < 10; i ++) {
+        try {
+            instance.exports.main();
+        } catch (e) {
+            // We expect to either overflow the stack, or end up throwing $exc back into JavaScript.
+            assert.truthy(e instanceof RangeError || e instanceof WebAssembly.Exception);
+            caughtCount ++;
+        }
+    }
+
+    assert.eq(caughtCount, 10);
+}
+
+assert.asyncTest(test());
diff --git a/JSTests/wasm/stress/rethrow-should-set-callsite-index.js b/JSTests/wasm/stress/rethrow-should-set-callsite-index.js
new file mode 100644
index 000000000000..07519a62e491
--- /dev/null
+++ b/JSTests/wasm/stress/rethrow-should-set-callsite-index.js
@@ -0,0 +1,40 @@
+import * as assert from '../assert.js';
+import { instantiate } from "../wabt-wrapper.js";
+
+let wat = `
+(module
+    (type (func))
+    (tag $exc (type 0))
+    (func $empty)
+    (func $rethrower
+        try
+            call $empty
+            throw $exc
+        catch_all
+            rethrow 0
+        end
+    )
+    (func $call-rethrower
+        call $rethrower
+    )
+    (export "call_rethrower" (func $call-rethrower))
+)
+`;
+
+async function test() {
+    let instance = await instantiate(wat, {}, {exceptions: true});
+
+    let caughtCount = 0;
+    for (let i = 0; i < 10000; i ++) {
+        try {
+            instance.exports.call_rethrower();
+        } catch (e) {
+            assert.instanceof(e, WebAssembly.Exception);
+            caughtCount ++;
+        }
+    }
+
+    assert.eq(caughtCount, 10000);
+}
+
+assert.asyncTest(test());
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/JSTests/wasm/stress/inlinee-may-have-exception-handlers.js b/JSTests/wasm/stress/inlinee-may-have-exception-handlers.js
new file mode 100644
index 000000000000..686066ec9274
--- /dev/null
+++ b/JSTests/wasm/stress/inlinee-may-have-exception-handlers.js
@@ -0,0 +1,84 @@
+import * as assert from '../assert.js';
+import { instantiate } from "../wabt-wrapper.js";
+
+let wat = `
+(module
+    (func $main (export "main")
+        call $catcher
+        i64.const 3127057505886423800
+        i64.const -5049743701649469475
+        i32.const 1279394412
+        i32.const 1249280136
+        i32.const -851957055
+        i32.div_s
+        i32.div_s
+        select (result i64)
+        loop (result i64)
+        i64.const 7052281334997434446
+        f64.const 0x1.f0094d7063744p+967
+        i64.trunc_sat_f64_u
+        i64.rem_s
+        end
+        i64.add
+        f64.convert_i64_s
+        i32.const 993640798
+        i32.const -291103156
+        i32.atomic.rmw.xor offset=36014
+        f32.const 0x1.2e5784p-38
+        i64.const 8981315711995315489
+        i64.const 5932917051412947299
+        i64.const 6207621187208520631
+        i32.const 1636976966
+        select (result i64)
+        i64.ne
+        br 0
+        i32.trunc_sat_f32_u
+        i32.div_s
+        f64.const 0x1.7c35ecf56d865p-116
+        i32.const 1987656988
+        i64.const -1947434429939530388
+        f64.const -0x1.ca05a27b00c85p+372
+        i64.const 5169929820610505455
+        block (param f64 i32 f64 i32 i64 f64 i64)
+        drop
+        drop
+        drop
+        drop
+        drop
+        drop
+        drop
+        end
+    )
+    (func $empty (param i32))
+    (func $catcher (type 0)
+        try
+        i32.const 1
+        call $empty
+        catch_all
+        throw $exc
+        end
+        call $main
+    )
+    (memory 32 64)
+    (tag $exc)
+)
+`;
+
+async function test() {
+    let instance = await instantiate(wat, {}, {threads: true, exceptions: true});
+
+    let caughtCount = 0;
+    for (let i = 0; i < 10; i ++) {
+        try {
+            instance.exports.main();
+        } catch (e) {
+            // We expect to either overflow the stack, or end up throwing $exc back into JavaScript.
+            assert.truthy(e instanceof RangeError || e instanceof WebAssembly.Exception);
+            caughtCount ++;
+        }
+    }
+
+    assert.eq(caughtCount, 10);
+}
+
+assert.asyncTest(test());
diff --git a/JSTests/wasm/stress/rethrow-should-set-callsite-index.js b/JSTests/wasm/stress/rethrow-should-set-callsite-index.js
new file mode 100644
index 000000000000..07519a62e491
--- /dev/null
+++ b/JSTests/wasm/stress/rethrow-should-set-callsite-index.js
@@ -0,0 +1,40 @@
+import * as assert from '../assert.js';
+import { instantiate } from "../wabt-wrapper.js";
+
+let wat = `
+(module
+    (type (func))
+    (tag $exc (type 0))
+    (func $empty)
+    (func $rethrower
+        try
+            call $empty
+            throw $exc
+        catch_all
+            rethrow 0
+        end
+    )
+    (func $call-rethrower
+        call $rethrower
+    )
+    (export "call_rethrower" (func $call-rethrower))
+)
+`;
+
+async function test() {
+    let instance = await instantiate(wat, {}, {exceptions: true});
+
+    let caughtCount = 0;
+    for (let i = 0; i < 10000; i ++) {
+        try {
+            instance.exports.call_rethrower();
+        } catch (e) {
+            assert.instanceof(e, WebAssembly.Exception);
+            caughtCount ++;
+        }
+    }
+
+    assert.eq(caughtCount, 10000);
+}
+
+assert.asyncTest(test());
diff --git a/JSTests/wasm/stress/throw-should-set-callsite-index.js b/JSTests/wasm/stress/throw-should-set-callsite-index.js
new file mode 100644
index 000000000000..ee752f63010b
--- /dev/null
+++ b/JSTests/wasm/stress/throw-should-set-callsite-index.js
@@ -0,0 +1,39 @@
+import * as assert from '../assert.js';
+import { instantiate } from "../wabt-wrapper.js";
+
+let wat = `
+(module
+    (type (func))
+    (tag $exc (type 0))
+    (func $empty)
+    (func $thrower
+        try
+            call $empty
+        catch_all
+        end
+        throw $exc
+    )
+    (func $call-thrower
+        call $thrower
+    )
+    (export "call_thrower" (func $call-thrower))
+)
+`;
+
+async function test() {
+    let instance = await instantiate(wat, {}, {exceptions: true});
+
+    let caughtCount = 0;
+    for (let i = 0; i < 10000; i ++) {
+        try {
+            instance.exports.call_thrower();
+        } catch (e) {
+            assert.instanceof(e, WebAssembly.Exception);
+            caughtCount ++;
+        }
+    }
+
+    assert.eq(caughtCount, 10000);
+}
+
+assert.asyncTest(test());
Loading diff…

Original Bug Report

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