CVE-2024-27851
Overview
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.
Attack Path
- 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.
- 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.
- Emit invalid call-site metadata The inlined region is compiled with placeholder/invalid call-site indices because m_hasExceptionHandlers was false.
- 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.
- 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
Changed Functions
| Function | Change | Notes |
|---|---|---|
BBQDisassemblerSource/JavaScriptCore/wasm/WasmB3IRGenerator.h |
modified | |
CalleeGroupSource/JavaScriptCore/wasm/WasmB3IRGenerator.h |
modified | |
MemoryInformationSource/JavaScriptCore/wasm/WasmB3IRGenerator.h |
modified | |
OptimizingJITCalleeSource/JavaScriptCore/wasm/WasmB3IRGenerator.h |
modified | |
TierUpCountSource/JavaScriptCore/wasm/WasmB3IRGenerator.h |
modified | |
ifSource/JavaScriptCore/wasm/WasmIRGeneratorHelpers.h |
modified | |
forJSTests/wasm/stress/inlinee-may-have-exception-handlers.js |
modified | |
forJSTests/wasm/stress/rethrow-should-set-callsite-index.js |
modified |
Files Changed
Source/JavaScriptCore/wasm/WasmB3IRGenerator.cppSource/JavaScriptCore/wasm/WasmB3IRGenerator.hSource/JavaScriptCore/wasm/WasmIRGeneratorHelpers.hSource/JavaScriptCore/wasm/WasmOMGPlan.cppSource/JavaScriptCore/wasm/WasmOSREntryPlan.cppJSTests/wasm/stress/inlinee-may-have-exception-handlers.jsJSTests/wasm/stress/rethrow-should-set-callsite-index.js
Audit Directions
- Other inlining-propagated stateAudit B3IRGenerator’s inlining constructor for other fields defaulted/hardcoded rather than derived from the inlinee (stack checks, memory mode, tier-up).
- Call-site index emissiongrep wasm/ for s_invalidCallSiteIndex and mayHaveExceptionHandlers to find other places that substitute placeholder call-site indices based on possibly-wrong EH state.
- hasExceptionHandlers plumbingTrace hasExceptionHandlers/CalleeGroup::hasExceptionHandlers through OMG/OSR/BBQ compilation to ensure every inlined or tiered path uses the callee’s true value.
Patch
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());
Regression Test / PoC
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());