From e68eb1fd62e3c2155154d80baed9697a2a704ea0 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:49:17 +0000 Subject: [PATCH] inspector: check exceptions in jsToInspectorValue jsToInspectorValue calls getOwnPropertyNames (which reaches getOwnNonIndexPropertyNames and declares a ThrowScope) and then calls object.get() on each property without checking for an exception in between. Under validateExceptionChecks this aborts when the inspector backend dispatches with no JS on the stack, e.g. when InspectorConsoleAgent::enable() replays buffered console messages: the inner ThrowScope destructor simulates a throw because there is no topEntryFrame to attribute it to, and the next ThrowScope constructor in JSObject::get sees it unchecked. Declare a ThrowScope in the object branch and RETURN_IF_EXCEPTION after getIndex, getOwnPropertyNames, get, and the recursive calls. Give toInspectorValue (the JSLockHolder entry point) a TopExceptionScope and clear any exception before returning so the simulated throw does not leak to scope-free inspector callers. --- .../inspector/InjectedScriptBase.cpp | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Source/JavaScriptCore/inspector/InjectedScriptBase.cpp b/Source/JavaScriptCore/inspector/InjectedScriptBase.cpp index 4d9152423abf3..3a95c52da1deb 100644 --- a/Source/JavaScriptCore/inspector/InjectedScriptBase.cpp +++ b/Source/JavaScriptCore/inspector/InjectedScriptBase.cpp @@ -38,6 +38,7 @@ #include "JSLock.h" #include "JSNativeStdFunction.h" #include "ScriptFunctionCall.h" +#include "TopExceptionScope.h" #include #include @@ -67,25 +68,33 @@ static RefPtr jsToInspectorValue(JSC::JSGlobalObject* globalObject, return JSON::Value::create(asString(value)->value(globalObject).data); if (value.isObject()) { + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); if (isJSArray(value)) { auto inspectorArray = JSON::Array::create(); auto& array = *asArray(value); unsigned length = array.length(); for (unsigned i = 0; i < length; i++) { - auto elementValue = jsToInspectorValue(globalObject, array.getIndex(globalObject, i), maxDepth); + auto element = array.getIndex(globalObject, i); + RETURN_IF_EXCEPTION(scope, nullptr); + auto elementValue = jsToInspectorValue(globalObject, element, maxDepth); + RETURN_IF_EXCEPTION(scope, nullptr); if (!elementValue) return nullptr; inspectorArray->pushValue(elementValue.releaseNonNull()); } return inspectorArray; } - JSC::VM& vm = globalObject->vm(); auto inspectorObject = JSON::Object::create(); auto& object = *value.getObject(); JSC::PropertyNameArrayBuilder propertyNames(vm, JSC::PropertyNameMode::Strings, JSC::PrivateSymbolMode::Exclude); object.methodTable()->getOwnPropertyNames(&object, globalObject, propertyNames, JSC::DontEnumPropertiesMode::Exclude); + RETURN_IF_EXCEPTION(scope, nullptr); for (auto& name : propertyNames) { - auto inspectorValue = jsToInspectorValue(globalObject, object.get(globalObject, name), maxDepth); + auto propertyValue = object.get(globalObject, name); + RETURN_IF_EXCEPTION(scope, nullptr); + auto inspectorValue = jsToInspectorValue(globalObject, propertyValue, maxDepth); + RETURN_IF_EXCEPTION(scope, nullptr); if (!inspectorValue) return nullptr; inspectorObject->setValue(name.string(), inspectorValue.releaseNonNull()); @@ -102,7 +111,14 @@ RefPtr toInspectorValue(JSC::JSGlobalObject* globalObject, JSC::JSV // FIXME: Maybe we should move the JSLockHolder stuff to the callers since this function takes a JSValue directly. // Doing the locking here made sense when we were trying to abstract the difference between multiple JavaScript engines. JSC::JSLockHolder holder(globalObject); - return jsToInspectorValue(globalObject, value, JSON::Value::maxDepth); + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm); + auto result = jsToInspectorValue(globalObject, value, JSON::Value::maxDepth); + if (scope.exception()) [[unlikely]] { + scope.clearExceptionExceptTermination(); + return nullptr; + } + return result; } InjectedScriptBase::InjectedScriptBase(const InjectedScriptBase&) = default;