| /* -*- 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 ®s) |
| { |
| 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 ®s) |
| { |
| 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 ®s) |
| { |
| /* 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 ®s) |
| : 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(®s.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 ®s) |
| : 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, ®s, 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(®s.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(®s.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(®s.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(®s.sp[-1]); |
| if (!IteratorMore(cx, ®s.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(®s.sp[-1]); |
| RootedObject &obj = rootObject0; |
| obj = ®s.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 = ®s.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 = ®s.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(®s.sp[-2]); |
| MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(®s.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(®s.sp[-2]); |
| MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(®s.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(®s.sp[-2]); |
| MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(®s.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(®s.sp[-2]); |
| MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(®s.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(®s.sp[-2]); |
| HandleValue rval = HandleValue::fromMarkedLocation(®s.sp[-1]); |
| if (!UrshOperation(cx, script, regs.pc, lval, rval, ®s.sp[-2])) |
| goto error; |
| regs.sp--; |
| } |
| END_CASE(JSOP_URSH) |
| |
| BEGIN_CASE(JSOP_ADD) |
| { |
| MutableHandleValue lval = MutableHandleValue::fromMarkedLocation(®s.sp[-2]); |
| MutableHandleValue rval = MutableHandleValue::fromMarkedLocation(®s.sp[-1]); |
| if (!AddOperation(cx, script, regs.pc, lval, rval, ®s.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, ®s.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, ®s.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, ®s.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, ®s.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(®s.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(®s.sp[-1]); |
| if (!NegOperation(cx, script, regs.pc, val, res)) |
| goto error; |
| } |
| END_CASE(JSOP_NEG) |
| |
| BEGIN_CASE(JSOP_POS) |
| if (!ToNumber(cx, ®s.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(®s.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(®s.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(®s.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(®s.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(®s.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(®s.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(®s.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 = ®s.sp[-2].toObject(); |
| |
| HandleValue value = HandleValue::fromMarkedLocation(®s.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(®s.sp[-2]); |
| HandleValue rval = HandleValue::fromMarkedLocation(®s.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(®s.sp[-2]); |
| HandleValue rval = HandleValue::fromMarkedLocation(®s.sp[-1]); |
| MutableHandleValue res = MutableHandleValue::fromMarkedLocation(®s.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 = ®s.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 = ®s.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 = ®s.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 = ®s.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(®s.sp[-1]); |
| HandleValue id = HandleValue::fromMarkedLocation(®s.sp[-2]); |
| |
| RootedObject &obj = rootObject0; |
| obj = ®s.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(®s.sp[-1]); |
| |
| RootedObject &obj = rootObject0; |
| obj = ®s.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(®s.sp[-1]); |
| |
| RootedObject &obj = rootObject0; |
| obj = ®s.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 = ®s.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(®s.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); |
| } |
|