blob: 234c7b57ae89c75a5bb445bd88d768597fc49898 [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 "Interpreter.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/PodOperations.h"
#include <string.h>
#include "jsapi.h"
#include "jsarray.h"
#include "jsatom.h"
#include "jscntxt.h"
#include "jsdbgapi.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jsiter.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/BaselineJIT.h"
#include "jit/Ion.h"
#include "vm/Debugger.h"
#include "vm/Shape.h"
#include "jsatominlines.h"
#include "jsboolinlines.h"
#include "jsinferinlines.h"
#include "jsopcodeinlines.h"
#include "jsscriptinlines.h"
#include "builtin/Iterator-inl.h"
#include "jit/IonFrames-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/Probes-inl.h"
#include "vm/Stack-inl.h"
#include "jsautooplen.h"
#if JS_TRACE_LOGGING
#include "TraceLogging.h"
#endif
using namespace js;
using namespace js::gc;
using namespace js::types;
using mozilla::DebugOnly;
using mozilla::PodCopy;
/* Some objects (e.g., With) delegate 'this' to another object. */
static inline JSObject *
CallThisObjectHook(JSContext *cx, HandleObject obj, Value *argv)
{
JSObject *thisp = JSObject::thisObject(cx, obj);
if (!thisp)
return NULL;
argv[-1].setObject(*thisp);
return thisp;
}
/*
* Note: when Clang 3.2 (32-bit) inlines the two functions below in Interpret,
* the conservative stack scanner leaks a ton of memory and this negatively
* influences performance. The JS_NEVER_INLINE is a temporary workaround until
* we can remove the conservative scanner. See bug 849526 for more info.
*/
#if defined(__clang__) && defined(JS_CPU_X86)
static JS_NEVER_INLINE bool
#else
static bool
#endif
ToBooleanOp(const FrameRegs &regs)
{
return ToBoolean(regs.sp[-1]);
}
template <bool Eq>
#if defined(__clang__) && defined(JS_CPU_X86)
static JS_NEVER_INLINE bool
#else
static bool
#endif
LooseEqualityOp(JSContext *cx, FrameRegs &regs)
{
Value rval = regs.sp[-1];
Value lval = regs.sp[-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, MutableHandleValue thisv, bool *modified)
{
/*
* Check for SynthesizeFrame poisoning and fast constructors which
* didn't check their callee properly.
*/
JS_ASSERT(!thisv.isMagic());
*modified = false;
if (thisv.isNullOrUndefined()) {
Rooted<GlobalObject*> global(cx, cx->global());
JSObject *thisp = JSObject::thisObject(cx, global);
if (!thisp)
return false;
thisv.set(ObjectValue(*thisp));
*modified = true;
return true;
}
if (!thisv.isObject()) {
if (!js_PrimitiveToObject(cx, thisv.address()))
return false;
*modified = true;
}
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)
{
/*
* Check for SynthesizeFrame poisoning and fast constructors which
* didn't check their callee properly.
*/
RootedValue thisv(cx, call.thisv());
JS_ASSERT(!thisv.isMagic());
#ifdef DEBUG
JSFunction *fun = call.callee().is<JSFunction>() ? &call.callee().as<JSFunction>() : NULL;
JS_ASSERT_IF(fun && fun->isInterpreted(), !fun->strict());
#endif
bool modified;
if (!BoxNonStrictThis(cx, &thisv, &modified))
return false;
if (modified)
call.setThis(thisv);
return true;
}
#if JS_HAS_NO_SUCH_METHOD
const uint32_t JSSLOT_FOUND_FUNCTION = 0;
const uint32_t JSSLOT_SAVED_ID = 1;
Class js_NoSuchMethodClass = {
"NoSuchMethod",
JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_IS_ANONYMOUS,
JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
};
/*
* When JSOP_CALLPROP or JSOP_CALLELEM does not find the method property of
* the base object, we search for the __noSuchMethod__ method in the base.
* If it exists, we store the method and the property's id into an object of
* NoSuchMethod class and store this object into the callee's stack slot.
* Later, Invoke will recognise such an object and transfer control to
* NoSuchMethod that invokes the method like:
*
* this.__noSuchMethod__(id, args)
*
* where id is the name of the method that this invocation attempted to
* call by name, and args is an Array containing this invocation's actual
* parameters.
*/
bool
js::OnUnknownMethod(JSContext *cx, HandleObject obj, Value idval_, MutableHandleValue vp)
{
RootedValue idval(cx, idval_);
RootedValue value(cx);
if (!JSObject::getProperty(cx, obj, obj, cx->names().noSuchMethod, &value))
return false;
TypeScript::MonitorUnknown(cx);
if (value.get().isPrimitive()) {
vp.set(value);
} else {
JSObject *obj = NewObjectWithClassProto(cx, &js_NoSuchMethodClass, NULL, NULL);
if (!obj)
return false;
obj->setSlot(JSSLOT_FOUND_FUNCTION, value);
obj->setSlot(JSSLOT_SAVED_ID, idval);
vp.setObject(*obj);
}
return true;
}
static JSBool
NoSuchMethod(JSContext *cx, unsigned argc, Value *vp)
{
InvokeArgs args(cx);
if (!args.init(2))
return JS_FALSE;
JS_ASSERT(vp[0].isObject());
JS_ASSERT(vp[1].isObject());
JSObject *obj = &vp[0].toObject();
JS_ASSERT(obj->getClass() == &js_NoSuchMethodClass);
args.setCallee(obj->getSlot(JSSLOT_FOUND_FUNCTION));
args.setThis(vp[1]);
args[0] = obj->getSlot(JSSLOT_SAVED_ID);
JSObject *argsobj = NewDenseCopiedArray(cx, argc, vp + 2);
if (!argsobj)
return JS_FALSE;
args[1].setObject(*argsobj);
JSBool ok = Invoke(cx, args);
vp[0] = args.rval();
return ok;
}
#endif /* JS_HAS_NO_SUCH_METHOD */
inline bool
GetPropertyOperation(JSContext *cx, StackFrame *fp, HandleScript script, jsbytecode *pc,
MutableHandleValue lval, MutableHandleValue vp)
{
JSOp op = JSOp(*pc);
if (op == JSOP_LENGTH) {
if (IsOptimizedArguments(fp, lval.address())) {
vp.setInt32(fp->numActualArgs());
return true;
}
if (GetLengthProperty(lval, vp))
return true;
}
JSObject *obj = ToObjectFromStack(cx, lval);
if (!obj)
return false;
bool wasObject = lval.isObject();
RootedId id(cx, NameToId(script->getName(pc)));
RootedObject nobj(cx, obj);
if (obj->getOps()->getProperty) {
if (!JSObject::getGeneric(cx, nobj, nobj, id, vp))
return false;
} else {
if (!GetPropertyHelper(cx, nobj, id, 0, vp))
return false;
}
#if JS_HAS_NO_SUCH_METHOD
if (op == JSOP_CALLPROP &&
JS_UNLIKELY(vp.isPrimitive()) &&
wasObject)
{
if (!OnUnknownMethod(cx, nobj, IdToValue(id), vp))
return false;
}
#endif
return true;
}
static inline bool
NameOperation(JSContext *cx, StackFrame *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)))
obj = &obj->global();
Shape *shape = NULL;
JSObject *scope = NULL, *pobj = NULL;
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_NAME_LENGTH]);
if (op2 == JSOP_TYPEOF)
return FetchName<true>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
return FetchName<false>(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp);
}
inline bool
SetPropertyOperation(JSContext *cx, HandleScript script, jsbytecode *pc, HandleValue lval,
HandleValue rval)
{
JS_ASSERT(*pc == JSOP_SETPROP);
RootedObject obj(cx, ToObjectFromStack(cx, lval));
if (!obj)
return false;
RootedValue rref(cx, rval);
RootedId id(cx, NameToId(script->getName(pc)));
if (JS_LIKELY(!obj->getOps()->setProperty)) {
if (!baseops::SetPropertyHelper(cx, obj, obj, id, 0, &rref, script->strict))
return false;
} else {
if (!JSObject::setGeneric(cx, obj, obj, id, &rref, script->strict))
return false;
}
return true;
}
bool
js::ReportIsNotFunction(JSContext *cx, const Value &v, int numToSkip, MaybeConstruct construct)
{
unsigned error = construct ? JSMSG_NOT_CONSTRUCTOR : JSMSG_NOT_FUNCTION;
int spIndex = numToSkip >= 0 ? -(numToSkip + 1) : JSDVG_SEARCH_STACK;
RootedValue val(cx, v);
js_ReportValueError3(cx, error, spIndex, val, NullPtr(), NULL, NULL);
return false;
}
JSObject *
js::ValueToCallable(JSContext *cx, const Value &v, int numToSkip, MaybeConstruct construct)
{
if (v.isObject()) {
JSObject *callable = &v.toObject();
if (callable->isCallable())
return callable;
}
ReportIsNotFunction(cx, v, numToSkip, construct);
return NULL;
}
static JS_NEVER_INLINE bool
Interpret(JSContext *cx, RunState &state);
StackFrame *
InvokeState::pushInterpreterFrame(JSContext *cx, FrameGuard *fg)
{
return cx->runtime()->interpreterStack().pushInvokeFrame(cx, args_, initial_, fg);
}
StackFrame *
ExecuteState::pushInterpreterFrame(JSContext *cx, FrameGuard *fg)
{
return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, thisv_, scopeChain_,
type_, evalInFrame_, fg);
}
bool
js::RunScript(JSContext *cx, RunState &state)
{
JS_CHECK_RECURSION(cx, return false);
SPSEntryMarker marker(cx->runtime());
#ifdef JS_ION
if (jit::IsIonEnabled(cx)) {
jit::MethodStatus status = jit::CanEnter(cx, state);
if (status == jit::Method_Error)
return false;
if (status == jit::Method_Compiled) {
jit::IonExecStatus status = jit::Cannon(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::IonExecStatus status = jit::EnterBaselineMethod(cx, state);
return !IsErrorStatus(status);
}
}
#endif
if (state.isInvoke()) {
InvokeState &invoke = *state.asInvoke();
TypeMonitorCall(cx, invoke.args(), invoke.constructing());
}
return Interpret(cx, state);
}
/*
* 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, CallArgs args, MaybeConstruct construct)
{
JS_ASSERT(args.length() <= ARGS_LENGTH_MAX);
JS_ASSERT(!cx->compartment()->activeAnalysis);
/* We should never enter a new script while cx->iterValue is live. */
JS_ASSERT(cx->iterValue.isMagic(JS_NO_ITER_VALUE));
/* MaybeConstruct is a subset of InitialFrameFlags */
InitialFrameFlags initial = (InitialFrameFlags) construct;
if (args.calleev().isPrimitive())
return ReportIsNotFunction(cx, args.calleev().get(), args.length() + 1, construct);
JSObject &callee = args.callee();
Class *clasp = callee.getClass();
/* Invoke non-functions. */
if (JS_UNLIKELY(clasp != &JSFunction::class_)) {
#if JS_HAS_NO_SUCH_METHOD
if (JS_UNLIKELY(clasp == &js_NoSuchMethodClass))
return NoSuchMethod(cx, args.length(), args.base());
#endif
JS_ASSERT_IF(construct, !clasp->construct);
if (!clasp->call)
return ReportIsNotFunction(cx, args.calleev().get(), args.length() + 1, construct);
return CallJSNative(cx, clasp->call, args);
}
/* Invoke native functions. */
JSFunction *fun = &callee.as<JSFunction>();
JS_ASSERT_IF(construct, !fun->isNativeConstructor());
if (fun->isNative())
return CallJSNative(cx, fun->native(), args);
if (!fun->getOrCreateScript(cx))
return false;
/* Run function until JSOP_STOP, JSOP_RETURN or error. */
InvokeState state(cx, args, initial);
// Check to see if useNewType flag should be set for this frame.
if (construct && cx->typeInferenceEnabled()) {
ScriptFrameIter iter(cx);
if (!iter.done()) {
JSScript *script = iter.script();
jsbytecode *pc = iter.pc();
if (UseNewType(cx, script, pc))
state.setUseNewType();
}
}
JSBool ok = RunScript(cx, state);
JS_ASSERT_IF(ok && construct, !args.rval().isPrimitive());
return ok;
}
bool
js::Invoke(JSContext *cx, const Value &thisv, const Value &fval, unsigned argc, Value *argv,
Value *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 thisObject 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())
{
RootedObject thisObj(cx, &args.thisv().toObject());
JSObject *thisp = JSObject::thisObject(cx, thisObj);
if (!thisp)
return false;
args.setThis(ObjectValue(*thisp));
}
}
if (!Invoke(cx, args))
return false;
*rval = args.rval();
return true;
}
bool
js::InvokeConstructor(JSContext *cx, CallArgs args)
{
JS_ASSERT(!JSFunction::class_.construct);
args.setThis(MagicValue(JS_IS_CONSTRUCTING));
if (!args.calleev().isObject())
return ReportIsNotFunction(cx, args.calleev().get(), args.length() + 1, CONSTRUCT);
JSObject &callee = args.callee();
if (callee.is<JSFunction>()) {
RootedFunction fun(cx, &callee.as<JSFunction>());
if (fun->isNativeConstructor()) {
bool ok = CallJSNativeConstructor(cx, fun->native(), args);
return ok;
}
if (!fun->isInterpretedConstructor())
return ReportIsNotFunction(cx, args.calleev().get(), args.length() + 1, CONSTRUCT);
if (!Invoke(cx, args, CONSTRUCT))
return false;
JS_ASSERT(args.rval().isObject());
return true;
}
Class *clasp = callee.getClass();
if (!clasp->construct)
return ReportIsNotFunction(cx, args.calleev().get(), args.length() + 1, CONSTRUCT);
return CallJSNativeConstructor(cx, clasp->construct, args);
}
bool
js::InvokeConstructor(JSContext *cx, const Value &fval, unsigned argc, Value *argv, Value *rval)
{
InvokeArgs args(cx);
if (!args.init(argc))
return false;
args.setCallee(fval);
args.setThis(MagicValue(JS_THIS_POISON));
PodCopy(args.array(), argv, argc);
if (!InvokeConstructor(cx, args))
return false;
*rval = args.rval();
return true;
}
bool
js::InvokeGetterOrSetter(JSContext *cx, JSObject *obj, const Value &fval, unsigned argc, Value *argv,
Value *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, ObjectValue(*obj), fval, argc, argv, rval);
}
bool
js::ExecuteKernel(JSContext *cx, HandleScript script, JSObject &scopeChainArg, const Value &thisv,
ExecuteType type, AbstractFramePtr evalInFrame, Value *result)
{
JS_ASSERT_IF(evalInFrame, type == EXECUTE_DEBUG);
JS_ASSERT_IF(type == EXECUTE_GLOBAL, !scopeChainArg.is<ScopeObject>());
if (script->isEmpty()) {
if (result)
result->setUndefined();
return true;
}
TypeScript::SetThis(cx, script, thisv);
Probes::startExecution(script);
ExecuteState state(cx, script, thisv, 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 could be anything, so innerize just in case. */
RootedObject scopeChain(cx, &scopeChainArg);
scopeChain = GetInnerObject(cx, scopeChain);
if (!scopeChain)
return false;
/* Ensure the scope chain is all same-compartment and terminates in a global. */
#ifdef DEBUG
JSObject *s = scopeChain;
do {
assertSameCompartment(cx, s);
JS_ASSERT_IF(!s->enclosingScope(), s->is<GlobalObject>());
} while ((s = s->enclosingScope()));
#endif
/* The VAROBJFIX option makes varObj == globalObj in global code. */
if (!cx->hasOption(JSOPTION_VAROBJFIX)) {
if (!scopeChain->setVarObj(cx))
return false;
}
/* Use the scope chain as 'this', modulo outerization. */
JSObject *thisObj = JSObject::thisObject(cx, scopeChain);
if (!thisObj)
return false;
Value thisv = ObjectValue(*thisObj);
return ExecuteKernel(cx, script, *scopeChain, thisv, EXECUTE_GLOBAL,
NullFramePtr() /* evalInFrame */, rval);
}
bool
js::HasInstance(JSContext *cx, HandleObject obj, HandleValue v, JSBool *bp)
{
Class *clasp = obj->getClass();
RootedValue local(cx, v);
if (clasp->hasInstance)
return clasp->hasInstance(cx, obj, &local, bp);
RootedValue val(cx, ObjectValue(*obj));
js_ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS,
JSDVG_SEARCH_STACK, val, NullPtr());
return JS_FALSE;
}
bool
js::LooselyEqual(JSContext *cx, const Value &lval, const Value &rval, bool *result)
{
if (SameType(lval, rval)) {
if (lval.isString()) {
JSString *l = lval.toString();
JSString *r = rval.toString();
return EqualStrings(cx, l, r, result);
}
if (lval.isDouble()) {
double l = lval.toDouble(), r = rval.toDouble();
*result = (l == r);
return true;
}
if (lval.isObject()) {
JSObject *l = &lval.toObject();
JSObject *r = &rval.toObject();
*result = l == r;
return true;
}
*result = lval.payloadAsRawUint32() == rval.payloadAsRawUint32();
return true;
}
if (lval.isNullOrUndefined()) {
*result = rval.isNullOrUndefined() ||
(rval.isObject() && EmulatesUndefined(&rval.toObject()));
return true;
}
if (rval.isNullOrUndefined()) {
*result = (lval.isObject() && EmulatesUndefined(&lval.toObject()));
return true;
}
RootedValue lvalue(cx, lval);
RootedValue rvalue(cx, rval);
if (!ToPrimitive(cx, &lvalue))
return false;
if (!ToPrimitive(cx, &rvalue))
return false;
if (lvalue.get().isString() && rvalue.get().isString()) {
JSString *l = lvalue.get().toString();
JSString *r = rvalue.get().toString();
return EqualStrings(cx, l, r, result);
}
double l, r;
if (!ToNumber(cx, lvalue, &l) || !ToNumber(cx, rvalue, &r))
return false;
*result = (l == r);
return true;
}
bool
js::StrictlyEqual(JSContext *cx, const Value &lref, const Value &rref, bool *equal)
{
Value lval = lref, rval = rref;
if (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.isObject()) {
*equal = lval.toObject() == rval.toObject();
return true;
}
if (lval.isUndefined()) {
*equal = true;
return true;
}
*equal = lval.payloadAsRawUint32() == rval.payloadAsRawUint32();
return true;
}
if (lval.isDouble() && rval.isInt32()) {
double ld = lval.toDouble();
double rd = rval.toInt32();
*equal = (ld == rd);
return true;
}
if (lval.isInt32() && rval.isDouble()) {
double ld = lval.toInt32();
double rd = rval.toDouble();
*equal = (ld == rd);
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, const Value &v1, const Value &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::TypeOfValue(JSContext *cx, const Value &vref)
{
Value v = vref;
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()) {
RootedObject obj(cx, &v.toObject());
return baseops::TypeOf(cx, obj);
}
JS_ASSERT(v.isBoolean());
return JSTYPE_BOOLEAN;
}
/*
* Enter the new with scope using an object at sp[-1] and associate the depth
* of the with block with sp + stackIndex.
*/
static bool
EnterWith(JSContext *cx, AbstractFramePtr frame, HandleValue val, uint32_t stackDepth)
{
RootedObject obj(cx);
if (val.isObject()) {
obj = &val.toObject();
} else {
obj = js_ValueToNonNullObject(cx, val);
if (!obj)
return false;
}
RootedObject scopeChain(cx, frame.scopeChain());
WithObject *withobj = WithObject::create(cx, obj, scopeChain, stackDepth);
if (!withobj)
return false;
frame.pushOnScopeChain(*withobj);
return true;
}
/* Unwind block and scope chains to match the given depth. */
void
js::UnwindScope(JSContext *cx, AbstractFramePtr frame, uint32_t stackDepth)
{
JS_ASSERT_IF(frame.isStackFrame(), frame.asStackFrame() == cx->interpreterFrame());
JS_ASSERT_IF(frame.isStackFrame(), stackDepth <= cx->interpreterRegs().stackDepth());
for (ScopeIter si(frame, cx); !si.done(); ++si) {
switch (si.type()) {
case ScopeIter::Block:
if (si.staticBlock().stackDepth() < stackDepth)
return;
frame.popBlock(cx);
break;
case ScopeIter::With:
if (si.scope().as<WithObject>().stackDepth() < stackDepth)
return;
frame.popWith(cx);
break;
case ScopeIter::Call:
case ScopeIter::StrictEvalScope:
break;
}
}
}
void
js::UnwindForUncatchableException(JSContext *cx, const FrameRegs &regs)
{
/* c.f. the regular (catchable) TryNoteIter loop in Interpret. */
for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) {
JSTryNote *tn = *tni;
if (tn->kind == JSTRY_ITER) {
Value *sp = regs.spForStackDepth(tn->stackDepth);
UnwindIteratorForUncatchableException(cx, &sp[-1].toObject());
}
}
}
TryNoteIter::TryNoteIter(JSContext *cx, const FrameRegs &regs)
: regs(regs),
script(cx, regs.fp()->script()),
pcOffset(regs.pc - script->main())
{
if (script->hasTrynotes()) {
tn = script->trynotes()->vector;
tnEnd = tn + script->trynotes()->length;
} else {
tn = tnEnd = NULL;
}
settle();
}
void
TryNoteIter::operator++()
{
++tn;
settle();
}
bool
TryNoteIter::done() const
{
return tn == tnEnd;
}
void
TryNoteIter::settle()
{
for (; tn != tnEnd; ++tn) {
/* If pc is out of range, try the next one. */
if (pcOffset - tn->start >= tn->length)
continue;
/*
* We have a note that covers the exception pc but we must check
* whether the interpreter has already executed the corresponding
* handler. This is possible when the executed bytecode implements
* break or return from inside a for-in loop.
*
* In this case the emitter generates additional [enditer] and [gosub]
* opcodes to close all outstanding iterators and execute the finally
* blocks. If such an [enditer] throws an exception, its pc can still
* be inside several nested for-in loops and try-finally statements
* even if we have already closed the corresponding iterators and
* invoked the finally blocks.
*
* To address this, we make [enditer] always decrease the stack even
* when its implementation throws an exception. Thus already executed
* [enditer] and [gosub] opcodes will have try notes with the stack
* depth exceeding the current one and this condition is what we use to
* filter them out.
*/
if (tn->stackDepth <= regs.stackDepth())
break;
}
}
#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_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 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 = HandleValue::fromMarkedLocation(&regs.sp[n]); \
obj = ToObjectFromStack(cx, (val)); \
if (!obj) \
goto error; \
JS_END_MACRO
template<typename T>
class GenericInterruptEnabler : public InterpreterFrames::InterruptEnablerBase {
public:
GenericInterruptEnabler(T *variable, T value) : variable(variable), value(value) { }
void enable() const { *variable = value; }
private:
T *variable;
T value;
};
inline InterpreterFrames::InterpreterFrames(JSContext *cx, FrameRegs *regs,
const InterruptEnablerBase &enabler)
: context(cx), regs(regs), enabler(enabler)
{
older = cx->runtime()->interpreterFrames;
cx->runtime()->interpreterFrames = this;
}
inline InterpreterFrames::~InterpreterFrames()
{
context->runtime()->interpreterFrames = older;
}
/*
* Ensure that the interpreter switch can close call-bytecode cases in the
* same way as non-call bytecodes.
*/
JS_STATIC_ASSERT(JSOP_NAME_LENGTH == JSOP_CALLNAME_LENGTH);
JS_STATIC_ASSERT(JSOP_GETARG_LENGTH == JSOP_CALLARG_LENGTH);
JS_STATIC_ASSERT(JSOP_GETLOCAL_LENGTH == JSOP_CALLLOCAL_LENGTH);
/*
* 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);
/*
* Inline fast paths for iteration. js_IteratorMore and js_IteratorNext handle
* all cases, but we inline the most frequently taken paths here.
*/
bool
js::IteratorMore(JSContext *cx, JSObject *iterobj, bool *cond, MutableHandleValue rval)
{
if (iterobj->is<PropertyIteratorObject>()) {
NativeIterator *ni = iterobj->as<PropertyIteratorObject>().getNativeIterator();
if (ni->isKeyIter()) {
*cond = (ni->props_cursor < ni->props_end);
return true;
}
}
Rooted<JSObject*> iobj(cx, iterobj);
if (!js_IteratorMore(cx, iobj, rval))
return false;
*cond = rval.isTrue();
return true;
}
bool
js::IteratorNext(JSContext *cx, HandleObject iterobj, MutableHandleValue rval)
{
if (iterobj->is<PropertyIteratorObject>()) {
NativeIterator *ni = iterobj->as<PropertyIteratorObject>().getNativeIterator();
if (ni->isKeyIter()) {
JS_ASSERT(ni->props_cursor < ni->props_end);
rval.setString(*ni->current());
ni->incCursor();
return true;
}
}
return js_IteratorNext(cx, iterobj, rval);
}
FrameGuard::FrameGuard(RunState &state, FrameRegs &regs)
: state_(state),
regs_(regs),
stack_(NULL),
fp_(NULL)
{ }
FrameGuard::~FrameGuard()
{
if (state_.isGenerator()) {
JSGenerator *gen = state_.asGenerator()->gen();
gen->fp->unsetPushedSPSFrame();
gen->regs = regs_;
return;
}
if (fp_)
stack_->releaseFrame(fp_);
}
static JS_NEVER_INLINE bool
Interpret(JSContext *cx, RunState &state)
{
JSAutoResolveFlags rf(cx, RESOLVE_INFER);
gc::MaybeVerifyBarriers(cx, true);
JS_ASSERT(!cx->compartment()->activeAnalysis);
#define CHECK_PCCOUNT_INTERRUPTS() JS_ASSERT_IF(script->hasScriptCounts, switchMask == -1)
register int switchMask = 0;
int switchOp;
typedef GenericInterruptEnabler<int> InterruptEnabler;
InterruptEnabler interrupts(&switchMask, -1);
# define DO_OP() goto do_op
# define DO_NEXT_OP(n) JS_BEGIN_MACRO \
JS_ASSERT((n) == len); \
goto advance_pc; \
JS_END_MACRO
# define BEGIN_CASE(OP) case OP:
# define END_CASE(OP) END_CASE_LEN(OP##_LENGTH)
# define END_CASE_LEN(n) END_CASE_LENX(n)
# define END_CASE_LENX(n) END_CASE_LEN##n
/*
* To share the code for all len == 1 cases we use the specialized label with
* code that falls through to advance_pc: .
*/
# define END_CASE_LEN1 goto advance_pc_by_one;
# define END_CASE_LEN2 len = 2; goto advance_pc;
# define END_CASE_LEN3 len = 3; goto advance_pc;
# define END_CASE_LEN4 len = 4; goto advance_pc;
# define END_CASE_LEN5 len = 5; goto advance_pc;
# define END_CASE_LEN6 len = 6; goto advance_pc;
# define END_CASE_LEN7 len = 7; goto advance_pc;
# define END_CASE_LEN8 len = 8; goto advance_pc;
# define END_CASE_LEN9 len = 9; goto advance_pc;
# define END_CASE_LEN10 len = 10; goto advance_pc;
# define END_CASE_LEN11 len = 11; goto advance_pc;
# define END_CASE_LEN12 len = 12; goto advance_pc;
# define END_VARLEN_CASE goto advance_pc;
# define ADD_EMPTY_CASE(OP) BEGIN_CASE(OP)
# define END_EMPTY_CASES goto advance_pc_by_one;
#define LOAD_DOUBLE(PCOFF, dbl) \
(dbl = script->getConst(GET_UINT32_INDEX(regs.pc + (PCOFF))).toDouble())
/*
* Prepare to call a user-supplied branch handler, and abort the script
* if it returns false.
*/
#define CHECK_BRANCH() \
JS_BEGIN_MACRO \
if (cx->runtime()->interrupt && !js_HandleExecutionInterrupt(cx)) \
goto error; \
JS_END_MACRO
#define BRANCH(n) \
JS_BEGIN_MACRO \
regs.pc += (n); \
op = (JSOp) *regs.pc; \
if ((n) <= 0) \
goto check_backedge; \
DO_OP(); \
JS_END_MACRO
#define SET_SCRIPT(s) \
JS_BEGIN_MACRO \
script = (s); \
if (script->hasAnyBreakpointsOrStepMode() || script->hasScriptCounts) \
interrupts.enable(); \
JS_END_MACRO
FrameRegs regs;
FrameGuard fg(state, regs);
StackFrame *entryFrame = state.pushInterpreterFrame(cx, &fg);
if (!entryFrame)
return false;
if (!state.isGenerator()) {
regs.prepareToRun(*entryFrame, state.script());
JS_ASSERT(regs.pc == state.script()->code);
} else {
regs = state.asGenerator()->gen()->regs;
}
JS_ASSERT_IF(entryFrame->isEvalFrame(), state.script()->isActiveEval);
InterpreterActivation activation(cx, entryFrame, regs);
/*
* Help Debugger find frames running scripts that it has put in
* single-step mode.
*/
InterpreterFrames interpreterFrame(cx, &regs, interrupts);
/* Copy in hot values that change infrequently. */
JSRuntime *const rt = cx->runtime();
RootedScript script(cx);
SET_SCRIPT(regs.fp()->script());
#if JS_TRACE_LOGGING
AutoTraceLog logger(TraceLogging::defaultLogger(),
TraceLogging::INTERPRETER_START,
TraceLogging::INTERPRETER_STOP,
script);
#endif
/*
* 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);
RootedFunction rootFunction0(cx);
RootedTypeObject rootType0(cx);
RootedPropertyName rootName0(cx);
RootedId rootId0(cx);
RootedShape rootShape0(cx);
RootedScript rootScript0(cx);
DebugOnly<uint32_t> blockDepth;
#if JS_HAS_GENERATORS
if (JS_UNLIKELY(regs.fp()->isGeneratorFrame())) {
JS_ASSERT(size_t(regs.pc - script->code) <= script->length);
JS_ASSERT(regs.stackDepth() <= script->nslots);
/*
* To support generator_throw and to catch ignored exceptions,
* fail if cx->isExceptionPending() is true.
*/
if (cx->isExceptionPending()) {
Probes::enterScript(cx, script, script->function(), regs.fp());
goto error;
}
}
#endif
/* State communicated between non-local jumps: */
bool interpReturnOK;
if (!entryFrame->isGeneratorFrame()) {
if (!entryFrame->prologue(cx))
goto error;
} else {
Probes::enterScript(cx, script, script->function(), entryFrame);
}
if (cx->compartment()->debugMode()) {
JSTrapStatus status = ScriptDebugPrologue(cx, entryFrame);
switch (status) {
case JSTRAP_CONTINUE:
break;
case JSTRAP_RETURN:
interpReturnOK = true;
goto forced_return;
case JSTRAP_THROW:
case JSTRAP_ERROR:
goto error;
default:
JS_NOT_REACHED("bad ScriptDebugPrologue status");
}
}
/*
* It is important that "op" be initialized before calling DO_OP because
* it is possible for "op" to be specially assigned during the normal
* processing of an opcode while looping. We rely on DO_NEXT_OP to manage
* "op" correctly in all other cases.
*/
JSOp op;
int32_t len;
len = 0;
if (rt->profilingScripts || cx->runtime()->debugHooks.interruptHook)
interrupts.enable();
DO_NEXT_OP(len);
for (;;) {
advance_pc_by_one:
JS_ASSERT(js_CodeSpec[op].length == 1);
len = 1;
advance_pc:
js::gc::MaybeVerifyBarriers(cx);
regs.pc += len;
op = (JSOp) *regs.pc;
do_op:
CHECK_PCCOUNT_INTERRUPTS();
switchOp = int(op) | switchMask;
do_switch:
switch (switchOp) {
case -1:
JS_ASSERT(switchMask == -1);
{
bool moreInterrupts = false;
if (cx->runtime()->profilingScripts) {
if (!script->hasScriptCounts)
script->initScriptCounts(cx);
moreInterrupts = true;
}
if (script->hasScriptCounts) {
PCCounts counts = script->getPCCounts(regs.pc);
counts.get(PCCounts::BASE_INTERP)++;
moreInterrupts = true;
}
JSInterruptHook hook = cx->runtime()->debugHooks.interruptHook;
if (hook || script->stepModeEnabled()) {
RootedValue rval(cx);
JSTrapStatus status = JSTRAP_CONTINUE;
if (hook)
status = hook(cx, script, regs.pc, rval.address(), cx->runtime()->debugHooks.interruptHookData);
if (status == JSTRAP_CONTINUE && script->stepModeEnabled())
status = Debugger::onSingleStep(cx, &rval);
switch (status) {
case JSTRAP_ERROR:
goto error;
case JSTRAP_CONTINUE:
break;
case JSTRAP_RETURN:
regs.fp()->setReturnValue(rval);
interpReturnOK = true;
goto forced_return;
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);
interpReturnOK = true;
goto forced_return;
case JSTRAP_THROW:
cx->setPendingException(rval);
goto error;
default:
break;
}
JS_ASSERT(status == JSTRAP_CONTINUE);
JS_ASSERT(rval.isInt32() && rval.toInt32() == op);
}
switchMask = moreInterrupts ? -1 : 0;
switchOp = int(op);
goto do_switch;
}
/* No-ops for ease of decompilation. */
ADD_EMPTY_CASE(JSOP_NOP)
ADD_EMPTY_CASE(JSOP_UNUSED125)
ADD_EMPTY_CASE(JSOP_UNUSED126)
ADD_EMPTY_CASE(JSOP_UNUSED132)
ADD_EMPTY_CASE(JSOP_UNUSED148)
ADD_EMPTY_CASE(JSOP_UNUSED161)
ADD_EMPTY_CASE(JSOP_UNUSED162)
ADD_EMPTY_CASE(JSOP_UNUSED163)
ADD_EMPTY_CASE(JSOP_UNUSED164)
ADD_EMPTY_CASE(JSOP_UNUSED165)
ADD_EMPTY_CASE(JSOP_UNUSED166)
ADD_EMPTY_CASE(JSOP_UNUSED167)
ADD_EMPTY_CASE(JSOP_UNUSED168)
ADD_EMPTY_CASE(JSOP_UNUSED169)
ADD_EMPTY_CASE(JSOP_UNUSED170)
ADD_EMPTY_CASE(JSOP_UNUSED171)
ADD_EMPTY_CASE(JSOP_UNUSED172)
ADD_EMPTY_CASE(JSOP_UNUSED173)
ADD_EMPTY_CASE(JSOP_UNUSED174)
ADD_EMPTY_CASE(JSOP_UNUSED175)
ADD_EMPTY_CASE(JSOP_UNUSED176)
ADD_EMPTY_CASE(JSOP_UNUSED177)
ADD_EMPTY_CASE(JSOP_UNUSED178)
ADD_EMPTY_CASE(JSOP_UNUSED179)
ADD_EMPTY_CASE(JSOP_UNUSED180)
ADD_EMPTY_CASE(JSOP_UNUSED181)
ADD_EMPTY_CASE(JSOP_UNUSED182)
ADD_EMPTY_CASE(JSOP_UNUSED183)
ADD_EMPTY_CASE(JSOP_UNUSED188)
ADD_EMPTY_CASE(JSOP_UNUSED189)
ADD_EMPTY_CASE(JSOP_UNUSED190)
ADD_EMPTY_CASE(JSOP_UNUSED200)
ADD_EMPTY_CASE(JSOP_UNUSED201)
ADD_EMPTY_CASE(JSOP_UNUSED208)
ADD_EMPTY_CASE(JSOP_UNUSED209)
ADD_EMPTY_CASE(JSOP_UNUSED210)
ADD_EMPTY_CASE(JSOP_UNUSED219)
ADD_EMPTY_CASE(JSOP_UNUSED220)
ADD_EMPTY_CASE(JSOP_UNUSED221)
ADD_EMPTY_CASE(JSOP_UNUSED222)
ADD_EMPTY_CASE(JSOP_UNUSED223)
ADD_EMPTY_CASE(JSOP_CONDSWITCH)
ADD_EMPTY_CASE(JSOP_TRY)
END_EMPTY_CASES
BEGIN_CASE(JSOP_LOOPHEAD)
END_CASE(JSOP_LOOPHEAD)
BEGIN_CASE(JSOP_LABEL)
END_CASE(JSOP_LABEL)
check_backedge:
{
CHECK_BRANCH();
DO_OP();
}
BEGIN_CASE(JSOP_LOOPENTRY)
#ifdef JS_ION
// 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) {
jit::IonExecStatus maybeOsr = jit::EnterBaselineAtBranch(cx, regs.fp(), regs.pc);
// We failed to call into baseline at all, so treat as an error.
if (maybeOsr == jit::IonExec_Aborted)
goto error;
interpReturnOK = (maybeOsr == jit::IonExec_Ok);
if (entryFrame != regs.fp())
goto jit_return_pop_frame;
goto leave_on_safe_point;
}
}
#endif /* JS_ION */
END_CASE(JSOP_LOOPENTRY)
BEGIN_CASE(JSOP_NOTEARG)
END_CASE(JSOP_NOTEARG)
/* ADD_EMPTY_CASE is not used here as JSOP_LINENO_LENGTH == 3. */
BEGIN_CASE(JSOP_LINENO)
END_CASE(JSOP_LINENO)
BEGIN_CASE(JSOP_UNDEFINED)
PUSH_UNDEFINED();
END_CASE(JSOP_UNDEFINED)
BEGIN_CASE(JSOP_POP)
regs.sp--;
END_CASE(JSOP_POP)
BEGIN_CASE(JSOP_POPN)
JS_ASSERT(GET_UINT16(regs.pc) <= regs.stackDepth());
regs.sp -= GET_UINT16(regs.pc);
#ifdef DEBUG
if (StaticBlockObject *block = regs.fp()->maybeBlockChain())
JS_ASSERT(regs.stackDepth() >= block->stackDepth() + block->slotCount());
#endif
END_CASE(JSOP_POPN)
BEGIN_CASE(JSOP_SETRVAL)
BEGIN_CASE(JSOP_POPV)
POP_RETURN_VALUE();
END_CASE(JSOP_POPV)
BEGIN_CASE(JSOP_ENTERWITH)
{
RootedValue &val = rootValue0;
val = regs.sp[-1];
if (!EnterWith(cx, regs.fp(), val, regs.stackDepth() - 1))
goto error;
/*
* We must ensure that different "with" blocks have different stack depth
* associated with them. This allows the try handler search to properly
* recover the scope chain. Thus we must keep the stack at least at the
* current level.
*
* We set sp[-1] to the current "with" object to help asserting the
* enter/leave balance in [leavewith].
*/
regs.sp[-1].setObject(*regs.fp()->scopeChain());
}
END_CASE(JSOP_ENTERWITH)
BEGIN_CASE(JSOP_LEAVEWITH)
JS_ASSERT(regs.sp[-1].toObject() == *regs.fp()->scopeChain());
regs.fp()->popWith(cx);
regs.sp--;
END_CASE(JSOP_LEAVEWITH)
BEGIN_CASE(JSOP_RETURN)
POP_RETURN_VALUE();
/* FALL THROUGH */
BEGIN_CASE(JSOP_RETRVAL) /* fp return value already set */
BEGIN_CASE(JSOP_STOP)
{
/*
* When the inlined frame exits with an exception or an error, ok will be
* false after the inline_return label.
*/
CHECK_BRANCH();
interpReturnOK = true;
if (entryFrame != regs.fp())
inline_return:
{
if (cx->compartment()->debugMode())
interpReturnOK = ScriptDebugEpilogue(cx, regs.fp(), interpReturnOK);
if (!regs.fp()->isYielding())
regs.fp()->epilogue(cx);
else
Probes::exitScript(cx, script, script->function(), regs.fp());
#if defined(JS_ION)
jit_return_pop_frame:
#endif
activation.popInlineFrame(regs.fp());
SET_SCRIPT(regs.fp()->script());
#if defined(JS_ION)
jit_return:
#endif
JS_ASSERT(js_CodeSpec[*regs.pc].format & JOF_INVOKE);
/* Resume execution in the calling frame. */
if (JS_LIKELY(interpReturnOK)) {
TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
len = JSOP_CALL_LENGTH;
DO_NEXT_OP(len);
}
/* Increment pc so that |sp - fp->slots == ReconstructStackDepth(pc)|. */
regs.pc += JSOP_CALL_LENGTH;
goto error;
} else {
JS_ASSERT(regs.stackDepth() == 0);
}
interpReturnOK = true;
goto exit;
}
BEGIN_CASE(JSOP_DEFAULT)
regs.sp--;
/* FALL THROUGH */
BEGIN_CASE(JSOP_GOTO)
{
len = GET_JUMP_OFFSET(regs.pc);
BRANCH(len);
}
END_CASE(JSOP_GOTO)
BEGIN_CASE(JSOP_IFEQ)
{
bool cond = ToBooleanOp(regs);
regs.sp--;
if (cond == false) {
len = GET_JUMP_OFFSET(regs.pc);
BRANCH(len);
}
}
END_CASE(JSOP_IFEQ)
BEGIN_CASE(JSOP_IFNE)
{
bool cond = ToBooleanOp(regs);
regs.sp--;
if (cond != false) {
len = GET_JUMP_OFFSET(regs.pc);
BRANCH(len);
}
}
END_CASE(JSOP_IFNE)
BEGIN_CASE(JSOP_OR)
{
bool cond = ToBooleanOp(regs);
if (cond == true) {
len = GET_JUMP_OFFSET(regs.pc);
DO_NEXT_OP(len);
}
}
END_CASE(JSOP_OR)
BEGIN_CASE(JSOP_AND)
{
bool cond = ToBooleanOp(regs);
if (cond == false) {
len = GET_JUMP_OFFSET(regs.pc);
DO_NEXT_OP(len);
}
}
END_CASE(JSOP_AND)
#define FETCH_ELEMENT_ID(n, id) \
JS_BEGIN_MACRO \
if (!ValueToId<CanGC>(cx, HandleValue::fromMarkedLocation(&regs.sp[n]), &id))\
goto error; \
JS_END_MACRO
#define TRY_BRANCH_AFTER_COND(cond,spdec) \
JS_BEGIN_MACRO \
JS_ASSERT(js_CodeSpec[op].length == 1); \
unsigned diff_ = (unsigned) GET_UINT8(regs.pc) - (unsigned) JSOP_IFEQ; \
if (diff_ <= 1) { \
regs.sp -= spdec; \
if (cond == (diff_ != 0)) { \
++regs.pc; \
len = GET_JUMP_OFFSET(regs.pc); \
BRANCH(len); \
} \
len = 1 + JSOP_IFEQ_LENGTH; \
DO_NEXT_OP(len); \
} \
JS_END_MACRO
BEGIN_CASE(JSOP_IN)
{
HandleValue rref = HandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!rref.isObject()) {
js_ReportValueError(cx, JSMSG_IN_NOT_OBJECT, -1, rref, NullPtr());
goto error;
}
RootedObject &obj = rootObject0;
obj = &rref.toObject();
RootedId &id = rootId0;
FETCH_ELEMENT_ID(-2, id);
RootedObject &obj2 = rootObject1;
RootedShape &prop = rootShape0;
if (!JSObject::lookupGeneric(cx, obj, id, &obj2, &prop))
goto error;
bool cond = prop != NULL;
prop = NULL;
TRY_BRANCH_AFTER_COND(cond, 2);
regs.sp--;
regs.sp[-1].setBoolean(cond);
}
END_CASE(JSOP_IN)
BEGIN_CASE(JSOP_ITER)
{
JS_ASSERT(regs.stackDepth() >= 1);
uint8_t flags = GET_UINT8(regs.pc);
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!ValueToIterator(cx, flags, res))
goto error;
JS_ASSERT(!res.isPrimitive());
}
END_CASE(JSOP_ITER)
BEGIN_CASE(JSOP_MOREITER)
{
JS_ASSERT(regs.stackDepth() >= 1);
JS_ASSERT(regs.sp[-1].isObject());
PUSH_NULL();
bool cond;
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!IteratorMore(cx, &regs.sp[-2].toObject(), &cond, res))
goto error;
regs.sp[-1].setBoolean(cond);
}
END_CASE(JSOP_MOREITER)
BEGIN_CASE(JSOP_ITERNEXT)
{
JS_ASSERT(regs.sp[-1].isObject());
PUSH_NULL();
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-1]);
RootedObject &obj = rootObject0;
obj = &regs.sp[-2].toObject();
if (!IteratorNext(cx, obj, res))
goto error;
}
END_CASE(JSOP_ITERNEXT)
BEGIN_CASE(JSOP_ENDITER)
{
JS_ASSERT(regs.stackDepth() >= 1);
RootedObject &obj = rootObject0;
obj = &regs.sp[-1].toObject();
bool ok = CloseIterator(cx, obj);
regs.sp--;
if (!ok)
goto error;
}
END_CASE(JSOP_ENDITER)
BEGIN_CASE(JSOP_DUP)
{
JS_ASSERT(regs.stackDepth() >= 1);
const Value &rref = regs.sp[-1];
PUSH_COPY(rref);
}
END_CASE(JSOP_DUP)
BEGIN_CASE(JSOP_DUP2)
{
JS_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)
BEGIN_CASE(JSOP_SWAP)
{
JS_ASSERT(regs.stackDepth() >= 2);
Value &lref = regs.sp[-2];
Value &rref = regs.sp[-1];
lref.swap(rref);
}
END_CASE(JSOP_SWAP)
BEGIN_CASE(JSOP_PICK)
{
unsigned i = GET_UINT8(regs.pc);
JS_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)
BEGIN_CASE(JSOP_SETCONST)
{
RootedPropertyName &name = rootName0;
name = script->getName(regs.pc);
RootedValue &rval = rootValue0;
rval = regs.sp[-1];
RootedObject &obj = rootObject0;
obj = &regs.fp()->varObj();
if (!SetConstOperation(cx, obj, name, rval))
goto error;
}
END_CASE(JSOP_SETCONST);
#if JS_HAS_DESTRUCTURING
BEGIN_CASE(JSOP_ENUMCONSTELEM)
{
RootedValue &rval = rootValue0;
rval = regs.sp[-3];
RootedObject &obj = rootObject0;
FETCH_OBJECT(cx, -2, obj);
RootedId &id = rootId0;
FETCH_ELEMENT_ID(-1, id);
if (!JSObject::defineGeneric(cx, obj, id, rval,
JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY)) {
goto error;
}
regs.sp -= 3;
}
END_CASE(JSOP_ENUMCONSTELEM)
#endif
BEGIN_CASE(JSOP_BINDGNAME)
PUSH_OBJECT(regs.fp()->global());
END_CASE(JSOP_BINDGNAME)
BEGIN_CASE(JSOP_BINDINTRINSIC)
PUSH_OBJECT(*cx->global()->intrinsicsHolder());
END_CASE(JSOP_BINDGNAME)
BEGIN_CASE(JSOP_BINDNAME)
{
RootedObject &scopeChain = rootObject0;
scopeChain = regs.fp()->scopeChain();
RootedPropertyName &name = rootName0;
name = script->getName(regs.pc);
/* Assigning to an undeclared name adds a property to the global object. */
RootedObject &scope = rootObject1;
if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &scope))
goto error;
PUSH_OBJECT(*scope);
}
END_CASE(JSOP_BINDNAME)
#define BITWISE_OP(OP) \
JS_BEGIN_MACRO \
int32_t i, j; \
if (!ToInt32(cx, regs.sp[-2], &i)) \
goto error; \
if (!ToInt32(cx, regs.sp[-1], &j)) \
goto error; \
i = i OP j; \
regs.sp--; \
regs.sp[-1].setInt32(i); \
JS_END_MACRO
BEGIN_CASE(JSOP_BITOR)
BITWISE_OP(|);
END_CASE(JSOP_BITOR)
BEGIN_CASE(JSOP_BITXOR)
BITWISE_OP(^);
END_CASE(JSOP_BITXOR)
BEGIN_CASE(JSOP_BITAND)
BITWISE_OP(&);
END_CASE(JSOP_BITAND)
#undef BITWISE_OP
BEGIN_CASE(JSOP_EQ)
if (!LooseEqualityOp<true>(cx, regs))
goto error;
END_CASE(JSOP_EQ)
BEGIN_CASE(JSOP_NE)
if (!LooseEqualityOp<false>(cx, regs))
goto error;
END_CASE(JSOP_NE)
#define STRICT_EQUALITY_OP(OP, COND) \
JS_BEGIN_MACRO \
const Value &rref = regs.sp[-1]; \
const Value &lref = regs.sp[-2]; \
bool equal; \
if (!StrictlyEqual(cx, lref, rref, &equal)) \
goto error; \
COND = equal OP JS_TRUE; \
regs.sp--; \
JS_END_MACRO
BEGIN_CASE(JSOP_STRICTEQ)
{
bool cond;
STRICT_EQUALITY_OP(==, cond);
regs.sp[-1].setBoolean(cond);
}
END_CASE(JSOP_STRICTEQ)
BEGIN_CASE(JSOP_STRICTNE)
{
bool cond;
STRICT_EQUALITY_OP(!=, cond);
regs.sp[-1].setBoolean(cond);
}
END_CASE(JSOP_STRICTNE)
BEGIN_CASE(JSOP_CASE)
{
bool cond;
STRICT_EQUALITY_OP(==, cond);
if (cond) {
regs.sp--;
len = GET_JUMP_OFFSET(regs.pc);
BRANCH(len);
}
}
END_CASE(JSOP_CASE)
#undef STRICT_EQUALITY_OP
BEGIN_CASE(JSOP_LT)
{
bool cond;
MutableHandleValue lval = MutableHandleValue::fromMarkedLocation(&regs.sp[-2]);
MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(&regs.sp[-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)
BEGIN_CASE(JSOP_LE)
{
bool cond;
MutableHandleValue lval = MutableHandleValue::fromMarkedLocation(&regs.sp[-2]);
MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(&regs.sp[-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)
BEGIN_CASE(JSOP_GT)
{
bool cond;
MutableHandleValue lval = MutableHandleValue::fromMarkedLocation(&regs.sp[-2]);
MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(&regs.sp[-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)
BEGIN_CASE(JSOP_GE)
{
bool cond;
MutableHandleValue lval = MutableHandleValue::fromMarkedLocation(&regs.sp[-2]);
MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(&regs.sp[-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.sp[-2], &i)) \
goto error; \
if (!ToInt32(cx, regs.sp[-1], &j)) \
goto error; \
i = i OP (j & 31); \
regs.sp--; \
regs.sp[-1].setInt32(i); \
JS_END_MACRO
BEGIN_CASE(JSOP_LSH)
SIGNED_SHIFT_OP(<<);
END_CASE(JSOP_LSH)
BEGIN_CASE(JSOP_RSH)
SIGNED_SHIFT_OP(>>);
END_CASE(JSOP_RSH)
#undef SIGNED_SHIFT_OP
BEGIN_CASE(JSOP_URSH)
{
HandleValue lval = HandleValue::fromMarkedLocation(&regs.sp[-2]);
HandleValue rval = HandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!UrshOperation(cx, script, regs.pc, lval, rval, &regs.sp[-2]))
goto error;
regs.sp--;
}
END_CASE(JSOP_URSH)
BEGIN_CASE(JSOP_ADD)
{
MutableHandleValue lval = MutableHandleValue::fromMarkedLocation(&regs.sp[-2]);
MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!AddOperation(cx, script, regs.pc, lval, rval, &regs.sp[-2]))
goto error;
regs.sp--;
}
END_CASE(JSOP_ADD)
BEGIN_CASE(JSOP_SUB)
{
RootedValue &lval = rootValue0, &rval = rootValue1;
lval = regs.sp[-2];
rval = regs.sp[-1];
if (!SubOperation(cx, script, regs.pc, lval, rval, &regs.sp[-2]))
goto error;
regs.sp--;
}
END_CASE(JSOP_SUB)
BEGIN_CASE(JSOP_MUL)
{
RootedValue &lval = rootValue0, &rval = rootValue1;
lval = regs.sp[-2];
rval = regs.sp[-1];
if (!MulOperation(cx, script, regs.pc, lval, rval, &regs.sp[-2]))
goto error;
regs.sp--;
}
END_CASE(JSOP_MUL)
BEGIN_CASE(JSOP_DIV)
{
RootedValue &lval = rootValue0, &rval = rootValue1;
lval = regs.sp[-2];
rval = regs.sp[-1];
if (!DivOperation(cx, script, regs.pc, lval, rval, &regs.sp[-2]))
goto error;
regs.sp--;
}
END_CASE(JSOP_DIV)
BEGIN_CASE(JSOP_MOD)
{
RootedValue &lval = rootValue0, &rval = rootValue1;
lval = regs.sp[-2];
rval = regs.sp[-1];
if (!ModOperation(cx, script, regs.pc, lval, rval, &regs.sp[-2]))
goto error;
regs.sp--;
}
END_CASE(JSOP_MOD)
BEGIN_CASE(JSOP_NOT)
{
bool cond = ToBooleanOp(regs);
regs.sp--;
PUSH_BOOLEAN(!cond);
}
END_CASE(JSOP_NOT)
BEGIN_CASE(JSOP_BITNOT)
{
int32_t i;
HandleValue value = HandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!BitNot(cx, value, &i))
goto error;
regs.sp[-1].setInt32(i);
}
END_CASE(JSOP_BITNOT)
BEGIN_CASE(JSOP_NEG)
{
RootedValue &val = rootValue0;
val = regs.sp[-1];
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!NegOperation(cx, script, regs.pc, val, res))
goto error;
}
END_CASE(JSOP_NEG)
BEGIN_CASE(JSOP_POS)
if (!ToNumber(cx, &regs.sp[-1]))
goto error;
if (!regs.sp[-1].isInt32())
TypeScript::MonitorOverflow(cx, script, regs.pc);
END_CASE(JSOP_POS)
BEGIN_CASE(JSOP_DELNAME)
{
/* Strict mode code should never contain JSOP_DELNAME opcodes. */
JS_ASSERT(!script->strict);
RootedPropertyName &name = rootName0;
name = script->getName(regs.pc);
RootedObject &scopeObj = rootObject0;
scopeObj = regs.fp()->scopeChain();
PUSH_BOOLEAN(true);
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!DeleteNameOperation(cx, name, scopeObj, res))
goto error;
}
END_CASE(JSOP_DELNAME)
BEGIN_CASE(JSOP_DELPROP)
{
RootedPropertyName &name = rootName0;
name = script->getName(regs.pc);
RootedObject &obj = rootObject0;
FETCH_OBJECT(cx, -1, obj);
JSBool succeeded;
if (!JSObject::deleteProperty(cx, obj, name, &succeeded))
goto error;
if (!succeeded && script->strict) {
obj->reportNotConfigurable(cx, NameToId(name));
goto error;
}
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-1]);
res.setBoolean(succeeded);
}
END_CASE(JSOP_DELPROP)
BEGIN_CASE(JSOP_DELELEM)
{
/* Fetch the left part and resolve it to a non-null object. */
RootedObject &obj = rootObject0;
FETCH_OBJECT(cx, -2, obj);
RootedValue &propval = rootValue0;
propval = regs.sp[-1];
JSBool succeeded;
if (!JSObject::deleteByValue(cx, obj, propval, &succeeded))
goto error;
if (!succeeded && script->strict) {
// XXX This observably calls ToString(propval). We should convert to
// PropertyKey and use that to delete, and to report an error if
// necessary!
RootedId id(cx);
if (!ValueToId<CanGC>(cx, propval, &id))
goto error;
obj->reportNotConfigurable(cx, id);
goto error;
}
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-2]);
res.setBoolean(succeeded);
regs.sp--;
}
END_CASE(JSOP_DELELEM)
BEGIN_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.
*/
RootedValue &objval = rootValue0, &idval = rootValue1;
objval = regs.sp[-2];
idval = regs.sp[-1];
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!ToIdOperation(cx, script, regs.pc, objval, idval, res))
goto error;
}
END_CASE(JSOP_TOID)
BEGIN_CASE(JSOP_TYPEOFEXPR)
BEGIN_CASE(JSOP_TYPEOF)
{
HandleValue ref = HandleValue::fromMarkedLocation(&regs.sp[-1]);
regs.sp[-1].setString(TypeOfOperation(cx, ref));
}
END_CASE(JSOP_TYPEOF)
BEGIN_CASE(JSOP_VOID)
regs.sp[-1].setUndefined();
END_CASE(JSOP_VOID)
BEGIN_CASE(JSOP_THIS)
if (!ComputeThis(cx, regs.fp()))
goto error;
PUSH_COPY(regs.fp()->thisValue());
END_CASE(JSOP_THIS)
BEGIN_CASE(JSOP_GETPROP)
BEGIN_CASE(JSOP_GETXPROP)
BEGIN_CASE(JSOP_LENGTH)
BEGIN_CASE(JSOP_CALLPROP)
{
MutableHandleValue lval = MutableHandleValue::fromMarkedLocation(&regs.sp[-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)
BEGIN_CASE(JSOP_SETINTRINSIC)
{
HandleValue value = HandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!SetIntrinsicOperation(cx, script, regs.pc, value))
goto error;
regs.sp[-2] = regs.sp[-1];
regs.sp--;
}
END_CASE(JSOP_SETINTRINSIC)
BEGIN_CASE(JSOP_SETGNAME)
BEGIN_CASE(JSOP_SETNAME)
{
RootedObject &scope = rootObject0;
scope = &regs.sp[-2].toObject();
HandleValue value = HandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!SetNameOperation(cx, script, regs.pc, scope, value))
goto error;
regs.sp[-2] = regs.sp[-1];
regs.sp--;
}
END_CASE(JSOP_SETNAME)
BEGIN_CASE(JSOP_SETPROP)
{
HandleValue lval = HandleValue::fromMarkedLocation(&regs.sp[-2]);
HandleValue rval = HandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!SetPropertyOperation(cx, script, regs.pc, lval, rval))
goto error;
regs.sp[-2] = regs.sp[-1];
regs.sp--;
}
END_CASE(JSOP_SETPROP)
BEGIN_CASE(JSOP_GETELEM)
BEGIN_CASE(JSOP_CALLELEM)
{
MutableHandleValue lval = MutableHandleValue::fromMarkedLocation(&regs.sp[-2]);
HandleValue rval = HandleValue::fromMarkedLocation(&regs.sp[-1]);
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-2]);
bool done = false;
if (!GetElemOptimizedArguments(cx, regs.fp(), lval, rval, res, &done))
goto error;
if (!done) {
if (!GetElementOperation(cx, op, lval, rval, res))
goto error;
}
TypeScript::Monitor(cx, script, regs.pc, res);
regs.sp--;
}
END_CASE(JSOP_GETELEM)
BEGIN_CASE(JSOP_SETELEM)
{
RootedObject &obj = rootObject0;
FETCH_OBJECT(cx, -3, obj);
RootedId &id = rootId0;
FETCH_ELEMENT_ID(-2, id);
Value &value = regs.sp[-1];
if (!SetObjectElementOperation(cx, obj, id, value, script->strict))
goto error;
regs.sp[-3] = value;
regs.sp -= 2;
}
END_CASE(JSOP_SETELEM)
BEGIN_CASE(JSOP_ENUMELEM)
{
RootedObject &obj = rootObject0;
RootedValue &rval = rootValue0;
/* Funky: the value to set is under the [obj, id] pair. */
FETCH_OBJECT(cx, -2, obj);
RootedId &id = rootId0;
FETCH_ELEMENT_ID(-1, id);
rval = regs.sp[-3];
if (!JSObject::setGeneric(cx, obj, obj, id, &rval, script->strict))
goto error;
regs.sp -= 3;
}
END_CASE(JSOP_ENUMELEM)
BEGIN_CASE(JSOP_EVAL)
{
CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
if (IsBuiltinEvalForScope(regs.fp()->scopeChain(), 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)
BEGIN_CASE(JSOP_FUNAPPLY)
{
CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
if (!GuardFunApplyArgumentsOptimization(cx, regs.fp(), args.calleev(), args.array(),
args.length()))
goto error;
/* FALL THROUGH */
}
BEGIN_CASE(JSOP_NEW)
BEGIN_CASE(JSOP_CALL)
BEGIN_CASE(JSOP_FUNCALL)
{
if (regs.fp()->hasPushedSPSFrame())
cx->runtime()->spsProfiler.updatePC(script, regs.pc);
JS_ASSERT(regs.stackDepth() >= 2 + GET_ARGC(regs.pc));
CallArgs args = CallArgsFromSp(GET_ARGC(regs.pc), regs.sp);
bool construct = (*regs.pc == JSOP_NEW);
RootedFunction &fun = rootFunction0;
RootedScript &funScript = rootScript0;
bool isFunction = IsFunctionObject(args.calleev(), fun.address());
/*
* Some builtins are marked as clone-at-callsite to increase precision of
* TI and JITs.
*/
if (isFunction && fun->isInterpreted()) {
funScript = fun->getOrCreateScript(cx);
if (!funScript)
goto error;
if (cx->typeInferenceEnabled() && funScript->shouldCloneAtCallsite) {
fun = CloneFunctionAtCallsite(cx, fun, script, regs.pc);
if (!fun)
goto error;
args.setCallee(ObjectValue(*fun));
}
}
/* Don't bother trying to fast-path calls to scripted non-constructors. */
if (!isFunction || !fun->isInterpretedConstructor()) {
if (construct) {
if (!InvokeConstructor(cx, args))
goto error;
} else {
if (!Invoke(cx, args))
goto error;
}
Value *newsp = args.spAfterCall();
TypeScript::Monitor(cx, script, regs.pc, newsp[-1]);
regs.sp = newsp;
len = JSOP_CALL_LENGTH;
DO_NEXT_OP(len);
}
InitialFrameFlags initial = construct ? INITIAL_CONSTRUCT : INITIAL_NONE;
bool newType = cx->typeInferenceEnabled() && UseNewType(cx, script, regs.pc);
#ifdef JS_ION
InvokeState state(cx, args, initial);
if (newType)
state.setUseNewType();
if (!newType && jit::IsIonEnabled(cx)) {
jit::MethodStatus status = jit::CanEnter(cx, state);
if (status == jit::Method_Error)
goto error;
if (status == jit::Method_Compiled) {
jit::IonExecStatus exec = jit::Cannon(cx, state);
CHECK_BRANCH();
regs.sp = args.spAfterCall();
interpReturnOK = !IsErrorStatus(exec);
goto jit_return;
}
}
if (jit::IsBaselineEnabled(cx)) {
jit::MethodStatus status = jit::CanEnterBaselineMethod(cx, state);
if (status == jit::Method_Error)
goto error;
if (status == jit::Method_Compiled) {
jit::IonExecStatus exec = jit::EnterBaselineMethod(cx, state);
CHECK_BRANCH();
regs.sp = args.spAfterCall();
interpReturnOK = !IsErrorStatus(exec);
goto jit_return;
}
}
#endif
TypeMonitorCall(cx, args, construct);
funScript = fun->nonLazyScript();
if (!activation.pushInlineFrame(args, funScript, initial))
goto error;
if (newType)
regs.fp()->setUseNewType();
SET_SCRIPT(regs.fp()->script());
if (!regs.fp()->prologue(cx))
goto error;
if (cx->compartment()->debugMode()) {
switch (ScriptDebugPrologue(cx, regs.fp())) {
case JSTRAP_CONTINUE:
break;
case JSTRAP_RETURN:
interpReturnOK = true;
goto forced_return;
case JSTRAP_THROW:
case JSTRAP_ERROR:
goto error;
default:
JS_NOT_REACHED("bad ScriptDebugPrologue status");
}
}
/* Load first op and dispatch it (safe since JSOP_STOP). */
op = (JSOp) *regs.pc;
DO_OP();
}
BEGIN_CASE(JSOP_SETCALL)
{
JS_ALWAYS_FALSE(SetCallOperation(cx));
goto error;
}
END_CASE(JSOP_SETCALL)
BEGIN_CASE(JSOP_IMPLICITTHIS)
{
RootedPropertyName &name = rootName0;
name = script->getName(regs.pc);
RootedObject &scopeObj = rootObject0;
scopeObj = regs.fp()->scopeChain();
RootedObject &scope = rootObject1;
if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &scope))
goto error;
RootedValue &v = rootValue0;
if (!ComputeImplicitThis(cx, scope, &v))
goto error;
PUSH_COPY(v);
}
END_CASE(JSOP_IMPLICITTHIS)
BEGIN_CASE(JSOP_GETGNAME)
BEGIN_CASE(JSOP_CALLGNAME)
BEGIN_CASE(JSOP_NAME)
BEGIN_CASE(JSOP_CALLNAME)
{
RootedValue &rval = rootValue0;
if (!NameOperation(cx, regs.fp(), regs.pc, &rval))
goto error;
PUSH_COPY(rval);
TypeScript::Monitor(cx, script, regs.pc, rval);
}
END_CASE(JSOP_NAME)
BEGIN_CASE(JSOP_GETINTRINSIC)
BEGIN_CASE(JSOP_CALLINTRINSIC)
{
RootedValue &rval = rootValue0;
if (!GetIntrinsicOperation(cx, regs.pc, &rval))
goto error;
PUSH_COPY(rval);
TypeScript::Monitor(cx, script, regs.pc, rval);
}
END_CASE(JSOP_GETINTRINSIC)
BEGIN_CASE(JSOP_UINT16)
PUSH_INT32((int32_t) GET_UINT16(regs.pc));
END_CASE(JSOP_UINT16)
BEGIN_CASE(JSOP_UINT24)
PUSH_INT32((int32_t) GET_UINT24(regs.pc));
END_CASE(JSOP_UINT24)
BEGIN_CASE(JSOP_INT8)
PUSH_INT32(GET_INT8(regs.pc));
END_CASE(JSOP_INT8)
BEGIN_CASE(JSOP_INT32)
PUSH_INT32(GET_INT32(regs.pc));
END_CASE(JSOP_INT32)
BEGIN_CASE(JSOP_DOUBLE)
{
double dbl;
LOAD_DOUBLE(0, dbl);
PUSH_DOUBLE(dbl);
}
END_CASE(JSOP_DOUBLE)
BEGIN_CASE(JSOP_STRING)
PUSH_STRING(script->getAtom(regs.pc));
END_CASE(JSOP_STRING)
BEGIN_CASE(JSOP_OBJECT)
PUSH_OBJECT(*script->getObject(regs.pc));
END_CASE(JSOP_OBJECT)
BEGIN_CASE(JSOP_REGEXP)
{
/*
* Push a regexp object cloned from the regexp literal object mapped by the
* bytecode at pc.
*/
uint32_t index = GET_UINT32_INDEX(regs.pc);
JSObject *proto = regs.fp()->global().getOrCreateRegExpPrototype(cx);
if (!proto)
goto error;
JSObject *obj = CloneRegExpObject(cx, script->getRegExp(index), proto);
if (!obj)
goto error;
PUSH_OBJECT(*obj);
}
END_CASE(JSOP_REGEXP)
BEGIN_CASE(JSOP_ZERO)
PUSH_INT32(0);
END_CASE(JSOP_ZERO)
BEGIN_CASE(JSOP_ONE)
PUSH_INT32(1);
END_CASE(JSOP_ONE)
BEGIN_CASE(JSOP_NULL)
PUSH_NULL();
END_CASE(JSOP_NULL)
BEGIN_CASE(JSOP_FALSE)
PUSH_BOOLEAN(false);
END_CASE(JSOP_FALSE)
BEGIN_CASE(JSOP_TRUE)
PUSH_BOOLEAN(true);
END_CASE(JSOP_TRUE)
{
BEGIN_CASE(JSOP_TABLESWITCH)
{
jsbytecode *pc2 = regs.pc;
len = GET_JUMP_OFFSET(pc2);
/*
* ECMAv2+ forbids conversion of discriminant, so we will skip to the
* default case if the discriminant isn't already an int jsval. (This
* opcode is emitted only for dense int-domain switches.)
*/
const Value &rref = *--regs.sp;
int32_t i;
if (rref.isInt32()) {
i = rref.toInt32();
} else {
double d;
/* Don't use mozilla::DoubleIsInt32; treat -0 (double) as 0. */
if (!rref.isDouble() || (d = rref.toDouble()) != (i = int32_t(rref.toDouble())))
DO_NEXT_OP(len);
}
pc2 += JUMP_OFFSET_LEN;
int32_t low = GET_JUMP_OFFSET(pc2);
pc2 += JUMP_OFFSET_LEN;
int32_t high = GET_JUMP_OFFSET(pc2);
i -= low;
if ((uint32_t)i < (uint32_t)(high - low + 1)) {
pc2 += JUMP_OFFSET_LEN + JUMP_OFFSET_LEN * i;
int32_t off = (int32_t) GET_JUMP_OFFSET(pc2);
if (off)
len = off;
}
}
END_VARLEN_CASE
}
BEGIN_CASE(JSOP_ARGUMENTS)
JS_ASSERT(!regs.fp()->fun()->hasRest());
if (!script->analyzedArgsUsage() && !script->ensureRanAnalysis(cx))
goto error;
if (script->needsArgsObj()) {
ArgumentsObject *obj = ArgumentsObject::createExpected(cx, regs.fp());
if (!obj)
goto error;
PUSH_COPY(ObjectValue(*obj));
} else {
PUSH_COPY(MagicValue(JS_OPTIMIZED_ARGUMENTS));
}
END_CASE(JSOP_ARGUMENTS)
BEGIN_CASE(JSOP_RUNONCE)
{
if (!RunOnceScriptPrologue(cx, script))
goto error;
}
END_CASE(JSOP_RUNONCE)
BEGIN_CASE(JSOP_REST)
{
RootedObject &rest = rootObject0;
rest = regs.fp()->createRestParameter(cx);
if (!rest)
goto error;
PUSH_COPY(ObjectValue(*rest));
}
END_CASE(JSOP_REST)
BEGIN_CASE(JSOP_CALLALIASEDVAR)
BEGIN_CASE(JSOP_GETALIASEDVAR)
{
ScopeCoordinate sc = ScopeCoordinate(regs.pc);
PUSH_COPY(regs.fp()->aliasedVarScope(sc).aliasedVar(sc));
TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
}
END_CASE(JSOP_GETALIASEDVAR)
BEGIN_CASE(JSOP_SETALIASEDVAR)
{
ScopeCoordinate sc = ScopeCoordinate(regs.pc);
ScopeObject &obj = regs.fp()->aliasedVarScope(sc);
// Avoid computing the name if no type updates are needed, as this may be
// expensive on scopes with large numbers of variables.
PropertyName *name = obj.hasSingletonType() ? ScopeCoordinateName(cx, script, regs.pc) : NULL;
obj.setAliasedVar(cx, sc, name, regs.sp[-1]);
}
END_CASE(JSOP_SETALIASEDVAR)
BEGIN_CASE(JSOP_GETARG)
BEGIN_CASE(JSOP_CALLARG)
{
unsigned i = GET_ARGNO(regs.pc);
if (script->argsObjAliasesFormals())
PUSH_COPY(regs.fp()->argsObj().arg(i));
else
PUSH_COPY(regs.fp()->unaliasedFormal(i));
}
END_CASE(JSOP_GETARG)
BEGIN_CASE(JSOP_SETARG)
{
unsigned i = GET_ARGNO(regs.pc);
if (script->argsObjAliasesFormals())
regs.fp()->argsObj().setArg(i, regs.sp[-1]);
else
regs.fp()->unaliasedFormal(i) = regs.sp[-1];
}
END_CASE(JSOP_SETARG)
BEGIN_CASE(JSOP_GETLOCAL)
BEGIN_CASE(JSOP_CALLLOCAL)
{
unsigned i = GET_SLOTNO(regs.pc);
PUSH_COPY_SKIP_CHECK(regs.fp()->unaliasedLocal(i));
/*
* Skip the same-compartment assertion if the local will be immediately
* popped. We do not guarantee sync for dead locals when coming in from the
* method JIT, and a GETLOCAL followed by POP is not considered to be
* a use of the variable.
*/
if (regs.pc[JSOP_GETLOCAL_LENGTH] != JSOP_POP)
assertSameCompartmentDebugOnly(cx, regs.sp[-1]);
}
END_CASE(JSOP_GETLOCAL)
BEGIN_CASE(JSOP_SETLOCAL)
{
unsigned i = GET_SLOTNO(regs.pc);
regs.fp()->unaliasedLocal(i) = regs.sp[-1];
}
END_CASE(JSOP_SETLOCAL)
BEGIN_CASE(JSOP_DEFCONST)
BEGIN_CASE(JSOP_DEFVAR)
{
/* ES5 10.5 step 8 (with subsequent errata). */
unsigned attrs = JSPROP_ENUMERATE;
if (!regs.fp()->isEvalFrame())
attrs |= JSPROP_PERMANENT;
if (op == JSOP_DEFCONST)
attrs |= JSPROP_READONLY;
/* Step 8b. */
RootedObject &obj = rootObject0;
obj = &regs.fp()->varObj();
RootedPropertyName &name = rootName0;
name = script->getName(regs.pc);
if (!DefVarOrConstOperation(cx, obj, name, attrs))
goto error;
}
END_CASE(JSOP_DEFVAR)
BEGIN_CASE(JSOP_DEFFUN)
{
/*
* A top-level function defined in Global or Eval code (see ECMA-262
* Ed. 3), or else a SpiderMonkey extension: a named function statement in
* a compound statement (not at the top statement level of global code, or
* at the top level of a function body).
*/
RootedFunction &fun = rootFunction0;
fun = script->getFunction(GET_UINT32_INDEX(regs.pc));
if (!DefFunOperation(cx, script, regs.fp()->scopeChain(), fun))
goto error;
}
END_CASE(JSOP_DEFFUN)
BEGIN_CASE(JSOP_LAMBDA)
{
/* Load the specified function object literal. */
RootedFunction &fun = rootFunction0;
fun = script->getFunction(GET_UINT32_INDEX(regs.pc));
JSObject *obj = Lambda(cx, fun, regs.fp()->scopeChain());
if (!obj)
goto error;
JS_ASSERT(obj->getProto());
PUSH_OBJECT(*obj);
}
END_CASE(JSOP_LAMBDA)
BEGIN_CASE(JSOP_CALLEE)
JS_ASSERT(regs.fp()->isNonEvalFunctionFrame());
PUSH_COPY(regs.fp()->calleev());
END_CASE(JSOP_CALLEE)
BEGIN_CASE(JSOP_INITPROP_GETTER)
BEGIN_CASE(JSOP_INITPROP_SETTER)
{
RootedObject &obj = rootObject0;
RootedPropertyName &name = rootName0;
RootedValue &val = rootValue0;
JS_ASSERT(regs.stackDepth() >= 2);
obj = &regs.sp[-2].toObject();
name = script->getName(regs.pc);
val = regs.sp[-1];
if (!InitGetterSetterOperation(cx, regs.pc, obj, name, val))
goto error;
regs.sp--;
}
END_CASE(JSOP_INITPROP_GETTER)
BEGIN_CASE(JSOP_INITELEM_GETTER)
BEGIN_CASE(JSOP_INITELEM_SETTER)
{
RootedObject &obj = rootObject0;
RootedValue &idval = rootValue0;
RootedValue &val = rootValue1;
JS_ASSERT(regs.stackDepth() >= 3);
obj = &regs.sp[-3].toObject();
idval = regs.sp[-2];
val = regs.sp[-1];
if (!InitGetterSetterOperation(cx, regs.pc, obj, idval, val))
goto error;
regs.sp -= 2;
}
END_CASE(JSOP_INITELEM_GETTER)
BEGIN_CASE(JSOP_HOLE)
PUSH_HOLE();
END_CASE(JSOP_HOLE)
BEGIN_CASE(JSOP_NEWINIT)
{
uint8_t i = GET_UINT8(regs.pc);
JS_ASSERT(i == JSProto_Array || i == JSProto_Object);
RootedObject &obj = rootObject0;
NewObjectKind newKind;
if (i == JSProto_Array) {
newKind = UseNewTypeForInitializer(cx, script, regs.pc, &ArrayClass);
obj = NewDenseEmptyArray(cx, NULL, newKind);
} else {
gc::AllocKind allocKind = GuessObjectGCKind(0);
newKind = UseNewTypeForInitializer(cx, script, regs.pc, &ObjectClass);
obj = NewBuiltinClassInstance(cx, &ObjectClass, allocKind, newKind);
}
if (!obj || !SetInitializerObjectType(cx, script, regs.pc, obj, newKind))
goto error;
PUSH_OBJECT(*obj);
TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
}
END_CASE(JSOP_NEWINIT)
BEGIN_CASE(JSOP_NEWARRAY)
{
unsigned count = GET_UINT24(regs.pc);
RootedObject &obj = rootObject0;
NewObjectKind newKind = UseNewTypeForInitializer(cx, script, regs.pc, &ArrayClass);
obj = NewDenseAllocatedArray(cx, count, NULL, newKind);
if (!obj || !SetInitializerObjectType(cx, script, regs.pc, obj, newKind))
goto error;
PUSH_OBJECT(*obj);
TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
}
END_CASE(JSOP_NEWARRAY)
BEGIN_CASE(JSOP_NEWOBJECT)
{
RootedObject &baseobj = rootObject0;
baseobj = script->getObject(regs.pc);
RootedObject &obj = rootObject1;
NewObjectKind newKind = UseNewTypeForInitializer(cx, script, regs.pc, baseobj->getClass());
obj = CopyInitializerObject(cx, baseobj, newKind);
if (!obj || !SetInitializerObjectType(cx, script, regs.pc, obj, newKind))
goto error;
PUSH_OBJECT(*obj);
TypeScript::Monitor(cx, script, regs.pc, regs.sp[-1]);
}
END_CASE(JSOP_NEWOBJECT)
BEGIN_CASE(JSOP_ENDINIT)
{
/* FIXME remove JSOP_ENDINIT bug 588522 */
JS_ASSERT(regs.stackDepth() >= 1);
JS_ASSERT(regs.sp[-1].isObject() || regs.sp[-1].isUndefined());
}
END_CASE(JSOP_ENDINIT)
BEGIN_CASE(JSOP_INITPROP)
{
/* Load the property's initial value into rval. */
JS_ASSERT(regs.stackDepth() >= 2);
RootedValue &rval = rootValue0;
rval = regs.sp[-1];
/* Load the object being initialized into lval/obj. */
RootedObject &obj = rootObject0;
obj = &regs.sp[-2].toObject();
JS_ASSERT(obj->isObject());
PropertyName *name = script->getName(regs.pc);
RootedId &id = rootId0;
id = NameToId(name);
if (JS_UNLIKELY(name == cx->names().proto)
? !baseops::SetPropertyHelper(cx, obj, obj, id, 0, &rval, script->strict)
: !DefineNativeProperty(cx, obj, id, rval, NULL, NULL,
JSPROP_ENUMERATE, 0, 0, 0)) {
goto error;
}
regs.sp--;
}
END_CASE(JSOP_INITPROP);
BEGIN_CASE(JSOP_INITELEM)
{
JS_ASSERT(regs.stackDepth() >= 3);
HandleValue val = HandleValue::fromMarkedLocation(&regs.sp[-1]);
HandleValue id = HandleValue::fromMarkedLocation(&regs.sp[-2]);
RootedObject &obj = rootObject0;
obj = &regs.sp[-3].toObject();
if (!InitElemOperation(cx, obj, id, val))
goto error;
regs.sp -= 2;
}
END_CASE(JSOP_INITELEM)
BEGIN_CASE(JSOP_INITELEM_ARRAY)
{
JS_ASSERT(regs.stackDepth() >= 2);
HandleValue val = HandleValue::fromMarkedLocation(&regs.sp[-1]);
RootedObject &obj = rootObject0;
obj = &regs.sp[-2].toObject();
JS_ASSERT(obj->isArray());
uint32_t index = GET_UINT24(regs.pc);
if (!InitArrayElemOperation(cx, regs.pc, obj, index, val))
goto error;
regs.sp--;
}
END_CASE(JSOP_INITELEM_ARRAY)
BEGIN_CASE(JSOP_INITELEM_INC)
{
JS_ASSERT(regs.stackDepth() >= 3);
HandleValue val = HandleValue::fromMarkedLocation(&regs.sp[-1]);
RootedObject &obj = rootObject0;
obj = &regs.sp[-3].toObject();
uint32_t index = regs.sp[-2].toInt32();
if (!InitArrayElemOperation(cx, regs.pc, obj, index, val))
goto error;
regs.sp[-2].setInt32(index + 1);
regs.sp--;
}
END_CASE(JSOP_INITELEM_INC)
BEGIN_CASE(JSOP_SPREAD)
{
int32_t count = regs.sp[-2].toInt32();
RootedObject &arr = rootObject0;
arr = &regs.sp[-3].toObject();
const Value iterable = regs.sp[-1];
ForOfIterator iter(cx, iterable);
RootedValue &iterVal = rootValue0;
while (iter.next()) {
if (count == INT32_MAX) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_SPREAD_TOO_LARGE);
goto error;
}
iterVal = iter.value();
if (!JSObject::defineElement(cx, arr, count++, iterVal, NULL, NULL, JSPROP_ENUMERATE))
goto error;
}
if (!iter.close())
goto error;
regs.sp[-2].setInt32(count);
regs.sp--;
}
END_CASE(JSOP_SPREAD)
{
BEGIN_CASE(JSOP_GOSUB)
PUSH_BOOLEAN(false);
int32_t i = (regs.pc - script->code) + JSOP_GOSUB_LENGTH;
len = GET_JUMP_OFFSET(regs.pc);
PUSH_INT32(i);
END_VARLEN_CASE
}
{
BEGIN_CASE(JSOP_RETSUB)
/* Pop [exception or hole, retsub pc-index]. */
Value rval, lval;
POP_COPY_TO(rval);
POP_COPY_TO(lval);
JS_ASSERT(lval.isBoolean());
if (lval.toBoolean()) {
/*
* Exception was pending during finally, throw it *before* we adjust
* pc, because pc indexes into script->trynotes. This turns out not to
* be necessary, but it seems clearer. And it points out a FIXME:
* 350509, due to Igor Bukanov.
*/
cx->setPendingException(rval);
goto error;
}
JS_ASSERT(rval.isInt32());
/* Increment the PC by this much. */
len = rval.toInt32() - int32_t(regs.pc - script->code);
END_VARLEN_CASE
}
BEGIN_CASE(JSOP_EXCEPTION)
{
PUSH_NULL();
MutableHandleValue res = MutableHandleValue::fromMarkedLocation(&regs.sp[-1]);
if (!GetAndClearException(cx, res))
goto error;
}
END_CASE(JSOP_EXCEPTION)
BEGIN_CASE(JSOP_FINALLY)
CHECK_BRANCH();
END_CASE(JSOP_FINALLY)
BEGIN_CASE(JSOP_THROWING)
{
JS_ASSERT(!cx->isExceptionPending());
Value v;
POP_COPY_TO(v);
cx->setPendingException(v);
}