mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2024-11-26 21:50:53 +00:00
1338 lines
53 KiB
C++
1338 lines
53 KiB
C++
/*
|
|
* Copyright (C) 2008-2020 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2008 Cameron Zwarich <cwzwarich@uwaterloo.ca>
|
|
*
|
|
* 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 "BatchedTransitionOptimizer.h"
|
|
#include "Bytecodes.h"
|
|
#include "CallFrameClosure.h"
|
|
#include "CatchScope.h"
|
|
#include "CheckpointOSRExitSideState.h"
|
|
#include "CodeBlock.h"
|
|
#include "DirectArguments.h"
|
|
#include "Debugger.h"
|
|
#include "DirectEvalCodeCache.h"
|
|
#include "EvalCodeBlock.h"
|
|
#include "ExecutableBaseInlines.h"
|
|
#include "FrameTracers.h"
|
|
#include "InterpreterInlines.h"
|
|
#include "JITCodeInlines.h"
|
|
#include "JSArrayInlines.h"
|
|
#include "JSCInlines.h"
|
|
#include "JSImmutableButterfly.h"
|
|
#include "JSLexicalEnvironment.h"
|
|
#include "JSModuleEnvironment.h"
|
|
#include "JSString.h"
|
|
#include "LLIntThunks.h"
|
|
#include "LiteralParser.h"
|
|
#include "ModuleProgramCodeBlock.h"
|
|
#include "ProgramCodeBlock.h"
|
|
#include "ProtoCallFrameInlines.h"
|
|
#include "Register.h"
|
|
#include "RegisterAtOffsetList.h"
|
|
#include "ScopedArguments.h"
|
|
#include "StackFrame.h"
|
|
#include "StackVisitor.h"
|
|
#include "StrictEvalActivation.h"
|
|
#include "VMEntryScope.h"
|
|
#include "VMInlines.h"
|
|
#include "VirtualRegister.h"
|
|
#include <stdio.h>
|
|
#include <wtf/NeverDestroyed.h>
|
|
#include <wtf/Scope.h>
|
|
#include <wtf/StdLibExtras.h>
|
|
#include <wtf/text/StringBuilder.h>
|
|
|
|
#if ENABLE(WEBASSEMBLY)
|
|
#include "WasmContextInlines.h"
|
|
#include "WebAssemblyFunction.h"
|
|
#endif
|
|
|
|
namespace JSC {
|
|
|
|
JSValue eval(JSGlobalObject* globalObject, CallFrame* callFrame, ECMAMode ecmaMode)
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
auto clobberizeValidator = makeScopeExit([&] {
|
|
vm.didEnterVM = true;
|
|
});
|
|
|
|
if (!callFrame->argumentCount())
|
|
return jsUndefined();
|
|
|
|
JSValue program = callFrame->argument(0);
|
|
if (!program.isString())
|
|
return program;
|
|
|
|
TopCallFrameSetter topCallFrame(vm, callFrame);
|
|
if (!globalObject->evalEnabled()) {
|
|
throwException(globalObject, scope, createEvalError(globalObject, globalObject->evalDisabledErrorMessage()));
|
|
return jsUndefined();
|
|
}
|
|
String programSource = asString(program)->value(globalObject);
|
|
RETURN_IF_EXCEPTION(scope, JSValue());
|
|
|
|
CallFrame* callerFrame = callFrame->callerFrame();
|
|
CallSiteIndex callerCallSiteIndex = callerFrame->callSiteIndex();
|
|
CodeBlock* callerCodeBlock = callerFrame->codeBlock();
|
|
JSScope* callerScopeChain = callerFrame->uncheckedR(callerCodeBlock->scopeRegister()).Register::scope();
|
|
UnlinkedCodeBlock* callerUnlinkedCodeBlock = callerCodeBlock->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;
|
|
|
|
DirectEvalExecutable* eval = callerCodeBlock->directEvalCodeCache().tryGet(programSource, callerCallSiteIndex);
|
|
if (!eval) {
|
|
if (!ecmaMode.isStrict()) {
|
|
if (programSource.is8Bit()) {
|
|
LiteralParser<LChar> preparser(globalObject, programSource.characters8(), programSource.length(), NonStrictJSON, callerCodeBlock);
|
|
if (JSValue parsedObject = preparser.tryLiteralParse())
|
|
RELEASE_AND_RETURN(scope, parsedObject);
|
|
|
|
} else {
|
|
LiteralParser<UChar> preparser(globalObject, programSource.characters16(), programSource.length(), NonStrictJSON, callerCodeBlock);
|
|
if (JSValue parsedObject = preparser.tryLiteralParse())
|
|
RELEASE_AND_RETURN(scope, parsedObject);
|
|
|
|
}
|
|
RETURN_IF_EXCEPTION(scope, JSValue());
|
|
}
|
|
|
|
TDZEnvironment variablesUnderTDZ;
|
|
VariableEnvironment privateNames;
|
|
JSScope::collectClosureVariablesUnderTDZ(callerScopeChain, variablesUnderTDZ, privateNames);
|
|
eval = DirectEvalExecutable::create(globalObject, makeSource(programSource, callerCodeBlock->source().provider()->sourceOrigin()), derivedContextType, callerUnlinkedCodeBlock->needsClassFieldInitializer(), isArrowFunctionContext, callerCodeBlock->ownerExecutable()->isInsideOrdinaryFunction(), evalContextType, &variablesUnderTDZ, &privateNames, ecmaMode);
|
|
EXCEPTION_ASSERT(!!scope.exception() == !eval);
|
|
if (!eval)
|
|
return jsUndefined();
|
|
|
|
callerCodeBlock->directEvalCodeCache().set(globalObject, callerCodeBlock, programSource, callerCallSiteIndex, eval);
|
|
}
|
|
|
|
JSValue thisValue = callerFrame->thisValue();
|
|
Interpreter* interpreter = vm.interpreter;
|
|
RELEASE_AND_RETURN(scope, interpreter->execute(eval, globalObject, thisValue, callerScopeChain));
|
|
}
|
|
|
|
unsigned sizeOfVarargs(JSGlobalObject* globalObject, JSValue arguments, uint32_t firstVarArgOffset)
|
|
{
|
|
VM& vm = globalObject->vm();
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
if (UNLIKELY(!arguments.isCell())) {
|
|
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 JSImmutableButterflyType:
|
|
length = jsCast<JSImmutableButterfly*>(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 (UNLIKELY(!vm.ensureStackCapacityFor(calleeFrame->registers())))
|
|
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 (UNLIKELY(length > maxArguments || !vm.ensureStackCapacityFor(calleeFrame->registers()))) {
|
|
throwStackOverflowError(globalObject, scope);
|
|
return 0;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
void loadVarargs(JSGlobalObject* globalObject, JSValue* firstElementDest, JSValue arguments, uint32_t offset, uint32_t length)
|
|
{
|
|
if (UNLIKELY(!arguments.isCell()) || !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 JSImmutableButterflyType:
|
|
scope.release();
|
|
jsCast<JSImmutableButterfly*>(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;
|
|
} }
|
|
}
|
|
|
|
void setupVarargsFrame(JSGlobalObject* globalObject, CallFrame* callFrame, CallFrame* newCallFrame, JSValue arguments, uint32_t offset, uint32_t length)
|
|
{
|
|
VirtualRegister calleeFrameOffset(newCallFrame - callFrame);
|
|
|
|
loadVarargs(
|
|
globalObject,
|
|
bitwise_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);
|
|
memcpy(reinterpret_cast<char*>(execCallee) + offset, reinterpret_cast<char*>(execCaller) + offset, length * sizeof(Register));
|
|
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(VM& vm)
|
|
: m_vm(vm)
|
|
#if ENABLE(C_LOOP)
|
|
, m_cloopStack(vm)
|
|
#endif
|
|
{
|
|
#if ASSERT_ENABLED
|
|
static std::once_flag assertOnceKey;
|
|
std::call_once(assertOnceKey, [] {
|
|
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()
|
|
{
|
|
}
|
|
|
|
#if ENABLE(COMPUTED_GOTO_OPCODES)
|
|
#if !ENABLE(LLINT_EMBEDDED_OPCODE_ID) || ASSERT_ENABLED
|
|
HashMap<Opcode, OpcodeID>& Interpreter::opcodeIDTable()
|
|
{
|
|
static LazyNeverDestroyed<HashMap<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)
|
|
|
|
#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
|
|
|
|
class GetStackTraceFunctor {
|
|
public:
|
|
GetStackTraceFunctor(VM& vm, JSCell* owner, Vector<StackFrame>& results, size_t framesToSkip, size_t capacity)
|
|
: m_vm(vm)
|
|
, m_owner(owner)
|
|
, m_results(results)
|
|
, m_framesToSkip(framesToSkip)
|
|
, m_remainingCapacityForFrameCapture(capacity)
|
|
{
|
|
m_results.reserveInitialCapacity(capacity);
|
|
}
|
|
|
|
StackVisitor::Status operator()(StackVisitor& visitor) const
|
|
{
|
|
if (m_framesToSkip > 0) {
|
|
m_framesToSkip--;
|
|
return StackVisitor::Continue;
|
|
}
|
|
|
|
if (m_remainingCapacityForFrameCapture) {
|
|
if (visitor->isWasmFrame()) {
|
|
m_results.append(StackFrame(visitor->wasmFunctionIndexOrName()));
|
|
} else if (!!visitor->codeBlock() && !visitor->codeBlock()->unlinkedCodeBlock()->isBuiltinFunction()) {
|
|
m_results.append(
|
|
StackFrame(m_vm, m_owner, visitor->callee().asCell(), visitor->codeBlock(), visitor->bytecodeIndex()));
|
|
} else {
|
|
m_results.append(
|
|
StackFrame(m_vm, m_owner, visitor->callee().asCell()));
|
|
}
|
|
|
|
m_remainingCapacityForFrameCapture--;
|
|
return StackVisitor::Continue;
|
|
}
|
|
return StackVisitor::Done;
|
|
}
|
|
|
|
private:
|
|
VM& m_vm;
|
|
JSCell* m_owner;
|
|
Vector<StackFrame>& m_results;
|
|
mutable size_t m_framesToSkip;
|
|
mutable size_t m_remainingCapacityForFrameCapture;
|
|
};
|
|
|
|
void Interpreter::getStackTrace(JSCell* owner, Vector<StackFrame>& results, size_t framesToSkip, size_t maxStackSize)
|
|
{
|
|
DisallowGC disallowGC;
|
|
VM& vm = m_vm;
|
|
CallFrame* callFrame = vm.topCallFrame;
|
|
if (!callFrame || !maxStackSize)
|
|
return;
|
|
|
|
size_t framesCount = 0;
|
|
size_t maxFramesCountNeeded = maxStackSize + framesToSkip;
|
|
StackVisitor::visit(callFrame, vm, [&] (StackVisitor&) -> StackVisitor::Status {
|
|
if (++framesCount < maxFramesCountNeeded)
|
|
return StackVisitor::Continue;
|
|
return StackVisitor::Done;
|
|
});
|
|
if (framesCount <= framesToSkip)
|
|
return;
|
|
|
|
framesCount -= framesToSkip;
|
|
framesCount = std::min(maxStackSize, framesCount);
|
|
|
|
GetStackTraceFunctor functor(vm, owner, results, framesToSkip, framesCount);
|
|
StackVisitor::visit(callFrame, vm, functor);
|
|
ASSERT(results.size() == results.capacity());
|
|
}
|
|
|
|
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->isInlinedFrame());
|
|
#endif
|
|
|
|
CallFrame* callFrame = visitor->callFrame();
|
|
unsigned exceptionHandlerIndex;
|
|
if (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; }
|
|
|
|
StackVisitor::Status operator()(StackVisitor& visitor) const
|
|
{
|
|
visitor.unwindToMachineCodeBlockFrame();
|
|
|
|
CodeBlock* codeBlock = visitor->codeBlock();
|
|
if (!codeBlock)
|
|
return StackVisitor::Continue;
|
|
|
|
m_handler = findExceptionHandler(visitor, codeBlock, RequiredHandler::CatchHandler);
|
|
if (m_handler)
|
|
return StackVisitor::Done;
|
|
|
|
return StackVisitor::Continue;
|
|
}
|
|
|
|
private:
|
|
mutable HandlerInfo* m_handler;
|
|
};
|
|
|
|
ALWAYS_INLINE static void notifyDebuggerOfUnwinding(VM& vm, CallFrame* callFrame)
|
|
{
|
|
JSGlobalObject* globalObject = callFrame->lexicalGlobalObject(vm);
|
|
auto catchScope = DECLARE_CATCH_SCOPE(vm);
|
|
if (Debugger* debugger = globalObject->debugger()) {
|
|
SuspendExceptionScope scope(&vm);
|
|
if (callFrame->isAnyWasmCallee()
|
|
|| (callFrame->callee().isCell() && callFrame->callee().asCell()->inherits<JSFunction>(vm)))
|
|
debugger->unwindEvent(callFrame);
|
|
else
|
|
debugger->didExecuteProgram(callFrame);
|
|
catchScope.assertNoException();
|
|
}
|
|
}
|
|
|
|
class UnwindFunctor {
|
|
public:
|
|
UnwindFunctor(VM& vm, CallFrame*& callFrame, bool isTermination, CodeBlock*& codeBlock, HandlerInfo*& handler)
|
|
: m_vm(vm)
|
|
, m_callFrame(callFrame)
|
|
, m_isTermination(isTermination)
|
|
, m_codeBlock(codeBlock)
|
|
, m_handler(handler)
|
|
{
|
|
}
|
|
|
|
StackVisitor::Status operator()(StackVisitor& visitor) const
|
|
{
|
|
visitor.unwindToMachineCodeBlockFrame();
|
|
m_callFrame = visitor->callFrame();
|
|
m_codeBlock = visitor->codeBlock();
|
|
|
|
m_handler = nullptr;
|
|
if (m_codeBlock) {
|
|
if (!m_isTermination) {
|
|
m_handler = findExceptionHandler(visitor, m_codeBlock, RequiredHandler::AnyHandler);
|
|
if (m_handler)
|
|
return StackVisitor::Done;
|
|
}
|
|
}
|
|
|
|
#if ENABLE(WEBASSEMBLY)
|
|
if (visitor->callee().isCell()) {
|
|
if (auto* jsToWasmICCallee = jsDynamicCast<JSToWasmICCallee*>(m_vm, visitor->callee().asCell()))
|
|
m_vm.wasmContext.store(jsToWasmICCallee->function()->previousInstance(m_callFrame), m_vm.softStackLimit());
|
|
}
|
|
#endif
|
|
|
|
notifyDebuggerOfUnwinding(m_vm, m_callFrame);
|
|
|
|
copyCalleeSavesToEntryFrameCalleeSavesBuffer(visitor);
|
|
|
|
bool shouldStopUnwinding = visitor->callerIsEntryFrame();
|
|
if (shouldStopUnwinding)
|
|
return StackVisitor::Done;
|
|
|
|
return StackVisitor::Continue;
|
|
}
|
|
|
|
private:
|
|
void copyCalleeSavesToEntryFrameCalleeSavesBuffer(StackVisitor& visitor) const
|
|
{
|
|
#if ENABLE(ASSEMBLER)
|
|
Optional<RegisterAtOffsetList> currentCalleeSaves = visitor->calleeSaveRegistersForUnwinding();
|
|
|
|
if (!currentCalleeSaves)
|
|
return;
|
|
|
|
RegisterAtOffsetList* allCalleeSaves = RegisterSet::vmCalleeSaveRegisterOffsets();
|
|
RegisterSet dontCopyRegisters = RegisterSet::stackRegisters();
|
|
CPURegister* frame = reinterpret_cast<CPURegister*>(m_callFrame->registers());
|
|
|
|
unsigned registerCount = currentCalleeSaves->size();
|
|
VMEntryRecord* record = vmEntryRecord(m_vm.topEntryFrame);
|
|
for (unsigned i = 0; i < registerCount; i++) {
|
|
RegisterAtOffset currentEntry = currentCalleeSaves->at(i);
|
|
if (dontCopyRegisters.get(currentEntry.reg()))
|
|
continue;
|
|
RegisterAtOffset* calleeSavesEntry = allCalleeSaves->find(currentEntry.reg());
|
|
|
|
record->calleeSaveRegistersBuffer[calleeSavesEntry->offsetAsIndex()] = *(frame + currentEntry.offsetAsIndex());
|
|
}
|
|
#else
|
|
UNUSED_PARAM(visitor);
|
|
#endif
|
|
}
|
|
|
|
VM& m_vm;
|
|
CallFrame*& m_callFrame;
|
|
bool m_isTermination;
|
|
CodeBlock*& m_codeBlock;
|
|
HandlerInfo*& m_handler;
|
|
};
|
|
|
|
NEVER_INLINE HandlerInfo* Interpreter::unwind(VM& vm, CallFrame*& callFrame, Exception* exception)
|
|
{
|
|
auto scope = DECLARE_CATCH_SCOPE(vm);
|
|
|
|
ASSERT(reinterpret_cast<void*>(callFrame) != vm.topEntryFrame);
|
|
CodeBlock* codeBlock = 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.
|
|
HandlerInfo* handler = nullptr;
|
|
UnwindFunctor functor(vm, callFrame, isTerminatedExecutionException(vm, exception), codeBlock, handler);
|
|
StackVisitor::visit<StackVisitor::TerminateIfTopEntryFrameIsEmpty>(callFrame, vm, functor);
|
|
if (vm.hasCheckpointOSRSideState())
|
|
vm.popAllCheckpointOSRSideStateUntil(callFrame);
|
|
|
|
if (!handler)
|
|
return nullptr;
|
|
|
|
return handler;
|
|
}
|
|
|
|
void Interpreter::notifyDebuggerOfExceptionToBeThrown(VM& vm, JSGlobalObject* globalObject, CallFrame* callFrame, Exception* 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
|
|
|
|
bool hasCatchHandler;
|
|
bool isTermination = isTerminatedExecutionException(vm, exception);
|
|
if (isTermination)
|
|
hasCatchHandler = false;
|
|
else {
|
|
GetCatchHandlerFunctor functor;
|
|
StackVisitor::visit(callFrame, vm, functor);
|
|
HandlerInfo* handler = functor.handler();
|
|
ASSERT(!handler || handler->isCatchHandler());
|
|
hasCatchHandler = !!handler;
|
|
}
|
|
|
|
debugger->exception(globalObject, callFrame, exception->value(), hasCatchHandler);
|
|
}
|
|
exception->setDidNotifyInspectorOfThrow();
|
|
}
|
|
|
|
JSValue Interpreter::executeProgram(const SourceCode& source, JSGlobalObject*, JSObject* thisObj)
|
|
{
|
|
JSScope* scope = thisObj->globalObject()->globalScope();
|
|
VM& vm = scope->vm();
|
|
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
|
JSGlobalObject* globalObject = scope->globalObject(vm);
|
|
JSCallee* globalCallee = globalObject->globalCallee();
|
|
|
|
auto clobberizeValidator = makeScopeExit([&] {
|
|
vm.didEnterVM = true;
|
|
});
|
|
|
|
ProgramExecutable* program = ProgramExecutable::create(globalObject, source);
|
|
EXCEPTION_ASSERT(throwScope.exception() || program);
|
|
RETURN_IF_EXCEPTION(throwScope, { });
|
|
|
|
throwScope.assertNoException();
|
|
ASSERT(!vm.isCollectorBusyOnCurrentThread());
|
|
RELEASE_ASSERT(vm.currentThreadIsHoldingAPILock());
|
|
if (vm.isCollectorBusyOnCurrentThread())
|
|
return jsNull();
|
|
|
|
if (UNLIKELY(!vm.isSafeToRecurseSoft()))
|
|
return checkedReturn(throwStackOverflowError(globalObject, throwScope));
|
|
|
|
// 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();
|
|
if (programSource.isNull())
|
|
return jsUndefined();
|
|
if (programSource.is8Bit()) {
|
|
LiteralParser<LChar> literalParser(globalObject, programSource.characters8(), programSource.length(), JSONP);
|
|
parseResult = literalParser.tryJSONPParse(JSONPData, globalObject->globalObjectMethodTable()->supportsRichSourceInfo(globalObject));
|
|
} else {
|
|
LiteralParser<UChar> literalParser(globalObject, programSource.characters16(), programSource.length(), JSONP);
|
|
parseResult = literalParser.tryJSONPParse(JSONPData, globalObject->globalObjectMethodTable()->supportsRichSourceInfo(globalObject));
|
|
}
|
|
|
|
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) {
|
|
globalObject->addVar(globalObject, JSONPPath[0].m_pathEntryName);
|
|
RETURN_IF_EXCEPTION(throwScope, { });
|
|
PutPropertySlot slot(globalObject);
|
|
globalObject->methodTable(vm)->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();
|
|
}
|
|
}
|
|
|
|
if (JSONPPath.size() == 1 && JSONPPath.last().m_type != JSONPPathEntryTypeLookup) {
|
|
RELEASE_ASSERT(baseObject == globalObject);
|
|
JSGlobalLexicalEnvironment* scope = globalObject->globalLexicalEnvironment();
|
|
if (scope->hasProperty(globalObject, JSONPPath.last().m_pathEntryName))
|
|
baseObject = scope;
|
|
RETURN_IF_EXCEPTION(throwScope, JSValue());
|
|
}
|
|
|
|
PutPropertySlot slot(baseObject);
|
|
switch (JSONPPath.last().m_type) {
|
|
case JSONPPathEntryTypeCall: {
|
|
JSValue function = baseObject.get(globalObject, JSONPPath.last().m_pathEntryName);
|
|
RETURN_IF_EXCEPTION(throwScope, JSValue());
|
|
auto callData = getCallData(vm, 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, JSONPPath.last().m_pathEntryName, 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.
|
|
|
|
VMEntryScope entryScope(vm, globalObject);
|
|
|
|
// Compile source to bytecode if necessary:
|
|
JSObject* error = program->initializeGlobalProperties(vm, globalObject, scope);
|
|
EXCEPTION_ASSERT(!throwScope.exception() || !error);
|
|
if (UNLIKELY(error))
|
|
return checkedReturn(throwException(globalObject, throwScope, error));
|
|
|
|
constexpr auto trapsMask = VMTraps::interruptingTraps();
|
|
if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
|
|
vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
|
|
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
|
|
}
|
|
|
|
if (scope->structure(vm)->isUncacheableDictionary())
|
|
scope->flattenDictionaryObject(vm);
|
|
|
|
ProgramCodeBlock* codeBlock;
|
|
{
|
|
CodeBlock* tempCodeBlock;
|
|
Exception* error = program->prepareForExecution<ProgramExecutable>(vm, nullptr, scope, CodeForCall, tempCodeBlock);
|
|
EXCEPTION_ASSERT(throwScope.exception() == error);
|
|
if (UNLIKELY(error))
|
|
return checkedReturn(error);
|
|
codeBlock = jsCast<ProgramCodeBlock*>(tempCodeBlock);
|
|
ASSERT(codeBlock->numParameters() == 1); // 1 parameter for 'this'.
|
|
}
|
|
|
|
RefPtr<JITCode> jitCode;
|
|
ProtoCallFrame protoCallFrame;
|
|
{
|
|
DisallowGC disallowGC; // Ensure no GC happens. GC can replace CodeBlock in Executable.
|
|
jitCode = program->generatedJITCode();
|
|
protoCallFrame.init(codeBlock, globalObject, globalCallee, thisObj, 1);
|
|
}
|
|
|
|
// Execute the code:
|
|
throwScope.release();
|
|
ASSERT(jitCode == program->generatedJITCode().ptr());
|
|
JSValue result = jitCode->execute(&vm, &protoCallFrame);
|
|
return checkedReturn(result);
|
|
}
|
|
|
|
JSValue Interpreter::executeCall(JSGlobalObject* lexicalGlobalObject, JSObject* function, const CallData& callData, JSValue thisValue, const ArgList& args)
|
|
{
|
|
VM& vm = lexicalGlobalObject->vm();
|
|
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
auto clobberizeValidator = makeScopeExit([&] {
|
|
vm.didEnterVM = true;
|
|
});
|
|
|
|
throwScope.assertNoException();
|
|
ASSERT(!vm.isCollectorBusyOnCurrentThread());
|
|
if (vm.isCollectorBusyOnCurrentThread())
|
|
return jsNull();
|
|
|
|
bool isJSCall = (callData.type == CallData::Type::JS);
|
|
JSScope* scope = nullptr;
|
|
size_t argsCount = 1 + args.size(); // implicit "this" parameter
|
|
|
|
JSGlobalObject* globalObject;
|
|
|
|
if (isJSCall) {
|
|
scope = callData.js.scope;
|
|
globalObject = scope->globalObject(vm);
|
|
} else {
|
|
ASSERT(callData.type == CallData::Type::Native);
|
|
globalObject = function->globalObject(vm);
|
|
}
|
|
|
|
VMEntryScope entryScope(vm, globalObject);
|
|
if (UNLIKELY(!vm.isSafeToRecurseSoft() || args.size() > maxArguments))
|
|
return checkedReturn(throwStackOverflowError(globalObject, throwScope));
|
|
|
|
constexpr auto trapsMask = VMTraps::interruptingTraps();
|
|
if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
|
|
vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
|
|
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
|
|
}
|
|
|
|
CodeBlock* newCodeBlock = nullptr;
|
|
if (isJSCall) {
|
|
// Compile the callee:
|
|
Exception* compileError = callData.js.functionExecutable->prepareForExecution<FunctionExecutable>(vm, jsCast<JSFunction*>(function), scope, CodeForCall, newCodeBlock);
|
|
EXCEPTION_ASSERT(throwScope.exception() == compileError);
|
|
if (UNLIKELY(!!compileError))
|
|
return checkedReturn(compileError);
|
|
|
|
ASSERT(!!newCodeBlock);
|
|
newCodeBlock->m_shouldAlwaysBeInlined = false;
|
|
}
|
|
|
|
RefPtr<JITCode> jitCode;
|
|
ProtoCallFrame protoCallFrame;
|
|
{
|
|
DisallowGC disallowGC; // Ensure no GC happens. GC can replace CodeBlock in Executable.
|
|
if (isJSCall)
|
|
jitCode = callData.js.functionExecutable->generatedJITCodeForCall();
|
|
protoCallFrame.init(newCodeBlock, globalObject, function, thisValue, argsCount, args.data());
|
|
}
|
|
|
|
JSValue result;
|
|
// Execute the code:
|
|
if (isJSCall) {
|
|
throwScope.release();
|
|
ASSERT(jitCode == callData.js.functionExecutable->generatedJITCodeForCall().ptr());
|
|
result = jitCode->execute(&vm, &protoCallFrame);
|
|
} else {
|
|
result = JSValue::decode(vmEntryToNative(callData.native.function.rawPointer(), &vm, &protoCallFrame));
|
|
RETURN_IF_EXCEPTION(throwScope, JSValue());
|
|
}
|
|
|
|
return checkedReturn(result);
|
|
}
|
|
|
|
JSObject* Interpreter::executeConstruct(JSGlobalObject* lexicalGlobalObject, JSObject* constructor, const CallData& constructData, const ArgList& args, JSValue newTarget)
|
|
{
|
|
VM& vm = lexicalGlobalObject->vm();
|
|
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
auto clobberizeValidator = makeScopeExit([&] {
|
|
vm.didEnterVM = true;
|
|
});
|
|
|
|
throwScope.assertNoException();
|
|
ASSERT(!vm.isCollectorBusyOnCurrentThread());
|
|
// We throw in this case because we have to return something "valid" but we're
|
|
// already in an invalid state.
|
|
if (UNLIKELY(vm.isCollectorBusyOnCurrentThread())) {
|
|
throwStackOverflowError(lexicalGlobalObject, throwScope);
|
|
return nullptr;
|
|
}
|
|
|
|
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(vm);
|
|
} else {
|
|
ASSERT(constructData.type == CallData::Type::Native);
|
|
globalObject = constructor->globalObject(vm);
|
|
}
|
|
|
|
VMEntryScope entryScope(vm, globalObject);
|
|
if (UNLIKELY(!vm.isSafeToRecurseSoft() || args.size() > maxArguments)) {
|
|
throwStackOverflowError(globalObject, throwScope);
|
|
return nullptr;
|
|
}
|
|
|
|
constexpr auto trapsMask = VMTraps::interruptingTraps();
|
|
if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
|
|
vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
|
|
RETURN_IF_EXCEPTION(throwScope, nullptr);
|
|
}
|
|
|
|
CodeBlock* newCodeBlock = nullptr;
|
|
if (isJSConstruct) {
|
|
// Compile the callee:
|
|
Exception* compileError = constructData.js.functionExecutable->prepareForExecution<FunctionExecutable>(vm, jsCast<JSFunction*>(constructor), scope, CodeForConstruct, newCodeBlock);
|
|
EXCEPTION_ASSERT(throwScope.exception() == compileError);
|
|
if (UNLIKELY(!!compileError))
|
|
return nullptr;
|
|
|
|
ASSERT(!!newCodeBlock);
|
|
newCodeBlock->m_shouldAlwaysBeInlined = false;
|
|
}
|
|
|
|
RefPtr<JITCode> jitCode;
|
|
ProtoCallFrame protoCallFrame;
|
|
{
|
|
DisallowGC disallowGC; // Ensure no GC happens. GC can replace CodeBlock in Executable.
|
|
if (isJSConstruct)
|
|
jitCode = constructData.js.functionExecutable->generatedJITCodeForConstruct();
|
|
protoCallFrame.init(newCodeBlock, globalObject, constructor, newTarget, argsCount, args.data());
|
|
}
|
|
|
|
JSValue result;
|
|
// Execute the code.
|
|
if (isJSConstruct) {
|
|
ASSERT(jitCode == constructData.js.functionExecutable->generatedJITCodeForConstruct().ptr());
|
|
result = jitCode->execute(&vm, &protoCallFrame);
|
|
} else {
|
|
result = JSValue::decode(vmEntryToNative(constructData.native.function.rawPointer(), &vm, &protoCallFrame));
|
|
|
|
if (LIKELY(!throwScope.exception()))
|
|
RELEASE_ASSERT(result.isObject());
|
|
}
|
|
|
|
RETURN_IF_EXCEPTION(throwScope, nullptr);
|
|
ASSERT(result.isObject());
|
|
return checkedReturn(asObject(result));
|
|
}
|
|
|
|
CallFrameClosure Interpreter::prepareForRepeatCall(FunctionExecutable* functionExecutable, CallFrame* callFrame, ProtoCallFrame* protoCallFrame, JSFunction* function, int argumentCountIncludingThis, JSScope* scope, const ArgList& args)
|
|
{
|
|
VM& vm = scope->vm();
|
|
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
|
throwScope.assertNoException();
|
|
|
|
if (vm.isCollectorBusyOnCurrentThread())
|
|
return CallFrameClosure();
|
|
|
|
// Compile the callee:
|
|
CodeBlock* newCodeBlock;
|
|
Exception* error = functionExecutable->prepareForExecution<FunctionExecutable>(vm, function, scope, CodeForCall, newCodeBlock);
|
|
EXCEPTION_ASSERT(throwScope.exception() == error);
|
|
if (UNLIKELY(error))
|
|
return CallFrameClosure();
|
|
newCodeBlock->m_shouldAlwaysBeInlined = false;
|
|
|
|
size_t argsCount = argumentCountIncludingThis;
|
|
|
|
protoCallFrame->init(newCodeBlock, function->globalObject(), function, jsUndefined(), argsCount, args.data());
|
|
// Return the successful closure:
|
|
CallFrameClosure result = { callFrame, protoCallFrame, function, functionExecutable, &vm, scope, newCodeBlock->numParameters(), argumentCountIncludingThis };
|
|
return result;
|
|
}
|
|
|
|
JSValue Interpreter::execute(EvalExecutable* eval, JSGlobalObject* lexicalGlobalObject, JSValue thisValue, JSScope* scope)
|
|
{
|
|
VM& vm = scope->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());
|
|
if (vm.isCollectorBusyOnCurrentThread())
|
|
return jsNull();
|
|
|
|
JSGlobalObject* globalObject = scope->globalObject(vm);
|
|
VMEntryScope entryScope(vm, globalObject);
|
|
if (UNLIKELY(!vm.isSafeToRecurseSoft()))
|
|
return checkedReturn(throwStackOverflowError(globalObject, throwScope));
|
|
|
|
unsigned numVariables = eval->numVariables();
|
|
unsigned numTopLevelFunctionDecls = eval->numTopLevelFunctionDecls();
|
|
unsigned numFunctionHoistingCandidates = eval->numFunctionHoistingCandidates();
|
|
|
|
JSScope* variableObject;
|
|
if ((numVariables || numTopLevelFunctionDecls) && 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
constexpr auto trapsMask = VMTraps::interruptingTraps();
|
|
if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
|
|
vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
|
|
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
|
|
}
|
|
|
|
auto loadCodeBlock = [&](Exception*& compileError) -> EvalCodeBlock* {
|
|
CodeBlock* tempCodeBlock;
|
|
compileError = eval->prepareForExecution<EvalExecutable>(vm, nullptr, scope, CodeForCall, tempCodeBlock);
|
|
EXCEPTION_ASSERT(throwScope.exception() == compileError);
|
|
if (UNLIKELY(!!compileError))
|
|
return nullptr;
|
|
return jsCast<EvalCodeBlock*>(tempCodeBlock);
|
|
};
|
|
|
|
EvalCodeBlock* codeBlock;
|
|
{
|
|
Exception* compileError = nullptr;
|
|
codeBlock = loadCodeBlock(compileError);
|
|
EXCEPTION_ASSERT(throwScope.exception() == compileError);
|
|
if (UNLIKELY(!!compileError))
|
|
return checkedReturn(compileError);
|
|
ASSERT(codeBlock->numParameters() == 1); // 1 parameter for 'this'.
|
|
}
|
|
UnlinkedEvalCodeBlock* unlinkedCodeBlock = codeBlock->unlinkedEvalCodeBlock();
|
|
|
|
// We can't declare a "var"/"function" that overwrites a global "let"/"const"/"class" in a sloppy-mode eval.
|
|
if (variableObject->isGlobalObject() && !eval->isInStrictContext() && (numVariables || numTopLevelFunctionDecls)) {
|
|
JSGlobalLexicalEnvironment* globalLexicalEnvironment = jsCast<JSGlobalObject*>(variableObject)->globalLexicalEnvironment();
|
|
for (unsigned i = 0; i < numVariables; ++i) {
|
|
const Identifier& ident = unlinkedCodeBlock->variable(i);
|
|
PropertySlot slot(globalLexicalEnvironment, PropertySlot::InternalMethodType::VMInquiry, &vm);
|
|
if (JSGlobalLexicalEnvironment::getOwnPropertySlot(globalLexicalEnvironment, globalObject, ident, slot)) {
|
|
return checkedReturn(throwTypeError(globalObject, throwScope, makeString("Can't create duplicate global variable in eval: '", String(ident.impl()), "'")));
|
|
}
|
|
}
|
|
|
|
for (unsigned i = 0; i < numTopLevelFunctionDecls; ++i) {
|
|
FunctionExecutable* function = codeBlock->functionDecl(i);
|
|
PropertySlot slot(globalLexicalEnvironment, PropertySlot::InternalMethodType::VMInquiry, &vm);
|
|
if (JSGlobalLexicalEnvironment::getOwnPropertySlot(globalLexicalEnvironment, globalObject, function->name(), slot)) {
|
|
return checkedReturn(throwTypeError(globalObject, throwScope, makeString("Can't create duplicate global variable in eval: '", String(function->name().impl()), "'")));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (variableObject->structure(vm)->isUncacheableDictionary())
|
|
variableObject->flattenDictionaryObject(vm);
|
|
|
|
if (numVariables || numTopLevelFunctionDecls || numFunctionHoistingCandidates) {
|
|
BatchedTransitionOptimizer optimizer(vm, variableObject);
|
|
if (variableObject->next() && !eval->isInStrictContext())
|
|
variableObject->globalObject(vm)->varInjectionWatchpoint()->fireAll(vm, "Executed eval, fired VarInjection watchpoint");
|
|
|
|
for (unsigned i = 0; i < numVariables; ++i) {
|
|
const Identifier& ident = unlinkedCodeBlock->variable(i);
|
|
bool hasProperty = variableObject->hasProperty(globalObject, ident);
|
|
RETURN_IF_EXCEPTION(throwScope, checkedReturn(throwScope.exception()));
|
|
if (!hasProperty) {
|
|
PutPropertySlot slot(variableObject);
|
|
if (!variableObject->isExtensible(globalObject))
|
|
return checkedReturn(throwTypeError(globalObject, throwScope, NonExtensibleObjectPropertyDefineError));
|
|
variableObject->methodTable(vm)->put(variableObject, globalObject, ident, jsUndefined(), slot);
|
|
RETURN_IF_EXCEPTION(throwScope, checkedReturn(throwScope.exception()));
|
|
}
|
|
}
|
|
|
|
if (eval->isInStrictContext()) {
|
|
for (unsigned i = 0; i < numTopLevelFunctionDecls; ++i) {
|
|
FunctionExecutable* function = codeBlock->functionDecl(i);
|
|
PutPropertySlot slot(variableObject);
|
|
// We need create this variables because it will be used to emits code by bytecode generator
|
|
variableObject->methodTable(vm)->put(variableObject, globalObject, function->name(), jsUndefined(), slot);
|
|
RETURN_IF_EXCEPTION(throwScope, checkedReturn(throwScope.exception()));
|
|
}
|
|
} else {
|
|
for (unsigned i = 0; i < numTopLevelFunctionDecls; ++i) {
|
|
FunctionExecutable* function = codeBlock->functionDecl(i);
|
|
JSValue resolvedScope = JSScope::resolveScopeForHoistingFuncDeclInEval(globalObject, scope, function->name());
|
|
RETURN_IF_EXCEPTION(throwScope, checkedReturn(throwScope.exception()));
|
|
if (resolvedScope.isUndefined())
|
|
return checkedReturn(throwSyntaxError(globalObject, throwScope, makeString("Can't create duplicate variable in eval: '", String(function->name().impl()), "'")));
|
|
PutPropertySlot slot(variableObject);
|
|
// We need create this variables because it will be used to emits code by bytecode generator
|
|
variableObject->methodTable(vm)->put(variableObject, globalObject, function->name(), jsUndefined(), slot);
|
|
RETURN_IF_EXCEPTION(throwScope, checkedReturn(throwScope.exception()));
|
|
}
|
|
|
|
for (unsigned i = 0; i < numFunctionHoistingCandidates; ++i) {
|
|
const Identifier& ident = unlinkedCodeBlock->functionHoistingCandidate(i);
|
|
JSValue resolvedScope = JSScope::resolveScopeForHoistingFuncDeclInEval(globalObject, scope, ident);
|
|
RETURN_IF_EXCEPTION(throwScope, checkedReturn(throwScope.exception()));
|
|
if (!resolvedScope.isUndefined()) {
|
|
bool hasProperty = variableObject->hasProperty(globalObject, ident);
|
|
RETURN_IF_EXCEPTION(throwScope, checkedReturn(throwScope.exception()));
|
|
if (!hasProperty) {
|
|
PutPropertySlot slot(variableObject);
|
|
variableObject->methodTable(vm)->put(variableObject, globalObject, ident, jsUndefined(), slot);
|
|
RETURN_IF_EXCEPTION(throwScope, checkedReturn(throwScope.exception()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
JSCallee* callee = nullptr;
|
|
if (scope == globalObject->globalScope())
|
|
callee = globalObject->globalCallee();
|
|
else
|
|
callee = JSCallee::create(vm, globalObject, scope);
|
|
|
|
// Reload CodeBlock. It is possible that we replaced CodeBlock while setting up the environment.
|
|
{
|
|
Exception* compileError = nullptr;
|
|
codeBlock = loadCodeBlock(compileError);
|
|
EXCEPTION_ASSERT(throwScope.exception() == compileError);
|
|
if (UNLIKELY(!!compileError))
|
|
return checkedReturn(compileError);
|
|
ASSERT(codeBlock->numParameters() == 1); // 1 parameter for 'this'.
|
|
}
|
|
|
|
RefPtr<JITCode> jitCode;
|
|
ProtoCallFrame protoCallFrame;
|
|
{
|
|
DisallowGC disallowGC; // Ensure no GC happens. GC can replace CodeBlock in Executable.
|
|
jitCode = eval->generatedJITCode();
|
|
protoCallFrame.init(codeBlock, globalObject, callee, thisValue, 1);
|
|
}
|
|
|
|
// Execute the code:
|
|
throwScope.release();
|
|
ASSERT(jitCode == eval->generatedJITCode().ptr());
|
|
JSValue result = jitCode->execute(&vm, &protoCallFrame);
|
|
|
|
return checkedReturn(result);
|
|
}
|
|
|
|
JSValue Interpreter::executeModuleProgram(ModuleProgramExecutable* executable, JSGlobalObject* lexicalGlobalObject, JSModuleEnvironment* scope)
|
|
{
|
|
VM& vm = scope->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());
|
|
if (vm.isCollectorBusyOnCurrentThread())
|
|
return jsNull();
|
|
|
|
JSGlobalObject* globalObject = scope->globalObject(vm);
|
|
VMEntryScope entryScope(vm, scope->globalObject(vm));
|
|
if (UNLIKELY(!vm.isSafeToRecurseSoft()))
|
|
return checkedReturn(throwStackOverflowError(globalObject, throwScope));
|
|
|
|
constexpr auto trapsMask = VMTraps::interruptingTraps();
|
|
if (UNLIKELY(vm.needTrapHandling(trapsMask))) {
|
|
vm.handleTraps(globalObject, vm.topCallFrame, trapsMask);
|
|
RETURN_IF_EXCEPTION(throwScope, throwScope.exception());
|
|
}
|
|
|
|
if (scope->structure(vm)->isUncacheableDictionary())
|
|
scope->flattenDictionaryObject(vm);
|
|
|
|
JSCallee* callee = JSCallee::create(vm, globalObject, scope);
|
|
ModuleProgramCodeBlock* codeBlock;
|
|
{
|
|
CodeBlock* tempCodeBlock;
|
|
Exception* compileError = executable->prepareForExecution<ModuleProgramExecutable>(vm, nullptr, scope, CodeForCall, tempCodeBlock);
|
|
EXCEPTION_ASSERT(throwScope.exception() == compileError);
|
|
if (UNLIKELY(!!compileError))
|
|
return checkedReturn(compileError);
|
|
codeBlock = jsCast<ModuleProgramCodeBlock*>(tempCodeBlock);
|
|
ASSERT(codeBlock->numParameters() == 1); // 1 parameter for 'this'.
|
|
}
|
|
|
|
RefPtr<JITCode> jitCode;
|
|
ProtoCallFrame protoCallFrame;
|
|
{
|
|
DisallowGC disallowGC; // 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(), 1);
|
|
}
|
|
|
|
// Execute the code:
|
|
throwScope.release();
|
|
ASSERT(jitCode == executable->generatedJITCode().ptr());
|
|
JSValue result = jitCode->execute(&vm, &protoCallFrame);
|
|
|
|
return checkedReturn(result);
|
|
}
|
|
|
|
NEVER_INLINE void Interpreter::debug(CallFrame* callFrame, DebugHookType debugHookType)
|
|
{
|
|
VM& vm = callFrame->deprecatedVM();
|
|
auto scope = DECLARE_CATCH_SCOPE(vm);
|
|
|
|
if (UNLIKELY(Options::debuggerTriggersBreakpointException()) && 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 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;
|
|
}
|
|
}
|
|
|
|
} // namespace WTF
|