blob: c773c66f72af7fa37cb59f66540beed60e911a88 [file] [log] [blame]
/*
* Copyright (C) 2008-2025 Apple Inc. All rights reserved.
* Copyright (C) 2008 Cameron Zwarich <[email protected]>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "Interpreter.h"
#include "AbortReason.h"
#include "AbstractModuleRecord.h"
#include "ArgList.h"
#include "BatchedTransitionOptimizer.h"
#include "BuiltinNames.h"
#include "Bytecodes.h"
#include "CallLinkInfo.h"
#include "CatchScope.h"
#include "CheckpointOSRExitSideState.h"
#include "CodeBlock.h"
#include "Debugger.h"
#include "DirectArguments.h"
#include "DirectEvalCodeCache.h"
#include "EvalCodeBlock.h"
#include "ExecutableBaseInlines.h"
#include "FrameTracers.h"
#include "GlobalObjectMethodTable.h"
#include "InlineCallFrame.h"
#include "InterpreterInlines.h"
#include "JITCode.h"
#include "JSArrayInlines.h"
#include "JSBoundFunction.h"
#include "JSCInlines.h"
#include "JSCellButterfly.h"
#include "JSLexicalEnvironment.h"
#include "JSModuleEnvironment.h"
#include "JSModuleRecord.h"
#include "JSObject.h"
#include "JSPromise.h"
#include "JSPromiseCombinatorsContext.h"
#include "JSPromiseCombinatorsGlobalContext.h"
#include "JSPromiseReaction.h"
#include "JSRemoteFunction.h"
#include "JSString.h"
#include "JSWebAssemblyException.h"
#include "LLIntThunks.h"
#include "LiteralParser.h"
#include "ModuleProgramCodeBlock.h"
#include "NativeCallee.h"
#include "ProgramCodeBlock.h"
#include "ProtoCallFrameInlines.h"
#include "Register.h"
#include "RegisterAtOffsetList.h"
#include "ScopedArguments.h"
#include "SourceProfiler.h"
#include "StackFrame.h"
#include "StackVisitor.h"
#include "StrictEvalActivation.h"
#include "VMEntryScopeInlines.h"
#include "VMInlines.h"
#include "VMTrapsInlines.h"
#include "VirtualRegister.h"
#include "WasmThunks.h"
#include "parser/ParserModes.h"
#include <stdio.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Scope.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/MakeString.h>
#include <wtf/text/StringBuilder.h>
#if ENABLE(WEBASSEMBLY)
#include "JSWebAssemblyInstance.h"
#include "WasmContext.h"
#include "WebAssemblyFunction.h"
#endif
namespace JSC {
static inline DirectEvalCodeCache::CacheLookupKey directEvalCacheKey(JSGlobalObject* globalObject, JSString* string, BytecodeIndex bytecodeIndex)
{
if (string->isRope()) {
auto rope = string->asRope();
if (auto source = rope->tryGetLHS("()"_s))
return DirectEvalCodeCache::CacheLookupKey(source, bytecodeIndex, DirectEvalCodeCache::RopeSuffix::FunctionCall);
return DirectEvalCodeCache::CacheLookupKey(rope->resolveRope(globalObject).impl(), bytecodeIndex, DirectEvalCodeCache::RopeSuffix::None);
}
return DirectEvalCodeCache::CacheLookupKey(string->getValueImpl(), bytecodeIndex, DirectEvalCodeCache::RopeSuffix::None);
}
JSValue eval(CallFrame* callFrame, JSValue thisValue, JSScope* callerScopeChain, CodeBlock* callerBaselineCodeBlock, BytecodeIndex bytecodeIndex, LexicallyScopedFeatures lexicallyScopedFeatures)
{
JSGlobalObject* globalObject = callerBaselineCodeBlock->globalObject();
if (callFrame->guaranteedJSValueCallee() != globalObject->evalFunction()) [[unlikely]]
return { };
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto clobberizeValidator = makeScopeExit([&] {
vm.didEnterVM = true;
});
if (!callFrame->argumentCount())
return jsUndefined();
JSValue program = callFrame->uncheckedArgument(0);
JSString* programString = nullptr;
bool isTrusted = false;
if (program.isString()) [[likely]]
programString = asString(program);
else {
if (Options::useTrustedTypes() && program.isObject()) {
auto* structure = globalObject->trustedScriptStructure();
if (structure == asObject(program)->structure()) {
programString = program.toString(globalObject);
RETURN_IF_EXCEPTION(scope, { });
isTrusted = true;
} else {
auto code = globalObject->globalObjectMethodTable()->codeForEval(globalObject, program);
RETURN_IF_EXCEPTION(scope, { });
if (!code.isNull()) {
programString = jsString(vm, code);
isTrusted = true;
}
}
}
if (!programString) [[unlikely]]
return program;
}
if (globalObject->trustedTypesEnforcement() != TrustedTypesEnforcement::None && !isTrusted) [[unlikely]] {
bool canCompileStrings = globalObject->globalObjectMethodTable()->canCompileStrings(globalObject, CompilationType::DirectEval, programString->value(globalObject).data, *vm.emptyList);
RETURN_IF_EXCEPTION(scope, { });
if (!canCompileStrings) [[unlikely]] {
throwException(globalObject, scope, createEvalError(globalObject, "Refused to evaluate a string as JavaScript because this document requires a 'Trusted Type' assignment."_s));
return { };
}
}
TopCallFrameSetter topCallFrame(vm, callFrame);
if (!globalObject->evalEnabled() && globalObject->trustedTypesEnforcement() != TrustedTypesEnforcement::EnforcedWithEvalEnabled) [[unlikely]] {
globalObject->globalObjectMethodTable()->reportViolationForUnsafeEval(globalObject, programString->value(globalObject).data);
throwException(globalObject, scope, createEvalError(globalObject, globalObject->evalDisabledErrorMessage()));
return { };
}
auto cacheKey = directEvalCacheKey(globalObject, programString, bytecodeIndex);
RETURN_IF_EXCEPTION(scope, { });
DirectEvalExecutable* eval = callerBaselineCodeBlock->directEvalCodeCache().get(cacheKey);
if (!eval) {
auto programSource = programString->value(globalObject).data;
if (SourceProfiler::g_profilerHook) [[unlikely]] {
SourceTaintedOrigin sourceTaintedOrigin = computeNewSourceTaintedOriginFromStack(vm, callFrame);
auto source = makeSource(programSource, callerBaselineCodeBlock->source().provider()->sourceOrigin(), sourceTaintedOrigin);
SourceProfiler::profile(SourceProfiler::Type::Eval, source);
}
if (!(lexicallyScopedFeatures & StrictModeLexicallyScopedFeature)) {
JSValue parsedValue;
if (programSource.is8Bit()) {
LiteralParser<Latin1Character, JSONReviverMode::Disabled> preparser(globalObject, programSource.span8(), SloppyJSON, callerBaselineCodeBlock);
parsedValue = preparser.tryEval();
} else {
LiteralParser<char16_t, JSONReviverMode::Disabled> preparser(globalObject, programSource.span16(), SloppyJSON, callerBaselineCodeBlock);
parsedValue = preparser.tryEval();
}
RETURN_IF_EXCEPTION(scope, { });
if (parsedValue)
RELEASE_AND_RETURN(scope, parsedValue);
}
TDZEnvironment variablesUnderTDZ;
PrivateNameEnvironment privateNameEnvironment;
JSScope::collectClosureVariablesUnderTDZ(callerScopeChain, variablesUnderTDZ, privateNameEnvironment);
SourceTaintedOrigin sourceTaintedOrigin = computeNewSourceTaintedOriginFromStack(vm, callFrame);
UnlinkedCodeBlock* callerUnlinkedCodeBlock = callerBaselineCodeBlock->unlinkedCodeBlock();
bool isArrowFunctionContext = callerUnlinkedCodeBlock->isArrowFunction() || callerUnlinkedCodeBlock->isArrowFunctionContext();
DerivedContextType derivedContextType = callerUnlinkedCodeBlock->derivedContextType();
if (!isArrowFunctionContext && callerUnlinkedCodeBlock->isClassContext()) {
derivedContextType = callerUnlinkedCodeBlock->isConstructor()
? DerivedContextType::DerivedConstructorContext
: DerivedContextType::DerivedMethodContext;
}
EvalContextType evalContextType;
if (callerUnlinkedCodeBlock->parseMode() == SourceParseMode::ClassFieldInitializerMode)
evalContextType = EvalContextType::InstanceFieldEvalContext;
else if (isFunctionParseMode(callerUnlinkedCodeBlock->parseMode()))
evalContextType = EvalContextType::FunctionEvalContext;
else if (callerUnlinkedCodeBlock->codeType() == EvalCode)
evalContextType = callerUnlinkedCodeBlock->evalContextType();
else
evalContextType = EvalContextType::None;
eval = DirectEvalExecutable::create(globalObject, makeSource(programSource, callerBaselineCodeBlock->source().provider()->sourceOrigin(), sourceTaintedOrigin), lexicallyScopedFeatures, derivedContextType, callerUnlinkedCodeBlock->needsClassFieldInitializer(), callerUnlinkedCodeBlock->privateBrandRequirement(), isArrowFunctionContext, callerBaselineCodeBlock->ownerExecutable()->isInsideOrdinaryFunction(), evalContextType, &variablesUnderTDZ, &privateNameEnvironment);
EXCEPTION_ASSERT(!!scope.exception() == !eval);
if (!eval) [[unlikely]]
return { };
// Skip the eval cache if tainted since another eval call could have a different taintedness.
if (sourceTaintedOrigin == SourceTaintedOrigin::Untainted)
callerBaselineCodeBlock->directEvalCodeCache().set(globalObject, callerBaselineCodeBlock, cacheKey, eval);
}
RELEASE_AND_RETURN(scope, vm.interpreter.executeEval(eval, thisValue, callerScopeChain));
}
unsigned sizeOfVarargs(JSGlobalObject* globalObject, JSValue arguments, uint32_t firstVarArgOffset)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!arguments.isCell()) [[unlikely]] {
if (arguments.isUndefinedOrNull())
return 0;
throwException(globalObject, scope, createInvalidFunctionApplyParameterError(globalObject, arguments));
return 0;
}
JSCell* cell = arguments.asCell();
unsigned length;
switch (cell->type()) {
case DirectArgumentsType:
length = jsCast<DirectArguments*>(cell)->length(globalObject);
break;
case ScopedArgumentsType:
length = jsCast<ScopedArguments*>(cell)->length(globalObject);
break;
case ClonedArgumentsType:
length = jsCast<ClonedArguments*>(cell)->length(globalObject);
break;
case JSCellButterflyType:
length = jsCast<JSCellButterfly*>(cell)->length();
break;
case StringType:
case SymbolType:
case HeapBigIntType:
throwException(globalObject, scope, createInvalidFunctionApplyParameterError(globalObject, arguments));
return 0;
default:
RELEASE_ASSERT(arguments.isObject());
length = clampToUnsigned(toLength(globalObject, jsCast<JSObject*>(cell)));
break;
}
RETURN_IF_EXCEPTION(scope, 0);
if (length > maxArguments)
throwStackOverflowError(globalObject, scope);
if (length >= firstVarArgOffset)
length -= firstVarArgOffset;
else
length = 0;
return length;
}
unsigned sizeFrameForForwardArguments(JSGlobalObject* globalObject, CallFrame* callFrame, VM& vm, unsigned numUsedStackSlots)
{
auto scope = DECLARE_THROW_SCOPE(vm);
unsigned length = callFrame->argumentCount();
CallFrame* calleeFrame = calleeFrameForVarargs(callFrame, numUsedStackSlots, length + 1);
if (!vm.ensureJSStackCapacityFor(calleeFrame->registers())) [[unlikely]]
throwStackOverflowError(globalObject, scope);
return length;
}
unsigned sizeFrameForVarargs(JSGlobalObject* globalObject, CallFrame* callFrame, VM& vm, JSValue arguments, unsigned numUsedStackSlots, uint32_t firstVarArgOffset)
{
auto scope = DECLARE_THROW_SCOPE(vm);
unsigned length = sizeOfVarargs(globalObject, arguments, firstVarArgOffset);
RETURN_IF_EXCEPTION(scope, 0);
CallFrame* calleeFrame = calleeFrameForVarargs(callFrame, numUsedStackSlots, length + 1);
if (length > maxArguments || !vm.ensureJSStackCapacityFor(calleeFrame->registers())) [[unlikely]] {
throwStackOverflowError(globalObject, scope);
return 0;
}
return length;
}
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
void loadVarargs(JSGlobalObject* globalObject, JSValue* firstElementDest, JSValue arguments, uint32_t offset, uint32_t length)
{
if (!arguments.isCell()) [[unlikely]]
return;
if (!length)
return;
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSCell* cell = arguments.asCell();
switch (cell->type()) {
case DirectArgumentsType:
scope.release();
jsCast<DirectArguments*>(cell)->copyToArguments(globalObject, firstElementDest, offset, length);
return;
case ScopedArgumentsType:
scope.release();
jsCast<ScopedArguments*>(cell)->copyToArguments(globalObject, firstElementDest, offset, length);
return;
case ClonedArgumentsType:
scope.release();
jsCast<ClonedArguments*>(cell)->copyToArguments(globalObject, firstElementDest, offset, length);
return;
case JSCellButterflyType:
scope.release();
jsCast<JSCellButterfly*>(cell)->copyToArguments(globalObject, firstElementDest, offset, length);
return;
default: {
ASSERT(arguments.isObject());
JSObject* object = jsCast<JSObject*>(cell);
if (isJSArray(object)) {
scope.release();
jsCast<JSArray*>(object)->copyToArguments(globalObject, firstElementDest, offset, length);
return;
}
unsigned i;
for (i = 0; i < length && object->canGetIndexQuickly(i + offset); ++i)
firstElementDest[i] = object->getIndexQuickly(i + offset);
for (; i < length; ++i) {
JSValue value = object->get(globalObject, i + offset);
RETURN_IF_EXCEPTION(scope, void());
firstElementDest[i] = value;
}
return;
}
}
}
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
void setupVarargsFrame(JSGlobalObject* globalObject, CallFrame* callFrame, CallFrame* newCallFrame, JSValue arguments, uint32_t offset, uint32_t length)
{
VirtualRegister calleeFrameOffset(newCallFrame - callFrame);
loadVarargs(
globalObject,
std::bit_cast<JSValue*>(&callFrame->r(calleeFrameOffset + CallFrame::argumentOffset(0))),
arguments, offset, length);
newCallFrame->setArgumentCountIncludingThis(length + 1);
}
void setupVarargsFrameAndSetThis(JSGlobalObject* globalObject, CallFrame* callFrame, CallFrame* newCallFrame, JSValue thisValue, JSValue arguments, uint32_t firstVarArgOffset, uint32_t length)
{
setupVarargsFrame(globalObject, callFrame, newCallFrame, arguments, firstVarArgOffset, length);
newCallFrame->setThisValue(thisValue);
}
void setupForwardArgumentsFrame(JSGlobalObject*, CallFrame* execCaller, CallFrame* execCallee, uint32_t length)
{
ASSERT(length == execCaller->argumentCount());
unsigned offset = execCaller->argumentOffset(0) * sizeof(Register);
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
memcpy(reinterpret_cast<char*>(execCallee) + offset, reinterpret_cast<char*>(execCaller) + offset, length * sizeof(Register));
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
execCallee->setArgumentCountIncludingThis(length + 1);
}
void setupForwardArgumentsFrameAndSetThis(JSGlobalObject* globalObject, CallFrame* execCaller, CallFrame* execCallee, JSValue thisValue, uint32_t length)
{
setupForwardArgumentsFrame(globalObject, execCaller, execCallee, length);
execCallee->setThisValue(thisValue);
}
Interpreter::Interpreter()
{
#if ASSERT_ENABLED
static std::once_flag assertOnceKey;
std::call_once(assertOnceKey, [] {
if (g_jscConfig.vmEntryDisallowed)
return;
for (unsigned i = 0; i < NUMBER_OF_BYTECODE_IDS; ++i) {
OpcodeID opcodeID = static_cast<OpcodeID>(i);
RELEASE_ASSERT(getOpcodeID(getOpcode(opcodeID)) == opcodeID);
}
});
#endif // ASSERT_ENABLED
}
Interpreter::~Interpreter() = default;
WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN
#if ENABLE(COMPUTED_GOTO_OPCODES)
#if !ENABLE(LLINT_EMBEDDED_OPCODE_ID) || ASSERT_ENABLED
UncheckedKeyHashMap<Opcode, OpcodeID>& Interpreter::opcodeIDTable()
{
static LazyNeverDestroyed<UncheckedKeyHashMap<Opcode, OpcodeID>> opcodeIDTable;
static std::once_flag initializeKey;
std::call_once(initializeKey, [&] {
opcodeIDTable.construct();
const Opcode* opcodeTable = LLInt::opcodeMap();
for (unsigned i = 0; i < NUMBER_OF_BYTECODE_IDS; ++i)
opcodeIDTable->add(opcodeTable[i], static_cast<OpcodeID>(i));
});
return opcodeIDTable;
}
#endif // !ENABLE(LLINT_EMBEDDED_OPCODE_ID) || ASSERT_ENABLED
#endif // ENABLE(COMPUTED_GOTO_OPCODES)
WTF_ALLOW_UNSAFE_BUFFER_USAGE_END
#if ASSERT_ENABLED
bool Interpreter::isOpcode(Opcode opcode)
{
#if ENABLE(COMPUTED_GOTO_OPCODES)
return opcode != HashTraits<Opcode>::emptyValue()
&& !HashTraits<Opcode>::isDeletedValue(opcode)
&& opcodeIDTable().contains(opcode);
#else
return opcode >= 0 && opcode <= op_end;
#endif
}
#endif // ASSERT_ENABLED
void Interpreter::getAsyncStackTrace(JSCell* owner, Vector<StackFrame>& results, JSGenerator* generator, size_t maxStackSize)
{
RELEASE_ASSERT(Options::useAsyncStackTrace());
ASSERT(generator);
AssertNoGC assertNoGC;
VM& vm = this->vm();
auto getContextValueFromPromise = [&](JSPromise* promise) -> JSValue {
if (promise && promise->status() == JSPromise::Status::Pending) {
JSValue reactionsValue = promise->internalField(JSPromise::Field::ReactionsOrResult).get();
if (auto* reaction = jsDynamicCast<JSPromiseReaction*>(reactionsValue))
return reaction->context();
}
return JSValue();
};
auto getParentGenerator = [&](JSGenerator* gen) -> JSGenerator* {
JSValue generatorContext = gen->internalField(static_cast<unsigned>(JSGenerator::Field::Context)).get();
ASSERT(generatorContext);
JSPromise* awaitedPromise = jsDynamicCast<JSPromise*>(generatorContext);
JSValue promiseContext = getContextValueFromPromise(awaitedPromise);
if (!promiseContext)
return nullptr;
// handle simple `await`
if (auto* generator = jsDynamicCast<JSGenerator*>(promiseContext))
return generator;
// handle `Promise.all`, `Promise.allSettled`, and `Promise.any`
if (auto* promiseCombinatorsContext = jsDynamicCast<JSPromiseCombinatorsContext*>(promiseContext)) {
if (auto* globalContext = jsDynamicCast<JSPromiseCombinatorsGlobalContext*>(promiseCombinatorsContext->globalContext())) {
JSValue promiseValue = globalContext->promise();
ASSERT(promiseValue);
if (auto* promise = jsDynamicCast<JSPromise*>(promiseValue)) {
if (JSValue promiseContext = getContextValueFromPromise(promise)) {
if (auto* generator = jsDynamicCast<JSGenerator*>(promiseContext))
return generator;
}
}
}
}
// handle and `Promise.race`
if (auto* contextPromise = jsDynamicCast<JSPromise*>(promiseContext)) {
if (JSValue parentContext = getContextValueFromPromise(contextPromise)) {
if (auto* generator = jsDynamicCast<JSGenerator*>(parentContext))
return generator;
}
}
return nullptr;
};
auto computeBytecodeIndex = [&](CodeBlock* codeBlock, JSGenerator* generator) -> BytecodeIndex {
BytecodeIndex bytecodeIndex(0);
JSValue stateValue = generator->internalField(static_cast<unsigned>(JSGenerator::Field::State)).get();
if (stateValue.isInt32()) {
int32_t state = stateValue.asInt32();
size_t numberOfJumpTables = codeBlock->numberOfUnlinkedSwitchJumpTables();
if (state > 0 && numberOfJumpTables > 0) {
size_t lastTableIndex = numberOfJumpTables - 1;
const UnlinkedSimpleJumpTable& jumpTable = codeBlock->unlinkedSwitchJumpTable(lastTableIndex);
int32_t offset = jumpTable.offsetForValue(state);
if (offset)
bytecodeIndex = BytecodeIndex(offset);
}
}
return bytecodeIndex;
};
JSGenerator* currentGenerator = getParentGenerator(generator);
while (currentGenerator && results.size() < maxStackSize) {
JSValue nextValue = currentGenerator->internalField(static_cast<unsigned>(JSGenerator::Field::Next)).get();
JSFunction* asyncFunction = jsDynamicCast<JSFunction*>(nextValue);
if (asyncFunction && !asyncFunction->isHostOrBuiltinFunction()) {
if (FunctionExecutable* executable = asyncFunction->jsExecutable()) {
// If a CodeBlock doesn't already exist, the stack trace will only show the filename and won't show line column
if (CodeBlock* codeBlock = executable->codeBlockForCall()) {
BytecodeIndex bytecodeIndex = computeBytecodeIndex(codeBlock, currentGenerator);
results.append(StackFrame(vm, owner, asyncFunction, codeBlock, bytecodeIndex, /* isAsyncFrame */ true));
} else
results.append(StackFrame(vm, owner, asyncFunction, /* isAsyncFrame */ true));
}
}
currentGenerator = getParentGenerator(currentGenerator);
}
}
void Interpreter::getStackTrace(JSCell* owner, Vector<StackFrame>& results, size_t framesToSkip, size_t maxStackSize, JSCell* caller, JSCell* ownerOfCallLinkInfo, CallLinkInfo* callLinkInfo)
{
AssertNoGC assertNoGC;
VM& vm = this->vm();
CallFrame* callFrame = vm.topCallFrame;
if (!callFrame || !maxStackSize)
return;
size_t skippedFrames = 0;
auto isImplementationVisibilityPrivate = [&](CodeBlock* codeBlock) {
if (auto* executable = codeBlock->ownerExecutable())
return executable->implementationVisibility() != ImplementationVisibility::Public;
return false;
};
// This is OK since we never cause GC inside it (see AssertNoGC).
auto appendFrame = [&](CodeBlock* codeBlock, BytecodeIndex bytecodeIndex) {
if (results.size() >= maxStackSize)
return IterationStatus::Done;
if (skippedFrames < framesToSkip) {
skippedFrames++;
return IterationStatus::Continue;
}
if (isImplementationVisibilityPrivate(codeBlock))
return IterationStatus::Continue;
results.append(StackFrame(vm, owner, codeBlock, bytecodeIndex));
return IterationStatus::Continue;
};
if (!caller && ownerOfCallLinkInfo && callLinkInfo && callLinkInfo->isTailCall()) {
// Reconstruct the top frame from CallLinkInfo*
CodeBlock* codeBlock = jsDynamicCast<CodeBlock*>(ownerOfCallLinkInfo);
if (codeBlock) {
CodeOrigin codeOrigin = callLinkInfo->codeOrigin();
if (codeOrigin.inlineCallFrame()) {
for (auto currentCodeOrigin = &codeOrigin; currentCodeOrigin && currentCodeOrigin->inlineCallFrame(); currentCodeOrigin = currentCodeOrigin->inlineCallFrame()->getCallerSkippingTailCalls()) {
if (appendFrame(baselineCodeBlockForInlineCallFrame(currentCodeOrigin->inlineCallFrame()), currentCodeOrigin->bytecodeIndex()) == IterationStatus::Done)
return;
}
} else
if (appendFrame(codeBlock, codeOrigin.bytecodeIndex()) == IterationStatus::Done)
return;
}
}
bool foundCaller = !caller;
JSGenerator* asyncStackTraceOriginGenerator = nullptr;
size_t asyncStackTraceInsertPos = 0;
EntryFrame* previousEntryFrame = nullptr;
size_t previousEntryFrameStackTraceInsertPos = 0;
auto updateAsyncStackTraceOriginGenerator = [&]() -> void {
ASSERT(Options::useAsyncStackTrace());
auto* record = vmEntryRecord(previousEntryFrame);
if (record->m_context) {
if (auto* generator = jsDynamicCast<JSGenerator*>(record->m_context)) {
asyncStackTraceOriginGenerator = generator;
asyncStackTraceInsertPos = previousEntryFrameStackTraceInsertPos;
}
}
};
StackVisitor::visit(callFrame, vm, [&] (StackVisitor& visitor) ALWAYS_INLINE_LAMBDA {
if (results.size() >= maxStackSize)
return IterationStatus::Done;
if (skippedFrames < framesToSkip) {
skippedFrames++;
return IterationStatus::Continue;
}
if (!foundCaller) {
if (!visitor->callee().isNativeCallee() && visitor->callee().asCell() == caller)
foundCaller = true;
skippedFrames++;
return IterationStatus::Continue;
}
auto* currentEntryFrame = visitor->entryFrame();
if (Options::useAsyncStackTrace()) {
if (currentEntryFrame != previousEntryFrame && previousEntryFrame)
updateAsyncStackTraceOriginGenerator();
}
if (!visitor->isImplementationVisibilityPrivate()) {
if (visitor->isNativeCalleeFrame()) {
auto* nativeCallee = visitor->callee().asNativeCallee();
switch (nativeCallee->category()) {
case NativeCallee::Category::Wasm: {
results.append(StackFrame(visitor->wasmFunctionIndexOrName(), visitor->wasmFunctionIndex()));
break;
}
case NativeCallee::Category::InlineCache: {
break;
}
}
} else if (!!visitor->codeBlock() && !visitor->codeBlock()->unlinkedCodeBlock()->isBuiltinFunction())
results.append(StackFrame(vm, owner, visitor->callee().asCell(), visitor->codeBlock(), visitor->bytecodeIndex()));
else
results.append(StackFrame(vm, owner, visitor->callee().asCell()));
previousEntryFrame = currentEntryFrame;
previousEntryFrameStackTraceInsertPos = results.size();
}
return IterationStatus::Continue;
});
if (Options::useAsyncStackTrace()) {
// Check if the last entry frame had a generator context
// that wasn't captured during stack traversal (e.g. TLA)
if (!asyncStackTraceOriginGenerator && previousEntryFrame)
updateAsyncStackTraceOriginGenerator();
if (asyncStackTraceOriginGenerator) {
Vector<StackFrame> asyncFrames;
getAsyncStackTrace(owner, asyncFrames, asyncStackTraceOriginGenerator, maxStackSize);
if (!asyncFrames.isEmpty())
results.insertVector(asyncStackTraceInsertPos, asyncFrames);
}
}
}
String Interpreter::stackTraceAsString(VM& vm, const Vector<StackFrame>& stackTrace)
{
// FIXME: JSStringJoiner could be more efficient than StringBuilder here.
StringBuilder builder;
for (unsigned i = 0; i < stackTrace.size(); i++) {
builder.append(String(stackTrace[i].toString(vm)));
if (i != stackTrace.size() - 1)
builder.append('\n');
}
return builder.toString();
}
ALWAYS_INLINE static HandlerInfo* findExceptionHandler(StackVisitor& visitor, CodeBlock* codeBlock, RequiredHandler requiredHandler)
{
ASSERT(codeBlock);
#if ENABLE(DFG_JIT)
ASSERT(!visitor->isInlinedDFGFrame());
#endif
CallFrame* callFrame = visitor->callFrame();
unsigned exceptionHandlerIndex;
if (JSC::JITCode::isOptimizingJIT(codeBlock->jitType()))
exceptionHandlerIndex = callFrame->callSiteIndex().bits();
else
exceptionHandlerIndex = callFrame->bytecodeIndex().offset();
return codeBlock->handlerForIndex(exceptionHandlerIndex, requiredHandler);
}
class GetCatchHandlerFunctor {
public:
GetCatchHandlerFunctor()
: m_handler(nullptr)
{
}
HandlerInfo* handler() { return m_handler; }
IterationStatus operator()(StackVisitor& visitor) const
{
visitor.unwindToMachineCodeBlockFrame();
CodeBlock* codeBlock = visitor->codeBlock();
if (!codeBlock)
return IterationStatus::Continue;
m_handler = findExceptionHandler(visitor, codeBlock, RequiredHandler::CatchHandler);
if (m_handler)
return IterationStatus::Done;
return IterationStatus::Continue;
}
private:
mutable HandlerInfo* m_handler;
};
CatchInfo::CatchInfo(const HandlerInfo* handler, CodeBlock* codeBlock)
{
m_valid = !!handler;
if (m_valid) {
m_type = handler->type();
#if ENABLE(JIT)
m_nativeCode = handler->nativeCode;
#endif
// handler->target is meaningless for getting a code offset when catching
// the exception in a DFG/FTL frame. This bytecode target offset could be
// something that's in an inlined frame, which means an array access
// with this bytecode offset in the machine frame is utterly meaningless
// and can cause an overflow. OSR exit properly exits to handler->target
// in the proper frame.
if (!JSC::JITCode::isOptimizingJIT(codeBlock->jitType()))
m_catchPCForInterpreter = { codeBlock->instructions().at(handler->target).ptr() };
else
m_catchPCForInterpreter = { static_cast<JSInstruction*>(nullptr) };
}
}
#if ENABLE(WEBASSEMBLY)
CatchInfo::CatchInfo(const Wasm::HandlerInfo* handler, const Wasm::Callee* callee)
{
m_valid = !!handler;
if (m_valid) {
m_type = HandlerType::Catch;
#if ENABLE(JIT)
m_nativeCode = handler->m_nativeCode;
m_nativeCodeForDispatchAndCatch = nullptr;
#endif
m_catchPCForInterpreter = { static_cast<JSInstruction*>(nullptr) };
if (callee->compilationMode() == Wasm::CompilationMode::IPIntMode) {
m_catchPCForInterpreter = handler->m_target;
m_catchMetadataPCForInterpreter = handler->m_targetMetadata;
m_tryDepthForThrow = handler->m_tryDepth;
} else {
#if ENABLE(JIT)
m_nativeCode = Wasm::Thunks::singleton().stub(Wasm::catchInWasmThunkGenerator).template retagged<ExceptionHandlerPtrTag>().code();
m_nativeCodeForDispatchAndCatch = handler->m_nativeCode;
#endif
}
}
}
#endif
class UnwindFunctor : UnwindFunctorBase {
public:
UnwindFunctor(VM& vm, CallFrame*& callFrame, Exception* exception, JSValue thrownValue, CodeBlock*& codeBlock, CatchInfo& handler, JSRemoteFunction*& seenRemoteFunction)
: UnwindFunctorBase(vm)
, m_callFrame(callFrame)
, m_isTermination(vm.isTerminationException(exception))
, m_codeBlock(codeBlock)
, m_handler(handler)
, m_seenRemoteFunction(seenRemoteFunction)
#if ENABLE(WEBASSEMBLY)
, m_exception(exception)
{
if (!m_isTermination) {
if (JSWebAssemblyException* wasmException = jsDynamicCast<JSWebAssemblyException*>(thrownValue)) {
m_catchableFromWasm = true;
m_wasmTag = &wasmException->tag();
if (m_wasmTag == &Wasm::Tag::jsExceptionTag())
m_exception->tryUnwrapValueForJSTag(m_vm);
} else if (ErrorInstance* error = jsDynamicCast<ErrorInstance*>(thrownValue))
m_catchableFromWasm = error->isCatchableFromWasm();
else
m_catchableFromWasm = true;
// https://webassembly.github.io/exception-handling/js-api/#create-a-host-function
if (!m_wasmTag)
m_wasmTag = &Wasm::Tag::jsExceptionTag();
}
}
#else
{
UNUSED_PARAM(thrownValue);
}
#endif
IterationStatus operator()(StackVisitor& visitor) const
{
visitor.unwindToMachineCodeBlockFrame();
m_callFrame = visitor->callFrame();
m_codeBlock = visitor->codeBlock();
m_handler.m_valid = false;
if (m_codeBlock) {
if (!m_isTermination) {
m_handler = { findExceptionHandler(visitor, m_codeBlock, RequiredHandler::AnyHandler), m_codeBlock };
if (m_handler.m_valid)
return IterationStatus::Done;
}
}
CalleeBits callee = visitor->callee();
if (callee.isNativeCallee()) {
NativeCallee* nativeCallee = callee.asNativeCallee();
switch (nativeCallee->category()) {
case NativeCallee::Category::Wasm: {
#if ENABLE(WEBASSEMBLY)
if (m_catchableFromWasm) {
auto* wasmCallee = uncheckedDowncast<Wasm::Callee>(nativeCallee);
if (wasmCallee->hasExceptionHandlers()) {
JSWebAssemblyInstance* instance = m_callFrame->wasmInstance();
unsigned exceptionHandlerIndex = visitor->wasmCallSiteIndex().bits();
auto* wasmHandler = wasmCallee->handlerForIndex(*instance, exceptionHandlerIndex, m_wasmTag.get());
m_handler = { wasmHandler, wasmCallee };
if (m_handler.m_valid) {
if (m_wasmTag == &Wasm::Tag::jsExceptionTag())
m_exception->wrapValueForJSTag(instance->globalObject());
m_callFrame->setCallSiteIndex(visitor->wasmCallSiteIndex());
return IterationStatus::Done;
}
}
}
#endif
break;
}
case NativeCallee::Category::InlineCache: {
break;
}
}
}
if (!m_callFrame->isNativeCalleeFrame() && JSC::isRemoteFunction(m_callFrame->jsCallee()) && !m_isTermination) {
// Continue searching for a handler, but mark that a marshalling function was on the stack so that we can
// translate the exception before jumping to the handler.
m_seenRemoteFunction = jsCast<JSRemoteFunction*>(m_callFrame->jsCallee());
}
JSGlobalObject* globalObject = m_callFrame->lexicalGlobalObject(m_vm);
notifyDebuggerOfUnwinding(globalObject, m_callFrame);
copyCalleeSavesToEntryFrameCalleeSavesBuffer(visitor);
bool shouldStopUnwinding = visitor->callerIsEntryFrame();
if (shouldStopUnwinding)
return IterationStatus::Done;
return IterationStatus::Continue;
}
private:
CallFrame*& m_callFrame;
bool m_isTermination;
CodeBlock*& m_codeBlock;
CatchInfo& m_handler;
JSRemoteFunction*& m_seenRemoteFunction;
#if ENABLE(WEBASSEMBLY)
Exception* m_exception;
mutable RefPtr<const Wasm::Tag> m_wasmTag;
bool m_catchableFromWasm { false };
#endif
};
// Replace an exception which passes across a marshalling boundary with a TypeError for its handler's global object.
static void sanitizeRemoteFunctionException(VM& vm, JSRemoteFunction* remoteFunction, Exception* exception)
{
ASSERT(vm.traps().isDeferringTermination());
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT(exception);
ASSERT(!vm.isTerminationException(exception));
JSGlobalObject* globalObject = remoteFunction->globalObject();
JSValue exceptionValue = exception->value();
scope.clearException();
// Avoid user-observable ToString()
String exceptionString;
if (exceptionValue.isPrimitive())
exceptionString = exceptionValue.toWTFString(globalObject);
else if (exceptionValue.asCell()->inherits<ErrorInstance>())
exceptionString = static_cast<ErrorInstance*>(exceptionValue.asCell())->sanitizedMessageString(globalObject);
EXCEPTION_ASSERT(!scope.exception()); // We must not have entered JS at this point
if (exceptionString.length()) {
throwVMTypeError(globalObject, scope, exceptionString);
return;
}
throwVMTypeError(globalObject, scope);
}
NEVER_INLINE CatchInfo Interpreter::unwind(VM& vm, CallFrame*& callFrame, Exception* exception)
{
// If we're unwinding the stack due to a regular exception (not a TerminationException), then
// we want to use a DeferTerminationForAWhile scope. This is because we want to avoid a
// TerminationException being raised (due to a concurrent termination request) in the middle
// of unwinding. The unwinding code only checks if we're handling a TerminationException before
// it starts unwinding and is not expecting this status to change in the middle. Without the
// DeferTerminationForAWhile scope, control flow may end up in an exception handler, and effectively
// "catch" the newly raised TerminationException, which should not be catchable.
//
// On the other hand, if we're unwinding the stack due to a TerminationException, we do not need
// nor want the DeferTerminationForAWhile scope. This is because on exit, DeferTerminationForAWhile
// will set the VMTraps NeedTermination bit if termination is in progress. The system expects the
// NeedTermination bit to be have been cleared by VMTraps::handleTraps() once the TerminationException
// has been raised. Some legacy client apps relies on this and expects to be able to re-enter the
// VM after it exits due to termination. If the NeedTermination bit is set, upon re-entry, the
// VM will behave as if a termination request is pending and terminate almost immediately, thereby
// breaking the legacy client apps.
//
// FIXME: Revisit this once we can deprecate this legacy behavior of being able to re-enter the VM
// after termination.
std::optional<DeferTerminationForAWhile> deferScope;
if (!vm.isTerminationException(exception))
deferScope.emplace(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
ASSERT(reinterpret_cast<void*>(callFrame) != vm.topEntryFrame);
CodeBlock* codeBlock = callFrame->isNativeCalleeFrame() ? nullptr : callFrame->codeBlock();
JSValue exceptionValue = exception->value();
ASSERT(!exceptionValue.isEmpty());
ASSERT(!exceptionValue.isCell() || exceptionValue.asCell());
// This shouldn't be possible (hence the assertions), but we're already in the slowest of
// slow cases, so let's harden against it anyway to be safe.
if (exceptionValue.isEmpty() || (exceptionValue.isCell() && !exceptionValue.asCell()))
exceptionValue = jsNull();
EXCEPTION_ASSERT_UNUSED(scope, scope.exception());
// Calculate an exception handler vPC, unwinding call frames as necessary.
CatchInfo catchInfo;
JSRemoteFunction* seenRemoteFunction = nullptr;
UnwindFunctor functor(vm, callFrame, exception, exceptionValue, codeBlock, catchInfo, seenRemoteFunction);
StackVisitor::visit<StackVisitor::TerminateIfTopEntryFrameIsEmpty>(callFrame, vm, functor);
if (seenRemoteFunction) {
ASSERT(!vm.isTerminationException(exception));
sanitizeRemoteFunctionException(vm, seenRemoteFunction, exception);
exception = scope.exception(); // clear m_needExceptionCheck
}
if (vm.hasCheckpointOSRSideState())
vm.popAllCheckpointOSRSideStateUntil(callFrame);
return catchInfo;
}
void Interpreter::notifyDebuggerOfExceptionToBeThrown(VM& vm, JSGlobalObject* globalObject, CallFrame* callFrame, Exception* exception)
{
ASSERT(!vm.isTerminationException(exception));
Debugger* debugger = globalObject->debugger();
if (debugger && debugger->needsExceptionCallbacks() && !exception->didNotifyInspectorOfThrow()) {
// This code assumes that if the debugger is enabled then there is no inlining.
// If that assumption turns out to be false then we'll ignore the inlined call
// frames.
// https://bugs.webkit.org/show_bug.cgi?id=121754
GetCatchHandlerFunctor functor;
if (callFrame)
StackVisitor::visit(callFrame, vm, functor);
HandlerInfo* handler = functor.handler();
ASSERT(!handler || handler->isCatchHandler());
bool hasCatchHandler = !!handler;
if (!hasCatchHandler) {
if (vm.topEntryFrame) {
auto* entryRecord = vmEntryRecord(vm.topEntryFrame);
if (entryRecord->m_context)
hasCatchHandler = true;
}
}
debugger->exception(globalObject, callFrame, exception->value(), hasCatchHandler);
}
exception->setDidNotifyInspectorOfThrow();
}
NEVER_INLINE JSValue Interpreter::checkVMEntryPermission()
{
if (Options::crashOnDisallowedVMEntry() || g_jscConfig.vmEntryDisallowed)
CRASH_WITH_EXTRA_SECURITY_IMPLICATION_AND_INFO(VMEntryDisallowed, "VM entry disallowed"_s);
return jsUndefined();
}
JSValue Interpreter::executeProgram(const SourceCode& source, JSGlobalObject*, JSObject* thisObj)
{
VM& vm = this->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSScope* scope = thisObj->globalObject()->globalScope();
JSGlobalObject* globalObject = scope->globalObject();
JSCallee* globalCallee = globalObject->globalCallee();
VMEntryScope entryScope(vm, globalObject);
auto clobberizeValidator = makeScopeExit([&] {
vm.didEnterVM = true;
});
if (SourceProfiler::g_profilerHook) [[unlikely]]
SourceProfiler::profile(SourceProfiler::Type::Program, source);
ProgramExecutable* program = ProgramExecutable::create(globalObject, source);
EXCEPTION_ASSERT(throwScope.exception() || program);
RETURN_IF_EXCEPTION(throwScope, { });
if (globalObject->globalScopeExtension())
program->setTaintedByWithScope();
ASSERT(!vm.isCollectorBusyOnCurrentThread());
RELEASE_ASSERT(vm.currentThreadIsHoldingAPILock());
if (!vm.isSafeToRecurseSoft()) [[unlikely]]
return throwStackOverflowError(globalObject, throwScope);
if (vm.disallowVMEntryCount) [[unlikely]]
return checkVMEntryPermission();
// First check if the "program" is actually just a JSON object. If so,
// we'll handle the JSON object here. Else, we'll handle real JS code
// below at failedJSONP.
Vector<JSONPData> JSONPData;
bool parseResult;
StringView programSource = program->source().view();
// Skip JSONP if the program is tainted. We want there to be a tainted
// frame on the stack in case the program does an eval via a setter.
if (source.provider()->sourceTaintedOrigin() != SourceTaintedOrigin::Untainted)
goto failedJSONP;
if (programSource.isNull())
return jsUndefined();
if (programSource.is8Bit()) {
LiteralParser<Latin1Character, JSONReviverMode::Disabled> literalParser(globalObject, programSource.span8(), JSONP);
parseResult = literalParser.tryJSONPParse(JSONPData, globalObject->globalObjectMethodTable()->supportsRichSourceInfo(globalObject));
} else {
LiteralParser<char16_t, JSONReviverMode::Disabled> literalParser(globalObject, programSource.span16(), JSONP);
parseResult = literalParser.tryJSONPParse(JSONPData, globalObject->globalObjectMethodTable()->supportsRichSourceInfo(globalObject));
}
// FIXME: The patterns to trigger JSONP fast path should be more idiomatic.
// https://bugs.webkit.org/show_bug.cgi?id=243578
RETURN_IF_EXCEPTION(throwScope, { });
if (parseResult) {
JSValue result;
for (unsigned entry = 0; entry < JSONPData.size(); entry++) {
Vector<JSONPPathEntry> JSONPPath;
JSONPPath.swap(JSONPData[entry].m_path);
JSValue JSONPValue = JSONPData[entry].m_value.get();
if (JSONPPath.size() == 1 && JSONPPath[0].m_type == JSONPPathEntryTypeDeclareVar) {
if (!globalObject->isStructureExtensible()) [[unlikely]]
goto failedJSONP;
globalObject->createGlobalVarBinding<BindingCreationContext::Global>(JSONPPath[0].m_pathEntryName);
RETURN_IF_EXCEPTION(throwScope, { });
PutPropertySlot slot(globalObject);
globalObject->methodTable()->put(globalObject, globalObject, JSONPPath[0].m_pathEntryName, JSONPValue, slot);
RETURN_IF_EXCEPTION(throwScope, { });
result = jsUndefined();
continue;
}
JSValue baseObject(globalObject);
for (unsigned i = 0; i < JSONPPath.size() - 1; i++) {
ASSERT(JSONPPath[i].m_type != JSONPPathEntryTypeDeclareVar);
switch (JSONPPath[i].m_type) {
case JSONPPathEntryTypeDot: {
if (i == 0) {
RELEASE_ASSERT(baseObject == globalObject);
auto doGet = [&] (JSSegmentedVariableObject* scope) {
PropertySlot slot(scope, PropertySlot::InternalMethodType::Get);
if (scope->getPropertySlot(globalObject, JSONPPath[i].m_pathEntryName, slot))
return slot.getValue(globalObject, JSONPPath[i].m_pathEntryName);
return JSValue();
};
JSValue result = doGet(globalObject->globalLexicalEnvironment());
RETURN_IF_EXCEPTION(throwScope, JSValue());
if (result) {
baseObject = result;
continue;
}
result = doGet(globalObject);
RETURN_IF_EXCEPTION(throwScope, JSValue());
if (result) {
baseObject = result;
continue;
}
if (entry)
return throwException(globalObject, throwScope, createUndefinedVariableError(globalObject, JSONPPath[i].m_pathEntryName));
goto failedJSONP;
}
baseObject = baseObject.get(globalObject, JSONPPath[i].m_pathEntryName);
RETURN_IF_EXCEPTION(throwScope, JSValue());
continue;
}
case JSONPPathEntryTypeLookup: {
baseObject = baseObject.get(globalObject, static_cast<unsigned>(JSONPPath[i].m_pathIndex));
RETURN_IF_EXCEPTION(throwScope, JSValue());
continue;
}
default:
RELEASE_ASSERT_NOT_REACHED();
return jsUndefined();
}
}
const Identifier& ident = JSONPPath.last().m_pathEntryName;
if (JSONPPath.size() == 1 && JSONPPath.last().m_type != JSONPPathEntryTypeLookup) {
RELEASE_ASSERT(baseObject == globalObject);
JSGlobalLexicalEnvironment* scope = globalObject->globalLexicalEnvironment();
bool hasProperty = scope->hasProperty(globalObject, ident);
RETURN_IF_EXCEPTION(throwScope, JSValue());
if (hasProperty) {
PropertySlot slot(scope, PropertySlot::InternalMethodType::Get);
JSGlobalLexicalEnvironment::getOwnPropertySlot(scope, globalObject, ident, slot);
if (slot.getValue(globalObject, ident) == jsTDZValue())
return throwException(globalObject, throwScope, createTDZError(globalObject, ident.string()));
baseObject = scope;
}
}
PutPropertySlot slot(baseObject);
switch (JSONPPath.last().m_type) {
case JSONPPathEntryTypeCall: {
JSValue function = baseObject.get(globalObject, ident);
RETURN_IF_EXCEPTION(throwScope, JSValue());
auto callData = JSC::getCallDataInline(function);
if (callData.type == CallData::Type::None)
return throwException(globalObject, throwScope, createNotAFunctionError(globalObject, function));
MarkedArgumentBuffer jsonArg;
jsonArg.append(JSONPValue);
ASSERT(!jsonArg.hasOverflowed());
JSValue thisValue = JSONPPath.size() == 1 ? jsUndefined() : baseObject;
JSONPValue = JSC::call(globalObject, function, callData, thisValue, jsonArg);
RETURN_IF_EXCEPTION(throwScope, JSValue());
break;
}
case JSONPPathEntryTypeDot: {
baseObject.put(globalObject, ident, JSONPValue, slot);
RETURN_IF_EXCEPTION(throwScope, JSValue());
break;
}
case JSONPPathEntryTypeLookup: {
baseObject.putByIndex(globalObject, JSONPPath.last().m_pathIndex, JSONPValue, slot.isStrictMode());
RETURN_IF_EXCEPTION(throwScope, JSValue());
break;
}
default:
RELEASE_ASSERT_NOT_REACHED();
return jsUndefined();
}
result = JSONPValue;
}
return result;
}
failedJSONP:
// If we get here, then we have already proven that the script is not a JSON
// object.
// Compile source to bytecode if necessary:
JSObject* error = program->initializeGlobalProperties(vm, globalObject, scope);
EXCEPTION_ASSERT(!throwScope.exception() || !error || vm.hasPendingTerminationException());
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
if (error) [[unlikely]]
return throwException(globalObject, throwScope, error);
if (scope->structure()->isUncacheableDictionary())
scope->flattenDictionaryObject(vm);
RefPtr<JSC::JITCode> jitCode;
ProtoCallFrame protoCallFrame;
{
DeferTraps deferTraps(vm); // We can't jettison this code if we're about to run it.
ProgramCodeBlock* codeBlock;
{
CodeBlock* tempCodeBlock;
program->prepareForExecution<ProgramExecutable>(vm, nullptr, scope, CodeSpecializationKind::CodeForCall, tempCodeBlock);
RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(throwScope, throwScope.exception());
codeBlock = jsCast<ProgramCodeBlock*>(tempCodeBlock);
ASSERT(codeBlock && codeBlock->numParameters() == 1); // 1 parameter for 'this'.
}
{
AssertNoGC assertNoGC; // Ensure no GC happens. GC can replace CodeBlock in Executable.
jitCode = program->generatedJITCode();
protoCallFrame.init(codeBlock, globalObject, globalCallee, thisObj, nullptr, 1);
}
}
// Execute the code:
throwScope.release();
ASSERT(jitCode == program->generatedJITCode().ptr());
return JSValue::decode(vmEntryToJavaScript(jitCode->addressForCall(), &vm, &protoCallFrame));
}
JSValue Interpreter::executeBoundCall(VM& vm, JSBoundFunction* function, JSCell* context, const ArgList& args)
{
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT(function->boundArgsLength());
MarkedArgumentBuffer combinedArgs;
combinedArgs.ensureCapacity(function->boundArgsLength() + args.size());
function->forEachBoundArg([&](JSValue argument) -> IterationStatus {
combinedArgs.append(argument);
return IterationStatus::Continue;
});
for (unsigned i = 0; i < args.size(); ++i)
combinedArgs.append(args.at(i));
if (combinedArgs.hasOverflowed()) [[unlikely]]
return throwStackOverflowError(function->globalObject(), scope);
JSObject* targetFunction = function->targetFunction();
JSValue boundThis = function->boundThis();
auto callData = JSC::getCallDataInline(targetFunction);
ASSERT(callData.type != CallData::Type::None);
RELEASE_AND_RETURN(scope, executeCallImpl(vm, targetFunction, callData, boundThis, context, combinedArgs));
}
ALWAYS_INLINE JSValue Interpreter::executeCallImpl(VM& vm, JSObject* function, const CallData& callData, JSValue thisValue, JSCell* context, const ArgList& args)
{
auto clobberizeValidator = makeScopeExit([&] {
vm.didEnterVM = true;
});
auto scope = DECLARE_THROW_SCOPE(vm);
scope.assertNoException();
ASSERT(!vm.isCollectorBusyOnCurrentThread());
bool isJSCall = callData.type == CallData::Type::JS;
JSScope* functionScope = nullptr;
FunctionExecutable* functionExecutable = nullptr;
TaggedNativeFunction nativeFunction;
JSGlobalObject* globalObject = nullptr;
if (isJSCall) {
functionScope = callData.js.scope;
functionExecutable = callData.js.functionExecutable;
globalObject = functionScope->globalObject();
} else {
ASSERT(callData.type == CallData::Type::Native);
nativeFunction = callData.native.function;
globalObject = function->globalObject();
}
size_t argsCount = 1 + args.size(); // implicit "this" parameter
VMEntryScope entryScope(vm, globalObject);
if (!vm.isSafeToRecurseSoft() || args.size() > maxArguments) [[unlikely]]
return throwStackOverflowError(globalObject, scope);
if (vm.disallowVMEntryCount) [[unlikely]]
return checkVMEntryPermission();
RefPtr<JSC::JITCode> jitCode;
ProtoCallFrame protoCallFrame;
{
DeferTraps deferTraps(vm); // We can't jettison this code if we're about to run it.
CodeBlock* newCodeBlock = nullptr;
if (isJSCall) {
// Compile the callee:
functionExecutable->prepareForExecution<FunctionExecutable>(vm, jsCast<JSFunction*>(function), functionScope, CodeSpecializationKind::CodeForCall, newCodeBlock);
RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(scope, scope.exception());
ASSERT(newCodeBlock);
newCodeBlock->m_shouldAlwaysBeInlined = false;
}
{
AssertNoGC assertNoGC; // Ensure no GC happens. GC can replace CodeBlock in Executable.
if (isJSCall)
jitCode = functionExecutable->generatedJITCodeForCall();
protoCallFrame.init(newCodeBlock, globalObject, function, thisValue, context, argsCount, args.data());
}
}
// Execute the code:
scope.release();
if (isJSCall) {
ASSERT(jitCode == functionExecutable->generatedJITCodeForCall().ptr());
return JSValue::decode(vmEntryToJavaScript(jitCode->addressForCall(), &vm, &protoCallFrame));
}
#if ENABLE(WEBASSEMBLY)
if (callData.native.isWasm)
return JSValue::decode(vmEntryToWasm(jsCast<WebAssemblyFunction*>(function)->jsToWasm(ArityCheckMode::MustCheckArity).taggedPtr(), &vm, &protoCallFrame));
#endif
return JSValue::decode(vmEntryToNative(nativeFunction.taggedPtr(), &vm, &protoCallFrame));
}
JSValue Interpreter::executeCall(JSObject* function, const CallData& callData, JSValue thisValue, JSCell* context, const ArgList& args)
{
VM& vm = this->vm();
if (callData.type == CallData::Type::JS || !callData.native.isBoundFunction)
return executeCallImpl(vm, function, callData, thisValue, context, args);
// Only one-level unwrap is enough! We already made JSBoundFunction's nest smaller.
auto* boundFunction = jsCast<JSBoundFunction*>(function);
if (boundFunction->isTainted())
vm.setMightBeExecutingTaintedCode();
if (!boundFunction->boundArgsLength()) {
// This is the simplest path, just replacing |this|. We do not need to go to executeBoundCall.
// Let's just replace and get unwrapped functions again.
JSObject* targetFunction = boundFunction->targetFunction();
JSValue boundThis = boundFunction->boundThis();
auto targetFunctionCallData = JSC::getCallDataInline(targetFunction);
ASSERT(targetFunctionCallData.type != CallData::Type::None);
return executeCallImpl(vm, targetFunction, targetFunctionCallData, boundThis, context, args);
}
return executeBoundCall(vm, boundFunction, context, args);
}
JSObject* Interpreter::executeConstruct(JSObject* constructor, const CallData& constructData, const ArgList& args, JSValue newTarget)
{
VM& vm = this->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto clobberizeValidator = makeScopeExit([&] {
vm.didEnterVM = true;
});
throwScope.assertNoException();
ASSERT(!vm.isCollectorBusyOnCurrentThread());
bool isJSConstruct = (constructData.type == CallData::Type::JS);
JSScope* scope = nullptr;
size_t argsCount = 1 + args.size(); // implicit "this" parameter
JSGlobalObject* globalObject;
if (isJSConstruct) {
scope = constructData.js.scope;
globalObject = scope->globalObject();
} else {
ASSERT(constructData.type == CallData::Type::Native);
globalObject = constructor->globalObject();
}
VMEntryScope entryScope(vm, globalObject);
if (!vm.isSafeToRecurseSoft() || args.size() > maxArguments) [[unlikely]] {
throwStackOverflowError(globalObject, throwScope);
return nullptr;
}
if (vm.disallowVMEntryCount) [[unlikely]] {
checkVMEntryPermission();
return globalObject->globalThis();
}
RefPtr<JSC::JITCode> jitCode;
ProtoCallFrame protoCallFrame;
{
DeferTraps deferTraps(vm); // We can't jettison this code if we're about to run it.
CodeBlock* newCodeBlock = nullptr;
if (isJSConstruct) {
// Compile the callee:
constructData.js.functionExecutable->prepareForExecution<FunctionExecutable>(vm, jsCast<JSFunction*>(constructor), scope, CodeSpecializationKind::CodeForConstruct, newCodeBlock);
RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(throwScope, nullptr);
ASSERT(newCodeBlock);
newCodeBlock->m_shouldAlwaysBeInlined = false;
}
{
AssertNoGC assertNoGC; // Ensure no GC happens. GC can replace CodeBlock in Executable.
if (isJSConstruct)
jitCode = constructData.js.functionExecutable->generatedJITCodeForConstruct();
protoCallFrame.init(newCodeBlock, globalObject, constructor, newTarget, nullptr, argsCount, args.data());
}
}
EncodedJSValue result;
// Execute the code.
if (isJSConstruct) {
ASSERT(jitCode == constructData.js.functionExecutable->generatedJITCodeForConstruct().ptr());
result = vmEntryToJavaScript(jitCode->addressForCall(), &vm, &protoCallFrame);
} else
result = vmEntryToNative(constructData.native.function.taggedPtr(), &vm, &protoCallFrame);
// We need to do an explicit exception check so that we don't return a non-null JSObject*
// if an exception was thrown.
RETURN_IF_EXCEPTION(throwScope, nullptr);
return asObject(JSValue::decode(result));
}
CodeBlock* Interpreter::prepareForCachedCall(CachedCall& cachedCall, JSFunction* function)
{
VM& vm = this->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
throwScope.assertNoException();
// Compile the callee:
CodeBlock* newCodeBlock;
cachedCall.functionExecutable()->prepareForExecution<FunctionExecutable>(vm, function, cachedCall.scope(), CodeSpecializationKind::CodeForCall, newCodeBlock);
RETURN_IF_EXCEPTION(throwScope, { });
ASSERT(newCodeBlock);
newCodeBlock->m_shouldAlwaysBeInlined = false;
cachedCall.m_addressForCall = newCodeBlock->jitCode()->addressForCall();
newCodeBlock->linkIncomingCall(nullptr, &cachedCall);
return newCodeBlock;
}
JSValue Interpreter::executeEval(EvalExecutable* eval, JSValue thisValue, JSScope* scope)
{
VM& vm = this->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto clobberizeValidator = makeScopeExit([&] {
vm.didEnterVM = true;
});
ASSERT(&vm == &scope->vm());
throwScope.assertNoException();
ASSERT(!vm.isCollectorBusyOnCurrentThread());
ASSERT(vm.currentThreadIsHoldingAPILock());
JSGlobalObject* globalObject = scope->globalObject();
if (!vm.isSafeToRecurseSoft()) [[unlikely]]
return throwStackOverflowError(globalObject, throwScope);
auto topLevelFunctionDecls = eval->topLevelFunctionDecls();
auto variables = eval->variables();
auto functionHoistingCandidates = eval->functionHoistingCandidates();
if (!variables.empty() || !topLevelFunctionDecls.empty() || !functionHoistingCandidates.empty()) {
JSScope* variableObject = nullptr;
if ((!variables.empty() || !topLevelFunctionDecls.empty()) && eval->isInStrictContext()) {
scope = StrictEvalActivation::create(vm, globalObject->strictEvalActivationStructure(), scope);
variableObject = scope;
} else {
for (JSScope* node = scope; ; node = node->next()) {
RELEASE_ASSERT(node);
if (node->isGlobalObject()) {
variableObject = node;
break;
}
if (node->isJSLexicalEnvironment()) {
JSLexicalEnvironment* lexicalEnvironment = jsCast<JSLexicalEnvironment*>(node);
if (lexicalEnvironment->symbolTable()->scopeType() == SymbolTable::ScopeType::VarScope) {
variableObject = node;
break;
}
}
}
if (variableObject->structure()->isUncacheableDictionary())
variableObject->flattenDictionaryObject(vm);
}
EvalCodeBlock* codeBlock = nullptr;
{
CodeBlock* tempCodeBlock;
eval->prepareForExecution<EvalExecutable>(vm, nullptr, scope, CodeSpecializationKind::CodeForCall, tempCodeBlock);
RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(throwScope, throwScope.exception());
codeBlock = jsCast<EvalCodeBlock*>(tempCodeBlock);
ASSERT(codeBlock && codeBlock->numParameters() == 1); // 1 parameter for 'this'.
}
auto functionDecls = codeBlock->functionDecls();
BatchedTransitionOptimizer optimizer(vm, variableObject);
if (variableObject->next() && !eval->isInStrictContext())
variableObject->globalObject()->varInjectionWatchpointSet().fireAll(vm, "Executed eval, fired VarInjection watchpoint");
if (!eval->isInStrictContext()) {
for (auto& ident : variables) {
JSValue resolvedScope = JSScope::resolveScopeForHoistingFuncDeclInEval(globalObject, scope, ident);
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
if (resolvedScope.isUndefined())
return throwSyntaxError(globalObject, throwScope, makeString("Can't create duplicate variable in eval: '"_s, StringView(ident.impl()), '\''));
}
for (auto& slot : functionDecls) {
FunctionExecutable* function = slot.get();
JSValue resolvedScope = JSScope::resolveScopeForHoistingFuncDeclInEval(globalObject, scope, function->name());
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
if (resolvedScope.isUndefined())
return throwSyntaxError(globalObject, throwScope, makeString("Can't create duplicate variable in eval: '"_s, StringView(function->name().impl()), '\''));
}
}
bool isGlobalVariableEnvironment = variableObject->isGlobalObject();
if (isGlobalVariableEnvironment) {
for (auto& slot : functionDecls) {
FunctionExecutable* function = slot.get();
bool canDeclare = jsCast<JSGlobalObject*>(variableObject)->canDeclareGlobalFunction(function->name());
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
if (!canDeclare)
return throwException(globalObject, throwScope, createErrorForInvalidGlobalFunctionDeclaration(globalObject, function->name()));
}
if (!variableObject->isStructureExtensible()) {
for (auto& ident : variables) {
bool canDeclare = jsCast<JSGlobalObject*>(variableObject)->canDeclareGlobalVar(ident);
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
if (!canDeclare)
return throwException(globalObject, throwScope, createErrorForInvalidGlobalVarDeclaration(globalObject, ident));
}
}
}
auto ensureBindingExists = [&](const Identifier& ident) {
bool hasProperty = variableObject->hasOwnProperty(globalObject, ident);
RETURN_IF_EXCEPTION(throwScope, void());
if (!hasProperty) {
bool shouldThrow = true;
PutPropertySlot slot(variableObject, shouldThrow);
variableObject->methodTable()->put(variableObject, globalObject, ident, jsUndefined(), slot);
RETURN_IF_EXCEPTION(throwScope, void());
}
};
if (!eval->isInStrictContext()) {
for (auto& ident : functionHoistingCandidates) {
JSValue resolvedScope = JSScope::resolveScopeForHoistingFuncDeclInEval(globalObject, scope, ident);
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
if (!resolvedScope.isUndefined()) {
if (isGlobalVariableEnvironment) {
bool canDeclare = jsCast<JSGlobalObject*>(variableObject)->canDeclareGlobalVar(ident);
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
if (canDeclare) {
jsCast<JSGlobalObject*>(variableObject)->createGlobalVarBinding<BindingCreationContext::Eval>(ident);
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
}
} else {
ensureBindingExists(ident);
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
}
}
}
}
for (auto& slot : functionDecls) {
FunctionExecutable* function = slot.get();
if (isGlobalVariableEnvironment) {
jsCast<JSGlobalObject*>(variableObject)->createGlobalFunctionBinding<BindingCreationContext::Eval>(function->name());
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
} else {
ensureBindingExists(function->name());
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
}
}
for (auto& ident : variables) {
if (isGlobalVariableEnvironment) {
jsCast<JSGlobalObject*>(variableObject)->createGlobalVarBinding<BindingCreationContext::Eval>(ident);
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
} else {
ensureBindingExists(ident);
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
}
}
ensureStillAliveHere(codeBlock);
}
EvalCodeBlock* codeBlock = nullptr;
JSCallee* callee = globalObject->evalCallee();
#if CPU(ARM64) && CPU(ADDRESS64) && !ENABLE(C_LOOP)
void* entry = nullptr;
{
DeferTraps deferTraps(vm); // We can't jettison this code if we're about to run it.
// Reload CodeBlock. It is possible that we replaced CodeBlock while setting up the environment.
{
CodeBlock* tempCodeBlock;
eval->prepareForExecution<EvalExecutable>(vm, nullptr, scope, CodeSpecializationKind::CodeForCall, tempCodeBlock);
RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(throwScope, throwScope.exception());
codeBlock = jsCast<EvalCodeBlock*>(tempCodeBlock);
entry = codeBlock->jitCode()->addressForCall();
ASSERT(codeBlock && codeBlock->numParameters() == 1); // 1 parameter for 'this'.
}
}
callee->setScope(vm, scope);
EncodedJSValue result = vmEntryToJavaScriptWith0Arguments(entry, &vm, codeBlock, callee, thisValue);
callee->setScope(vm, nullptr);
#else
RefPtr<JSC::JITCode> jitCode;
ProtoCallFrame protoCallFrame;
{
DeferTraps deferTraps(vm); // We can't jettison this code if we're about to run it.
// Reload CodeBlock. It is possible that we replaced CodeBlock while setting up the environment.
{
CodeBlock* tempCodeBlock;
eval->prepareForExecution<EvalExecutable>(vm, nullptr, scope, CodeSpecializationKind::CodeForCall, tempCodeBlock);
RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(throwScope, throwScope.exception());
codeBlock = jsCast<EvalCodeBlock*>(tempCodeBlock);
ASSERT(codeBlock && codeBlock->numParameters() == 1); // 1 parameter for 'this'.
}
{
AssertNoGC assertNoGC; // Ensure no GC happens. GC can replace CodeBlock in Executable.
jitCode = eval->generatedJITCode();
protoCallFrame.init(codeBlock, globalObject, callee, thisValue, nullptr, 1);
}
}
// Execute the code:
throwScope.release();
ASSERT(jitCode == eval->generatedJITCode().ptr());
// eval code only uses scope at the beginning (op_enter).
// We can replace the current scope for the subsequent run.
callee->setScope(vm, scope);
EncodedJSValue result = vmEntryToJavaScript(jitCode->addressForCall(), &vm, &protoCallFrame);
callee->setScope(vm, nullptr);
#endif
ensureStillAliveHere(eval);
ensureStillAliveHere(codeBlock);
return JSValue::decode(result);
}
JSValue Interpreter::executeModuleProgram(JSModuleRecord* record, ModuleProgramExecutable* executable, JSGlobalObject* lexicalGlobalObject, JSModuleEnvironment* scope, JSValue sentValue, JSValue resumeMode)
{
VM& vm = this->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto clobberizeValidator = makeScopeExit([&] {
vm.didEnterVM = true;
});
ASSERT_UNUSED(lexicalGlobalObject, &vm == &lexicalGlobalObject->vm());
throwScope.assertNoException();
ASSERT(!vm.isCollectorBusyOnCurrentThread());
RELEASE_ASSERT(vm.currentThreadIsHoldingAPILock());
JSGlobalObject* globalObject = scope->globalObject();
VMEntryScope entryScope(vm, scope->globalObject());
if (!vm.isSafeToRecurseSoft()) [[unlikely]]
return throwStackOverflowError(globalObject, throwScope);
if (vm.disallowVMEntryCount) [[unlikely]]
return checkVMEntryPermission();
if (scope->structure()->isUncacheableDictionary())
scope->flattenDictionaryObject(vm);
const unsigned numberOfArguments = static_cast<unsigned>(AbstractModuleRecord::Argument::NumberOfArguments);
JSCallee* callee = JSCallee::create(vm, globalObject, scope);
RefPtr<JSC::JITCode> jitCode;
ProtoCallFrame protoCallFrame;
EncodedJSValue args[numberOfArguments] = {
JSValue::encode(record),
JSValue::encode(record->internalField(JSModuleRecord::Field::State).get()),
JSValue::encode(sentValue),
JSValue::encode(resumeMode),
JSValue::encode(scope),
};
{
DeferTraps deferTraps(vm); // We can't jettison this code if we're about to run it.
ModuleProgramCodeBlock* codeBlock;
{
CodeBlock* tempCodeBlock;
executable->prepareForExecution<ModuleProgramExecutable>(vm, nullptr, scope, CodeSpecializationKind::CodeForCall, tempCodeBlock);
RETURN_IF_EXCEPTION_WITH_TRAPS_DEFERRED(throwScope, throwScope.exception());
codeBlock = jsCast<ModuleProgramCodeBlock*>(tempCodeBlock);
ASSERT(codeBlock && codeBlock->numParameters() == numberOfArguments + 1);
}
{
AssertNoGC assertNoGC; // Ensure no GC happens. GC can replace CodeBlock in Executable.
jitCode = executable->generatedJITCode();
// The |this| of the module is always `undefined`.
// http://www.ecma-international.org/ecma-262/6.0/#sec-module-environment-records-hasthisbinding
// http://www.ecma-international.org/ecma-262/6.0/#sec-module-environment-records-getthisbinding
protoCallFrame.init(codeBlock, globalObject, callee, jsUndefined(), nullptr, numberOfArguments + 1, args);
}
record->internalField(JSModuleRecord::Field::State).set(vm, record, jsNumber(static_cast<int>(JSModuleRecord::State::Executing)));
}
// Execute the code:
throwScope.release();
ASSERT(jitCode == executable->generatedJITCode().ptr());
return JSValue::decode(vmEntryToJavaScript(jitCode->addressForCall(), &vm, &protoCallFrame));
}
NEVER_INLINE void Interpreter::debug(CallFrame* callFrame, DebugHookType debugHookType, JSValue data)
{
VM& vm = this->vm();
DeferTermination deferScope(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
if (Options::debuggerTriggersBreakpointException()) [[unlikely]] {
if (debugHookType == DidReachDebuggerStatement)
WTFBreakpointTrap();
}
Debugger* debugger = callFrame->lexicalGlobalObject(vm)->debugger();
if (!debugger)
return;
ASSERT(callFrame->codeBlock()->hasDebuggerRequests());
scope.assertNoException();
switch (debugHookType) {
case DidEnterCallFrame:
debugger->callEvent(callFrame);
break;
case WillLeaveCallFrame:
debugger->returnEvent(callFrame);
break;
case WillExecuteStatement:
debugger->atStatement(callFrame);
break;
case WillExecuteExpression:
debugger->atExpression(callFrame);
break;
case WillAwait:
debugger->willAwait(callFrame, data);
break;
case DidAwait:
debugger->didAwait(callFrame, data);
break;
case WillExecuteProgram:
debugger->willExecuteProgram(callFrame);
break;
case DidExecuteProgram:
debugger->didExecuteProgram(callFrame);
break;
case DidReachDebuggerStatement:
debugger->didReachDebuggerStatement(callFrame);
break;
}
scope.assertNoException();
}
} // namespace JSC
namespace WTF {
void printInternal(PrintStream& out, JSC::DebugHookType type)
{
switch (type) {
case JSC::WillExecuteProgram:
out.print("WillExecuteProgram");
return;
case JSC::DidExecuteProgram:
out.print("DidExecuteProgram");
return;
case JSC::DidEnterCallFrame:
out.print("DidEnterCallFrame");
return;
case JSC::DidReachDebuggerStatement:
out.print("DidReachDebuggerStatement");
return;
case JSC::WillLeaveCallFrame:
out.print("WillLeaveCallFrame");
return;
case JSC::WillExecuteStatement:
out.print("WillExecuteStatement");
return;
case JSC::WillExecuteExpression:
out.print("WillExecuteExpression");
return;
case JSC::WillAwait:
out.print("WillAwait");
return;
case JSC::DidAwait:
out.print("DidAwait");
return;
}
}
} // namespace WTF