blob: e7d9df28c7fd04e5aa47fcd725a94a8696b357ed [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* JavaScript bytecode interpreter.
*/
#include "vm/Interpreter-inl.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/PodOperations.h"
#include <string.h>
#include "jsarray.h"
#include "jsatom.h"
#include "jscntxt.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsiter.h"
#include "jslibmath.h"
#include "jsnum.h"
#include "jsobj.h"
#include "jsopcode.h"
#include "jsprf.h"
#include "jsscript.h"
#include "jsstr.h"
#include "builtin/Eval.h"
#include "jit/AtomicOperations.h"
#include "jit/BaselineJIT.h"
#include "jit/Ion.h"
#include "jit/IonAnalysis.h"
#include "vm/Debugger.h"
#include "vm/GeneratorObject.h"
#include "vm/Opcodes.h"
#include "vm/Shape.h"
#include "vm/Stopwatch.h"
#include "vm/TraceLogging.h"
#include "jsatominlines.h"
#include "jsboolinlines.h"
#include "jsfuninlines.h"
#include "jsscriptinlines.h"
#include "jit/JitFrames-inl.h"
#include "vm/Debugger-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/Probes-inl.h"
#include "vm/ScopeObject-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
using namespace js::gc;
using mozilla::ArrayLength;
using mozilla::DebugOnly;
using mozilla::NumberEqualsInt32;
using mozilla::PodCopy;
using JS::ForOfIterator;
template <bool Eq>
static MOZ_ALWAYS_INLINE bool
LooseEqualityOp(JSContext* cx, InterpreterRegs& regs)
{
HandleValue rval = regs.stackHandleAt(-1);
HandleValue lval = regs.stackHandleAt(-2);
bool cond;
if (!LooselyEqual(cx, lval, rval, &cond))
return false;
cond = (cond == Eq);
regs.sp--;
regs.sp[-1].setBoolean(cond);
return true;
}
bool
js::BoxNonStrictThis(JSContext* cx, HandleValue thisv, MutableHandleValue vp)
{
/*
* Check for SynthesizeFrame poisoning and fast constructors which
* didn't check their callee properly.
*/
MOZ_ASSERT(!thisv.isMagic());
if (thisv.isNullOrUndefined()) {
vp.set(GetThisValue(cx->global()));
return true;
}
if (thisv.isObject()) {
vp.set(thisv);
return true;
}
JSObject* obj = PrimitiveToObject(cx, thisv);
if (!obj)
return false;
vp.setObject(*obj);
return true;
}
/*
* ECMA requires "the global object", but in embeddings such as the browser,
* which have multiple top-level objects (windows, frames, etc. in the DOM),
* we prefer fun's parent. An example that causes this code to run:
*
* // in window w1
* function f() { return this }
* function g() { return f }
*
* // in window w2
* var h = w1.g()
* alert(h() == w1)
*
* The alert should display "true".
*/
bool
js::BoxNonStrictThis(JSContext* cx, const CallReceiver& call)
{
MOZ_ASSERT(!call.thisv().isMagic());
#ifdef DEBUG
JSFunction* fun = call.callee().is<JSFunction>() ? &call.callee().as<JSFunction>() : nullptr;
MOZ_ASSERT_IF(fun && fun->isInterpreted(), !fun->strict());
#endif
return BoxNonStrictThis(cx, call.thisv(), call.mutableThisv());
}
bool
js::GetFunctionThis(JSContext* cx, AbstractFramePtr frame, MutableHandleValue res)
{
MOZ_ASSERT(frame.isNonEvalFunctionFrame());
MOZ_ASSERT(!frame.fun()->isArrow());
if (frame.thisArgument().isObject() ||
frame.fun()->strict() ||
frame.fun()->isSelfHostedBuiltin())
{
res.set(frame.thisArgument());
return true;
}
RootedValue thisv(cx, frame.thisArgument());
return BoxNonStrictThis(cx, thisv, res);
}
bool
js::GetNonSyntacticGlobalThis(JSContext* cx, HandleObject scopeChain, MutableHandleValue res)
{
RootedObject scope(cx, scopeChain);
while (true) {
if (IsExtensibleLexicalScope(scope)) {
res.set(scope->as<ClonedBlockObject>().thisValue());
return true;
}
if (!scope->enclosingScope()) {
// This can only happen in Debugger eval frames: in that case we
// don't always have a global lexical scope, see EvaluateInEnv.
MOZ_ASSERT(scope->is<GlobalObject>());
res.set(GetThisValue(scope));
return true;
}
scope = scope->enclosingScope();
}
return true;
}
static inline bool
GetPropertyOperation(JSContext* cx, InterpreterFrame* fp, HandleScript script, jsbytecode* pc,
MutableHandleValue lval, MutableHandleValue vp)
{
JSOp op = JSOp(*pc);
if (op == JSOP_LENGTH) {
if (IsOptimizedArguments(fp, lval)) {
vp.setInt32(fp->numActualArgs());
return true;
}
if (GetLengthProperty(lval, vp))
return true;
}
RootedPropertyName name(cx, script->getName(pc));
if (name == cx->names().callee && IsOptimizedArguments(fp, lval)) {
vp.setObject(fp->callee());
return true;
}
// Copy lval, because it might alias vp.
RootedValue v(cx, lval);
return GetProperty(cx, v, name, vp);
}
static inline bool
GetNameOperation(JSContext* cx, InterpreterFrame* fp, jsbytecode* pc, MutableHandleValue vp)
{
JSObject* obj = fp->scopeChain();
PropertyName* name = fp->script()->getName(pc);
/*
* Skip along the scope chain to the enclosing global object. This is
* used for GNAME opcodes where the bytecode emitter has determined a
* name access must be on the global. It also insulates us from bugs
* in the emitter: type inference will assume that GNAME opcodes are
* accessing the global object, and the inferred behavior should match
* the actual behavior even if the id could be found on the scope chain
* before the global object.
*/
if (IsGlobalOp(JSOp(*pc)) && !fp->script()->hasNonSyntacticScope())
obj = &obj->global().lexicalScope();
Shape* shape = nullptr;
JSObject* scope = nullptr;
JSObject* pobj = nullptr;
if (LookupNameNoGC(cx, name, obj, &scope, &pobj, &shape)) {
if (FetchNameNoGC(pobj, shape, vp))
return true;
}
RootedObject objRoot(cx, obj), scopeRoot(cx), pobjRoot(cx);
RootedPropertyName nameRoot(cx, name);
RootedShape shapeRoot(cx);
if (!LookupName(cx, nameRoot, objRoot, &scopeRoot, &pobjRoot, &shapeRoot))
return false;
/* Kludge to allow (typeof foo == "undefined") tests. */
JSOp op2 = JSOp(pc[JSOP_GETNAME_LENGTH]);
if (op2 == JSOP_TYPEOF)
return FetchName<true>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
return FetchName<false>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
}
static inline bool
GetImportOperation(JSContext* cx, InterpreterFrame* fp, jsbytecode* pc, MutableHandleValue vp)
{
RootedObject obj(cx, fp->scopeChain()), scope(cx), pobj(cx);
RootedPropertyName name(cx, fp->script()->getName(pc));
RootedShape shape(cx);
MOZ_ALWAYS_TRUE(LookupName(cx, name, obj, &scope, &pobj, &shape));
MOZ_ASSERT(scope && scope->is<ModuleEnvironmentObject>());
MOZ_ASSERT(scope->as<ModuleEnvironmentObject>().hasImportBinding(name));
return FetchName<false>(cx, scope, pobj, name, shape, vp);
}
static bool
SetPropertyOperation(JSContext* cx, JSOp op, HandleValue lval, HandleId id, HandleValue rval)
{
MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP);
RootedObject obj(cx, ToObjectFromStack(cx, lval));
if (!obj)
return false;
// Note: ES6 specifies that the value lval, not obj, is passed as receiver
// to obj's [[Set]] internal method. See bug 603201.
RootedValue receiver(cx, ObjectValue(*obj));
ObjectOpResult result;
return SetProperty(cx, obj, id, rval, receiver, result) &&
result.checkStrictErrorOrWarning(cx, obj, id, op == JSOP_STRICTSETPROP);
}
static JSFunction*
MakeDefaultConstructor(JSContext* cx, JSOp op, JSAtom* atom, HandleObject proto)
{
bool derived = op == JSOP_DERIVEDCONSTRUCTOR;
MOZ_ASSERT(derived == !!proto);
RootedAtom name(cx, atom == cx->names().empty ? nullptr : atom);
JSNative native = derived ? DefaultDerivedClassConstructor : DefaultClassConstructor;
return NewFunctionWithProto(cx, native, 0, JSFunction::NATIVE_CLASS_CTOR, nullptr, name, proto);
}
bool
js::ReportIsNotFunction(JSContext* cx, HandleValue v, int numToSkip, MaybeConstruct construct)
{
unsigned error = construct ? JSMSG_NOT_CONSTRUCTOR : JSMSG_NOT_FUNCTION;
int spIndex = numToSkip >= 0 ? -(numToSkip + 1) : JSDVG_SEARCH_STACK;
ReportValueError(cx, error, spIndex, v, nullptr);
return false;
}
JSObject*
js::ValueToCallable(JSContext* cx, HandleValue v, int numToSkip, MaybeConstruct construct)
{
if (v.isObject() && v.toObject().isCallable()) {
return &v.toObject();
}
ReportIsNotFunction(cx, v, numToSkip, construct);
return nullptr;
}
bool
RunState::maybeCreateThisForConstructor(JSContext* cx)
{
if (isInvoke()) {
InvokeState& invoke = *asInvoke();
if (invoke.constructing() && invoke.args().thisv().isPrimitive()) {
RootedObject callee(cx, &invoke.args().callee());
if (script()->isDerivedClassConstructor()) {
MOZ_ASSERT(callee->as<JSFunction>().isClassConstructor());
invoke.args().setThis(MagicValue(JS_UNINITIALIZED_LEXICAL));
} else {
RootedObject newTarget(cx, &invoke.args().newTarget().toObject());
NewObjectKind newKind = invoke.createSingleton() ? SingletonObject : GenericObject;
JSObject* obj = CreateThisForFunction(cx, callee, newTarget, newKind);
if (!obj)
return false;
invoke.args().setThis(ObjectValue(*obj));
}
}
}
return true;
}
static MOZ_NEVER_INLINE bool
Interpret(JSContext* cx, RunState& state);
InterpreterFrame*
InvokeState::pushInterpreterFrame(JSContext* cx)
{
return cx->runtime()->interpreterStack().pushInvokeFrame(cx, args_, initial_);
}
InterpreterFrame*
ExecuteState::pushInterpreterFrame(JSContext* cx)
{
return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, newTargetValue_,
scopeChain_, type_, evalInFrame_);
}
// MSVC with PGO inlines a lot of functions in RunScript, resulting in large
// stack frames and stack overflow issues, see bug 1167883. Turn off PGO to
// avoid this.
#ifdef _MSC_VER
# pragma optimize("g", off)
#endif
bool
js::RunScript(JSContext* cx, RunState& state)
{
JS_CHECK_RECURSION(cx, return false);
#if defined(NIGHTLY_BUILD) && defined(MOZ_HAVE_RDTSC)
js::AutoStopwatch stopwatch(cx);
#endif // defined(NIGHTLY_BUILD) && defined(MOZ_HAVE_RDTSC)
SPSEntryMarker marker(cx->runtime(), state.script());
state.script()->ensureNonLazyCanonicalFunction(cx);
if (jit::IsIonEnabled(cx)) {
jit::MethodStatus status = jit::CanEnter(cx, state);
if (status == jit::Method_Error)
return false;
if (status == jit::Method_Compiled) {
jit::JitExecStatus status = jit::IonCannon(cx, state);
return !IsErrorStatus(status);
}
}
if (jit::IsBaselineEnabled(cx)) {
jit::MethodStatus status = jit::CanEnterBaselineMethod(cx, state);
if (status == jit::Method_Error)
return false;
if (status == jit::Method_Compiled) {
jit::JitExecStatus status = jit::EnterBaselineMethod(cx, state);
return !IsErrorStatus(status);
}
}
if (state.isInvoke()) {
InvokeState& invoke = *state.asInvoke();
TypeMonitorCall(cx, invoke.args(), invoke.constructing());
}
return Interpret(cx, state);
}
#ifdef _MSC_VER
# pragma optimize("", on)
#endif
struct AutoGCIfRequested
{
JSRuntime* runtime;
explicit AutoGCIfRequested(JSRuntime* rt) : runtime(rt) {}
~AutoGCIfRequested() { runtime->gc.gcIfRequested(); }
};
/*
* Find a function reference and its 'this' value implicit first parameter
* under argc arguments on cx's stack, and call the function. Push missing
* required arguments, allocate declared local variables, and pop everything
* when done. Then push the return value.
*/
bool
js::Invoke(JSContext* cx, const CallArgs& args, MaybeConstruct construct)
{
MOZ_ASSERT(args.length() <= ARGS_LENGTH_MAX);
MOZ_ASSERT(!cx->zone()->types.activeAnalysis);
/* Perform GC if necessary on exit from the function. */
AutoGCIfRequested gcIfRequested(cx->runtime());
/* MaybeConstruct is a subset of InitialFrameFlags */
InitialFrameFlags initial = (InitialFrameFlags) construct;
unsigned skipForCallee = args.length() + 1 + (construct == CONSTRUCT);
if (args.calleev().isPrimitive())
return ReportIsNotFunction(cx, args.calleev(), skipForCallee, construct);
/* Invoke non-functions. */
if (MOZ_UNLIKELY(!args.callee().is<JSFunction>())) {
MOZ_ASSERT_IF(construct, !args.callee().constructHook());
JSNative call = args.callee().callHook();
if (!call)
return ReportIsNotFunction(cx, args.calleev(), skipForCallee, construct);
return CallJSNative(cx, call, args);
}
/* Invoke native functions. */
JSFunction* fun = &args.callee().as<JSFunction>();
if (construct != CONSTRUCT && fun->isClassConstructor()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CALL_CLASS_CONSTRUCTOR);
return false;
}
if (fun->isNative()) {
MOZ_ASSERT_IF(construct, !fun->isConstructor());
return CallJSNative(cx, fun->native(), args);
}
if (!fun->getOrCreateScript(cx))
return false;
/* Run function until JSOP_RETRVAL, JSOP_RETURN or error. */
InvokeState state(cx, args, initial);
// Check to see if createSingleton flag should be set for this frame.
if (construct) {
jsbytecode* pc;
if (JSScript* script = cx->currentScript(&pc)) {
if (ObjectGroup::useSingletonForNewObject(cx, script, pc))
state.setCreateSingleton();
}
}
bool ok = RunScript(cx, state);
MOZ_ASSERT_IF(ok && construct, args.rval().isObject());
return ok;
}
bool
js::Invoke(JSContext* cx, const Value& thisv, const Value& fval, unsigned argc, const Value* argv,
MutableHandleValue rval)
{
InvokeArgs args(cx);
if (!args.init(argc))
return false;
args.setCallee(fval);
args.setThis(thisv);
PodCopy(args.array(), argv, argc);
if (args.thisv().isObject()) {
/*
* We must call the thisValue hook in case we are not called from the
* interpreter, where a prior bytecode has computed an appropriate
* |this| already. But don't do that if fval is a DOM function.
*/
if (!fval.isObject() || !fval.toObject().is<JSFunction>() ||
!fval.toObject().as<JSFunction>().isNative() ||
!fval.toObject().as<JSFunction>().jitInfo() ||
fval.toObject().as<JSFunction>().jitInfo()->needsOuterizedThisObject())
{
JSObject* thisObj = &args.thisv().toObject();
args.mutableThisv().set(GetThisValue(thisObj));
}
}
if (!Invoke(cx, args))
return false;
rval.set(args.rval());
return true;
}
static bool
InternalConstruct(JSContext* cx, const CallArgs& args)
{
MOZ_ASSERT(args.array() + args.length() + 1 == args.end(),
"must pass constructing arguments to a construction attempt");
MOZ_ASSERT(!JSFunction::class_.construct);
// Callers are responsible for enforcing these preconditions.
MOZ_ASSERT(IsConstructor(args.calleev()),
"trying to construct a value that isn't a constructor");
MOZ_ASSERT(IsConstructor(args.newTarget()),
"provided new.target value must be a constructor");
JSObject& callee = args.callee();
if (callee.is<JSFunction>()) {
RootedFunction fun(cx, &callee.as<JSFunction>());
if (fun->isNative())
return CallJSNativeConstructor(cx, fun->native(), args);
if (!Invoke(cx, args, CONSTRUCT))
return false;
MOZ_ASSERT(args.rval().isObject());
return true;
}
JSNative construct = callee.constructHook();
MOZ_ASSERT(construct != nullptr, "IsConstructor without a construct hook?");
return CallJSNativeConstructor(cx, construct, args);
}
// Check that |callee|, the callee in a |new| expression, is a constructor.
static bool
StackCheckIsConstructorCalleeNewTarget(JSContext* cx, HandleValue callee, HandleValue newTarget)
{
// Calls from the stack could have any old non-constructor callee.
if (!IsConstructor(callee)) {
ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_SEARCH_STACK, callee, nullptr);
return false;
}
// The new.target has already been vetted by previous calls, or is the callee.
// We can just assert that it's a constructor.
MOZ_ASSERT(IsConstructor(newTarget));
return true;
}
static bool
ConstructFromStack(JSContext* cx, const CallArgs& args)
{
if (!StackCheckIsConstructorCalleeNewTarget(cx, args.calleev(), args.newTarget()))
return false;
args.setThis(MagicValue(JS_IS_CONSTRUCTING));
return InternalConstruct(cx, args);
}
bool
js::Construct(JSContext* cx, HandleValue fval, const ConstructArgs& args, HandleValue newTarget,
MutableHandleValue rval)
{
args.setCallee(fval);
args.setThis(MagicValue(JS_IS_CONSTRUCTING));
args.newTarget().set(newTarget);
if (!InternalConstruct(cx, args))
return false;
rval.set(args.rval());
return true;
}
bool
js::InternalConstructWithProvidedThis(JSContext* cx, HandleValue fval, HandleValue thisv,
const ConstructArgs& args, HandleValue newTarget,
MutableHandleValue rval)
{
args.setCallee(fval);
MOZ_ASSERT(thisv.isObject());
args.setThis(thisv);
args.newTarget().set(newTarget);
if (!InternalConstruct(cx, args))
return false;
rval.set(args.rval());
return true;
}
bool
js::InvokeGetter(JSContext* cx, const Value& thisv, Value fval, MutableHandleValue rval)
{
/*
* Invoke could result in another try to get or set the same id again, see
* bug 355497.
*/
JS_CHECK_RECURSION(cx, return false);
return Invoke(cx, thisv, fval, 0, nullptr, rval);
}
bool
js::InvokeSetter(JSContext* cx, const Value& thisv, Value fval, HandleValue v)
{
JS_CHECK_RECURSION(cx, return false);
RootedValue ignored(cx);
return Invoke(cx, thisv, fval, 1, v.address(), &ignored);
}
bool
js::ExecuteKernel(JSContext* cx, HandleScript script, JSObject& scopeChainArg,
const Value& newTargetValue, ExecuteType type, AbstractFramePtr evalInFrame,
Value* result)
{
MOZ_ASSERT_IF(evalInFrame, type == EXECUTE_DEBUG);
MOZ_ASSERT_IF(type == EXECUTE_GLOBAL, IsGlobalLexicalScope(&scopeChainArg) ||
!IsSyntacticScope(&scopeChainArg));
#ifdef DEBUG
RootedObject terminatingScope(cx, &scopeChainArg);
while (IsSyntacticScope(terminatingScope))
terminatingScope = terminatingScope->enclosingScope();
MOZ_ASSERT(terminatingScope->is<GlobalObject>() ||
script->hasNonSyntacticScope());
#endif
if (script->treatAsRunOnce()) {
if (script->hasRunOnce()) {
JS_ReportError(cx, "Trying to execute a run-once script multiple times");
return false;
}
script->setHasRunOnce();
}
if (script->isEmpty()) {
if (result)
result->setUndefined();
return true;
}
probes::StartExecution(script);
ExecuteState state(cx, script, newTargetValue, scopeChainArg, type, evalInFrame, result);
bool ok = RunScript(cx, state);
probes::StopExecution(script);
return ok;
}
bool
js::Execute(JSContext* cx, HandleScript script, JSObject& scopeChainArg, Value* rval)
{
/* The scope chain is something we control, so we know it can't
have any outer objects on it. */
RootedObject scopeChain(cx, &scopeChainArg);
MOZ_ASSERT(!IsWindowProxy(scopeChain));
if (script->module()) {
MOZ_RELEASE_ASSERT(scopeChain == script->module()->environment(),
"Module scripts can only be executed in the module's environment");
} else {
MOZ_RELEASE_ASSERT(IsGlobalLexicalScope(scopeChain) || script->hasNonSyntacticScope(),
"Only global scripts with non-syntactic scopes can be executed with "
"interesting scopechains");
}
/* Ensure the scope chain is all same-compartment and terminates in a global. */
#ifdef DEBUG
JSObject* s = scopeChain;
do {
assertSameCompartment(cx, s);
MOZ_ASSERT_IF(!s->enclosingScope(), s->is<GlobalObject>());
} while ((s = s->enclosingScope()));
#endif
ExecuteType type = script->module() ? EXECUTE_MODULE : EXECUTE_GLOBAL;
return ExecuteKernel(cx, script, *scopeChain, NullValue(), type,
NullFramePtr() /* evalInFrame */, rval);
}
bool
js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp)
{
const Class* clasp = obj->getClass();
RootedValue local(cx, v);
if (clasp->hasInstance)
return clasp->hasInstance(cx, obj, &local, bp);
RootedValue val(cx, ObjectValue(*obj));
ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
JSDVG_SEARCH_STACK, val, nullptr);
return false;
}
static inline bool
EqualGivenSameType(JSContext* cx, HandleValue lval, HandleValue rval, bool* equal)
{
MOZ_ASSERT(SameType(lval, rval));
if (lval.isString())
return EqualStrings(cx, lval.toString(), rval.toString(), equal);
if (lval.isDouble()) {
*equal = (lval.toDouble() == rval.toDouble());
return true;
}
if (lval.isGCThing()) { // objects or symbols
*equal = (lval.toGCThing() == rval.toGCThing());
return true;
}
*equal = lval.get().payloadAsRawUint32() == rval.get().payloadAsRawUint32();
MOZ_ASSERT_IF(lval.isUndefined() || lval.isNull(), *equal);
return true;
}
static inline bool
LooselyEqualBooleanAndOther(JSContext* cx, HandleValue lval, HandleValue rval, bool* result)
{
MOZ_ASSERT(!rval.isBoolean());
RootedValue lvalue(cx, Int32Value(lval.toBoolean() ? 1 : 0));
// The tail-call would end up in Step 3.
if (rval.isNumber()) {
*result = (lvalue.toNumber() == rval.toNumber());
return true;
}
// The tail-call would end up in Step 6.
if (rval.isString()) {
double num;
if (!StringToNumber(cx, rval.toString(), &num))
return false;
*result = (lvalue.toNumber() == num);
return true;
}
return LooselyEqual(cx, lvalue, rval, result);
}
// ES6 draft rev32 7.2.12 Abstract Equality Comparison
bool
js::LooselyEqual(JSContext* cx, HandleValue lval, HandleValue rval, bool* result)
{
// Step 3.
if (SameType(lval, rval))
return EqualGivenSameType(cx, lval, rval, result);
// Handle int32 x double.
if (lval.isNumber() && rval.isNumber()) {
*result = (lval.toNumber() == rval.toNumber());
return true;
}
// Step 4. This a bit more complex, because of the undefined emulating object.
if (lval.isNullOrUndefined()) {
// We can return early here, because null | undefined is only equal to the same set.
*result = rval.isNullOrUndefined() ||
(rval.isObject() && EmulatesUndefined(&rval.toObject()));
return true;
}
// Step 5.
if (rval.isNullOrUndefined()) {
MOZ_ASSERT(!lval.isNullOrUndefined());
*result = lval.isObject() && EmulatesUndefined(&lval.toObject());
return true;
}
// Step 6.
if (lval.isNumber() && rval.isString()) {
double num;
if (!StringToNumber(cx, rval.toString(), &num))
return false;
*result = (lval.toNumber() == num);
return true;
}
// Step 7.
if (lval.isString() && rval.isNumber()) {
double num;
if (!StringToNumber(cx, lval.toString(), &num))
return false;
*result = (num == rval.toNumber());
return true;
}
// Step 8.
if (lval.isBoolean())
return LooselyEqualBooleanAndOther(cx, lval, rval, result);
// Step 9.
if (rval.isBoolean())
return LooselyEqualBooleanAndOther(cx, rval, lval, result);
// Step 10.
if ((lval.isString() || lval.isNumber() || lval.isSymbol()) && rval.isObject()) {
RootedValue rvalue(cx, rval);
if (!ToPrimitive(cx, &rvalue))
return false;
return LooselyEqual(cx, lval, rvalue, result);
}
// Step 11.
if (lval.isObject() && (rval.isString() || rval.isNumber() || rval.isSymbol())) {
RootedValue lvalue(cx, lval);
if (!ToPrimitive(cx, &lvalue))
return false;
return LooselyEqual(cx, lvalue, rval, result);
}
// Step 12.
*result = false;
return true;
}
bool
js::StrictlyEqual(JSContext* cx, HandleValue lval, HandleValue rval, bool* equal)
{
if (SameType(lval, rval))
return EqualGivenSameType(cx, lval, rval, equal);
if (lval.isNumber() && rval.isNumber()) {
*equal = (lval.toNumber() == rval.toNumber());
return true;
}
*equal = false;
return true;
}
static inline bool
IsNegativeZero(const Value& v)
{
return v.isDouble() && mozilla::IsNegativeZero(v.toDouble());
}
static inline bool
IsNaN(const Value& v)
{
return v.isDouble() && mozilla::IsNaN(v.toDouble());
}
bool
js::SameValue(JSContext* cx, HandleValue v1, HandleValue v2, bool* same)
{
if (IsNegativeZero(v1)) {
*same = IsNegativeZero(v2);
return true;
}
if (IsNegativeZero(v2)) {
*same = false;
return true;
}
if (IsNaN(v1) && IsNaN(v2)) {
*same = true;
return true;
}
return StrictlyEqual(cx, v1, v2, same);
}
JSType
js::TypeOfObject(JSObject* obj)
{
if (EmulatesUndefined(obj))
return JSTYPE_VOID;
if (obj->isCallable())
return JSTYPE_FUNCTION;
return JSTYPE_OBJECT;
}
JSType
js::TypeOfValue(const Value& v)
{
if (v.isNumber())
return JSTYPE_NUMBER;
if (v.isString())
return JSTYPE_STRING;
if (v.isNull())
return JSTYPE_OBJECT;
if (v.isUndefined())
return JSTYPE_VOID;
if (v.isObject())
return TypeOfObject(&v.toObject());
if (v.isBoolean())
return JSTYPE_BOOLEAN;
MOZ_ASSERT(v.isSymbol());
return JSTYPE_SYMBOL;
}
/*
* Enter the new with scope using an object at sp[-1] and associate the depth
* of the with block with sp + stackIndex.
*/
bool
js::EnterWithOperation(JSContext* cx, AbstractFramePtr frame, HandleValue val,
HandleObject staticWith)
{
MOZ_ASSERT(staticWith->is<StaticWithObject>());
RootedObject obj(cx);
if (val.isObject()) {
obj = &val.toObject();
} else {
obj = ToObject(cx, val);
if (!obj)
return false;
}
RootedObject scopeChain(cx, frame.scopeChain());
DynamicWithObject* withobj = DynamicWithObject::create(cx, obj, scopeChain, staticWith);
if (!withobj)
return false;
frame.pushOnScopeChain(*withobj);
return true;
}
static void
PopScope(JSContext* cx, ScopeIter& si)
{
switch (si.type()) {
case ScopeIter::Block:
if (cx->compartment()->isDebuggee())
DebugScopes::onPopBlock(cx, si);
if (si.staticBlock().needsClone())
si.initialFrame().popBlock(cx);
break;
case ScopeIter::With:
si.initialFrame().popWith(cx);
break;
case ScopeIter::Module:
case ScopeIter::Call:
case ScopeIter::Eval:
case ScopeIter::NonSyntactic:
break;
}
}
// Unwind scope chain and iterator to match the static scope corresponding to
// the given bytecode position.
void
js::UnwindScope(JSContext* cx, ScopeIter& si, jsbytecode* pc)
{
if (!si.withinInitialFrame())
return;
RootedObject staticScope(cx, si.initialFrame().script()->innermostStaticScope(pc));
for (; si.maybeStaticScope() != staticScope; ++si)
PopScope(cx, si);
}
// Unwind all scopes. This is needed because block scopes may cover the
// first bytecode at a script's main(). e.g.,
//
// function f() { { let i = 0; } }
//
// will have no pc location distinguishing the first block scope from the
// outermost function scope.
void
js::UnwindAllScopesInFrame(JSContext* cx, ScopeIter& si)
{
for (; si.withinInitialFrame(); ++si)
PopScope(cx, si);
}
// Compute the pc needed to unwind the scope to the beginning of a try
// block. We cannot unwind to *after* the JSOP_TRY, because that might be the
// first opcode of an inner scope, with the same problem as above. e.g.,
//
// try { { let x; } }
//
// will have no pc location distinguishing the try block scope from the inner
// let block scope.
jsbytecode*
js::UnwindScopeToTryPc(JSScript* script, JSTryNote* tn)
{
jsbytecode* pc = script->main() + tn->start;
if (tn->kind == JSTRY_CATCH || tn->kind == JSTRY_FINALLY) {
pc -= JSOP_TRY_LENGTH;
MOZ_ASSERT(*pc == JSOP_TRY);
}
return pc;
}
static bool
ForcedReturn(JSContext* cx, ScopeIter& si, InterpreterRegs& regs, bool frameOk = true)
{
bool ok = Debugger::onLeaveFrame(cx, regs.fp(), frameOk);
UnwindAllScopesInFrame(cx, si);
// Point the frame to the end of the script, regardless of error. The
// caller must jump to the correct continuation depending on 'ok'.
regs.setToEndOfScript();
return ok;
}
static bool
ForcedReturn(JSContext* cx, InterpreterRegs& regs)
{
ScopeIter si(cx, regs.fp(), regs.pc);
return ForcedReturn(cx, si, regs);
}
static void
SettleOnTryNote(JSContext* cx, JSTryNote* tn, ScopeIter& si, InterpreterRegs& regs)
{
// Unwind the scope to the beginning of the JSOP_TRY.
UnwindScope(cx, si, UnwindScopeToTryPc(regs.fp()->script(), tn));
// Set pc to the first bytecode after the the try note to point
// to the beginning of catch or finally.
regs.pc = regs.fp()->script()->main() + tn->start + tn->length;
regs.sp = regs.spForStackDepth(tn->stackDepth);
}
class InterpreterFrameStackDepthOp
{
const InterpreterRegs& regs_;
public:
explicit InterpreterFrameStackDepthOp(const InterpreterRegs& regs)
: regs_(regs)
{ }
uint32_t operator()() { return regs_.stackDepth(); }
};
class TryNoteIterInterpreter : public TryNoteIter<InterpreterFrameStackDepthOp>
{
public:
TryNoteIterInterpreter(JSContext* cx, const InterpreterRegs& regs)
: TryNoteIter(cx, regs.fp()->script(), regs.pc, InterpreterFrameStackDepthOp(regs))
{ }
};
static void
UnwindIteratorsForUncatchableException(JSContext* cx, const InterpreterRegs& regs)
{
// c.f. the regular (catchable) TryNoteIterInterpreter loop in
// ProcessTryNotes.
for (TryNoteIterInterpreter tni(cx, regs); !tni.done(); ++tni) {
JSTryNote* tn = *tni;
if (tn->kind == JSTRY_FOR_IN) {
Value* sp = regs.spForStackDepth(tn->stackDepth);
UnwindIteratorForUncatchableException(cx, &sp[-1].toObject());
}
}
}
enum HandleErrorContinuation
{
SuccessfulReturnContinuation,
ErrorReturnContinuation,
CatchContinuation,
FinallyContinuation
};
static HandleErrorContinuation
ProcessTryNotes(JSContext* cx, ScopeIter& si, InterpreterRegs& regs)
{
for (TryNoteIterInterpreter tni(cx, regs); !tni.done(); ++tni) {
JSTryNote* tn = *tni;
switch (tn->kind) {
case JSTRY_CATCH:
/* Catch cannot intercept the closing of a generator. */
if (cx->isClosingGenerator())
break;
SettleOnTryNote(cx, tn, si, regs);
return CatchContinuation;
case JSTRY_FINALLY:
SettleOnTryNote(cx, tn, si, regs);
return FinallyContinuation;
case JSTRY_FOR_IN: {
/* This is similar to JSOP_ENDITER in the interpreter loop. */
DebugOnly<jsbytecode*> pc = regs.fp()->script()->main() + tn->start + tn->length;
MOZ_ASSERT(JSOp(*pc) == JSOP_ENDITER);
Value* sp = regs.spForStackDepth(tn->stackDepth);
RootedObject obj(cx, &sp[-1].toObject());
if (!UnwindIteratorForException(cx, obj)) {
// We should only settle on the note only if
// UnwindIteratorForException itself threw, as
// onExceptionUnwind should be called anew with the new
// location of the throw (the iterator). Indeed, we must
// settle to avoid infinitely handling the same exception.
SettleOnTryNote(cx, tn, si, regs);
return ErrorReturnContinuation;
}
break;
}
case JSTRY_FOR_OF:
case JSTRY_LOOP:
break;
default:
MOZ_CRASH("Invalid try note");
}
}
return SuccessfulReturnContinuation;
}
bool
js::HandleClosingGeneratorReturn(JSContext* cx, AbstractFramePtr frame, bool ok)
{
/*
* Propagate the exception or error to the caller unless the exception
* is an asynchronous return from a generator.
*/
if (cx->isClosingGenerator()) {
cx->clearPendingException();
ok = true;
SetReturnValueForClosingGenerator(cx, frame);
}
return ok;
}
static HandleErrorContinuation
HandleError(JSContext* cx, InterpreterRegs& regs)
{
MOZ_ASSERT(regs.fp()->script()->containsPC(regs.pc));
if (regs.fp()->script()->hasScriptCounts()) {
PCCounts* counts = regs.fp()->script()->getThrowCounts(regs.pc);
// If we failed to allocate, then skip the increment and continue to
// handle the exception.
if (counts)
counts->numExec()++;
}
ScopeIter si(cx, regs.fp(), regs.pc);
bool ok = false;
again:
if (cx->isExceptionPending()) {
/* Call debugger throw hooks. */
if (!cx->isClosingGenerator()) {
JSTrapStatus status = Debugger::onExceptionUnwind(cx, regs.fp());
switch (status) {
case JSTRAP_ERROR:
goto again;
case JSTRAP_CONTINUE:
case JSTRAP_THROW:
break;
case JSTRAP_RETURN:
UnwindIteratorsForUncatchableException(cx, regs);
if (!ForcedReturn(cx, si, regs))
return ErrorReturnContinuation;
return SuccessfulReturnContinuation;
default:
MOZ_CRASH("Bad Debugger::onExceptionUnwind status");
}
}
HandleErrorContinuation res = ProcessTryNotes(cx, si, regs);
switch (res) {
case SuccessfulReturnContinuation:
break;
case ErrorReturnContinuation:
goto again;
case CatchContinuation:
case FinallyContinuation:
// No need to increment the PCCounts number of execution here, as
// the interpreter increments any PCCounts if present.
MOZ_ASSERT_IF(regs.fp()->script()->hasScriptCounts(),
regs.fp()->script()->maybeGetPCCounts(regs.pc));
return res;
}
ok = HandleClosingGeneratorReturn(cx, regs.fp(), ok);
ok = Debugger::onLeaveFrame(cx, regs.fp(), ok);
} else {
// We may be propagating a forced return from the interrupt
// callback, which cannot easily force a return.
if (MOZ_UNLIKELY(cx->isPropagatingForcedReturn())) {
cx->clearPropagatingForcedReturn();
if (!ForcedReturn(cx, si, regs))
return ErrorReturnContinuation;
return SuccessfulReturnContinuation;
}
UnwindIteratorsForUncatchableException(cx, regs);
}
// After this point, we will pop the frame regardless. Settle the frame on
// the end of the script.
UnwindAllScopesInFrame(cx, si);
regs.setToEndOfScript();
return ok ? SuccessfulReturnContinuation : ErrorReturnContinuation;
}
#define REGS (activation.regs())
#define PUSH_COPY(v) do { *REGS.sp++ = (v); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
#define PUSH_COPY_SKIP_CHECK(v) *REGS.sp++ = (v)
#define PUSH_NULL() REGS.sp++->setNull()
#define PUSH_UNDEFINED() REGS.sp++->setUndefined()
#define PUSH_BOOLEAN(b) REGS.sp++->setBoolean(b)
#define PUSH_DOUBLE(d) REGS.sp++->setDouble(d)
#define PUSH_INT32(i) REGS.sp++->setInt32(i)
#define PUSH_SYMBOL(s) REGS.sp++->setSymbol(s)
#define PUSH_STRING(s) do { REGS.sp++->setString(s); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
#define PUSH_OBJECT(obj) do { REGS.sp++->setObject(obj); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
#define PUSH_OBJECT_OR_NULL(obj) do { REGS.sp++->setObjectOrNull(obj); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0)
#define PUSH_HOLE() REGS.sp++->setMagic(JS_ELEMENTS_HOLE)
#define PUSH_UNINITIALIZED() REGS.sp++->setMagic(JS_UNINITIALIZED_LEXICAL)
#define POP_COPY_TO(v) (v) = *--REGS.sp
#define POP_RETURN_VALUE() REGS.fp()->setReturnValue(*--REGS.sp)
#define FETCH_OBJECT(cx, n, obj) \
JS_BEGIN_MACRO \
HandleValue val = REGS.stackHandleAt(n); \
obj = ToObjectFromStack((cx), (val)); \
if (!obj) \
goto error; \
JS_END_MACRO
/*
* Same for JSOP_SETNAME and JSOP_SETPROP, which differ only slightly but
* remain distinct for the decompiler.
*/
JS_STATIC_ASSERT(JSOP_SETNAME_LENGTH == JSOP_SETPROP_LENGTH);
/* See TRY_BRANCH_AFTER_COND. */
JS_STATIC_ASSERT(JSOP_IFNE_LENGTH == JSOP_IFEQ_LENGTH);
JS_STATIC_ASSERT(JSOP_IFNE == JSOP_IFEQ + 1);
/*
* Compute the implicit |this| parameter for a call expression where the callee
* funval was resolved from an unqualified name reference to a property on obj
* (an object on the scope chain).
*
* We can avoid computing |this| eagerly and push the implicit callee-coerced
* |this| value, undefined, if either of these conditions hold:
*
* 1. The nominal |this|, obj, is a global object.
*
* 2. The nominal |this|, obj, has one of Block, Call, or DeclEnv class (this
* is what IsCacheableNonGlobalScope tests). Such objects-as-scopes must be
* censored with undefined.
*
* Otherwise, we bind |this| to the result of GetThisValue(). Only names inside
* |with| statements and embedding-specific scope objects fall into this
* category.
*
* If the callee is a strict mode function, then code implementing JSOP_THIS
* in the interpreter and JITs will leave undefined as |this|. If funval is a
* function not in strict mode, JSOP_THIS code replaces undefined with funval's
* global.
*/
static inline Value
ComputeImplicitThis(JSObject* obj)
{
if (IsGlobalLexicalScope(obj))
return UndefinedValue();
if (IsCacheableNonGlobalScope(obj))
return UndefinedValue();
return GetThisValue(obj);
}
static MOZ_ALWAYS_INLINE bool
AddOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res)
{
if (lhs.isInt32() && rhs.isInt32()) {
int32_t l = lhs.toInt32(), r = rhs.toInt32();
int32_t t;
if (MOZ_LIKELY(SafeAdd(l, r, &t))) {
res.setInt32(t);
return true;
}
}
if (!ToPrimitive(cx, lhs))
return false;
if (!ToPrimitive(cx, rhs))
return false;
bool lIsString, rIsString;
if ((lIsString = lhs.isString()) | (rIsString = rhs.isString())) {
JSString* lstr;
if (lIsString) {
lstr = lhs.toString();
} else {
lstr = ToString<CanGC>(cx, lhs);
if (!lstr)
return false;
}
JSString* rstr;
if (rIsString) {
rstr = rhs.toString();
} else {
// Save/restore lstr in case of GC activity under ToString.
lhs.setString(lstr);
rstr = ToString<CanGC>(cx, rhs);
if (!rstr)
return false;
lstr = lhs.toString();
}
JSString* str = ConcatStrings<NoGC>(cx, lstr, rstr);
if (!str) {
RootedString nlstr(cx, lstr), nrstr(cx, rstr);
str = ConcatStrings<CanGC>(cx, nlstr, nrstr);
if (!str)
return false;
}
res.setString(str);
} else {
double l, r;
if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r))
return false;
res.setNumber(l + r);
}
return true;
}
static MOZ_ALWAYS_INLINE bool
SubOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res)
{
double d1, d2;
if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2))
return false;
res.setNumber(d1 - d2);
return true;
}
static MOZ_ALWAYS_INLINE bool
MulOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res)
{
double d1, d2;
if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2))
return false;
res.setNumber(d1 * d2);
return true;
}
static MOZ_ALWAYS_INLINE bool
DivOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res)
{
double d1, d2;
if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2))
return false;
res.setNumber(NumberDiv(d1, d2));
return true;
}
static MOZ_ALWAYS_INLINE bool
ModOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res)
{
int32_t l, r;
if (lhs.isInt32() && rhs.isInt32() &&
(l = lhs.toInt32()) >= 0 && (r = rhs.toInt32()) > 0) {
int32_t mod = l % r;
res.setInt32(mod);
return true;
}
double d1, d2;
if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2))
return false;
res.setNumber(NumberMod(d1, d2));
return true;
}
static MOZ_ALWAYS_INLINE bool
SetObjectElementOperation(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id,
const Value& value, bool strict, JSScript* script = nullptr,
jsbytecode* pc = nullptr)
{
// receiver != obj happens only at super[expr], where we expect to find the property
// People probably aren't building hashtables with |super| anyway.
TypeScript::MonitorAssign(cx, obj, id);
if (obj->isNative() && JSID_IS_INT(id)) {
uint32_t length = obj->as<NativeObject>().getDenseInitializedLength();
int32_t i = JSID_TO_INT(id);
if ((uint32_t)i >= length) {
// Annotate script if provided with information (e.g. baseline)
if (script && script->hasBaselineScript() && *pc == JSOP_SETELEM)
script->baselineScript()->noteArrayWriteHole(script->pcToOffset(pc));
}
}
if (obj->isNative() && !JSID_IS_INT(id) && !obj->setHadElementsAccess(cx))
return false;
RootedValue tmp(cx, value);
ObjectOpResult result;
return SetProperty(cx, obj, id, tmp, receiver, result) &&
result.checkStrictErrorOrWarning(cx, obj, id, strict);
}
/*
* Get the innermost enclosing function that has a 'this' binding.
*
* Implements ES6 12.3.5.2 GetSuperConstructor() steps 1-3, including
* the loop in ES6 8.3.2 GetThisEnvironment(). Our implementation of
* ES6 12.3.5.3 MakeSuperPropertyReference() also uses this code.
*/
static JSFunction&
GetSuperEnvFunction(JSContext *cx, InterpreterRegs& regs)
{
ScopeIter si(cx, regs.fp()->scopeChain(), regs.fp()->script()->innermostStaticScope(regs.pc));
for (; !si.done(); ++si) {
if (si.hasSyntacticScopeObject() && si.type() == ScopeIter::Call) {
JSFunction& callee = si.scope().as<CallObject>().callee();
// Arrow functions don't have the information we're looking for,
// their enclosing scopes do. Nevertheless, they might have call
// objects. Skip them to find what we came for.
if (callee.isArrow())
continue;
return callee;
}
}
MOZ_CRASH("unexpected scope chain for GetSuperEnvFunction");
}
/*
* As an optimization, the interpreter creates a handful of reserved Rooted<T>
* variables at the beginning, thus inserting them into the Rooted list once
* upon entry. ReservedRooted "borrows" a reserved Rooted variable and uses it
* within a local scope, resetting the value to nullptr (or the appropriate
* equivalent for T) at scope end. This avoids inserting/removing the Rooted
* from the rooter list, while preventing stale values from being kept alive
* unnecessarily.
*/
template<typename T>
class ReservedRootedBase {
};
template<typename T>
class ReservedRooted : public ReservedRootedBase<T>
{
Rooted<T>* savedRoot;
public:
ReservedRooted(Rooted<T>* root, const T& ptr) : savedRoot(root) {
*root = ptr;
}
explicit ReservedRooted(Rooted<T>* root) : savedRoot(root) {
*root = js::GCMethods<T>::initial();
}
~ReservedRooted() {
*savedRoot = js::GCMethods<T>::initial();
}
void set(const T& p) const { *savedRoot = p; }
operator Handle<T>() { return *savedRoot; }
operator Rooted<T>&() { return *savedRoot; }
MutableHandle<T> operator&() { return &*savedRoot; }
DECLARE_NONPOINTER_ACCESSOR_METHODS(savedRoot->get())
DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(savedRoot->get())
DECLARE_POINTER_CONSTREF_OPS(T)
DECLARE_POINTER_ASSIGN_OPS(ReservedRooted, T)
};
template <>
class ReservedRootedBase<Value> : public ValueOperations<ReservedRooted<Value>>
{};
static MOZ_NEVER_INLINE bool
Interpret(JSContext* cx, RunState& state)
{
/*
* Define macros for an interpreter loop. Opcode dispatch may be either by a
* switch statement or by indirect goto (aka a threaded interpreter), depending
* on compiler support.
*
* Threaded interpretation appears to be well-supported by GCC 3 and higher.
* IBM's C compiler when run with the right options (e.g., -qlanglvl=extended)
* also supports threading. Ditto the SunPro C compiler.
*/
#if (defined(__GNUC__) || \
(__IBMC__ >= 700 && defined __IBM_COMPUTED_GOTO) || \
__SUNPRO_C >= 0x570)
// Non-standard but faster indirect-goto-based dispatch.
# define INTERPRETER_LOOP()
# define CASE(OP) label_##OP:
# define DEFAULT() label_default:
# define DISPATCH_TO(OP) goto* addresses[(OP)]
# define LABEL(X) (&&label_##X)
// Use addresses instead of offsets to optimize for runtime speed over
// load-time relocation overhead.
static const void* const addresses[EnableInterruptsPseudoOpcode + 1] = {
# define OPCODE_LABEL(op, ...) LABEL(op),
FOR_EACH_OPCODE(OPCODE_LABEL)
# undef OPCODE_LABEL
# define TRAILING_LABEL(v) \
((v) == EnableInterruptsPseudoOpcode \
? LABEL(EnableInterruptsPseudoOpcode) \
: LABEL(default)),
FOR_EACH_TRAILING_UNUSED_OPCODE(TRAILING_LABEL)
# undef TRAILING_LABEL
};
#else
// Portable switch-based dispatch.
# define INTERPRETER_LOOP() the_switch: switch (switchOp)
# define CASE(OP) case OP:
# define DEFAULT() default:
# define DISPATCH_TO(OP) \
JS_BEGIN_MACRO \
switchOp = (OP); \
goto the_switch; \
JS_END_MACRO
// This variable is effectively a parameter to the_switch.
jsbytecode switchOp;
#endif
/*
* Increment REGS.pc by N, load the opcode at that position,
* and jump to the code to execute it.
*
* When Debugger puts a script in single-step mode, all js::Interpret
* invocations that might be presently running that script must have
* interrupts enabled. It's not practical to simply check
* script->stepModeEnabled() at each point some callee could have changed
* it, because there are so many places js::Interpret could possibly cause
* JavaScript to run: each place an object might be coerced to a primitive
* or a number, for example. So instead, we expose a simple mechanism to
* let Debugger tweak the affected js::Interpret frames when an onStep
* handler is added: calling activation.enableInterruptsUnconditionally()
* will enable interrupts, and activation.opMask() is or'd with the opcode
* to implement a simple alternate dispatch.
*/
#define ADVANCE_AND_DISPATCH(N) \
JS_BEGIN_MACRO \
REGS.pc += (N); \
SANITY_CHECKS(); \
DISPATCH_TO(*REGS.pc | activation.opMask()); \
JS_END_MACRO
/*
* Shorthand for the common sequence at the end of a fixed-size opcode.
*/
#define END_CASE(OP) ADVANCE_AND_DISPATCH(OP##_LENGTH);
/*
* Prepare to call a user-supplied branch handler, and abort the script
* if it returns false.
*/
#define CHECK_BRANCH() \
JS_BEGIN_MACRO \
if (!CheckForInterrupt(cx)) \
goto error; \
JS_END_MACRO
/*
* This is a simple wrapper around ADVANCE_AND_DISPATCH which also does
* a CHECK_BRANCH() if n is not positive, which possibly indicates that it
* is the backedge of a loop.
*/
#define BRANCH(n) \
JS_BEGIN_MACRO \
int32_t nlen = (n); \
if (nlen <= 0) \
CHECK_BRANCH(); \
ADVANCE_AND_DISPATCH(nlen); \
JS_END_MACRO
#define LOAD_DOUBLE(PCOFF, dbl) \
((dbl) = script->getConst(GET_UINT32_INDEX(REGS.pc + (PCOFF))).toDouble())
#define SET_SCRIPT(s) \
JS_BEGIN_MACRO \
script = (s); \
if (script->hasAnyBreakpointsOrStepMode() || script->hasScriptCounts()) \
activation.enableInterruptsUnconditionally(); \
JS_END_MACRO
#define SANITY_CHECKS() \
JS_BEGIN_MACRO \
js::gc::MaybeVerifyBarriers(cx); \
MOZ_ASSERT_IF(script->hasScriptCounts(), \
activation.opMask() == EnableInterruptsPseudoOpcode); \
JS_END_MACRO
gc::MaybeVerifyBarriers(cx, true);
MOZ_ASSERT(!cx->zone()->types.activeAnalysis);
InterpreterFrame* entryFrame = state.pushInterpreterFrame(cx);
if (!entryFrame)
return false;
ActivationEntryMonitor entryMonitor(cx, entryFrame);
InterpreterActivation activation(state, cx, entryFrame);
/* The script is used frequently, so keep a local copy. */
RootedScript script(cx);
SET_SCRIPT(REGS.fp()->script());
TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
TraceLoggerEvent scriptEvent(logger, TraceLogger_Scripts, script);
TraceLogStartEvent(logger, scriptEvent);
TraceLogStartEvent(logger, TraceLogger_Interpreter);
/*
* Pool of rooters for use in this interpreter frame. References to these
* are used for local variables within interpreter cases. This avoids
* creating new rooters each time an interpreter case is entered, and also
* correctness pitfalls due to incorrect compilation of destructor calls
* around computed gotos.
*/
RootedValue rootValue0(cx), rootValue1(cx);
RootedString rootString0(cx), rootString1(cx);
RootedObject rootObject0(cx), rootObject1(cx), rootObject2(cx);
RootedNativeObject rootNativeObject0(cx);
RootedFunction rootFunction0(cx);
RootedPropertyName rootName0(cx);
RootedId rootId0(cx);
RootedShape rootShape0(cx);
RootedScript rootScript0(cx);
DebugOnly<uint32_t> blockDepth;
/* State communicated between non-local jumps: */
bool interpReturnOK;
if (!activation.entryFrame()->prologue(cx))
goto error;
switch (Debugger::onEnterFrame(cx, activation.entryFrame())) {
case JSTRAP_CONTINUE:
break;
case JSTRAP_RETURN:
if (!ForcedReturn(cx, REGS))
goto error;
goto successful_return_continuation;
case JSTRAP_THROW:
case JSTRAP_ERROR:
goto error;
default:
MOZ_CRASH("bad Debugger::onEnterFrame status");
}
if (cx->compartment()->collectCoverage())
activation.enableInterruptsUnconditionally();
// Enter the interpreter loop starting at the current pc.
ADVANCE_AND_DISPATCH(0);
INTERPRETER_LOOP() {
CASE(EnableInterruptsPseudoOpcode)
{
bool moreInterrupts = false;
jsbytecode op = *REGS.pc;
if (!script->hasScriptCounts() && cx->compartment()->collectCoverage()) {
if (!script->initScriptCounts(cx))
goto error;
moreInterrupts = true;
}
if (script->hasScriptCounts()) {
PCCounts* counts = script->maybeGetPCCounts(REGS.pc);
if (counts)
counts->numExec()++;
moreInterrupts = true;
}
if (script->isDebuggee()) {
if (script->stepModeEnabled()) {
RootedValue rval(cx);
JSTrapStatus status = JSTRAP_CONTINUE;
status = Debugger::onSingleStep(cx, &rval);
switch (status) {
case JSTRAP_ERROR:
goto error;
case JSTRAP_CONTINUE:
break;
case JSTRAP_RETURN:
REGS.fp()->setReturnValue(rval);
if (!ForcedReturn(cx, REGS))
goto error;
goto successful_return_continuation;
case JSTRAP_THROW:
cx->setPendingException(rval);
goto error;
default:;
}
moreInterrupts = true;
}
if (script->hasAnyBreakpointsOrStepMode())
moreInterrupts = true;
if (script->hasBreakpointsAt(REGS.pc)) {
RootedValue rval(cx);
JSTrapStatus status = Debugger::onTrap(cx, &rval);
switch (status) {
case JSTRAP_ERROR:
goto error;
case JSTRAP_RETURN:
REGS.fp()->setReturnValue(rval);
if (!ForcedReturn(cx, REGS))
goto error;
goto successful_return_continuation;
case JSTRAP_THROW:
cx->setPendingException(rval);
goto error;
default:
break;
}
MOZ_ASSERT(status == JSTRAP_CONTINUE);
MOZ_ASSERT(rval.isInt32() && rval.toInt32() == op);
}
}
MOZ_ASSERT(activation.opMask() == EnableInterruptsPseudoOpcode);
if (!moreInterrupts)
activation.clearInterruptsMask();
/* Commence executing the actual opcode. */
SANITY_CHECKS();
DISPATCH_TO(op);
}
/* Various 1-byte no-ops. */
CASE(JSOP_NOP)
CASE(JSOP_UNUSED14)
CASE(JSOP_UNUSED65)
CASE(JSOP_BACKPATCH)
CASE(JSOP_UNUSED177)
CASE(JSOP_UNUSED178)
CASE(JSOP_UNUSED179)
CASE(JSOP_UNUSED180)
CASE(JSOP_UNUSED181)
CASE(JSOP_UNUSED182)
CASE(JSOP_UNUSED183)
CASE(JSOP_UNUSED187)
CASE(JSOP_UNUSED192)
CASE(JSOP_UNUSED209)
CASE(JSOP_UNUSED210)
CASE(JSOP_UNUSED211)
CASE(JSOP_UNUSED212)
CASE(JSOP_UNUSED213)
CASE(JSOP_UNUSED219)
CASE(JSOP_UNUSED220)
CASE(JSOP_UNUSED221)
CASE(JSOP_UNUSED222)
CASE(JSOP_UNUSED223)
CASE(JSOP_CONDSWITCH)
CASE(JSOP_TRY)
{
MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1);
ADVANCE_AND_DISPATCH(1);
}
CASE(JSOP_LOOPHEAD)
END_CASE(JSOP_LOOPHEAD)
CASE(JSOP_LABEL)
END_CASE(JSOP_LABEL)
CASE(JSOP_LOOPENTRY)
// Attempt on-stack replacement with Baseline code.
if (jit::IsBaselineEnabled(cx)) {
jit::MethodStatus status = jit::CanEnterBaselineAtBranch(cx, REGS.fp(), false);
if (status == jit::Method_Error)
goto error;
if (status == jit::Method_Compiled) {
bool wasSPS = REGS.fp()->hasPushedSPSFrame();
jit::JitExecStatus maybeOsr;
{
SPSBaselineOSRMarker spsOSR(cx->runtime(), wasSPS);
maybeOsr = jit::EnterBaselineAtBranch(cx, REGS.fp(), REGS.pc);
}
// We failed to call into baseline at all, so treat as an error.
if (maybeOsr == jit::JitExec_Aborted)
goto error;
interpReturnOK = (maybeOsr == jit::JitExec_Ok);
// Pop the SPS frame pushed by the interpreter. (The compiled version of the
// function popped a copy of the frame pushed by the OSR trampoline.)
if (wasSPS)
cx->runtime()->spsProfiler.exit(script, script->functionNonDelazifying());
if (activation.entryFrame() != REGS.fp())
goto jit_return_pop_frame;
goto leave_on_safe_point;
}
}
END_CASE(JSOP_LOOPENTRY)
CASE(JSOP_LINENO)
END_CASE(JSOP_LINENO)
CASE(JSOP_FORCEINTERPRETER)
END_CASE(JSOP_FORCEINTERPRETER)
CASE(JSOP_UNDEFINED)
// If this ever changes, change what JSOP_GIMPLICITTHIS does too.
PUSH_UNDEFINED();
END_CASE(JSOP_UNDEFINED)
CASE(JSOP_POP)
REGS.sp--;
END_CASE(JSOP_POP)
CASE(JSOP_POPN)
MOZ_ASSERT(GET_UINT16(REGS.pc) <= REGS.stackDepth());
REGS.sp -= GET_UINT16(REGS.pc);
END_CASE(JSOP_POPN)
CASE(JSOP_DUPAT)
{
MOZ_ASSERT(GET_UINT24(REGS.pc) < REGS.stackDepth());
unsigned i = GET_UINT24(REGS.pc);
const Value& rref = REGS.sp[-int(i + 1)];
PUSH_COPY(rref);
}
END_CASE(JSOP_DUPAT)
CASE(JSOP_SETRVAL)
POP_RETURN_VALUE();
END_CASE(JSOP_SETRVAL)
CASE(JSOP_GETRVAL)
PUSH_COPY(REGS.fp()->returnValue());
END_CASE(JSOP_GETRVAL)
CASE(JSOP_ENTERWITH)
{
ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
REGS.sp--;
ReservedRooted<JSObject*> staticWith(&rootObject0, script->getObject(REGS.pc));
if (!EnterWithOperation(cx, REGS.fp(), val, staticWith))
goto error;
}
END_CASE(JSOP_ENTERWITH)
CASE(JSOP_LEAVEWITH)
REGS.fp()->popWith(cx);
END_CASE(JSOP_LEAVEWITH)
CASE(JSOP_RETURN)
POP_RETURN_VALUE();
/* FALL THROUGH */
CASE(JSOP_RETRVAL)
{
/*
* When the inlined frame exits with an exception or an error, ok will be
* false after the inline_return label.
*/
CHECK_BRANCH();
successful_return_continuation:
interpReturnOK = true;
return_continuation:
if (activation.entryFrame() != REGS.fp()) {
// Stop the engine. (No details about which engine exactly, could be
// interpreter, Baseline or IonMonkey.)
TraceLogStopEvent(logger, TraceLogger_Engine);
TraceLogStopEvent(logger, TraceLogger_Scripts);
interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), interpReturnOK);
REGS.fp()->epilogue(cx);
jit_return_pop_frame:
activation.popInlineFrame(REGS.fp());
SET_SCRIPT(REGS.fp()->script());
jit_return:
MOZ_ASSERT(CodeSpec[*REGS.pc].format & JOF_INVOKE);
/* Resume execution in the calling frame. */
if (MOZ_LIKELY(interpReturnOK)) {
TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]);
ADVANCE_AND_DISPATCH(JSOP_CALL_LENGTH);
}
goto error;
} else {
MOZ_ASSERT(REGS.stackDepth() == 0);
}
goto exit;
}
CASE(JSOP_DEFAULT)
REGS.sp--;
/* FALL THROUGH */
CASE(JSOP_GOTO)
{
BRANCH(GET_JUMP_OFFSET(REGS.pc));
}
CASE(JSOP_IFEQ)
{
bool cond = ToBoolean(REGS.stackHandleAt(-1));
REGS.sp--;
if (!cond)
BRANCH(GET_JUMP_OFFSET(REGS.pc));
}
END_CASE(JSOP_IFEQ)
CASE(JSOP_IFNE)
{
bool cond = ToBoolean(REGS.stackHandleAt(-1));
REGS.sp--;
if (cond)
BRANCH(GET_JUMP_OFFSET(REGS.pc));
}
END_CASE(JSOP_IFNE)
CASE(JSOP_OR)
{
bool cond = ToBoolean(REGS.stackHandleAt(-1));
if (cond)
ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc));
}
END_CASE(JSOP_OR)
CASE(JSOP_AND)
{
bool cond = ToBoolean(REGS.stackHandleAt(-1));
if (!cond)
ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc));
}
END_CASE(JSOP_AND)
#define FETCH_ELEMENT_ID(n, id) \
JS_BEGIN_MACRO \
if (!ToPropertyKey(cx, REGS.stackHandleAt(n), &(id))) \
goto error; \
JS_END_MACRO
#define TRY_BRANCH_AFTER_COND(cond,spdec) \
JS_BEGIN_MACRO \
MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1); \
unsigned diff_ = (unsigned) GET_UINT8(REGS.pc) - (unsigned) JSOP_IFEQ; \
if (diff_ <= 1) { \
REGS.sp -= (spdec); \
if ((cond) == (diff_ != 0)) { \
++REGS.pc; \
BRANCH(GET_JUMP_OFFSET(REGS.pc)); \
} \
ADVANCE_AND_DISPATCH(1 + JSOP_IFEQ_LENGTH); \
} \
JS_END_MACRO
CASE(JSOP_IN)
{
HandleValue rref = REGS.stackHandleAt(-1);
if (!rref.isObject()) {
ReportValueError(cx, JSMSG_IN_NOT_OBJECT, -1, rref, nullptr);
goto error;
}
bool found;
{
ReservedRooted<JSObject*> obj(&rootObject0, &rref.toObject());
ReservedRooted<jsid> id(&rootId0);
FETCH_ELEMENT_ID(-2, id);
if (!HasProperty(cx, obj, id, &found))
goto error;
}
TRY_BRANCH_AFTER_COND(found, 2);
REGS.sp--;
REGS.sp[-1].setBoolean(found);
}
END_CASE(JSOP_IN)
CASE(JSOP_ITER)
{
MOZ_ASSERT(REGS.stackDepth() >= 1);
uint8_t flags = GET_UINT8(REGS.pc);
MutableHandleValue res = REGS.stackHandleAt(-1);
if (!ValueToIterator(cx, flags, res))
goto error;
MOZ_ASSERT(res.isObject());
}
END_CASE(JSOP_ITER)
CASE(JSOP_MOREITER)
{
MOZ_ASSERT(REGS.stackDepth() >= 1);
MOZ_ASSERT(REGS.sp[-1].isObject());
PUSH_NULL();
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-2].toObject());
if (!IteratorMore(cx, obj, REGS.stackHandleAt(-1)))
goto error;
}
END_CASE(JSOP_MOREITER)
CASE(JSOP_ISNOITER)
{
bool b = REGS.sp[-1].isMagic(JS_NO_ITER_VALUE);
PUSH_BOOLEAN(b);
}
END_CASE(JSOP_ISNOITER)
CASE(JSOP_ENDITER)
{
MOZ_ASSERT(REGS.stackDepth() >= 1);
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-1].toObject());
bool ok = CloseIterator(cx, obj);
REGS.sp--;
if (!ok)
goto error;
}
END_CASE(JSOP_ENDITER)
CASE(JSOP_DUP)
{
MOZ_ASSERT(REGS.stackDepth() >= 1);
const Value& rref = REGS.sp[-1];
PUSH_COPY(rref);
}
END_CASE(JSOP_DUP)
CASE(JSOP_DUP2)
{
MOZ_ASSERT(REGS.stackDepth() >= 2);
const Value& lref = REGS.sp[-2];
const Value& rref = REGS.sp[-1];
PUSH_COPY(lref);
PUSH_COPY(rref);
}
END_CASE(JSOP_DUP2)
CASE(JSOP_SWAP)
{
MOZ_ASSERT(REGS.stackDepth() >= 2);
Value& lref = REGS.sp[-2];
Value& rref = REGS.sp[-1];
lref.swap(rref);
}
END_CASE(JSOP_SWAP)
CASE(JSOP_PICK)
{
unsigned i = GET_UINT8(REGS.pc);
MOZ_ASSERT(REGS.stackDepth() >= i + 1);
Value lval = REGS.sp[-int(i + 1)];
memmove(REGS.sp - (i + 1), REGS.sp - i, sizeof(Value) * i);
REGS.sp[-1] = lval;
}
END_CASE(JSOP_PICK)
CASE(JSOP_BINDGNAME)
CASE(JSOP_BINDNAME)
{
JSOp op = JSOp(*REGS.pc);
ReservedRooted<JSObject*> scopeChain(&rootObject0);
if (op == JSOP_BINDNAME || script->hasNonSyntacticScope())
scopeChain.set(REGS.fp()->scopeChain());
else
scopeChain.set(&REGS.fp()->global().lexicalScope());
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
/* Assigning to an undeclared name adds a property to the global object. */
ReservedRooted<JSObject*> scope(&rootObject1);
if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
goto error;
PUSH_OBJECT(*scope);
static_assert(JSOP_BINDNAME_LENGTH == JSOP_BINDGNAME_LENGTH,
"We're sharing the END_CASE so the lengths better match");
}
END_CASE(JSOP_BINDNAME)
#define BITWISE_OP(OP) \
JS_BEGIN_MACRO \
int32_t i, j; \
if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \
goto error; \
if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \
goto error; \
i = i OP j; \
REGS.sp--; \
REGS.sp[-1].setInt32(i); \
JS_END_MACRO
CASE(JSOP_BITOR)
BITWISE_OP(|);
END_CASE(JSOP_BITOR)
CASE(JSOP_BITXOR)
BITWISE_OP(^);
END_CASE(JSOP_BITXOR)
CASE(JSOP_BITAND)
BITWISE_OP(&);
END_CASE(JSOP_BITAND)
#undef BITWISE_OP
CASE(JSOP_EQ)
if (!LooseEqualityOp<true>(cx, REGS))
goto error;
END_CASE(JSOP_EQ)
CASE(JSOP_NE)
if (!LooseEqualityOp<false>(cx, REGS))
goto error;
END_CASE(JSOP_NE)
#define STRICT_EQUALITY_OP(OP, COND) \
JS_BEGIN_MACRO \
HandleValue lval = REGS.stackHandleAt(-2); \
HandleValue rval = REGS.stackHandleAt(-1); \
bool equal; \
if (!StrictlyEqual(cx, lval, rval, &equal)) \
goto error; \
(COND) = equal OP true; \
REGS.sp--; \
JS_END_MACRO
CASE(JSOP_STRICTEQ)
{
bool cond;
STRICT_EQUALITY_OP(==, cond);
REGS.sp[-1].setBoolean(cond);
}
END_CASE(JSOP_STRICTEQ)
CASE(JSOP_STRICTNE)
{
bool cond;
STRICT_EQUALITY_OP(!=, cond);
REGS.sp[-1].setBoolean(cond);
}
END_CASE(JSOP_STRICTNE)
CASE(JSOP_CASE)
{
bool cond;
STRICT_EQUALITY_OP(==, cond);
if (cond) {
REGS.sp--;
BRANCH(GET_JUMP_OFFSET(REGS.pc));
}
}
END_CASE(JSOP_CASE)
#undef STRICT_EQUALITY_OP
CASE(JSOP_LT)
{
bool cond;
MutableHandleValue lval = REGS.stackHandleAt(-2);
MutableHandleValue rval = REGS.stackHandleAt(-1);
if (!LessThanOperation(cx, lval, rval, &cond))
goto error;
TRY_BRANCH_AFTER_COND(cond, 2);
REGS.sp[-2].setBoolean(cond);
REGS.sp--;
}
END_CASE(JSOP_LT)
CASE(JSOP_LE)
{
bool cond;
MutableHandleValue lval = REGS.stackHandleAt(-2);
MutableHandleValue rval = REGS.stackHandleAt(-1);
if (!LessThanOrEqualOperation(cx, lval, rval, &cond))
goto error;
TRY_BRANCH_AFTER_COND(cond, 2);
REGS.sp[-2].setBoolean(cond);
REGS.sp--;
}
END_CASE(JSOP_LE)
CASE(JSOP_GT)
{
bool cond;
MutableHandleValue lval = REGS.stackHandleAt(-2);
MutableHandleValue rval = REGS.stackHandleAt(-1);
if (!GreaterThanOperation(cx, lval, rval, &cond))
goto error;
TRY_BRANCH_AFTER_COND(cond, 2);
REGS.sp[-2].setBoolean(cond);
REGS.sp--;
}
END_CASE(JSOP_GT)
CASE(JSOP_GE)
{
bool cond;
MutableHandleValue lval = REGS.stackHandleAt(-2);
MutableHandleValue rval = REGS.stackHandleAt(-1);
if (!GreaterThanOrEqualOperation(cx, lval, rval, &cond))
goto error;
TRY_BRANCH_AFTER_COND(cond, 2);
REGS.sp[-2].setBoolean(cond);
REGS.sp--;
}
END_CASE(JSOP_GE)
#define SIGNED_SHIFT_OP(OP) \
JS_BEGIN_MACRO \
int32_t i, j; \
if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \
goto error; \
if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \
goto error; \
i = i OP (j & 31); \
REGS.sp--; \
REGS.sp[-1].setInt32(i); \
JS_END_MACRO
CASE(JSOP_LSH)
SIGNED_SHIFT_OP(<<);
END_CASE(JSOP_LSH)
CASE(JSOP_RSH)
SIGNED_SHIFT_OP(>>);
END_CASE(JSOP_RSH)
#undef SIGNED_SHIFT_OP
CASE(JSOP_URSH)
{
HandleValue lval = REGS.stackHandleAt(-2);
HandleValue rval = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!UrshOperation(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_URSH)
CASE(JSOP_ADD)
{
MutableHandleValue lval = REGS.stackHandleAt(-2);
MutableHandleValue rval = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!AddOperation(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_ADD)
CASE(JSOP_SUB)
{
ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]);
ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!SubOperation(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_SUB)
CASE(JSOP_MUL)
{
ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]);
ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!MulOperation(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_MUL)
CASE(JSOP_DIV)
{
ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]);
ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!DivOperation(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_DIV)
CASE(JSOP_MOD)
{
ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]);
ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!ModOperation(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_MOD)
CASE(JSOP_POW)
{
ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]);
ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!math_pow_handle(cx, lval, rval, res))
goto error;
REGS.sp--;
}
END_CASE(JSOP_POW)
CASE(JSOP_NOT)
{
bool cond = ToBoolean(REGS.stackHandleAt(-1));
REGS.sp--;
PUSH_BOOLEAN(!cond);
}
END_CASE(JSOP_NOT)
CASE(JSOP_BITNOT)
{
int32_t i;
HandleValue value = REGS.stackHandleAt(-1);
if (!BitNot(cx, value, &i))
goto error;
REGS.sp[-1].setInt32(i);
}
END_CASE(JSOP_BITNOT)
CASE(JSOP_NEG)
{
ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-1);
if (!NegOperation(cx, script, REGS.pc, val, res))
goto error;
}
END_CASE(JSOP_NEG)
CASE(JSOP_POS)
if (!ToNumber(cx, REGS.stackHandleAt(-1)))
goto error;
END_CASE(JSOP_POS)
CASE(JSOP_DELNAME)
{
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
ReservedRooted<JSObject*> scopeObj(&rootObject0, REGS.fp()->scopeChain());
PUSH_BOOLEAN(true);
MutableHandleValue res = REGS.stackHandleAt(-1);
if (!DeleteNameOperation(cx, name, scopeObj, res))
goto error;
}
END_CASE(JSOP_DELNAME)
CASE(JSOP_DELPROP)
CASE(JSOP_STRICTDELPROP)
{
static_assert(JSOP_DELPROP_LENGTH == JSOP_STRICTDELPROP_LENGTH,
"delprop and strictdelprop must be the same size");
ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
ReservedRooted<JSObject*> obj(&rootObject0);
FETCH_OBJECT(cx, -1, obj);
ObjectOpResult result;
if (!DeleteProperty(cx, obj, id, result))
goto error;
if (!result && JSOp(*REGS.pc) == JSOP_STRICTDELPROP) {
result.reportError(cx, obj, id);
goto error;
}
MutableHandleValue res = REGS.stackHandleAt(-1);
res.setBoolean(result.ok());
}
END_CASE(JSOP_DELPROP)
CASE(JSOP_DELELEM)
CASE(JSOP_STRICTDELELEM)
{
static_assert(JSOP_DELELEM_LENGTH == JSOP_STRICTDELELEM_LENGTH,
"delelem and strictdelelem must be the same size");
/* Fetch the left part and resolve it to a non-null object. */
ReservedRooted<JSObject*> obj(&rootObject0);
FETCH_OBJECT(cx, -2, obj);
ReservedRooted<Value> propval(&rootValue0, REGS.sp[-1]);
ObjectOpResult result;
ReservedRooted<jsid> id(&rootId0);
if (!ToPropertyKey(cx, propval, &id))
goto error;
if (!DeleteProperty(cx, obj, id, result))
goto error;
if (!result && JSOp(*REGS.pc) == JSOP_STRICTDELELEM) {
result.reportError(cx, obj, id);
goto error;
}
MutableHandleValue res = REGS.stackHandleAt(-2);
res.setBoolean(result.ok());
REGS.sp--;
}
END_CASE(JSOP_DELELEM)
CASE(JSOP_TOID)
{
/*
* Increment or decrement requires use to lookup the same property twice,
* but we need to avoid the observable stringification the second time.
* There must be an object value below the id, which will not be popped.
*/
ReservedRooted<Value> idval(&rootValue1, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-1);
if (!ToIdOperation(cx, script, REGS.pc, idval, res))
goto error;
}
END_CASE(JSOP_TOID)
CASE(JSOP_TYPEOFEXPR)
CASE(JSOP_TYPEOF)
{
REGS.sp[-1].setString(TypeOfOperation(REGS.sp[-1], cx->runtime()));
}
END_CASE(JSOP_TYPEOF)
CASE(JSOP_VOID)
REGS.sp[-1].setUndefined();
END_CASE(JSOP_VOID)
CASE(JSOP_FUNCTIONTHIS)
PUSH_NULL();
if (!GetFunctionThis(cx, REGS.fp(), REGS.stackHandleAt(-1)))
goto error;
END_CASE(JSOP_FUNCTIONTHIS)
CASE(JSOP_GLOBALTHIS)
{
if (script->hasNonSyntacticScope()) {
PUSH_NULL();
if (!GetNonSyntacticGlobalThis(cx, REGS.fp()->scopeChain(), REGS.stackHandleAt(-1)))
goto error;
} else {
ClonedBlockObject* lexicalScope = &cx->global()->lexicalScope();
PUSH_COPY(lexicalScope->thisValue());
}
}
END_CASE(JSOP_GLOBALTHIS)
CASE(JSOP_CHECKTHIS)
{
if (REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) {
MOZ_ALWAYS_FALSE(ThrowUninitializedThis(cx, REGS.fp()));
goto error;
}
}
END_CASE(JSOP_CHECKTHIS)
CASE(JSOP_CHECKTHISREINIT)
{
if (!REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_REINIT_THIS);
goto error;
}
}
END_CASE(JSOP_CHECKTHISREINIT)
CASE(JSOP_CHECKRETURN)
{
if (!REGS.fp()->checkReturn(cx, REGS.stackHandleAt(-1)))
goto error;
REGS.sp--;
}
END_CASE(JSOP_CHECKRETURN)
CASE(JSOP_GETPROP)
CASE(JSOP_LENGTH)
CASE(JSOP_CALLPROP)
{
MutableHandleValue lval = REGS.stackHandleAt(-1);
if (!GetPropertyOperation(cx, REGS.fp(), script, REGS.pc, lval, lval))
goto error;
TypeScript::Monitor(cx, script, REGS.pc, lval);
assertSameCompartmentDebugOnly(cx, lval);
}
END_CASE(JSOP_GETPROP)
CASE(JSOP_GETPROP_SUPER)
{
ReservedRooted<JSObject*> receiver(&rootObject0);
FETCH_OBJECT(cx, -2, receiver);
ReservedRooted<JSObject*> obj(&rootObject1, &REGS.sp[-1].toObject());
MutableHandleValue rref = REGS.stackHandleAt(-2);
if (!GetProperty(cx, obj, receiver, script->getName(REGS.pc), rref))
goto error;
REGS.sp--;
}
END_CASE(JSOP_GETPROP_SUPER)
CASE(JSOP_GETXPROP)
{
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-1].toObject());
ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
MutableHandleValue rval = REGS.stackHandleAt(-1);
if (!GetPropertyForNameLookup(cx, obj, id, rval))
goto error;
TypeScript::Monitor(cx, script, REGS.pc, rval);
assertSameCompartmentDebugOnly(cx, rval);
}
END_CASE(JSOP_GETXPROP)
CASE(JSOP_SETINTRINSIC)
{
HandleValue value = REGS.stackHandleAt(-1);
if (!SetIntrinsicOperation(cx, script, REGS.pc, value))
goto error;
}
END_CASE(JSOP_SETINTRINSIC)
CASE(JSOP_SETGNAME)
CASE(JSOP_STRICTSETGNAME)
CASE(JSOP_SETNAME)
CASE(JSOP_STRICTSETNAME)
{
static_assert(JSOP_SETNAME_LENGTH == JSOP_STRICTSETNAME_LENGTH,
"setname and strictsetname must be the same size");
static_assert(JSOP_SETGNAME_LENGTH == JSOP_STRICTSETGNAME_LENGTH,
"setganem adn strictsetgname must be the same size");
static_assert(JSOP_SETNAME_LENGTH == JSOP_SETGNAME_LENGTH,
"We're sharing the END_CASE so the lengths better match");
ReservedRooted<JSObject*> scope(&rootObject0, &REGS.sp[-2].toObject());
HandleValue value = REGS.stackHandleAt(-1);
if (!SetNameOperation(cx, script, REGS.pc, scope, value))
goto error;
REGS.sp[-2] = REGS.sp[-1];
REGS.sp--;
}
END_CASE(JSOP_SETNAME)
CASE(JSOP_SETPROP)
CASE(JSOP_STRICTSETPROP)
{
static_assert(JSOP_SETPROP_LENGTH == JSOP_STRICTSETPROP_LENGTH,
"setprop and strictsetprop must be the same size");
HandleValue lval = REGS.stackHandleAt(-2);
HandleValue rval = REGS.stackHandleAt(-1);
ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
if (!SetPropertyOperation(cx, JSOp(*REGS.pc), lval, id, rval))
goto error;
REGS.sp[-2] = REGS.sp[-1];
REGS.sp--;
}
END_CASE(JSOP_SETPROP)
CASE(JSOP_SETPROP_SUPER)
CASE(JSOP_STRICTSETPROP_SUPER)
{
static_assert(JSOP_SETPROP_SUPER_LENGTH == JSOP_STRICTSETPROP_SUPER_LENGTH,
"setprop-super and strictsetprop-super must be the same size");
ReservedRooted<Value> receiver(&rootValue0, REGS.sp[-3]);
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-2].toObject());
ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]);
ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
ObjectOpResult result;
if (!SetProperty(cx, obj, id, rval, receiver, result))
goto error;
bool strict = JSOp(*REGS.pc) == JSOP_STRICTSETPROP_SUPER;
if (!result.checkStrictErrorOrWarning(cx, obj, id, strict))
goto error;
REGS.sp[-3] = REGS.sp[-1];
REGS.sp -= 2;
}
END_CASE(JSOP_SETPROP_SUPER)
CASE(JSOP_GETELEM)
CASE(JSOP_CALLELEM)
{
MutableHandleValue lval = REGS.stackHandleAt(-2);
HandleValue rval = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
bool done = false;
if (!GetElemOptimizedArguments(cx, REGS.fp(), lval, rval, res, &done))
goto error;
if (!done) {
if (!GetElementOperation(cx, JSOp(*REGS.pc), lval, rval, res))
goto error;
}
TypeScript::Monitor(cx, script, REGS.pc, res);
REGS.sp--;
}
END_CASE(JSOP_GETELEM)
CASE(JSOP_GETELEM_SUPER)
{
HandleValue rval = REGS.stackHandleAt(-3);
ReservedRooted<JSObject*> receiver(&rootObject0);
FETCH_OBJECT(cx, -2, receiver);
ReservedRooted<JSObject*> obj(&rootObject1, &REGS.sp[-1].toObject());
MutableHandleValue res = REGS.stackHandleAt(-3);
// Since we have asserted that obj has to be an object, it cannot be
// either optimized arguments, or indeed any primitive. This simplifies
// our task some.
if (!GetObjectElementOperation(cx, JSOp(*REGS.pc), obj, receiver, rval, res))
goto error;
TypeScript::Monitor(cx, script, REGS.pc, res);
REGS.sp -= 2;
}
END_CASE(JSOP_GETELEM_SUPER)
CASE(JSOP_SETELEM)
CASE(JSOP_STRICTSETELEM)
{
static_assert(JSOP_SETELEM_LENGTH == JSOP_STRICTSETELEM_LENGTH,
"setelem and strictsetelem must be the same size");
ReservedRooted<JSObject*> obj(&rootObject0);
FETCH_OBJECT(cx, -3, obj);
ReservedRooted<jsid> id(&rootId0);
FETCH_ELEMENT_ID(-2, id);
Value& value = REGS.sp[-1];
ReservedRooted<Value> receiver(&rootValue0, ObjectValue(*obj));
if (!SetObjectElementOperation(cx, obj, receiver, id, value, *REGS.pc == JSOP_STRICTSETELEM))
goto error;
REGS.sp[-3] = value;
REGS.sp -= 2;
}
END_CASE(JSOP_SETELEM)
CASE(JSOP_SETELEM_SUPER)
CASE(JSOP_STRICTSETELEM_SUPER)
{
static_assert(JSOP_SETELEM_SUPER_LENGTH == JSOP_STRICTSETELEM_SUPER_LENGTH,
"setelem-super and strictsetelem-super must be the same size");
ReservedRooted<jsid> id(&rootId0);
FETCH_ELEMENT_ID(-4, id);
ReservedRooted<Value> receiver(&rootValue0, REGS.sp[-3]);
ReservedRooted<JSObject*> obj(&rootObject1, &REGS.sp[-2].toObject());
Value& value = REGS.sp[-1];
bool strict = JSOp(*REGS.pc) == JSOP_STRICTSETELEM_SUPER;
if (!SetObjectElementOperation(cx, obj, receiver, id, value, strict))
goto error;
REGS.sp[-4] = value;
REGS.sp -= 3;
}
END_CASE(JSOP_SETELEM_SUPER)
CASE(JSOP_EVAL)
CASE(JSOP_STRICTEVAL)
{
static_assert(JSOP_EVAL_LENGTH == JSOP_STRICTEVAL_LENGTH,
"eval and stricteval must be the same size");
CallArgs args = CallArgsFromSp(GET_ARGC(REGS.pc), REGS.sp);
if (REGS.fp()->scopeChain()->global().valueIsEval(args.calleev())) {
if (!DirectEval(cx, args))
goto error;
} else {
if (!Invoke(cx, args))
goto error;
}
REGS.sp = args.spAfterCall();
TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]);
}
END_CASE(JSOP_EVAL)
CASE(JSOP_SPREADNEW)
CASE(JSOP_SPREADCALL)
CASE(JSOP_SPREADSUPERCALL)
if (REGS.fp()->hasPushedSPSFrame())
cx->runtime()->spsProfiler.updatePC(script, REGS.pc);
/* FALL THROUGH */
CASE(JSOP_SPREADEVAL)
CASE(JSOP_STRICTSPREADEVAL)
{
static_assert(JSOP_SPREADEVAL_LENGTH == JSOP_STRICTSPREADEVAL_LENGTH,
"spreadeval and strictspreadeval must be the same size");
bool construct = JSOp(*REGS.pc) == JSOP_SPREADNEW || JSOp(*REGS.pc) == JSOP_SPREADSUPERCALL;;
MOZ_ASSERT(REGS.stackDepth() >= 3u + construct);
HandleValue callee = REGS.stackHandleAt(-3 - construct);
HandleValue thisv = REGS.stackHandleAt(-2 - construct);
HandleValue arr = REGS.stackHandleAt(-1 - construct);
MutableHandleValue ret = REGS.stackHandleAt(-3 - construct);
RootedValue& newTarget = rootValue0;
if (construct)
newTarget = REGS.sp[-1];
else
newTarget = NullValue();
if (!SpreadCallOperation(cx, script, REGS.pc, thisv, callee, arr, newTarget, ret))
goto error;
REGS.sp -= 2 + construct;
}
END_CASE(JSOP_SPREADCALL)
CASE(JSOP_FUNAPPLY)
{
CallArgs args = CallArgsFromSp(GET_ARGC(REGS.pc), REGS.sp);
if (!GuardFunApplyArgumentsOptimization(cx, REGS.fp(), args))
goto error;
/* FALL THROUGH */
}
CASE(JSOP_NEW)
CASE(JSOP_CALL)
CASE(JSOP_CALLITER)
CASE(JSOP_SUPERCALL)
CASE(JSOP_FUNCALL)
{
if (REGS.fp()->hasPushedSPSFrame())
cx->runtime()->spsProfiler.updatePC(script, REGS.pc);
bool construct = (*REGS.pc == JSOP_NEW || *REGS.pc == JSOP_SUPERCALL);
unsigned argStackSlots = GET_ARGC(REGS.pc) + construct;
MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc));
CallArgs args = CallArgsFromSp(argStackSlots, REGS.sp, construct);
JSFunction* maybeFun;
bool isFunction = IsFunctionObject(args.calleev(), &maybeFun);
/* Don't bother trying to fast-path calls to scripted non-constructors. */
if (!isFunction || !maybeFun->isInterpreted() || !maybeFun->isConstructor() ||
(!construct && maybeFun->isClassConstructor()))
{
if (construct) {
if (!ConstructFromStack(cx, args))
goto error;
} else {
if (*REGS.pc == JSOP_CALLITER && args.calleev().isPrimitive()) {
MOZ_ASSERT(args.length() == 0, "thisv must be on top of the stack");
ReportValueError(cx, JSMSG_NOT_ITERABLE, -1, args.thisv(), nullptr);
goto error;
}
if (!Invoke(cx, args))
goto error;
}
Value* newsp = args.spAfterCall();
TypeScript::Monitor(cx, script, REGS.pc, newsp[-1]);
REGS.sp = newsp;
ADVANCE_AND_DISPATCH(JSOP_CALL_LENGTH);
}
{
MOZ_ASSERT(maybeFun);
ReservedRooted<JSFunction*> fun(&rootFunction0, maybeFun);
ReservedRooted<JSScript*> funScript(&rootScript0, fun->getOrCreateScript(cx));
if (!funScript)
goto error;
InitialFrameFlags initial = construct ? INITIAL_CONSTRUCT : INITIAL_NONE;
bool createSingleton = ObjectGroup::useSingletonForNewObject(cx, script, REGS.pc);
TypeMonitorCall(cx, args, construct);
mozilla::Maybe<InvokeState> state;
state.emplace(cx, args, initial);
if (createSingleton)
state->setCreateSingleton();
if (!createSingleton && jit::IsIonEnabled(cx)) {
jit::MethodStatus status = jit::CanEnter(cx, state.ref());
if (status == jit::Method_Error)
goto error;
if (status == jit::Method_Compiled) {
jit::JitExecStatus exec = jit::IonCannon(cx, state.ref());
CHECK_BRANCH();
REGS.sp = args.spAfterCall();
interpReturnOK = !IsErrorStatus(exec);
goto jit_return;
}
}
if (jit::IsBaselineEnabled(cx)) {
jit::MethodStatus status = jit::CanEnterBaselineMethod(cx, state.ref());
if (status == jit::Method_Error)
goto error;
if (status == jit::Method_Compiled) {
jit::JitExecStatus exec = jit::EnterBaselineMethod(cx, state.ref());
CHECK_BRANCH();
REGS.sp = args.spAfterCall();
interpReturnOK = !IsErrorStatus(exec);
goto jit_return;
}
}
state.reset();
funScript = fun->nonLazyScript();
if (!activation.pushInlineFrame(args, funScript, initial))
goto error;
if (createSingleton)
REGS.fp()->setCreateSingleton();
}
SET_SCRIPT(REGS.fp()->script());
{
TraceLoggerEvent event(logger, TraceLogger_Scripts, script);
TraceLogStartEvent(logger, event);
TraceLogStartEvent(logger, TraceLogger_Interpreter);
}
if (!REGS.fp()->prologue(cx))
goto error;
switch (Debugger::onEnterFrame(cx, REGS.fp())) {
case JSTRAP_CONTINUE:
break;
case JSTRAP_RETURN:
if (!ForcedReturn(cx, REGS))
goto error;
goto successful_return_continuation;
case JSTRAP_THROW:
case JSTRAP_ERROR:
goto error;
default:
MOZ_CRASH("bad Debugger::onEnterFrame status");
}
/* Load first op and dispatch it (safe since JSOP_RETRVAL). */
ADVANCE_AND_DISPATCH(0);
}
CASE(JSOP_THROWMSG)
{
JS_ALWAYS_FALSE(ThrowMsgOperation(cx, GET_UINT16(REGS.pc)));
goto error;
}
END_CASE(JSOP_THROWMSG)
CASE(JSOP_IMPLICITTHIS)
CASE(JSOP_GIMPLICITTHIS)
{
JSOp op = JSOp(*REGS.pc);
if (op == JSOP_IMPLICITTHIS || script->hasNonSyntacticScope()) {
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
ReservedRooted<JSObject*> scopeObj(&rootObject0, REGS.fp()->scopeChain());
ReservedRooted<JSObject*> scope(&rootObject1);
if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &scope))
goto error;
Value v = ComputeImplicitThis(scope);
PUSH_COPY(v);
} else {
// Treat it like JSOP_UNDEFINED.
PUSH_UNDEFINED();
}
static_assert(JSOP_IMPLICITTHIS_LENGTH == JSOP_GIMPLICITTHIS_LENGTH,
"We're sharing the END_CASE so the lengths better match");
}
END_CASE(JSOP_IMPLICITTHIS)
CASE(JSOP_GETGNAME)
CASE(JSOP_GETNAME)
{
ReservedRooted<Value> rval(&rootValue0);
if (!GetNameOperation(cx, REGS.fp(), REGS.pc, &rval))
goto error;
PUSH_COPY(rval);
TypeScript::Monitor(cx, script, REGS.pc, rval);
static_assert(JSOP_GETNAME_LENGTH == JSOP_GETGNAME_LENGTH,
"We're sharing the END_CASE so the lengths better match");
}
END_CASE(JSOP_GETNAME)
CASE(JSOP_GETIMPORT)
{
PUSH_NULL();
MutableHandleValue rval = REGS.stackHandleAt(-1);
if (!GetImportOperation(cx, REGS.fp(), REGS.pc, rval))
goto error;
TypeScript::Monitor(cx, script, REGS.pc, rval);
}
END_CASE(JSOP_GETIMPORT)
CASE(JSOP_GETINTRINSIC)
{
ReservedRooted<Value> rval(&rootValue0);
if (!GetIntrinsicOperation(cx, REGS.pc, &rval))
goto error;
PUSH_COPY(rval);
TypeScript::Monitor(cx, script, REGS.pc, rval);
}
END_CASE(JSOP_GE