From ecf6b4a8af6b5d93333b4b8d2f372af876ec4cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Fern=C3=A1ndez?= Date: Mon, 23 Mar 2026 10:39:56 -0300 Subject: [PATCH 1/4] FunctionHasExecutedCache: expose function names in getFunctionRanges Extend FunctionHasExecutedCache to store and return function names alongside execution status and byte ranges. This enables coverage tools to report per-function data (FN/FNDA records in LCOV) using real function names from JSC rather than heuristic source text parsing. Changes: - Add RangeValue struct holding hasExecuted bool + function name String - Change RangeMap value type from bool to RangeValue - Extend insertUnexecutedRange() to accept an optional function name - Extend getFunctionRanges() return type to include the name - Update all call sites to pass UnlinkedFunctionExecutable::ecmaName() - Add $vm.getFunctionRanges() test helper in JSDollarVM - Add JSTests/controlFlowProfiler/function-names.js test Co-Authored-By: Claude Opus 4.6 (1M context) --- JSTests/controlFlowProfiler/function-names.js | 42 +++++++++++++++++++ Source/JavaScriptCore/bytecode/CodeBlock.cpp | 4 +- .../runtime/ControlFlowProfiler.cpp | 2 +- .../runtime/FunctionHasExecutedCache.cpp | 20 +++++---- .../runtime/FunctionHasExecutedCache.h | 12 ++++-- .../JavaScriptCore/runtime/JSModuleRecord.cpp | 3 +- .../runtime/ProgramExecutable.cpp | 5 ++- Source/JavaScriptCore/tools/JSDollarVM.cpp | 32 ++++++++++++++ 8 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 JSTests/controlFlowProfiler/function-names.js diff --git a/JSTests/controlFlowProfiler/function-names.js b/JSTests/controlFlowProfiler/function-names.js new file mode 100644 index 000000000000..2831673f1670 --- /dev/null +++ b/JSTests/controlFlowProfiler/function-names.js @@ -0,0 +1,42 @@ +var getFunctionRanges = $vm.getFunctionRanges; + +load("./driver/driver.js"); + +function namedFunction() { return 1; } +function anotherFunction() { return 2; } +var arrowFunc = () => 3; + +// Only call namedFunction to test executed vs not-executed. +namedFunction(); + +var ranges = getFunctionRanges(namedFunction); + +// We should have at least the functions declared in this source. +assert(ranges.length >= 3, "Expected at least 3 function ranges, got " + ranges.length); + +// Find our named functions in the ranges. +var namedFunctionRange = null; +var anotherFunctionRange = null; +var arrowFuncRange = null; + +for (var i = 0; i < ranges.length; i++) { + if (ranges[i].name === "namedFunction") + namedFunctionRange = ranges[i]; + else if (ranges[i].name === "anotherFunction") + anotherFunctionRange = ranges[i]; + else if (ranges[i].name === "arrowFunc") + arrowFuncRange = ranges[i]; +} + +// Verify named functions have their names. +assert(namedFunctionRange !== null, "Should find 'namedFunction' in ranges"); +assert(anotherFunctionRange !== null, "Should find 'anotherFunction' in ranges"); +assert(arrowFuncRange !== null, "Should find 'arrowFunc' in ranges"); + +// Verify execution status. +assert(namedFunctionRange.hasExecuted === true, "namedFunction should have executed"); +assert(anotherFunctionRange.hasExecuted === false, "anotherFunction should not have executed"); + +// Verify ranges are valid (start < end). +assert(namedFunctionRange.start < namedFunctionRange.end, "namedFunction range should be valid"); +assert(anotherFunctionRange.start < anotherFunctionRange.end, "anotherFunction range should be valid"); diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.cpp b/Source/JavaScriptCore/bytecode/CodeBlock.cpp index 35c8be16a1a0..b16a2edb4810 100644 --- a/Source/JavaScriptCore/bytecode/CodeBlock.cpp +++ b/Source/JavaScriptCore/bytecode/CodeBlock.cpp @@ -434,7 +434,7 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink for (size_t count = unlinkedCodeBlock->numberOfFunctionDecls(), i = 0; i < count; ++i) { UnlinkedFunctionExecutable* unlinkedExecutable = unlinkedCodeBlock->functionDecl(i); if (shouldUpdateFunctionHasExecutedCache) - vm.functionHasExecutedCache()->insertUnexecutedRange(ownerExecutable->sourceID(), unlinkedExecutable->unlinkedFunctionStart(), unlinkedExecutable->unlinkedFunctionEnd()); + vm.functionHasExecutedCache()->insertUnexecutedRange(ownerExecutable->sourceID(), unlinkedExecutable->unlinkedFunctionStart(), unlinkedExecutable->unlinkedFunctionEnd(), unlinkedExecutable->ecmaName().string()); m_functionDecls[i].set(vm, this, unlinkedExecutable->link(vm, topLevelExecutable, ownerExecutable->source(), std::nullopt, NoIntrinsic, ownerExecutable->isInsideOrdinaryFunction())); } @@ -442,7 +442,7 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink for (size_t count = unlinkedCodeBlock->numberOfFunctionExprs(), i = 0; i < count; ++i) { UnlinkedFunctionExecutable* unlinkedExecutable = unlinkedCodeBlock->functionExpr(i); if (shouldUpdateFunctionHasExecutedCache) - vm.functionHasExecutedCache()->insertUnexecutedRange(ownerExecutable->sourceID(), unlinkedExecutable->unlinkedFunctionStart(), unlinkedExecutable->unlinkedFunctionEnd()); + vm.functionHasExecutedCache()->insertUnexecutedRange(ownerExecutable->sourceID(), unlinkedExecutable->unlinkedFunctionStart(), unlinkedExecutable->unlinkedFunctionEnd(), unlinkedExecutable->ecmaName().string()); m_functionExprs[i].set(vm, this, unlinkedExecutable->link(vm, topLevelExecutable, ownerExecutable->source(), std::nullopt, NoIntrinsic, ownerExecutable->isInsideOrdinaryFunction())); } diff --git a/Source/JavaScriptCore/runtime/ControlFlowProfiler.cpp b/Source/JavaScriptCore/runtime/ControlFlowProfiler.cpp index 1a13e46d1e3b..4662e831b531 100644 --- a/Source/JavaScriptCore/runtime/ControlFlowProfiler.cpp +++ b/Source/JavaScriptCore/runtime/ControlFlowProfiler.cpp @@ -118,7 +118,7 @@ Vector ControlFlowProfiler::getBasicBlocksForSourceID(SourceID } } - const Vector>& functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(sourceID); + const auto functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(sourceID); for (const auto& functionRange : functionRanges) { BasicBlockRange range; range.m_hasExecuted = std::get<0>(functionRange); diff --git a/Source/JavaScriptCore/runtime/FunctionHasExecutedCache.cpp b/Source/JavaScriptCore/runtime/FunctionHasExecutedCache.cpp index 1988ef89fbbe..74053968d71c 100644 --- a/Source/JavaScriptCore/runtime/FunctionHasExecutedCache.cpp +++ b/Source/JavaScriptCore/runtime/FunctionHasExecutedCache.cpp @@ -42,7 +42,7 @@ bool FunctionHasExecutedCache::hasExecutedAtOffset(SourceID id, unsigned offset) for (auto& pair : map) { const FunctionRange& range = pair.key.key(); if (range.m_start <= offset && offset <= range.m_end && range.m_end - range.m_start < distance) { - hasExecuted = pair.value; + hasExecuted = pair.value.m_hasExecuted; distance = range.m_end - range.m_start; } } @@ -50,7 +50,7 @@ bool FunctionHasExecutedCache::hasExecutedAtOffset(SourceID id, unsigned offset) return hasExecuted; } -void FunctionHasExecutedCache::insertUnexecutedRange(SourceID id, unsigned start, unsigned end) +void FunctionHasExecutedCache::insertUnexecutedRange(SourceID id, unsigned start, unsigned end, const String& functionName) { RangeMap& map = m_rangeMap.add(id, RangeMap { }).iterator->value; FunctionRange range; @@ -58,7 +58,7 @@ void FunctionHasExecutedCache::insertUnexecutedRange(SourceID id, unsigned start range.m_end = end; // Only insert unexecuted ranges once for a given sourceID because we may run into a situation where an executable executes, then is GCed, and then is allocated again, // and tries to reinsert itself, claiming it has never run, but this is false because it indeed already executed. - map.add(range, false); + map.add(range, RangeValue { false, functionName }); } void FunctionHasExecutedCache::removeUnexecutedRange(SourceID id, unsigned start, unsigned end) @@ -73,12 +73,16 @@ void FunctionHasExecutedCache::removeUnexecutedRange(SourceID id, unsigned start FunctionRange range; range.m_start = start; range.m_end = end; - map.set(range, true); + auto existingIt = map.find(range); + if (existingIt != map.end()) + existingIt->value.m_hasExecuted = true; + else + map.set(range, RangeValue { true, String() }); } -Vector> FunctionHasExecutedCache::getFunctionRanges(SourceID id) +Vector> FunctionHasExecutedCache::getFunctionRanges(SourceID id) { - Vector> ranges(0); + Vector> ranges(0); auto iterator = m_rangeMap.find(id); if (iterator == m_rangeMap.end()) return ranges; @@ -86,8 +90,8 @@ Vector> FunctionHasExecutedCache::getFuncti RangeMap& map = iterator->value; for (auto& pair : map) { const FunctionRange& range = pair.key.key(); - bool hasExecuted = pair.value; - ranges.append(std::tuple(hasExecuted, range.m_start, range.m_end)); + const RangeValue& value = pair.value; + ranges.append(std::make_tuple(value.m_hasExecuted, range.m_start, range.m_end, value.m_functionName)); } return ranges; diff --git a/Source/JavaScriptCore/runtime/FunctionHasExecutedCache.h b/Source/JavaScriptCore/runtime/FunctionHasExecutedCache.h index 805faa2fac8b..4582b6d6dc47 100644 --- a/Source/JavaScriptCore/runtime/FunctionHasExecutedCache.h +++ b/Source/JavaScriptCore/runtime/FunctionHasExecutedCache.h @@ -30,6 +30,7 @@ #include #include #include +#include namespace JSC { @@ -47,13 +48,18 @@ class FunctionHasExecutedCache { unsigned m_end; }; + struct RangeValue { + bool m_hasExecuted { false }; + String m_functionName; + }; + bool hasExecutedAtOffset(SourceID, unsigned offset); - void insertUnexecutedRange(SourceID, unsigned start, unsigned end); + void insertUnexecutedRange(SourceID, unsigned start, unsigned end, const String& functionName = String()); void removeUnexecutedRange(SourceID, unsigned start, unsigned end); - Vector> getFunctionRanges(SourceID); + Vector> getFunctionRanges(SourceID); private: - using RangeMap = UncheckedKeyHashMap, bool>; + using RangeMap = UncheckedKeyHashMap, RangeValue>; using SourceIDToRangeMap = UncheckedKeyHashMap, RangeMap>; SourceIDToRangeMap m_rangeMap; }; diff --git a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp index 24cea00a3f69..bb395d9d24c4 100644 --- a/Source/JavaScriptCore/runtime/JSModuleRecord.cpp +++ b/Source/JavaScriptCore/runtime/JSModuleRecord.cpp @@ -283,7 +283,8 @@ void JSModuleRecord::instantiateDeclarations(JSGlobalObject* globalObject, Modul if (vm.typeProfiler() || vm.controlFlowProfiler()) { vm.functionHasExecutedCache()->insertUnexecutedRange(moduleProgramExecutable->sourceID(), unlinkedFunctionExecutable->unlinkedFunctionStart(), - unlinkedFunctionExecutable->unlinkedFunctionEnd()); + unlinkedFunctionExecutable->unlinkedFunctionEnd(), + unlinkedFunctionExecutable->ecmaName().string()); } auto* executable = unlinkedFunctionExecutable->link(vm, moduleProgramExecutable, moduleProgramExecutable->source()); SourceParseMode parseMode = executable->parseMode(); diff --git a/Source/JavaScriptCore/runtime/ProgramExecutable.cpp b/Source/JavaScriptCore/runtime/ProgramExecutable.cpp index 1fbc30580398..5c02ffb43859 100644 --- a/Source/JavaScriptCore/runtime/ProgramExecutable.cpp +++ b/Source/JavaScriptCore/runtime/ProgramExecutable.cpp @@ -230,9 +230,10 @@ JSObject* ProgramExecutable::initializeGlobalProperties(VM& vm, JSGlobalObject* globalObject->createGlobalFunctionBinding(unlinkedFunctionExecutable->name()); RETURN_IF_EXCEPTION(throwScope, nullptr); if (vm.typeProfiler() || vm.controlFlowProfiler()) { - vm.functionHasExecutedCache()->insertUnexecutedRange(sourceID(), + vm.functionHasExecutedCache()->insertUnexecutedRange(sourceID(), unlinkedFunctionExecutable->unlinkedFunctionStart(), - unlinkedFunctionExecutable->unlinkedFunctionEnd()); + unlinkedFunctionExecutable->unlinkedFunctionEnd(), + unlinkedFunctionExecutable->ecmaName().string()); } } diff --git a/Source/JavaScriptCore/tools/JSDollarVM.cpp b/Source/JavaScriptCore/tools/JSDollarVM.cpp index b2e73561ad48..c748f2759bc4 100644 --- a/Source/JavaScriptCore/tools/JSDollarVM.cpp +++ b/Source/JavaScriptCore/tools/JSDollarVM.cpp @@ -2188,6 +2188,7 @@ static JSC_DECLARE_HOST_FUNCTION(functionFlattenDictionaryObject); static JSC_DECLARE_HOST_FUNCTION(functionDumpBasicBlockExecutionRanges); static JSC_DECLARE_HOST_FUNCTION(functionHasBasicBlockExecuted); static JSC_DECLARE_HOST_FUNCTION(functionBasicBlockExecutionCount); +static JSC_DECLARE_HOST_FUNCTION(functionGetFunctionRanges); static JSC_DECLARE_HOST_FUNCTION(functionEnableDebuggerModeWhenIdle); static JSC_DECLARE_HOST_FUNCTION(functionDisableDebuggerModeWhenIdle); static JSC_DECLARE_HOST_FUNCTION(functionDeleteAllCodeWhenIdle); @@ -3654,6 +3655,36 @@ JSC_DEFINE_HOST_FUNCTION(functionBasicBlockExecutionCount, (JSGlobalObject* glob return JSValue::encode(JSValue(executionCount)); } +// $vm.getFunctionRanges(sourceFunction) returns an array of { name, hasExecuted, start, end } +// for all functions declared in the same source as the given function. +JSC_DEFINE_HOST_FUNCTION(functionGetFunctionRanges, (JSGlobalObject* globalObject, CallFrame* callFrame)) +{ + DollarVMAssertScope assertScope; + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue functionValue = callFrame->argument(0); + RELEASE_ASSERT(functionValue.isCallable()); + FunctionExecutable* executable = (jsDynamicCast(functionValue.asCell()->getObject()))->jsExecutable(); + + const auto functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(executable->sourceID()); + + JSArray* result = constructEmptyArray(globalObject, nullptr, functionRanges.size()); + RETURN_IF_EXCEPTION(scope, encodedJSUndefined()); + + for (size_t i = 0; i < functionRanges.size(); i++) { + JSObject* entry = JSFinalObject::create(vm, JSFinalObject::createStructure(vm, globalObject, globalObject->objectPrototype(), 4)); + entry->putDirect(vm, Identifier::fromString(vm, "hasExecuted"_s), jsBoolean(std::get<0>(functionRanges[i]))); + entry->putDirect(vm, Identifier::fromString(vm, "start"_s), jsNumber(std::get<1>(functionRanges[i]))); + entry->putDirect(vm, Identifier::fromString(vm, "end"_s), jsNumber(std::get<2>(functionRanges[i]))); + const String& name = std::get<3>(functionRanges[i]); + entry->putDirect(vm, Identifier::fromString(vm, "name"_s), name.isEmpty() ? jsEmptyString(vm) : jsString(vm, name)); + result->putDirectIndex(globalObject, i, entry); + } + + return JSValue::encode(result); +} + class DoNothingDebugger final : public Debugger { WTF_MAKE_NONCOPYABLE(DoNothingDebugger); WTF_MAKE_TZONE_ALLOCATED(DoNothingDebugger); @@ -4438,6 +4469,7 @@ void JSDollarVM::finishCreation(VM& vm) addFunction(vm, "dumpBasicBlockExecutionRanges"_s, functionDumpBasicBlockExecutionRanges , 0); addFunction(vm, "hasBasicBlockExecuted"_s, functionHasBasicBlockExecuted, 2); addFunction(vm, "basicBlockExecutionCount"_s, functionBasicBlockExecutionCount, 2); + addFunction(vm, "getFunctionRanges"_s, functionGetFunctionRanges, 1); addFunction(vm, "enableDebuggerModeWhenIdle"_s, functionEnableDebuggerModeWhenIdle, 0); addFunction(vm, "disableDebuggerModeWhenIdle"_s, functionDisableDebuggerModeWhenIdle, 0); From 96d85c687322c37574bca9f95e6701410e27cbce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Fern=C3=A1ndez?= Date: Mon, 23 Mar 2026 14:05:40 -0300 Subject: [PATCH 2/4] Address review feedback for getFunctionRanges - Add null check for jsDynamicCast in $vm.getFunctionRanges to handle non-JSFunction callables (BoundFunction, Proxy, etc.) - Add RETURN_IF_EXCEPTION after putDirectIndex in the loop - Add missing arrowFuncRange assertions in function-names.js test Co-Authored-By: Claude Opus 4.6 (1M context) --- JSTests/controlFlowProfiler/function-names.js | 2 ++ Source/JavaScriptCore/tools/JSDollarVM.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/JSTests/controlFlowProfiler/function-names.js b/JSTests/controlFlowProfiler/function-names.js index 2831673f1670..8af099523f16 100644 --- a/JSTests/controlFlowProfiler/function-names.js +++ b/JSTests/controlFlowProfiler/function-names.js @@ -36,7 +36,9 @@ assert(arrowFuncRange !== null, "Should find 'arrowFunc' in ranges"); // Verify execution status. assert(namedFunctionRange.hasExecuted === true, "namedFunction should have executed"); assert(anotherFunctionRange.hasExecuted === false, "anotherFunction should not have executed"); +assert(arrowFuncRange.hasExecuted === false, "arrowFunc should not have executed"); // Verify ranges are valid (start < end). assert(namedFunctionRange.start < namedFunctionRange.end, "namedFunction range should be valid"); assert(anotherFunctionRange.start < anotherFunctionRange.end, "anotherFunction range should be valid"); +assert(arrowFuncRange.start < arrowFuncRange.end, "arrowFunc range should be valid"); diff --git a/Source/JavaScriptCore/tools/JSDollarVM.cpp b/Source/JavaScriptCore/tools/JSDollarVM.cpp index c748f2759bc4..662b0eecf173 100644 --- a/Source/JavaScriptCore/tools/JSDollarVM.cpp +++ b/Source/JavaScriptCore/tools/JSDollarVM.cpp @@ -3664,8 +3664,12 @@ JSC_DEFINE_HOST_FUNCTION(functionGetFunctionRanges, (JSGlobalObject* globalObjec auto scope = DECLARE_THROW_SCOPE(vm); JSValue functionValue = callFrame->argument(0); - RELEASE_ASSERT(functionValue.isCallable()); - FunctionExecutable* executable = (jsDynamicCast(functionValue.asCell()->getObject()))->jsExecutable(); + if (!functionValue.isCallable()) + return throwVMTypeError(globalObject, scope, "Expected argument to be callable"_s); + JSFunction* function = jsDynamicCast(functionValue); + if (!function) + return throwVMTypeError(globalObject, scope, "Expected argument to be a JSFunction"_s); + FunctionExecutable* executable = function->jsExecutable(); const auto functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(executable->sourceID()); @@ -3680,6 +3684,7 @@ JSC_DEFINE_HOST_FUNCTION(functionGetFunctionRanges, (JSGlobalObject* globalObjec const String& name = std::get<3>(functionRanges[i]); entry->putDirect(vm, Identifier::fromString(vm, "name"_s), name.isEmpty() ? jsEmptyString(vm) : jsString(vm, name)); result->putDirectIndex(globalObject, i, entry); + RETURN_IF_EXCEPTION(scope, encodedJSUndefined()); } return JSValue::encode(result); From c433d0e82093ddb493b501d314584a329a2be8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Fern=C3=A1ndez?= Date: Mon, 23 Mar 2026 16:07:00 -0300 Subject: [PATCH 3/4] Hoist Structure creation out of loop in getFunctionRanges The entry Structure is identical for every iteration, so create it once before the loop to reduce allocation churn. Co-Authored-By: Claude Opus 4.6 (1M context) --- Source/JavaScriptCore/tools/JSDollarVM.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/JavaScriptCore/tools/JSDollarVM.cpp b/Source/JavaScriptCore/tools/JSDollarVM.cpp index 662b0eecf173..872eccb431ad 100644 --- a/Source/JavaScriptCore/tools/JSDollarVM.cpp +++ b/Source/JavaScriptCore/tools/JSDollarVM.cpp @@ -3676,8 +3676,9 @@ JSC_DEFINE_HOST_FUNCTION(functionGetFunctionRanges, (JSGlobalObject* globalObjec JSArray* result = constructEmptyArray(globalObject, nullptr, functionRanges.size()); RETURN_IF_EXCEPTION(scope, encodedJSUndefined()); + Structure* entryStructure = JSFinalObject::createStructure(vm, globalObject, globalObject->objectPrototype(), 4); for (size_t i = 0; i < functionRanges.size(); i++) { - JSObject* entry = JSFinalObject::create(vm, JSFinalObject::createStructure(vm, globalObject, globalObject->objectPrototype(), 4)); + JSObject* entry = JSFinalObject::create(vm, entryStructure); entry->putDirect(vm, Identifier::fromString(vm, "hasExecuted"_s), jsBoolean(std::get<0>(functionRanges[i]))); entry->putDirect(vm, Identifier::fromString(vm, "start"_s), jsNumber(std::get<1>(functionRanges[i]))); entry->putDirect(vm, Identifier::fromString(vm, "end"_s), jsNumber(std::get<2>(functionRanges[i]))); From 9d2e644b6fda52d25d0fa2dc52112579f606506b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Fern=C3=A1ndez?= Date: Mon, 23 Mar 2026 20:36:37 -0300 Subject: [PATCH 4/4] Guard jsExecutable() call and fix comment field order - Use jsDynamicCast(function->executable()) instead of function->jsExecutable() to avoid assert on host/native functions - Fix comment to match actual field emission order: hasExecuted, start, end, name Co-Authored-By: Claude Opus 4.6 (1M context) --- Source/JavaScriptCore/tools/JSDollarVM.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/JavaScriptCore/tools/JSDollarVM.cpp b/Source/JavaScriptCore/tools/JSDollarVM.cpp index 872eccb431ad..5b4367fd830b 100644 --- a/Source/JavaScriptCore/tools/JSDollarVM.cpp +++ b/Source/JavaScriptCore/tools/JSDollarVM.cpp @@ -3655,7 +3655,7 @@ JSC_DEFINE_HOST_FUNCTION(functionBasicBlockExecutionCount, (JSGlobalObject* glob return JSValue::encode(JSValue(executionCount)); } -// $vm.getFunctionRanges(sourceFunction) returns an array of { name, hasExecuted, start, end } +// $vm.getFunctionRanges(sourceFunction) returns an array of { hasExecuted, start, end, name } // for all functions declared in the same source as the given function. JSC_DEFINE_HOST_FUNCTION(functionGetFunctionRanges, (JSGlobalObject* globalObject, CallFrame* callFrame)) { @@ -3669,7 +3669,9 @@ JSC_DEFINE_HOST_FUNCTION(functionGetFunctionRanges, (JSGlobalObject* globalObjec JSFunction* function = jsDynamicCast(functionValue); if (!function) return throwVMTypeError(globalObject, scope, "Expected argument to be a JSFunction"_s); - FunctionExecutable* executable = function->jsExecutable(); + FunctionExecutable* executable = jsDynamicCast(function->executable()); + if (!executable) + return throwVMTypeError(globalObject, scope, "Expected argument to be a non-host JSFunction"_s); const auto functionRanges = vm.functionHasExecutedCache()->getFunctionRanges(executable->sourceID());