michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * JavaScript bytecode interpreter. michael@0: */ michael@0: michael@0: #include "vm/Interpreter-inl.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/FloatingPoint.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #include michael@0: michael@0: #include "jsarray.h" michael@0: #include "jsatom.h" michael@0: #include "jscntxt.h" michael@0: #include "jsfun.h" michael@0: #include "jsgc.h" michael@0: #include "jsiter.h" michael@0: #include "jslibmath.h" michael@0: #include "jsnum.h" michael@0: #include "jsobj.h" michael@0: #include "jsopcode.h" michael@0: #include "jsprf.h" michael@0: #include "jsscript.h" michael@0: #include "jsstr.h" michael@0: michael@0: #include "builtin/Eval.h" michael@0: #include "jit/BaselineJIT.h" michael@0: #include "jit/Ion.h" michael@0: #include "jit/IonAnalysis.h" michael@0: #include "js/OldDebugAPI.h" michael@0: #include "vm/Debugger.h" michael@0: #include "vm/Opcodes.h" michael@0: #include "vm/Shape.h" michael@0: #include "vm/TraceLogging.h" michael@0: michael@0: #include "jsatominlines.h" michael@0: #include "jsboolinlines.h" michael@0: #include "jsfuninlines.h" michael@0: #include "jsinferinlines.h" michael@0: #include "jsscriptinlines.h" michael@0: michael@0: #include "jit/IonFrames-inl.h" michael@0: #include "vm/Probes-inl.h" michael@0: #include "vm/ScopeObject-inl.h" michael@0: #include "vm/Stack-inl.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using namespace js::types; michael@0: michael@0: using mozilla::DebugOnly; michael@0: using mozilla::NumberEqualsInt32; michael@0: using mozilla::PodCopy; michael@0: using JS::ForOfIterator; michael@0: michael@0: /* michael@0: * Note: when Clang 3.2 (32-bit) inlines the two functions below in Interpret, michael@0: * the conservative stack scanner leaks a ton of memory and this negatively michael@0: * influences performance. The MOZ_NEVER_INLINE is a temporary workaround until michael@0: * we can remove the conservative scanner. See bug 849526 for more info. michael@0: */ michael@0: #if defined(__clang__) && defined(JS_CPU_X86) michael@0: static MOZ_NEVER_INLINE bool michael@0: #else michael@0: static bool michael@0: #endif michael@0: ToBooleanOp(const InterpreterRegs ®s) michael@0: { michael@0: return ToBoolean(regs.stackHandleAt(-1)); michael@0: } michael@0: michael@0: template michael@0: #if defined(__clang__) && defined(JS_CPU_X86) michael@0: static MOZ_NEVER_INLINE bool michael@0: #else michael@0: static bool michael@0: #endif michael@0: LooseEqualityOp(JSContext *cx, InterpreterRegs ®s) michael@0: { michael@0: HandleValue rval = regs.stackHandleAt(-1); michael@0: HandleValue lval = regs.stackHandleAt(-2); michael@0: bool cond; michael@0: if (!LooselyEqual(cx, lval, rval, &cond)) michael@0: return false; michael@0: cond = (cond == Eq); michael@0: regs.sp--; michael@0: regs.sp[-1].setBoolean(cond); michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: js::BoxNonStrictThis(JSContext *cx, HandleValue thisv) michael@0: { michael@0: /* michael@0: * Check for SynthesizeFrame poisoning and fast constructors which michael@0: * didn't check their callee properly. michael@0: */ michael@0: JS_ASSERT(!thisv.isMagic()); michael@0: michael@0: if (thisv.isNullOrUndefined()) { michael@0: Rooted global(cx, cx->global()); michael@0: return JSObject::thisObject(cx, global); michael@0: } michael@0: michael@0: if (thisv.isObject()) michael@0: return &thisv.toObject(); michael@0: michael@0: return PrimitiveToObject(cx, thisv); michael@0: } michael@0: michael@0: /* michael@0: * ECMA requires "the global object", but in embeddings such as the browser, michael@0: * which have multiple top-level objects (windows, frames, etc. in the DOM), michael@0: * we prefer fun's parent. An example that causes this code to run: michael@0: * michael@0: * // in window w1 michael@0: * function f() { return this } michael@0: * function g() { return f } michael@0: * michael@0: * // in window w2 michael@0: * var h = w1.g() michael@0: * alert(h() == w1) michael@0: * michael@0: * The alert should display "true". michael@0: */ michael@0: bool michael@0: js::BoxNonStrictThis(JSContext *cx, const CallReceiver &call) michael@0: { michael@0: /* michael@0: * Check for SynthesizeFrame poisoning and fast constructors which michael@0: * didn't check their callee properly. michael@0: */ michael@0: JS_ASSERT(!call.thisv().isMagic()); michael@0: michael@0: #ifdef DEBUG michael@0: JSFunction *fun = call.callee().is() ? &call.callee().as() : nullptr; michael@0: JS_ASSERT_IF(fun && fun->isInterpreted(), !fun->strict()); michael@0: #endif michael@0: michael@0: JSObject *thisObj = BoxNonStrictThis(cx, call.thisv()); michael@0: if (!thisObj) michael@0: return false; michael@0: michael@0: call.setThis(ObjectValue(*thisObj)); michael@0: return true; michael@0: } michael@0: michael@0: #if JS_HAS_NO_SUCH_METHOD michael@0: michael@0: static const uint32_t JSSLOT_FOUND_FUNCTION = 0; michael@0: static const uint32_t JSSLOT_SAVED_ID = 1; michael@0: michael@0: static const Class js_NoSuchMethodClass = { michael@0: "NoSuchMethod", michael@0: JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_IS_ANONYMOUS, michael@0: JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, michael@0: JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, michael@0: }; michael@0: michael@0: /* michael@0: * When JSOP_CALLPROP or JSOP_CALLELEM does not find the method property of michael@0: * the base object, we search for the __noSuchMethod__ method in the base. michael@0: * If it exists, we store the method and the property's id into an object of michael@0: * NoSuchMethod class and store this object into the callee's stack slot. michael@0: * Later, Invoke will recognise such an object and transfer control to michael@0: * NoSuchMethod that invokes the method like: michael@0: * michael@0: * this.__noSuchMethod__(id, args) michael@0: * michael@0: * where id is the name of the method that this invocation attempted to michael@0: * call by name, and args is an Array containing this invocation's actual michael@0: * parameters. michael@0: */ michael@0: bool michael@0: js::OnUnknownMethod(JSContext *cx, HandleObject obj, Value idval_, MutableHandleValue vp) michael@0: { michael@0: RootedValue idval(cx, idval_); michael@0: michael@0: RootedValue value(cx); michael@0: if (!JSObject::getProperty(cx, obj, obj, cx->names().noSuchMethod, &value)) michael@0: return false; michael@0: michael@0: if (value.isObject()) { michael@0: JSObject *obj = NewObjectWithClassProto(cx, &js_NoSuchMethodClass, nullptr, nullptr); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: obj->setSlot(JSSLOT_FOUND_FUNCTION, value); michael@0: obj->setSlot(JSSLOT_SAVED_ID, idval); michael@0: vp.setObject(*obj); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NoSuchMethod(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: InvokeArgs args(cx); michael@0: if (!args.init(2)) michael@0: return false; michael@0: michael@0: JS_ASSERT(vp[0].isObject()); michael@0: JS_ASSERT(vp[1].isObject()); michael@0: JSObject *obj = &vp[0].toObject(); michael@0: JS_ASSERT(obj->getClass() == &js_NoSuchMethodClass); michael@0: michael@0: args.setCallee(obj->getReservedSlot(JSSLOT_FOUND_FUNCTION)); michael@0: args.setThis(vp[1]); michael@0: args[0].set(obj->getReservedSlot(JSSLOT_SAVED_ID)); michael@0: JSObject *argsobj = NewDenseCopiedArray(cx, argc, vp + 2); michael@0: if (!argsobj) michael@0: return false; michael@0: args[1].setObject(*argsobj); michael@0: bool ok = Invoke(cx, args); michael@0: vp[0] = args.rval(); michael@0: return ok; michael@0: } michael@0: michael@0: #endif /* JS_HAS_NO_SUCH_METHOD */ michael@0: michael@0: static inline bool michael@0: GetPropertyOperation(JSContext *cx, InterpreterFrame *fp, HandleScript script, jsbytecode *pc, michael@0: MutableHandleValue lval, MutableHandleValue vp) michael@0: { michael@0: JSOp op = JSOp(*pc); michael@0: michael@0: if (op == JSOP_LENGTH) { michael@0: if (IsOptimizedArguments(fp, lval.address())) { michael@0: vp.setInt32(fp->numActualArgs()); michael@0: return true; michael@0: } michael@0: michael@0: if (GetLengthProperty(lval, vp)) michael@0: return true; michael@0: } michael@0: michael@0: Rooted global(cx, &fp->global()); michael@0: RootedId id(cx, NameToId(script->getName(pc))); michael@0: RootedObject obj(cx); michael@0: michael@0: /* Optimize (.1).toString(). */ michael@0: if (lval.isNumber() && id == NameToId(cx->names().toString)) { michael@0: JSObject *proto = GlobalObject::getOrCreateNumberPrototype(cx, global); michael@0: if (!proto) michael@0: return false; michael@0: if (ClassMethodIsNative(cx, proto, &NumberObject::class_, id, js_num_toString)) michael@0: obj = proto; michael@0: } michael@0: michael@0: if (!obj) { michael@0: obj = ToObjectFromStack(cx, lval); michael@0: if (!obj) michael@0: return false; michael@0: } michael@0: michael@0: bool wasObject = lval.isObject(); michael@0: michael@0: if (!JSObject::getGeneric(cx, obj, obj, id, vp)) michael@0: return false; michael@0: michael@0: #if JS_HAS_NO_SUCH_METHOD michael@0: if (op == JSOP_CALLPROP && michael@0: MOZ_UNLIKELY(vp.isUndefined()) && michael@0: wasObject) michael@0: { michael@0: if (!OnUnknownMethod(cx, obj, IdToValue(id), vp)) michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static inline bool michael@0: NameOperation(JSContext *cx, InterpreterFrame *fp, jsbytecode *pc, MutableHandleValue vp) michael@0: { michael@0: JSObject *obj = fp->scopeChain(); michael@0: PropertyName *name = fp->script()->getName(pc); michael@0: michael@0: /* michael@0: * Skip along the scope chain to the enclosing global object. This is michael@0: * used for GNAME opcodes where the bytecode emitter has determined a michael@0: * name access must be on the global. It also insulates us from bugs michael@0: * in the emitter: type inference will assume that GNAME opcodes are michael@0: * accessing the global object, and the inferred behavior should match michael@0: * the actual behavior even if the id could be found on the scope chain michael@0: * before the global object. michael@0: */ michael@0: if (IsGlobalOp(JSOp(*pc))) michael@0: obj = &obj->global(); michael@0: michael@0: Shape *shape = nullptr; michael@0: JSObject *scope = nullptr, *pobj = nullptr; michael@0: if (LookupNameNoGC(cx, name, obj, &scope, &pobj, &shape)) { michael@0: if (FetchNameNoGC(pobj, shape, vp)) michael@0: return true; michael@0: } michael@0: michael@0: RootedObject objRoot(cx, obj), scopeRoot(cx), pobjRoot(cx); michael@0: RootedPropertyName nameRoot(cx, name); michael@0: RootedShape shapeRoot(cx); michael@0: michael@0: if (!LookupName(cx, nameRoot, objRoot, &scopeRoot, &pobjRoot, &shapeRoot)) michael@0: return false; michael@0: michael@0: /* Kludge to allow (typeof foo == "undefined") tests. */ michael@0: JSOp op2 = JSOp(pc[JSOP_NAME_LENGTH]); michael@0: if (op2 == JSOP_TYPEOF) michael@0: return FetchName(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp); michael@0: return FetchName(cx, scopeRoot, pobjRoot, nameRoot, shapeRoot, vp); michael@0: } michael@0: michael@0: static inline bool michael@0: SetPropertyOperation(JSContext *cx, HandleScript script, jsbytecode *pc, HandleValue lval, michael@0: HandleValue rval) michael@0: { michael@0: JS_ASSERT(*pc == JSOP_SETPROP); michael@0: michael@0: RootedObject obj(cx, ToObjectFromStack(cx, lval)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: RootedValue rref(cx, rval); michael@0: michael@0: RootedId id(cx, NameToId(script->getName(pc))); michael@0: if (MOZ_LIKELY(!obj->getOps()->setProperty)) { michael@0: if (!baseops::SetPropertyHelper(cx, obj, obj, id, baseops::Qualified, michael@0: &rref, script->strict())) michael@0: { michael@0: return false; michael@0: } michael@0: } else { michael@0: if (!JSObject::setGeneric(cx, obj, obj, id, &rref, script->strict())) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::ReportIsNotFunction(JSContext *cx, HandleValue v, int numToSkip, MaybeConstruct construct) michael@0: { michael@0: unsigned error = construct ? JSMSG_NOT_CONSTRUCTOR : JSMSG_NOT_FUNCTION; michael@0: int spIndex = numToSkip >= 0 ? -(numToSkip + 1) : JSDVG_SEARCH_STACK; michael@0: michael@0: js_ReportValueError3(cx, error, spIndex, v, NullPtr(), nullptr, nullptr); michael@0: return false; michael@0: } michael@0: michael@0: JSObject * michael@0: js::ValueToCallable(JSContext *cx, HandleValue v, int numToSkip, MaybeConstruct construct) michael@0: { michael@0: if (v.isObject()) { michael@0: JSObject *callable = &v.toObject(); michael@0: if (callable->isCallable()) michael@0: return callable; michael@0: } michael@0: michael@0: ReportIsNotFunction(cx, v, numToSkip, construct); michael@0: return nullptr; michael@0: } michael@0: michael@0: static MOZ_NEVER_INLINE bool michael@0: Interpret(JSContext *cx, RunState &state); michael@0: michael@0: InterpreterFrame * michael@0: InvokeState::pushInterpreterFrame(JSContext *cx) michael@0: { michael@0: return cx->runtime()->interpreterStack().pushInvokeFrame(cx, args_, initial_); michael@0: } michael@0: michael@0: InterpreterFrame * michael@0: ExecuteState::pushInterpreterFrame(JSContext *cx) michael@0: { michael@0: return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, thisv_, scopeChain_, michael@0: type_, evalInFrame_); michael@0: } michael@0: michael@0: bool michael@0: js::RunScript(JSContext *cx, RunState &state) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: SPSEntryMarker marker(cx->runtime()); michael@0: michael@0: state.script()->ensureNonLazyCanonicalFunction(cx); michael@0: michael@0: #ifdef JS_ION michael@0: if (jit::IsIonEnabled(cx)) { michael@0: jit::MethodStatus status = jit::CanEnter(cx, state); michael@0: if (status == jit::Method_Error) michael@0: return false; michael@0: if (status == jit::Method_Compiled) { michael@0: jit::IonExecStatus status = jit::IonCannon(cx, state); michael@0: return !IsErrorStatus(status); michael@0: } michael@0: } michael@0: michael@0: if (jit::IsBaselineEnabled(cx)) { michael@0: jit::MethodStatus status = jit::CanEnterBaselineMethod(cx, state); michael@0: if (status == jit::Method_Error) michael@0: return false; michael@0: if (status == jit::Method_Compiled) { michael@0: jit::IonExecStatus status = jit::EnterBaselineMethod(cx, state); michael@0: return !IsErrorStatus(status); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (state.isInvoke()) { michael@0: InvokeState &invoke = *state.asInvoke(); michael@0: TypeMonitorCall(cx, invoke.args(), invoke.constructing()); michael@0: } michael@0: michael@0: return Interpret(cx, state); michael@0: } michael@0: michael@0: struct AutoGCIfNeeded michael@0: { michael@0: JSContext *cx_; michael@0: AutoGCIfNeeded(JSContext *cx) : cx_(cx) {} michael@0: ~AutoGCIfNeeded() { js::gc::GCIfNeeded(cx_); } michael@0: }; michael@0: michael@0: /* michael@0: * Find a function reference and its 'this' value implicit first parameter michael@0: * under argc arguments on cx's stack, and call the function. Push missing michael@0: * required arguments, allocate declared local variables, and pop everything michael@0: * when done. Then push the return value. michael@0: */ michael@0: bool michael@0: js::Invoke(JSContext *cx, CallArgs args, MaybeConstruct construct) michael@0: { michael@0: JS_ASSERT(args.length() <= ARGS_LENGTH_MAX); michael@0: JS_ASSERT(!cx->compartment()->activeAnalysis); michael@0: michael@0: /* We should never enter a new script while cx->iterValue is live. */ michael@0: JS_ASSERT(cx->iterValue.isMagic(JS_NO_ITER_VALUE)); michael@0: michael@0: /* Perform GC if necessary on exit from the function. */ michael@0: AutoGCIfNeeded gcIfNeeded(cx); michael@0: michael@0: /* MaybeConstruct is a subset of InitialFrameFlags */ michael@0: InitialFrameFlags initial = (InitialFrameFlags) construct; michael@0: michael@0: if (args.calleev().isPrimitive()) michael@0: return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, construct); michael@0: michael@0: JSObject &callee = args.callee(); michael@0: const Class *clasp = callee.getClass(); michael@0: michael@0: /* Invoke non-functions. */ michael@0: if (MOZ_UNLIKELY(clasp != &JSFunction::class_)) { michael@0: #if JS_HAS_NO_SUCH_METHOD michael@0: if (MOZ_UNLIKELY(clasp == &js_NoSuchMethodClass)) michael@0: return NoSuchMethod(cx, args.length(), args.base()); michael@0: #endif michael@0: JS_ASSERT_IF(construct, !clasp->construct); michael@0: if (!clasp->call) michael@0: return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, construct); michael@0: return CallJSNative(cx, clasp->call, args); michael@0: } michael@0: michael@0: /* Invoke native functions. */ michael@0: JSFunction *fun = &callee.as(); michael@0: JS_ASSERT_IF(construct, !fun->isNativeConstructor()); michael@0: if (fun->isNative()) michael@0: return CallJSNative(cx, fun->native(), args); michael@0: michael@0: if (!fun->getOrCreateScript(cx)) michael@0: return false; michael@0: michael@0: /* Run function until JSOP_RETRVAL, JSOP_RETURN or error. */ michael@0: InvokeState state(cx, args, initial); michael@0: michael@0: // Check to see if useNewType flag should be set for this frame. michael@0: if (construct) { michael@0: FrameIter iter(cx); michael@0: if (!iter.done() && iter.hasScript()) { michael@0: JSScript *script = iter.script(); michael@0: jsbytecode *pc = iter.pc(); michael@0: if (UseNewType(cx, script, pc)) michael@0: state.setUseNewType(); michael@0: } michael@0: } michael@0: michael@0: bool ok = RunScript(cx, state); michael@0: michael@0: JS_ASSERT_IF(ok && construct, !args.rval().isPrimitive()); michael@0: return ok; michael@0: } michael@0: michael@0: bool michael@0: js::Invoke(JSContext *cx, const Value &thisv, const Value &fval, unsigned argc, const Value *argv, michael@0: MutableHandleValue rval) michael@0: { michael@0: InvokeArgs args(cx); michael@0: if (!args.init(argc)) michael@0: return false; michael@0: michael@0: args.setCallee(fval); michael@0: args.setThis(thisv); michael@0: PodCopy(args.array(), argv, argc); michael@0: michael@0: if (args.thisv().isObject()) { michael@0: /* michael@0: * We must call the thisObject hook in case we are not called from the michael@0: * interpreter, where a prior bytecode has computed an appropriate michael@0: * |this| already. But don't do that if fval is a DOM function. michael@0: */ michael@0: if (!fval.isObject() || !fval.toObject().is() || michael@0: !fval.toObject().as().isNative() || michael@0: !fval.toObject().as().jitInfo() || michael@0: fval.toObject().as().jitInfo()->needsOuterizedThisObject()) michael@0: { michael@0: RootedObject thisObj(cx, &args.thisv().toObject()); michael@0: JSObject *thisp = JSObject::thisObject(cx, thisObj); michael@0: if (!thisp) michael@0: return false; michael@0: args.setThis(ObjectValue(*thisp)); michael@0: } michael@0: } michael@0: michael@0: if (!Invoke(cx, args)) michael@0: return false; michael@0: michael@0: rval.set(args.rval()); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::InvokeConstructor(JSContext *cx, CallArgs args) michael@0: { michael@0: JS_ASSERT(!JSFunction::class_.construct); michael@0: michael@0: args.setThis(MagicValue(JS_IS_CONSTRUCTING)); michael@0: michael@0: if (!args.calleev().isObject()) michael@0: return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, CONSTRUCT); michael@0: michael@0: JSObject &callee = args.callee(); michael@0: if (callee.is()) { michael@0: RootedFunction fun(cx, &callee.as()); michael@0: michael@0: if (fun->isNativeConstructor()) { michael@0: bool ok = CallJSNativeConstructor(cx, fun->native(), args); michael@0: return ok; michael@0: } michael@0: michael@0: if (!fun->isInterpretedConstructor()) { michael@0: RootedValue orig(cx, ObjectValue(*fun->originalFunction())); michael@0: return ReportIsNotFunction(cx, orig, args.length() + 1, CONSTRUCT); michael@0: } michael@0: if (!Invoke(cx, args, CONSTRUCT)) michael@0: return false; michael@0: michael@0: JS_ASSERT(args.rval().isObject()); michael@0: return true; michael@0: } michael@0: michael@0: const Class *clasp = callee.getClass(); michael@0: if (!clasp->construct) michael@0: return ReportIsNotFunction(cx, args.calleev(), args.length() + 1, CONSTRUCT); michael@0: michael@0: return CallJSNativeConstructor(cx, clasp->construct, args); michael@0: } michael@0: michael@0: bool michael@0: js::InvokeConstructor(JSContext *cx, Value fval, unsigned argc, Value *argv, Value *rval) michael@0: { michael@0: InvokeArgs args(cx); michael@0: if (!args.init(argc)) michael@0: return false; michael@0: michael@0: args.setCallee(fval); michael@0: args.setThis(MagicValue(JS_THIS_POISON)); michael@0: PodCopy(args.array(), argv, argc); michael@0: michael@0: if (!InvokeConstructor(cx, args)) michael@0: return false; michael@0: michael@0: *rval = args.rval(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::InvokeGetterOrSetter(JSContext *cx, JSObject *obj, Value fval, unsigned argc, michael@0: Value *argv, MutableHandleValue rval) michael@0: { michael@0: /* michael@0: * Invoke could result in another try to get or set the same id again, see michael@0: * bug 355497. michael@0: */ michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: return Invoke(cx, ObjectValue(*obj), fval, argc, argv, rval); michael@0: } michael@0: michael@0: bool michael@0: js::ExecuteKernel(JSContext *cx, HandleScript script, JSObject &scopeChainArg, const Value &thisv, michael@0: ExecuteType type, AbstractFramePtr evalInFrame, Value *result) michael@0: { michael@0: JS_ASSERT_IF(evalInFrame, type == EXECUTE_DEBUG); michael@0: JS_ASSERT_IF(type == EXECUTE_GLOBAL, !scopeChainArg.is()); michael@0: #ifdef DEBUG michael@0: if (thisv.isObject()) { michael@0: RootedObject thisObj(cx, &thisv.toObject()); michael@0: AutoSuppressGC nogc(cx); michael@0: JS_ASSERT(GetOuterObject(cx, thisObj) == thisObj); michael@0: } michael@0: #endif michael@0: michael@0: if (script->isEmpty()) { michael@0: if (result) michael@0: result->setUndefined(); michael@0: return true; michael@0: } michael@0: michael@0: TypeScript::SetThis(cx, script, thisv); michael@0: michael@0: probes::StartExecution(script); michael@0: ExecuteState state(cx, script, thisv, scopeChainArg, type, evalInFrame, result); michael@0: bool ok = RunScript(cx, state); michael@0: probes::StopExecution(script); michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: bool michael@0: js::Execute(JSContext *cx, HandleScript script, JSObject &scopeChainArg, Value *rval) michael@0: { michael@0: /* The scope chain could be anything, so innerize just in case. */ michael@0: RootedObject scopeChain(cx, &scopeChainArg); michael@0: scopeChain = GetInnerObject(cx, scopeChain); michael@0: if (!scopeChain) michael@0: return false; michael@0: michael@0: /* Ensure the scope chain is all same-compartment and terminates in a global. */ michael@0: #ifdef DEBUG michael@0: JSObject *s = scopeChain; michael@0: do { michael@0: assertSameCompartment(cx, s); michael@0: JS_ASSERT_IF(!s->enclosingScope(), s->is()); michael@0: } while ((s = s->enclosingScope())); michael@0: #endif michael@0: michael@0: /* The VAROBJFIX option makes varObj == globalObj in global code. */ michael@0: if (!cx->options().varObjFix()) { michael@0: if (!scopeChain->setVarObj(cx)) michael@0: return false; michael@0: } michael@0: michael@0: /* Use the scope chain as 'this', modulo outerization. */ michael@0: JSObject *thisObj = JSObject::thisObject(cx, scopeChain); michael@0: if (!thisObj) michael@0: return false; michael@0: Value thisv = ObjectValue(*thisObj); michael@0: michael@0: return ExecuteKernel(cx, script, *scopeChain, thisv, EXECUTE_GLOBAL, michael@0: NullFramePtr() /* evalInFrame */, rval); michael@0: } michael@0: michael@0: bool michael@0: js::HasInstance(JSContext *cx, HandleObject obj, HandleValue v, bool *bp) michael@0: { michael@0: const Class *clasp = obj->getClass(); michael@0: RootedValue local(cx, v); michael@0: if (clasp->hasInstance) michael@0: return clasp->hasInstance(cx, obj, &local, bp); michael@0: michael@0: RootedValue val(cx, ObjectValue(*obj)); michael@0: js_ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS, michael@0: JSDVG_SEARCH_STACK, val, NullPtr()); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: js::LooselyEqual(JSContext *cx, const Value &lval, const Value &rval, bool *result) michael@0: { michael@0: if (SameType(lval, rval)) { michael@0: if (lval.isString()) { michael@0: JSString *l = lval.toString(); michael@0: JSString *r = rval.toString(); michael@0: return EqualStrings(cx, l, r, result); michael@0: } michael@0: michael@0: if (lval.isDouble()) { michael@0: double l = lval.toDouble(), r = rval.toDouble(); michael@0: *result = (l == r); michael@0: return true; michael@0: } michael@0: michael@0: if (lval.isObject()) { michael@0: JSObject *l = &lval.toObject(); michael@0: JSObject *r = &rval.toObject(); michael@0: *result = l == r; michael@0: return true; michael@0: } michael@0: michael@0: *result = lval.payloadAsRawUint32() == rval.payloadAsRawUint32(); michael@0: return true; michael@0: } michael@0: michael@0: if (lval.isNullOrUndefined()) { michael@0: *result = rval.isNullOrUndefined() || michael@0: (rval.isObject() && EmulatesUndefined(&rval.toObject())); michael@0: return true; michael@0: } michael@0: michael@0: if (rval.isNullOrUndefined()) { michael@0: *result = (lval.isObject() && EmulatesUndefined(&lval.toObject())); michael@0: return true; michael@0: } michael@0: michael@0: RootedValue lvalue(cx, lval); michael@0: RootedValue rvalue(cx, rval); michael@0: michael@0: if (!ToPrimitive(cx, &lvalue)) michael@0: return false; michael@0: if (!ToPrimitive(cx, &rvalue)) michael@0: return false; michael@0: michael@0: if (lvalue.get().isString() && rvalue.get().isString()) { michael@0: JSString *l = lvalue.get().toString(); michael@0: JSString *r = rvalue.get().toString(); michael@0: return EqualStrings(cx, l, r, result); michael@0: } michael@0: michael@0: double l, r; michael@0: if (!ToNumber(cx, lvalue, &l) || !ToNumber(cx, rvalue, &r)) michael@0: return false; michael@0: *result = (l == r); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::StrictlyEqual(JSContext *cx, const Value &lref, const Value &rref, bool *equal) michael@0: { michael@0: Value lval = lref, rval = rref; michael@0: if (SameType(lval, rval)) { michael@0: if (lval.isString()) michael@0: return EqualStrings(cx, lval.toString(), rval.toString(), equal); michael@0: if (lval.isDouble()) { michael@0: *equal = (lval.toDouble() == rval.toDouble()); michael@0: return true; michael@0: } michael@0: if (lval.isObject()) { michael@0: *equal = lval.toObject() == rval.toObject(); michael@0: return true; michael@0: } michael@0: if (lval.isUndefined()) { michael@0: *equal = true; michael@0: return true; michael@0: } michael@0: *equal = lval.payloadAsRawUint32() == rval.payloadAsRawUint32(); michael@0: return true; michael@0: } michael@0: michael@0: if (lval.isDouble() && rval.isInt32()) { michael@0: double ld = lval.toDouble(); michael@0: double rd = rval.toInt32(); michael@0: *equal = (ld == rd); michael@0: return true; michael@0: } michael@0: if (lval.isInt32() && rval.isDouble()) { michael@0: double ld = lval.toInt32(); michael@0: double rd = rval.toDouble(); michael@0: *equal = (ld == rd); michael@0: return true; michael@0: } michael@0: michael@0: *equal = false; michael@0: return true; michael@0: } michael@0: michael@0: static inline bool michael@0: IsNegativeZero(const Value &v) michael@0: { michael@0: return v.isDouble() && mozilla::IsNegativeZero(v.toDouble()); michael@0: } michael@0: michael@0: static inline bool michael@0: IsNaN(const Value &v) michael@0: { michael@0: return v.isDouble() && mozilla::IsNaN(v.toDouble()); michael@0: } michael@0: michael@0: bool michael@0: js::SameValue(JSContext *cx, const Value &v1, const Value &v2, bool *same) michael@0: { michael@0: if (IsNegativeZero(v1)) { michael@0: *same = IsNegativeZero(v2); michael@0: return true; michael@0: } michael@0: if (IsNegativeZero(v2)) { michael@0: *same = false; michael@0: return true; michael@0: } michael@0: if (IsNaN(v1) && IsNaN(v2)) { michael@0: *same = true; michael@0: return true; michael@0: } michael@0: return StrictlyEqual(cx, v1, v2, same); michael@0: } michael@0: michael@0: JSType michael@0: js::TypeOfObject(JSObject *obj) michael@0: { michael@0: if (EmulatesUndefined(obj)) michael@0: return JSTYPE_VOID; michael@0: if (obj->isCallable()) michael@0: return JSTYPE_FUNCTION; michael@0: return JSTYPE_OBJECT; michael@0: } michael@0: michael@0: JSType michael@0: js::TypeOfValue(const Value &v) michael@0: { michael@0: if (v.isNumber()) michael@0: return JSTYPE_NUMBER; michael@0: if (v.isString()) michael@0: return JSTYPE_STRING; michael@0: if (v.isNull()) michael@0: return JSTYPE_OBJECT; michael@0: if (v.isUndefined()) michael@0: return JSTYPE_VOID; michael@0: if (v.isObject()) michael@0: return TypeOfObject(&v.toObject()); michael@0: JS_ASSERT(v.isBoolean()); michael@0: return JSTYPE_BOOLEAN; michael@0: } michael@0: michael@0: /* michael@0: * Enter the new with scope using an object at sp[-1] and associate the depth michael@0: * of the with block with sp + stackIndex. michael@0: */ michael@0: bool michael@0: js::EnterWithOperation(JSContext *cx, AbstractFramePtr frame, HandleValue val, michael@0: HandleObject staticWith) michael@0: { michael@0: JS_ASSERT(staticWith->is()); michael@0: RootedObject obj(cx); michael@0: if (val.isObject()) { michael@0: obj = &val.toObject(); michael@0: } else { michael@0: obj = ToObject(cx, val); michael@0: if (!obj) michael@0: return false; michael@0: } michael@0: michael@0: RootedObject scopeChain(cx, frame.scopeChain()); michael@0: DynamicWithObject *withobj = DynamicWithObject::create(cx, obj, scopeChain, staticWith); michael@0: if (!withobj) michael@0: return false; michael@0: michael@0: frame.pushOnScopeChain(*withobj); michael@0: return true; michael@0: } michael@0: michael@0: // Unwind scope chain and iterator to match the static scope corresponding to michael@0: // the given bytecode position. michael@0: void michael@0: js::UnwindScope(JSContext *cx, ScopeIter &si, jsbytecode *pc) michael@0: { michael@0: if (si.done()) michael@0: return; michael@0: michael@0: Rooted staticScope(cx, si.frame().script()->getStaticScope(pc)); michael@0: michael@0: for (; si.staticScope() != staticScope; ++si) { michael@0: switch (si.type()) { michael@0: case ScopeIter::Block: michael@0: if (cx->compartment()->debugMode()) michael@0: DebugScopes::onPopBlock(cx, si); michael@0: if (si.staticBlock().needsClone()) michael@0: si.frame().popBlock(cx); michael@0: break; michael@0: case ScopeIter::With: michael@0: si.frame().popWith(cx); michael@0: break; michael@0: case ScopeIter::Call: michael@0: case ScopeIter::StrictEvalScope: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void michael@0: ForcedReturn(JSContext *cx, ScopeIter &si, InterpreterRegs ®s) michael@0: { michael@0: UnwindScope(cx, si, regs.fp()->script()->main()); michael@0: regs.setToEndOfScript(); michael@0: } michael@0: michael@0: static void michael@0: ForcedReturn(JSContext *cx, InterpreterRegs ®s) michael@0: { michael@0: ScopeIter si(regs.fp(), regs.pc, cx); michael@0: ForcedReturn(cx, si, regs); michael@0: } michael@0: michael@0: void michael@0: js::UnwindForUncatchableException(JSContext *cx, const InterpreterRegs ®s) michael@0: { michael@0: /* c.f. the regular (catchable) TryNoteIter loop in HandleError. */ michael@0: for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) { michael@0: JSTryNote *tn = *tni; michael@0: if (tn->kind == JSTRY_ITER) { michael@0: Value *sp = regs.spForStackDepth(tn->stackDepth); michael@0: UnwindIteratorForUncatchableException(cx, &sp[-1].toObject()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: TryNoteIter::TryNoteIter(JSContext *cx, const InterpreterRegs ®s) michael@0: : regs(regs), michael@0: script(cx, regs.fp()->script()), michael@0: pcOffset(regs.pc - script->main()) michael@0: { michael@0: if (script->hasTrynotes()) { michael@0: tn = script->trynotes()->vector; michael@0: tnEnd = tn + script->trynotes()->length; michael@0: } else { michael@0: tn = tnEnd = nullptr; michael@0: } michael@0: settle(); michael@0: } michael@0: michael@0: void michael@0: TryNoteIter::operator++() michael@0: { michael@0: ++tn; michael@0: settle(); michael@0: } michael@0: michael@0: bool michael@0: TryNoteIter::done() const michael@0: { michael@0: return tn == tnEnd; michael@0: } michael@0: michael@0: void michael@0: TryNoteIter::settle() michael@0: { michael@0: for (; tn != tnEnd; ++tn) { michael@0: /* If pc is out of range, try the next one. */ michael@0: if (pcOffset - tn->start >= tn->length) michael@0: continue; michael@0: michael@0: /* michael@0: * We have a note that covers the exception pc but we must check michael@0: * whether the interpreter has already executed the corresponding michael@0: * handler. This is possible when the executed bytecode implements michael@0: * break or return from inside a for-in loop. michael@0: * michael@0: * In this case the emitter generates additional [enditer] and [gosub] michael@0: * opcodes to close all outstanding iterators and execute the finally michael@0: * blocks. If such an [enditer] throws an exception, its pc can still michael@0: * be inside several nested for-in loops and try-finally statements michael@0: * even if we have already closed the corresponding iterators and michael@0: * invoked the finally blocks. michael@0: * michael@0: * To address this, we make [enditer] always decrease the stack even michael@0: * when its implementation throws an exception. Thus already executed michael@0: * [enditer] and [gosub] opcodes will have try notes with the stack michael@0: * depth exceeding the current one and this condition is what we use to michael@0: * filter them out. michael@0: */ michael@0: if (tn->stackDepth <= regs.stackDepth()) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: enum HandleErrorContinuation michael@0: { michael@0: SuccessfulReturnContinuation, michael@0: ErrorReturnContinuation, michael@0: CatchContinuation, michael@0: FinallyContinuation michael@0: }; michael@0: michael@0: static HandleErrorContinuation michael@0: HandleError(JSContext *cx, InterpreterRegs ®s) michael@0: { michael@0: JS_ASSERT(regs.fp()->script()->containsPC(regs.pc)); michael@0: michael@0: ScopeIter si(regs.fp(), regs.pc, cx); michael@0: bool ok = false; michael@0: michael@0: again: michael@0: if (cx->isExceptionPending()) { michael@0: /* Call debugger throw hooks. */ michael@0: if (MOZ_UNLIKELY(cx->compartment()->debugMode())) { michael@0: JSTrapStatus status = DebugExceptionUnwind(cx, regs.fp(), regs.pc); michael@0: switch (status) { michael@0: case JSTRAP_ERROR: michael@0: goto again; michael@0: michael@0: case JSTRAP_CONTINUE: michael@0: case JSTRAP_THROW: michael@0: break; michael@0: michael@0: case JSTRAP_RETURN: michael@0: ForcedReturn(cx, si, regs); michael@0: return SuccessfulReturnContinuation; michael@0: michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Invalid trap status"); michael@0: } michael@0: } michael@0: michael@0: RootedValue exception(cx); michael@0: for (TryNoteIter tni(cx, regs); !tni.done(); ++tni) { michael@0: JSTryNote *tn = *tni; michael@0: michael@0: UnwindScope(cx, si, regs.fp()->script()->main() + tn->start); michael@0: michael@0: /* michael@0: * Set pc to the first bytecode after the the try note to point michael@0: * to the beginning of catch or finally or to [enditer] closing michael@0: * the for-in loop. michael@0: */ michael@0: regs.pc = regs.fp()->script()->main() + tn->start + tn->length; michael@0: regs.sp = regs.spForStackDepth(tn->stackDepth); michael@0: michael@0: switch (tn->kind) { michael@0: case JSTRY_CATCH: michael@0: /* Catch cannot intercept the closing of a generator. */ michael@0: if (!cx->getPendingException(&exception)) michael@0: return ErrorReturnContinuation; michael@0: if (exception.isMagic(JS_GENERATOR_CLOSING)) michael@0: break; michael@0: return CatchContinuation; michael@0: michael@0: case JSTRY_FINALLY: michael@0: return FinallyContinuation; michael@0: michael@0: case JSTRY_ITER: { michael@0: /* This is similar to JSOP_ENDITER in the interpreter loop. */ michael@0: JS_ASSERT(JSOp(*regs.pc) == JSOP_ENDITER); michael@0: RootedObject obj(cx, ®s.sp[-1].toObject()); michael@0: bool ok = UnwindIteratorForException(cx, obj); michael@0: regs.sp -= 1; michael@0: if (!ok) michael@0: goto again; michael@0: break; michael@0: } michael@0: michael@0: case JSTRY_LOOP: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Propagate the exception or error to the caller unless the exception michael@0: * is an asynchronous return from a generator. michael@0: */ michael@0: if (cx->isExceptionPending()) { michael@0: RootedValue exception(cx); michael@0: if (!cx->getPendingException(&exception)) michael@0: return ErrorReturnContinuation; michael@0: michael@0: if (exception.isMagic(JS_GENERATOR_CLOSING)) { michael@0: cx->clearPendingException(); michael@0: ok = true; michael@0: regs.fp()->clearReturnValue(); michael@0: } michael@0: } michael@0: } else { michael@0: UnwindForUncatchableException(cx, regs); michael@0: } michael@0: michael@0: ForcedReturn(cx, si, regs); michael@0: return ok ? SuccessfulReturnContinuation : ErrorReturnContinuation; michael@0: } michael@0: michael@0: #define REGS (activation.regs()) michael@0: #define PUSH_COPY(v) do { *REGS.sp++ = (v); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0) michael@0: #define PUSH_COPY_SKIP_CHECK(v) *REGS.sp++ = (v) michael@0: #define PUSH_NULL() REGS.sp++->setNull() michael@0: #define PUSH_UNDEFINED() REGS.sp++->setUndefined() michael@0: #define PUSH_BOOLEAN(b) REGS.sp++->setBoolean(b) michael@0: #define PUSH_DOUBLE(d) REGS.sp++->setDouble(d) michael@0: #define PUSH_INT32(i) REGS.sp++->setInt32(i) michael@0: #define PUSH_STRING(s) do { REGS.sp++->setString(s); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0) michael@0: #define PUSH_OBJECT(obj) do { REGS.sp++->setObject(obj); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0) michael@0: #define PUSH_OBJECT_OR_NULL(obj) do { REGS.sp++->setObjectOrNull(obj); assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } while (0) michael@0: #define PUSH_HOLE() REGS.sp++->setMagic(JS_ELEMENTS_HOLE) michael@0: #define POP_COPY_TO(v) (v) = *--REGS.sp michael@0: #define POP_RETURN_VALUE() REGS.fp()->setReturnValue(*--REGS.sp) michael@0: michael@0: #define FETCH_OBJECT(cx, n, obj) \ michael@0: JS_BEGIN_MACRO \ michael@0: HandleValue val = REGS.stackHandleAt(n); \ michael@0: obj = ToObjectFromStack((cx), (val)); \ michael@0: if (!obj) \ michael@0: goto error; \ michael@0: JS_END_MACRO michael@0: michael@0: /* michael@0: * Same for JSOP_SETNAME and JSOP_SETPROP, which differ only slightly but michael@0: * remain distinct for the decompiler. michael@0: */ michael@0: JS_STATIC_ASSERT(JSOP_SETNAME_LENGTH == JSOP_SETPROP_LENGTH); michael@0: michael@0: /* See TRY_BRANCH_AFTER_COND. */ michael@0: JS_STATIC_ASSERT(JSOP_IFNE_LENGTH == JSOP_IFEQ_LENGTH); michael@0: JS_STATIC_ASSERT(JSOP_IFNE == JSOP_IFEQ + 1); michael@0: michael@0: /* michael@0: * Inline fast paths for iteration. js_IteratorMore and js_IteratorNext handle michael@0: * all cases, but we inline the most frequently taken paths here. michael@0: */ michael@0: bool michael@0: js::IteratorMore(JSContext *cx, JSObject *iterobj, bool *cond, MutableHandleValue rval) michael@0: { michael@0: if (iterobj->is()) { michael@0: NativeIterator *ni = iterobj->as().getNativeIterator(); michael@0: if (ni->isKeyIter()) { michael@0: *cond = (ni->props_cursor < ni->props_end); michael@0: return true; michael@0: } michael@0: } michael@0: Rooted iobj(cx, iterobj); michael@0: if (!js_IteratorMore(cx, iobj, rval)) michael@0: return false; michael@0: *cond = rval.isTrue(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::IteratorNext(JSContext *cx, HandleObject iterobj, MutableHandleValue rval) michael@0: { michael@0: if (iterobj->is()) { michael@0: NativeIterator *ni = iterobj->as().getNativeIterator(); michael@0: if (ni->isKeyIter()) { michael@0: JS_ASSERT(ni->props_cursor < ni->props_end); michael@0: rval.setString(*ni->current()); michael@0: ni->incCursor(); michael@0: return true; michael@0: } michael@0: } michael@0: return js_IteratorNext(cx, iterobj, rval); michael@0: } michael@0: michael@0: /* michael@0: * Compute the implicit |this| parameter for a call expression where the callee michael@0: * funval was resolved from an unqualified name reference to a property on obj michael@0: * (an object on the scope chain). michael@0: * michael@0: * We can avoid computing |this| eagerly and push the implicit callee-coerced michael@0: * |this| value, undefined, if any of these conditions hold: michael@0: * michael@0: * 1. The nominal |this|, obj, is a global object. michael@0: * michael@0: * 2. The nominal |this|, obj, has one of Block, Call, or DeclEnv class (this michael@0: * is what IsCacheableNonGlobalScope tests). Such objects-as-scopes must be michael@0: * censored with undefined. michael@0: * michael@0: * Otherwise, we bind |this| to obj->thisObject(). Only names inside |with| michael@0: * statements and embedding-specific scope objects fall into this category. michael@0: * michael@0: * If the callee is a strict mode function, then code implementing JSOP_THIS michael@0: * in the interpreter and JITs will leave undefined as |this|. If funval is a michael@0: * function not in strict mode, JSOP_THIS code replaces undefined with funval's michael@0: * global. michael@0: * michael@0: * We set *vp to undefined early to reduce code size and bias this code for the michael@0: * common and future-friendly cases. michael@0: */ michael@0: static inline bool michael@0: ComputeImplicitThis(JSContext *cx, HandleObject obj, MutableHandleValue vp) michael@0: { michael@0: vp.setUndefined(); michael@0: michael@0: if (obj->is()) michael@0: return true; michael@0: michael@0: if (IsCacheableNonGlobalScope(obj)) michael@0: return true; michael@0: michael@0: JSObject *nobj = JSObject::thisObject(cx, obj); michael@0: if (!nobj) michael@0: return false; michael@0: michael@0: vp.setObject(*nobj); michael@0: return true; michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE bool michael@0: AddOperation(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) michael@0: { michael@0: if (lhs.isInt32() && rhs.isInt32()) { michael@0: int32_t l = lhs.toInt32(), r = rhs.toInt32(); michael@0: int32_t t; michael@0: if (MOZ_LIKELY(SafeAdd(l, r, &t))) { michael@0: res.setInt32(t); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (!ToPrimitive(cx, lhs)) michael@0: return false; michael@0: if (!ToPrimitive(cx, rhs)) michael@0: return false; michael@0: michael@0: bool lIsString, rIsString; michael@0: if ((lIsString = lhs.isString()) | (rIsString = rhs.isString())) { michael@0: JSString *lstr, *rstr; michael@0: if (lIsString) { michael@0: lstr = lhs.toString(); michael@0: } else { michael@0: lstr = ToString(cx, lhs); michael@0: if (!lstr) michael@0: return false; michael@0: } michael@0: if (rIsString) { michael@0: rstr = rhs.toString(); michael@0: } else { michael@0: // Save/restore lstr in case of GC activity under ToString. michael@0: lhs.setString(lstr); michael@0: rstr = ToString(cx, rhs); michael@0: if (!rstr) michael@0: return false; michael@0: lstr = lhs.toString(); michael@0: } michael@0: JSString *str = ConcatStrings(cx, lstr, rstr); michael@0: if (!str) { michael@0: RootedString nlstr(cx, lstr), nrstr(cx, rstr); michael@0: str = ConcatStrings(cx, nlstr, nrstr); michael@0: if (!str) michael@0: return false; michael@0: } michael@0: res.setString(str); michael@0: } else { michael@0: double l, r; michael@0: if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r)) michael@0: return false; michael@0: res.setNumber(l + r); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE bool michael@0: SubOperation(JSContext *cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) michael@0: { michael@0: double d1, d2; michael@0: if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) michael@0: return false; michael@0: res.setNumber(d1 - d2); michael@0: return true; michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE bool michael@0: MulOperation(JSContext *cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) michael@0: { michael@0: double d1, d2; michael@0: if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) michael@0: return false; michael@0: res.setNumber(d1 * d2); michael@0: return true; michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE bool michael@0: DivOperation(JSContext *cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) michael@0: { michael@0: double d1, d2; michael@0: if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) michael@0: return false; michael@0: res.setNumber(NumberDiv(d1, d2)); michael@0: return true; michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE bool michael@0: ModOperation(JSContext *cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) michael@0: { michael@0: int32_t l, r; michael@0: if (lhs.isInt32() && rhs.isInt32() && michael@0: (l = lhs.toInt32()) >= 0 && (r = rhs.toInt32()) > 0) { michael@0: int32_t mod = l % r; michael@0: res.setInt32(mod); michael@0: return true; michael@0: } michael@0: michael@0: double d1, d2; michael@0: if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) michael@0: return false; michael@0: michael@0: res.setNumber(NumberMod(d1, d2)); michael@0: return true; michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE bool michael@0: SetObjectElementOperation(JSContext *cx, Handle obj, HandleId id, const Value &value, michael@0: bool strict, JSScript *script = nullptr, jsbytecode *pc = nullptr) michael@0: { michael@0: types::TypeScript::MonitorAssign(cx, obj, id); michael@0: michael@0: #ifdef JS_ION michael@0: if (obj->isNative() && JSID_IS_INT(id)) { michael@0: uint32_t length = obj->getDenseInitializedLength(); michael@0: int32_t i = JSID_TO_INT(id); michael@0: if ((uint32_t)i >= length) { michael@0: // Annotate script if provided with information (e.g. baseline) michael@0: if (script && script->hasBaselineScript() && *pc == JSOP_SETELEM) michael@0: script->baselineScript()->noteArrayWriteHole(script->pcToOffset(pc)); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (obj->isNative() && !JSID_IS_INT(id) && !obj->setHadElementsAccess(cx)) michael@0: return false; michael@0: michael@0: RootedValue tmp(cx, value); michael@0: return JSObject::setGeneric(cx, obj, obj, id, &tmp, strict); michael@0: } michael@0: michael@0: static MOZ_NEVER_INLINE bool michael@0: Interpret(JSContext *cx, RunState &state) michael@0: { michael@0: /* michael@0: * Define macros for an interpreter loop. Opcode dispatch may be either by a michael@0: * switch statement or by indirect goto (aka a threaded interpreter), depending michael@0: * on compiler support. michael@0: * michael@0: * Threaded interpretation appears to be well-supported by GCC 3 and higher. michael@0: * IBM's C compiler when run with the right options (e.g., -qlanglvl=extended) michael@0: * also supports threading. Ditto the SunPro C compiler. michael@0: */ michael@0: #if (__GNUC__ >= 3 || \ michael@0: (__IBMC__ >= 700 && defined __IBM_COMPUTED_GOTO) || \ michael@0: __SUNPRO_C >= 0x570) michael@0: // Non-standard but faster indirect-goto-based dispatch. michael@0: # define INTERPRETER_LOOP() michael@0: # define CASE(OP) label_##OP: michael@0: # define DEFAULT() label_default: michael@0: # define DISPATCH_TO(OP) goto *addresses[(OP)] michael@0: michael@0: # define LABEL(X) (&&label_##X) michael@0: michael@0: // Use addresses instead of offsets to optimize for runtime speed over michael@0: // load-time relocation overhead. michael@0: static const void *const addresses[EnableInterruptsPseudoOpcode + 1] = { michael@0: # define OPCODE_LABEL(op, ...) LABEL(op), michael@0: FOR_EACH_OPCODE(OPCODE_LABEL) michael@0: # undef OPCODE_LABEL michael@0: # define TRAILING_LABEL(v) \ michael@0: ((v) == EnableInterruptsPseudoOpcode \ michael@0: ? LABEL(EnableInterruptsPseudoOpcode) \ michael@0: : LABEL(default)), michael@0: FOR_EACH_TRAILING_UNUSED_OPCODE(TRAILING_LABEL) michael@0: # undef TRAILING_LABEL michael@0: }; michael@0: #else michael@0: // Portable switch-based dispatch. michael@0: # define INTERPRETER_LOOP() the_switch: switch (switchOp) michael@0: # define CASE(OP) case OP: michael@0: # define DEFAULT() default: michael@0: # define DISPATCH_TO(OP) \ michael@0: JS_BEGIN_MACRO \ michael@0: switchOp = (OP); \ michael@0: goto the_switch; \ michael@0: JS_END_MACRO michael@0: michael@0: // This variable is effectively a parameter to the_switch. michael@0: jsbytecode switchOp; michael@0: #endif michael@0: michael@0: /* michael@0: * Increment REGS.pc by N, load the opcode at that position, michael@0: * and jump to the code to execute it. michael@0: * michael@0: * When Debugger puts a script in single-step mode, all js::Interpret michael@0: * invocations that might be presently running that script must have michael@0: * interrupts enabled. It's not practical to simply check michael@0: * script->stepModeEnabled() at each point some callee could have changed michael@0: * it, because there are so many places js::Interpret could possibly cause michael@0: * JavaScript to run: each place an object might be coerced to a primitive michael@0: * or a number, for example. So instead, we expose a simple mechanism to michael@0: * let Debugger tweak the affected js::Interpret frames when an onStep michael@0: * handler is added: calling activation.enableInterruptsUnconditionally() michael@0: * will enable interrupts, and activation.opMask() is or'd with the opcode michael@0: * to implement a simple alternate dispatch. michael@0: */ michael@0: #define ADVANCE_AND_DISPATCH(N) \ michael@0: JS_BEGIN_MACRO \ michael@0: REGS.pc += (N); \ michael@0: SANITY_CHECKS(); \ michael@0: DISPATCH_TO(*REGS.pc | activation.opMask()); \ michael@0: JS_END_MACRO michael@0: michael@0: /* michael@0: * Shorthand for the common sequence at the end of a fixed-size opcode. michael@0: */ michael@0: #define END_CASE(OP) ADVANCE_AND_DISPATCH(OP##_LENGTH); michael@0: michael@0: /* michael@0: * Prepare to call a user-supplied branch handler, and abort the script michael@0: * if it returns false. michael@0: */ michael@0: #define CHECK_BRANCH() \ michael@0: JS_BEGIN_MACRO \ michael@0: if (!CheckForInterrupt(cx)) \ michael@0: goto error; \ michael@0: JS_END_MACRO michael@0: michael@0: /* michael@0: * This is a simple wrapper around ADVANCE_AND_DISPATCH which also does michael@0: * a CHECK_BRANCH() if n is not positive, which possibly indicates that it michael@0: * is the backedge of a loop. michael@0: */ michael@0: #define BRANCH(n) \ michael@0: JS_BEGIN_MACRO \ michael@0: int32_t nlen = (n); \ michael@0: if (nlen <= 0) \ michael@0: CHECK_BRANCH(); \ michael@0: ADVANCE_AND_DISPATCH(nlen); \ michael@0: JS_END_MACRO michael@0: michael@0: #define LOAD_DOUBLE(PCOFF, dbl) \ michael@0: ((dbl) = script->getConst(GET_UINT32_INDEX(REGS.pc + (PCOFF))).toDouble()) michael@0: michael@0: #define SET_SCRIPT(s) \ michael@0: JS_BEGIN_MACRO \ michael@0: script = (s); \ michael@0: if (script->hasAnyBreakpointsOrStepMode() || script->hasScriptCounts()) \ michael@0: activation.enableInterruptsUnconditionally(); \ michael@0: JS_END_MACRO michael@0: michael@0: #define SANITY_CHECKS() \ michael@0: JS_BEGIN_MACRO \ michael@0: js::gc::MaybeVerifyBarriers(cx); \ michael@0: JS_ASSERT_IF(script->hasScriptCounts(), \ michael@0: activation.opMask() == EnableInterruptsPseudoOpcode); \ michael@0: JS_END_MACRO michael@0: michael@0: gc::MaybeVerifyBarriers(cx, true); michael@0: JS_ASSERT(!cx->compartment()->activeAnalysis); michael@0: michael@0: InterpreterFrame *entryFrame = state.pushInterpreterFrame(cx); michael@0: if (!entryFrame) michael@0: return false; michael@0: michael@0: InterpreterActivation activation(state, cx, entryFrame); michael@0: michael@0: /* The script is used frequently, so keep a local copy. */ michael@0: RootedScript script(cx); michael@0: SET_SCRIPT(REGS.fp()->script()); michael@0: michael@0: TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); michael@0: uint32_t scriptLogId = TraceLogCreateTextId(logger, script); michael@0: TraceLogStartEvent(logger, scriptLogId); michael@0: TraceLogStartEvent(logger, TraceLogger::Interpreter); michael@0: michael@0: /* michael@0: * Pool of rooters for use in this interpreter frame. References to these michael@0: * are used for local variables within interpreter cases. This avoids michael@0: * creating new rooters each time an interpreter case is entered, and also michael@0: * correctness pitfalls due to incorrect compilation of destructor calls michael@0: * around computed gotos. michael@0: */ michael@0: RootedValue rootValue0(cx), rootValue1(cx); michael@0: RootedString rootString0(cx), rootString1(cx); michael@0: RootedObject rootObject0(cx), rootObject1(cx), rootObject2(cx); michael@0: RootedFunction rootFunction0(cx); michael@0: RootedTypeObject rootType0(cx); michael@0: RootedPropertyName rootName0(cx); michael@0: RootedId rootId0(cx); michael@0: RootedShape rootShape0(cx); michael@0: RootedScript rootScript0(cx); michael@0: DebugOnly blockDepth; michael@0: michael@0: if (MOZ_UNLIKELY(REGS.fp()->isGeneratorFrame())) { michael@0: JS_ASSERT(script->containsPC(REGS.pc)); michael@0: JS_ASSERT(REGS.stackDepth() <= script->nslots()); michael@0: michael@0: /* michael@0: * To support generator_throw and to catch ignored exceptions, michael@0: * fail if cx->isExceptionPending() is true. michael@0: */ michael@0: if (cx->isExceptionPending()) { michael@0: probes::EnterScript(cx, script, script->functionNonDelazifying(), REGS.fp()); michael@0: goto error; michael@0: } michael@0: } michael@0: michael@0: /* State communicated between non-local jumps: */ michael@0: bool interpReturnOK; michael@0: michael@0: if (!activation.entryFrame()->isGeneratorFrame()) { michael@0: if (!activation.entryFrame()->prologue(cx)) michael@0: goto error; michael@0: } else { michael@0: if (!probes::EnterScript(cx, script, script->functionNonDelazifying(), michael@0: activation.entryFrame())) michael@0: { michael@0: goto error; michael@0: } michael@0: } michael@0: if (MOZ_UNLIKELY(cx->compartment()->debugMode())) { michael@0: JSTrapStatus status = ScriptDebugPrologue(cx, activation.entryFrame(), REGS.pc); michael@0: switch (status) { michael@0: case JSTRAP_CONTINUE: michael@0: break; michael@0: case JSTRAP_RETURN: michael@0: ForcedReturn(cx, REGS); michael@0: goto successful_return_continuation; michael@0: case JSTRAP_THROW: michael@0: case JSTRAP_ERROR: michael@0: goto error; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad ScriptDebugPrologue status"); michael@0: } michael@0: } michael@0: michael@0: if (cx->runtime()->profilingScripts || cx->runtime()->debugHooks.interruptHook) michael@0: activation.enableInterruptsUnconditionally(); michael@0: michael@0: // Enter the interpreter loop starting at the current pc. michael@0: ADVANCE_AND_DISPATCH(0); michael@0: michael@0: INTERPRETER_LOOP() { michael@0: michael@0: CASE(EnableInterruptsPseudoOpcode) michael@0: { michael@0: bool moreInterrupts = false; michael@0: jsbytecode op = *REGS.pc; michael@0: michael@0: if (cx->runtime()->profilingScripts) { michael@0: if (!script->hasScriptCounts()) michael@0: script->initScriptCounts(cx); michael@0: moreInterrupts = true; michael@0: } michael@0: michael@0: if (script->hasScriptCounts()) { michael@0: PCCounts counts = script->getPCCounts(REGS.pc); michael@0: counts.get(PCCounts::BASE_INTERP)++; michael@0: moreInterrupts = true; michael@0: } michael@0: michael@0: if (cx->compartment()->debugMode()) { michael@0: JSInterruptHook hook = cx->runtime()->debugHooks.interruptHook; michael@0: if (hook || script->stepModeEnabled()) { michael@0: RootedValue rval(cx); michael@0: JSTrapStatus status = JSTRAP_CONTINUE; michael@0: if (hook) michael@0: status = hook(cx, script, REGS.pc, rval.address(), michael@0: cx->runtime()->debugHooks.interruptHookData); michael@0: if (status == JSTRAP_CONTINUE && script->stepModeEnabled()) michael@0: status = Debugger::onSingleStep(cx, &rval); michael@0: switch (status) { michael@0: case JSTRAP_ERROR: michael@0: goto error; michael@0: case JSTRAP_CONTINUE: michael@0: break; michael@0: case JSTRAP_RETURN: michael@0: REGS.fp()->setReturnValue(rval); michael@0: ForcedReturn(cx, REGS); michael@0: goto successful_return_continuation; michael@0: case JSTRAP_THROW: michael@0: cx->setPendingException(rval); michael@0: goto error; michael@0: default:; michael@0: } michael@0: moreInterrupts = true; michael@0: } michael@0: michael@0: if (script->hasAnyBreakpointsOrStepMode()) michael@0: moreInterrupts = true; michael@0: michael@0: if (script->hasBreakpointsAt(REGS.pc)) { michael@0: RootedValue rval(cx); michael@0: JSTrapStatus status = Debugger::onTrap(cx, &rval); michael@0: switch (status) { michael@0: case JSTRAP_ERROR: michael@0: goto error; michael@0: case JSTRAP_RETURN: michael@0: REGS.fp()->setReturnValue(rval); michael@0: ForcedReturn(cx, REGS); michael@0: goto successful_return_continuation; michael@0: case JSTRAP_THROW: michael@0: cx->setPendingException(rval); michael@0: goto error; michael@0: default: michael@0: break; michael@0: } michael@0: JS_ASSERT(status == JSTRAP_CONTINUE); michael@0: JS_ASSERT(rval.isInt32() && rval.toInt32() == op); michael@0: } michael@0: } michael@0: michael@0: JS_ASSERT(activation.opMask() == EnableInterruptsPseudoOpcode); michael@0: if (!moreInterrupts) michael@0: activation.clearInterruptsMask(); michael@0: michael@0: /* Commence executing the actual opcode. */ michael@0: SANITY_CHECKS(); michael@0: DISPATCH_TO(op); michael@0: } michael@0: michael@0: /* Various 1-byte no-ops. */ michael@0: CASE(JSOP_NOP) michael@0: CASE(JSOP_UNUSED2) michael@0: CASE(JSOP_UNUSED45) michael@0: CASE(JSOP_UNUSED46) michael@0: CASE(JSOP_UNUSED47) michael@0: CASE(JSOP_UNUSED48) michael@0: CASE(JSOP_UNUSED49) michael@0: CASE(JSOP_UNUSED50) michael@0: CASE(JSOP_UNUSED51) michael@0: CASE(JSOP_UNUSED52) michael@0: CASE(JSOP_UNUSED57) michael@0: CASE(JSOP_UNUSED101) michael@0: CASE(JSOP_UNUSED102) michael@0: CASE(JSOP_UNUSED103) michael@0: CASE(JSOP_UNUSED104) michael@0: CASE(JSOP_UNUSED105) michael@0: CASE(JSOP_UNUSED107) michael@0: CASE(JSOP_UNUSED124) michael@0: CASE(JSOP_UNUSED125) michael@0: CASE(JSOP_UNUSED126) michael@0: CASE(JSOP_UNUSED138) michael@0: CASE(JSOP_UNUSED139) michael@0: CASE(JSOP_UNUSED140) michael@0: CASE(JSOP_UNUSED141) michael@0: CASE(JSOP_UNUSED142) michael@0: CASE(JSOP_UNUSED146) michael@0: CASE(JSOP_UNUSED147) michael@0: CASE(JSOP_UNUSED148) michael@0: CASE(JSOP_BACKPATCH) michael@0: CASE(JSOP_UNUSED150) michael@0: CASE(JSOP_UNUSED156) michael@0: CASE(JSOP_UNUSED157) michael@0: CASE(JSOP_UNUSED158) michael@0: CASE(JSOP_UNUSED159) michael@0: CASE(JSOP_UNUSED161) michael@0: CASE(JSOP_UNUSED162) michael@0: CASE(JSOP_UNUSED163) michael@0: CASE(JSOP_UNUSED164) michael@0: CASE(JSOP_UNUSED165) michael@0: CASE(JSOP_UNUSED166) michael@0: CASE(JSOP_UNUSED167) michael@0: CASE(JSOP_UNUSED168) michael@0: CASE(JSOP_UNUSED169) michael@0: CASE(JSOP_UNUSED170) michael@0: CASE(JSOP_UNUSED171) michael@0: CASE(JSOP_UNUSED172) michael@0: CASE(JSOP_UNUSED173) michael@0: CASE(JSOP_UNUSED174) michael@0: CASE(JSOP_UNUSED175) michael@0: CASE(JSOP_UNUSED176) michael@0: CASE(JSOP_UNUSED177) michael@0: CASE(JSOP_UNUSED178) michael@0: CASE(JSOP_UNUSED179) michael@0: CASE(JSOP_UNUSED180) michael@0: CASE(JSOP_UNUSED181) michael@0: CASE(JSOP_UNUSED182) michael@0: CASE(JSOP_UNUSED183) michael@0: CASE(JSOP_UNUSED185) michael@0: CASE(JSOP_UNUSED186) michael@0: CASE(JSOP_UNUSED187) michael@0: CASE(JSOP_UNUSED189) michael@0: CASE(JSOP_UNUSED190) michael@0: CASE(JSOP_UNUSED191) michael@0: CASE(JSOP_UNUSED192) michael@0: CASE(JSOP_UNUSED196) michael@0: CASE(JSOP_UNUSED201) michael@0: CASE(JSOP_UNUSED205) michael@0: CASE(JSOP_UNUSED206) michael@0: CASE(JSOP_UNUSED207) michael@0: CASE(JSOP_UNUSED208) michael@0: CASE(JSOP_UNUSED209) michael@0: CASE(JSOP_UNUSED210) michael@0: CASE(JSOP_UNUSED211) michael@0: CASE(JSOP_UNUSED212) michael@0: CASE(JSOP_UNUSED213) michael@0: CASE(JSOP_UNUSED219) michael@0: CASE(JSOP_UNUSED220) michael@0: CASE(JSOP_UNUSED221) michael@0: CASE(JSOP_UNUSED222) michael@0: CASE(JSOP_UNUSED223) michael@0: CASE(JSOP_CONDSWITCH) michael@0: CASE(JSOP_TRY) michael@0: { michael@0: JS_ASSERT(js_CodeSpec[*REGS.pc].length == 1); michael@0: ADVANCE_AND_DISPATCH(1); michael@0: } michael@0: michael@0: CASE(JSOP_LOOPHEAD) michael@0: END_CASE(JSOP_LOOPHEAD) michael@0: michael@0: CASE(JSOP_LABEL) michael@0: END_CASE(JSOP_LABEL) michael@0: michael@0: CASE(JSOP_LOOPENTRY) michael@0: michael@0: #ifdef JS_ION michael@0: // Attempt on-stack replacement with Baseline code. michael@0: if (jit::IsBaselineEnabled(cx)) { michael@0: jit::MethodStatus status = jit::CanEnterBaselineAtBranch(cx, REGS.fp(), false); michael@0: if (status == jit::Method_Error) michael@0: goto error; michael@0: if (status == jit::Method_Compiled) { michael@0: bool wasSPS = REGS.fp()->hasPushedSPSFrame(); michael@0: jit::IonExecStatus maybeOsr = jit::EnterBaselineAtBranch(cx, REGS.fp(), REGS.pc); michael@0: michael@0: // We failed to call into baseline at all, so treat as an error. michael@0: if (maybeOsr == jit::IonExec_Aborted) michael@0: goto error; michael@0: michael@0: interpReturnOK = (maybeOsr == jit::IonExec_Ok); michael@0: michael@0: // Pop the SPS frame pushed by the interpreter. (The compiled version of the michael@0: // function popped a copy of the frame pushed by the OSR trampoline.) michael@0: if (wasSPS) michael@0: cx->runtime()->spsProfiler.exit(script, script->functionNonDelazifying()); michael@0: michael@0: if (activation.entryFrame() != REGS.fp()) michael@0: goto jit_return_pop_frame; michael@0: goto leave_on_safe_point; michael@0: } michael@0: } michael@0: #endif /* JS_ION */ michael@0: michael@0: END_CASE(JSOP_LOOPENTRY) michael@0: michael@0: CASE(JSOP_LINENO) michael@0: END_CASE(JSOP_LINENO) michael@0: michael@0: CASE(JSOP_UNDEFINED) michael@0: PUSH_UNDEFINED(); michael@0: END_CASE(JSOP_UNDEFINED) michael@0: michael@0: CASE(JSOP_POP) michael@0: REGS.sp--; michael@0: END_CASE(JSOP_POP) michael@0: michael@0: CASE(JSOP_POPN) michael@0: JS_ASSERT(GET_UINT16(REGS.pc) <= REGS.stackDepth()); michael@0: REGS.sp -= GET_UINT16(REGS.pc); michael@0: END_CASE(JSOP_POPN) michael@0: michael@0: CASE(JSOP_DUPAT) michael@0: { michael@0: JS_ASSERT(GET_UINT24(REGS.pc) < REGS.stackDepth()); michael@0: unsigned i = GET_UINT24(REGS.pc); michael@0: const Value &rref = REGS.sp[-int(i + 1)]; michael@0: PUSH_COPY(rref); michael@0: } michael@0: END_CASE(JSOP_DUPAT) michael@0: michael@0: CASE(JSOP_SETRVAL) michael@0: POP_RETURN_VALUE(); michael@0: END_CASE(JSOP_SETRVAL) michael@0: michael@0: CASE(JSOP_ENTERWITH) michael@0: { michael@0: RootedValue &val = rootValue0; michael@0: RootedObject &staticWith = rootObject0; michael@0: val = REGS.sp[-1]; michael@0: REGS.sp--; michael@0: staticWith = script->getObject(REGS.pc); michael@0: michael@0: if (!EnterWithOperation(cx, REGS.fp(), val, staticWith)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_ENTERWITH) michael@0: michael@0: CASE(JSOP_LEAVEWITH) michael@0: REGS.fp()->popWith(cx); michael@0: END_CASE(JSOP_LEAVEWITH) michael@0: michael@0: CASE(JSOP_RETURN) michael@0: POP_RETURN_VALUE(); michael@0: /* FALL THROUGH */ michael@0: michael@0: CASE(JSOP_RETRVAL) michael@0: { michael@0: /* michael@0: * When the inlined frame exits with an exception or an error, ok will be michael@0: * false after the inline_return label. michael@0: */ michael@0: CHECK_BRANCH(); michael@0: michael@0: successful_return_continuation: michael@0: interpReturnOK = true; michael@0: return_continuation: michael@0: if (activation.entryFrame() != REGS.fp()) michael@0: inline_return: michael@0: { michael@0: // Stop the engine. (No details about which engine exactly, could be michael@0: // interpreter, Baseline or IonMonkey.) michael@0: TraceLogStopEvent(logger); michael@0: // Stop the script. (Again no details about which script exactly.) michael@0: TraceLogStopEvent(logger); michael@0: michael@0: if (MOZ_UNLIKELY(cx->compartment()->debugMode())) michael@0: interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), REGS.pc, interpReturnOK); michael@0: michael@0: if (!REGS.fp()->isYielding()) michael@0: REGS.fp()->epilogue(cx); michael@0: else michael@0: probes::ExitScript(cx, script, script->functionNonDelazifying(), michael@0: REGS.fp()->hasPushedSPSFrame()); michael@0: michael@0: #if defined(JS_ION) michael@0: jit_return_pop_frame: michael@0: #endif michael@0: michael@0: activation.popInlineFrame(REGS.fp()); michael@0: SET_SCRIPT(REGS.fp()->script()); michael@0: michael@0: #if defined(JS_ION) michael@0: jit_return: michael@0: #endif michael@0: michael@0: JS_ASSERT(js_CodeSpec[*REGS.pc].format & JOF_INVOKE); michael@0: michael@0: /* Resume execution in the calling frame. */ michael@0: if (MOZ_LIKELY(interpReturnOK)) { michael@0: TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]); michael@0: michael@0: ADVANCE_AND_DISPATCH(JSOP_CALL_LENGTH); michael@0: } michael@0: michael@0: /* Increment pc so that |sp - fp->slots == ReconstructStackDepth(pc)|. */ michael@0: REGS.pc += JSOP_CALL_LENGTH; michael@0: goto error; michael@0: } else { michael@0: JS_ASSERT(REGS.stackDepth() == 0); michael@0: } michael@0: goto exit; michael@0: } michael@0: michael@0: CASE(JSOP_DEFAULT) michael@0: REGS.sp--; michael@0: /* FALL THROUGH */ michael@0: CASE(JSOP_GOTO) michael@0: { michael@0: BRANCH(GET_JUMP_OFFSET(REGS.pc)); michael@0: } michael@0: michael@0: CASE(JSOP_IFEQ) michael@0: { michael@0: bool cond = ToBooleanOp(REGS); michael@0: REGS.sp--; michael@0: if (!cond) michael@0: BRANCH(GET_JUMP_OFFSET(REGS.pc)); michael@0: } michael@0: END_CASE(JSOP_IFEQ) michael@0: michael@0: CASE(JSOP_IFNE) michael@0: { michael@0: bool cond = ToBooleanOp(REGS); michael@0: REGS.sp--; michael@0: if (cond) michael@0: BRANCH(GET_JUMP_OFFSET(REGS.pc)); michael@0: } michael@0: END_CASE(JSOP_IFNE) michael@0: michael@0: CASE(JSOP_OR) michael@0: { michael@0: bool cond = ToBooleanOp(REGS); michael@0: if (cond) michael@0: ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc)); michael@0: } michael@0: END_CASE(JSOP_OR) michael@0: michael@0: CASE(JSOP_AND) michael@0: { michael@0: bool cond = ToBooleanOp(REGS); michael@0: if (!cond) michael@0: ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc)); michael@0: } michael@0: END_CASE(JSOP_AND) michael@0: michael@0: #define FETCH_ELEMENT_ID(n, id) \ michael@0: JS_BEGIN_MACRO \ michael@0: if (!ValueToId(cx, REGS.stackHandleAt(n), &(id))) \ michael@0: goto error; \ michael@0: JS_END_MACRO michael@0: michael@0: #define TRY_BRANCH_AFTER_COND(cond,spdec) \ michael@0: JS_BEGIN_MACRO \ michael@0: JS_ASSERT(js_CodeSpec[*REGS.pc].length == 1); \ michael@0: unsigned diff_ = (unsigned) GET_UINT8(REGS.pc) - (unsigned) JSOP_IFEQ; \ michael@0: if (diff_ <= 1) { \ michael@0: REGS.sp -= (spdec); \ michael@0: if ((cond) == (diff_ != 0)) { \ michael@0: ++REGS.pc; \ michael@0: BRANCH(GET_JUMP_OFFSET(REGS.pc)); \ michael@0: } \ michael@0: ADVANCE_AND_DISPATCH(1 + JSOP_IFEQ_LENGTH); \ michael@0: } \ michael@0: JS_END_MACRO michael@0: michael@0: CASE(JSOP_IN) michael@0: { michael@0: HandleValue rref = REGS.stackHandleAt(-1); michael@0: if (!rref.isObject()) { michael@0: js_ReportValueError(cx, JSMSG_IN_NOT_OBJECT, -1, rref, js::NullPtr()); michael@0: goto error; michael@0: } michael@0: RootedObject &obj = rootObject0; michael@0: obj = &rref.toObject(); michael@0: RootedId &id = rootId0; michael@0: FETCH_ELEMENT_ID(-2, id); michael@0: RootedObject &obj2 = rootObject1; michael@0: RootedShape &prop = rootShape0; michael@0: if (!JSObject::lookupGeneric(cx, obj, id, &obj2, &prop)) michael@0: goto error; michael@0: bool cond = prop != nullptr; michael@0: prop = nullptr; michael@0: TRY_BRANCH_AFTER_COND(cond, 2); michael@0: REGS.sp--; michael@0: REGS.sp[-1].setBoolean(cond); michael@0: } michael@0: END_CASE(JSOP_IN) michael@0: michael@0: CASE(JSOP_ITER) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 1); michael@0: uint8_t flags = GET_UINT8(REGS.pc); michael@0: MutableHandleValue res = REGS.stackHandleAt(-1); michael@0: if (!ValueToIterator(cx, flags, res)) michael@0: goto error; michael@0: JS_ASSERT(!res.isPrimitive()); michael@0: } michael@0: END_CASE(JSOP_ITER) michael@0: michael@0: CASE(JSOP_MOREITER) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 1); michael@0: JS_ASSERT(REGS.sp[-1].isObject()); michael@0: PUSH_NULL(); michael@0: bool cond; michael@0: MutableHandleValue res = REGS.stackHandleAt(-1); michael@0: if (!IteratorMore(cx, ®S.sp[-2].toObject(), &cond, res)) michael@0: goto error; michael@0: REGS.sp[-1].setBoolean(cond); michael@0: } michael@0: END_CASE(JSOP_MOREITER) michael@0: michael@0: CASE(JSOP_ITERNEXT) michael@0: { michael@0: JS_ASSERT(REGS.sp[-1].isObject()); michael@0: PUSH_NULL(); michael@0: MutableHandleValue res = REGS.stackHandleAt(-1); michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.sp[-2].toObject(); michael@0: if (!IteratorNext(cx, obj, res)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_ITERNEXT) michael@0: michael@0: CASE(JSOP_ENDITER) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 1); michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.sp[-1].toObject(); michael@0: bool ok = CloseIterator(cx, obj); michael@0: REGS.sp--; michael@0: if (!ok) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_ENDITER) michael@0: michael@0: CASE(JSOP_DUP) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 1); michael@0: const Value &rref = REGS.sp[-1]; michael@0: PUSH_COPY(rref); michael@0: } michael@0: END_CASE(JSOP_DUP) michael@0: michael@0: CASE(JSOP_DUP2) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 2); michael@0: const Value &lref = REGS.sp[-2]; michael@0: const Value &rref = REGS.sp[-1]; michael@0: PUSH_COPY(lref); michael@0: PUSH_COPY(rref); michael@0: } michael@0: END_CASE(JSOP_DUP2) michael@0: michael@0: CASE(JSOP_SWAP) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 2); michael@0: Value &lref = REGS.sp[-2]; michael@0: Value &rref = REGS.sp[-1]; michael@0: lref.swap(rref); michael@0: } michael@0: END_CASE(JSOP_SWAP) michael@0: michael@0: CASE(JSOP_PICK) michael@0: { michael@0: unsigned i = GET_UINT8(REGS.pc); michael@0: JS_ASSERT(REGS.stackDepth() >= i + 1); michael@0: Value lval = REGS.sp[-int(i + 1)]; michael@0: memmove(REGS.sp - (i + 1), REGS.sp - i, sizeof(Value) * i); michael@0: REGS.sp[-1] = lval; michael@0: } michael@0: END_CASE(JSOP_PICK) michael@0: michael@0: CASE(JSOP_SETCONST) michael@0: { michael@0: RootedPropertyName &name = rootName0; michael@0: name = script->getName(REGS.pc); michael@0: michael@0: RootedValue &rval = rootValue0; michael@0: rval = REGS.sp[-1]; michael@0: michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.fp()->varObj(); michael@0: michael@0: if (!SetConstOperation(cx, obj, name, rval)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_SETCONST); michael@0: michael@0: CASE(JSOP_BINDGNAME) michael@0: PUSH_OBJECT(REGS.fp()->global()); michael@0: END_CASE(JSOP_BINDGNAME) michael@0: michael@0: CASE(JSOP_BINDINTRINSIC) michael@0: PUSH_OBJECT(*cx->global()->intrinsicsHolder()); michael@0: END_CASE(JSOP_BINDINTRINSIC) michael@0: michael@0: CASE(JSOP_BINDNAME) michael@0: { michael@0: RootedObject &scopeChain = rootObject0; michael@0: scopeChain = REGS.fp()->scopeChain(); michael@0: michael@0: RootedPropertyName &name = rootName0; michael@0: name = script->getName(REGS.pc); michael@0: michael@0: /* Assigning to an undeclared name adds a property to the global object. */ michael@0: RootedObject &scope = rootObject1; michael@0: if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &scope)) michael@0: goto error; michael@0: michael@0: PUSH_OBJECT(*scope); michael@0: } michael@0: END_CASE(JSOP_BINDNAME) michael@0: michael@0: #define BITWISE_OP(OP) \ michael@0: JS_BEGIN_MACRO \ michael@0: int32_t i, j; \ michael@0: if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \ michael@0: goto error; \ michael@0: if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \ michael@0: goto error; \ michael@0: i = i OP j; \ michael@0: REGS.sp--; \ michael@0: REGS.sp[-1].setInt32(i); \ michael@0: JS_END_MACRO michael@0: michael@0: CASE(JSOP_BITOR) michael@0: BITWISE_OP(|); michael@0: END_CASE(JSOP_BITOR) michael@0: michael@0: CASE(JSOP_BITXOR) michael@0: BITWISE_OP(^); michael@0: END_CASE(JSOP_BITXOR) michael@0: michael@0: CASE(JSOP_BITAND) michael@0: BITWISE_OP(&); michael@0: END_CASE(JSOP_BITAND) michael@0: michael@0: #undef BITWISE_OP michael@0: michael@0: CASE(JSOP_EQ) michael@0: if (!LooseEqualityOp(cx, REGS)) michael@0: goto error; michael@0: END_CASE(JSOP_EQ) michael@0: michael@0: CASE(JSOP_NE) michael@0: if (!LooseEqualityOp(cx, REGS)) michael@0: goto error; michael@0: END_CASE(JSOP_NE) michael@0: michael@0: #define STRICT_EQUALITY_OP(OP, COND) \ michael@0: JS_BEGIN_MACRO \ michael@0: const Value &rref = REGS.sp[-1]; \ michael@0: const Value &lref = REGS.sp[-2]; \ michael@0: bool equal; \ michael@0: if (!StrictlyEqual(cx, lref, rref, &equal)) \ michael@0: goto error; \ michael@0: (COND) = equal OP true; \ michael@0: REGS.sp--; \ michael@0: JS_END_MACRO michael@0: michael@0: CASE(JSOP_STRICTEQ) michael@0: { michael@0: bool cond; michael@0: STRICT_EQUALITY_OP(==, cond); michael@0: REGS.sp[-1].setBoolean(cond); michael@0: } michael@0: END_CASE(JSOP_STRICTEQ) michael@0: michael@0: CASE(JSOP_STRICTNE) michael@0: { michael@0: bool cond; michael@0: STRICT_EQUALITY_OP(!=, cond); michael@0: REGS.sp[-1].setBoolean(cond); michael@0: } michael@0: END_CASE(JSOP_STRICTNE) michael@0: michael@0: CASE(JSOP_CASE) michael@0: { michael@0: bool cond; michael@0: STRICT_EQUALITY_OP(==, cond); michael@0: if (cond) { michael@0: REGS.sp--; michael@0: BRANCH(GET_JUMP_OFFSET(REGS.pc)); michael@0: } michael@0: } michael@0: END_CASE(JSOP_CASE) michael@0: michael@0: #undef STRICT_EQUALITY_OP michael@0: michael@0: CASE(JSOP_LT) michael@0: { michael@0: bool cond; michael@0: MutableHandleValue lval = REGS.stackHandleAt(-2); michael@0: MutableHandleValue rval = REGS.stackHandleAt(-1); michael@0: if (!LessThanOperation(cx, lval, rval, &cond)) michael@0: goto error; michael@0: TRY_BRANCH_AFTER_COND(cond, 2); michael@0: REGS.sp[-2].setBoolean(cond); michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_LT) michael@0: michael@0: CASE(JSOP_LE) michael@0: { michael@0: bool cond; michael@0: MutableHandleValue lval = REGS.stackHandleAt(-2); michael@0: MutableHandleValue rval = REGS.stackHandleAt(-1); michael@0: if (!LessThanOrEqualOperation(cx, lval, rval, &cond)) michael@0: goto error; michael@0: TRY_BRANCH_AFTER_COND(cond, 2); michael@0: REGS.sp[-2].setBoolean(cond); michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_LE) michael@0: michael@0: CASE(JSOP_GT) michael@0: { michael@0: bool cond; michael@0: MutableHandleValue lval = REGS.stackHandleAt(-2); michael@0: MutableHandleValue rval = REGS.stackHandleAt(-1); michael@0: if (!GreaterThanOperation(cx, lval, rval, &cond)) michael@0: goto error; michael@0: TRY_BRANCH_AFTER_COND(cond, 2); michael@0: REGS.sp[-2].setBoolean(cond); michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_GT) michael@0: michael@0: CASE(JSOP_GE) michael@0: { michael@0: bool cond; michael@0: MutableHandleValue lval = REGS.stackHandleAt(-2); michael@0: MutableHandleValue rval = REGS.stackHandleAt(-1); michael@0: if (!GreaterThanOrEqualOperation(cx, lval, rval, &cond)) michael@0: goto error; michael@0: TRY_BRANCH_AFTER_COND(cond, 2); michael@0: REGS.sp[-2].setBoolean(cond); michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_GE) michael@0: michael@0: #define SIGNED_SHIFT_OP(OP) \ michael@0: JS_BEGIN_MACRO \ michael@0: int32_t i, j; \ michael@0: if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \ michael@0: goto error; \ michael@0: if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \ michael@0: goto error; \ michael@0: i = i OP (j & 31); \ michael@0: REGS.sp--; \ michael@0: REGS.sp[-1].setInt32(i); \ michael@0: JS_END_MACRO michael@0: michael@0: CASE(JSOP_LSH) michael@0: SIGNED_SHIFT_OP(<<); michael@0: END_CASE(JSOP_LSH) michael@0: michael@0: CASE(JSOP_RSH) michael@0: SIGNED_SHIFT_OP(>>); michael@0: END_CASE(JSOP_RSH) michael@0: michael@0: #undef SIGNED_SHIFT_OP michael@0: michael@0: CASE(JSOP_URSH) michael@0: { michael@0: HandleValue lval = REGS.stackHandleAt(-2); michael@0: HandleValue rval = REGS.stackHandleAt(-1); michael@0: MutableHandleValue res = REGS.stackHandleAt(-2); michael@0: if (!UrshOperation(cx, lval, rval, res)) michael@0: goto error; michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_URSH) michael@0: michael@0: CASE(JSOP_ADD) michael@0: { michael@0: MutableHandleValue lval = REGS.stackHandleAt(-2); michael@0: MutableHandleValue rval = REGS.stackHandleAt(-1); michael@0: MutableHandleValue res = REGS.stackHandleAt(-2); michael@0: if (!AddOperation(cx, lval, rval, res)) michael@0: goto error; michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_ADD) michael@0: michael@0: CASE(JSOP_SUB) michael@0: { michael@0: RootedValue &lval = rootValue0, &rval = rootValue1; michael@0: lval = REGS.sp[-2]; michael@0: rval = REGS.sp[-1]; michael@0: MutableHandleValue res = REGS.stackHandleAt(-2); michael@0: if (!SubOperation(cx, lval, rval, res)) michael@0: goto error; michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_SUB) michael@0: michael@0: CASE(JSOP_MUL) michael@0: { michael@0: RootedValue &lval = rootValue0, &rval = rootValue1; michael@0: lval = REGS.sp[-2]; michael@0: rval = REGS.sp[-1]; michael@0: MutableHandleValue res = REGS.stackHandleAt(-2); michael@0: if (!MulOperation(cx, lval, rval, res)) michael@0: goto error; michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_MUL) michael@0: michael@0: CASE(JSOP_DIV) michael@0: { michael@0: RootedValue &lval = rootValue0, &rval = rootValue1; michael@0: lval = REGS.sp[-2]; michael@0: rval = REGS.sp[-1]; michael@0: MutableHandleValue res = REGS.stackHandleAt(-2); michael@0: if (!DivOperation(cx, lval, rval, res)) michael@0: goto error; michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_DIV) michael@0: michael@0: CASE(JSOP_MOD) michael@0: { michael@0: RootedValue &lval = rootValue0, &rval = rootValue1; michael@0: lval = REGS.sp[-2]; michael@0: rval = REGS.sp[-1]; michael@0: MutableHandleValue res = REGS.stackHandleAt(-2); michael@0: if (!ModOperation(cx, lval, rval, res)) michael@0: goto error; michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_MOD) michael@0: michael@0: CASE(JSOP_NOT) michael@0: { michael@0: bool cond = ToBooleanOp(REGS); michael@0: REGS.sp--; michael@0: PUSH_BOOLEAN(!cond); michael@0: } michael@0: END_CASE(JSOP_NOT) michael@0: michael@0: CASE(JSOP_BITNOT) michael@0: { michael@0: int32_t i; michael@0: HandleValue value = REGS.stackHandleAt(-1); michael@0: if (!BitNot(cx, value, &i)) michael@0: goto error; michael@0: REGS.sp[-1].setInt32(i); michael@0: } michael@0: END_CASE(JSOP_BITNOT) michael@0: michael@0: CASE(JSOP_NEG) michael@0: { michael@0: RootedValue &val = rootValue0; michael@0: val = REGS.sp[-1]; michael@0: MutableHandleValue res = REGS.stackHandleAt(-1); michael@0: if (!NegOperation(cx, script, REGS.pc, val, res)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_NEG) michael@0: michael@0: CASE(JSOP_POS) michael@0: if (!ToNumber(cx, REGS.stackHandleAt(-1))) michael@0: goto error; michael@0: END_CASE(JSOP_POS) michael@0: michael@0: CASE(JSOP_DELNAME) michael@0: { michael@0: /* Strict mode code should never contain JSOP_DELNAME opcodes. */ michael@0: JS_ASSERT(!script->strict()); michael@0: michael@0: RootedPropertyName &name = rootName0; michael@0: name = script->getName(REGS.pc); michael@0: michael@0: RootedObject &scopeObj = rootObject0; michael@0: scopeObj = REGS.fp()->scopeChain(); michael@0: michael@0: PUSH_BOOLEAN(true); michael@0: MutableHandleValue res = REGS.stackHandleAt(-1); michael@0: if (!DeleteNameOperation(cx, name, scopeObj, res)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_DELNAME) michael@0: michael@0: CASE(JSOP_DELPROP) michael@0: { michael@0: RootedPropertyName &name = rootName0; michael@0: name = script->getName(REGS.pc); michael@0: michael@0: RootedObject &obj = rootObject0; michael@0: FETCH_OBJECT(cx, -1, obj); michael@0: michael@0: bool succeeded; michael@0: if (!JSObject::deleteProperty(cx, obj, name, &succeeded)) michael@0: goto error; michael@0: if (!succeeded && script->strict()) { michael@0: obj->reportNotConfigurable(cx, NameToId(name)); michael@0: goto error; michael@0: } michael@0: MutableHandleValue res = REGS.stackHandleAt(-1); michael@0: res.setBoolean(succeeded); michael@0: } michael@0: END_CASE(JSOP_DELPROP) michael@0: michael@0: CASE(JSOP_DELELEM) michael@0: { michael@0: /* Fetch the left part and resolve it to a non-null object. */ michael@0: RootedObject &obj = rootObject0; michael@0: FETCH_OBJECT(cx, -2, obj); michael@0: michael@0: RootedValue &propval = rootValue0; michael@0: propval = REGS.sp[-1]; michael@0: michael@0: bool succeeded; michael@0: if (!JSObject::deleteByValue(cx, obj, propval, &succeeded)) michael@0: goto error; michael@0: if (!succeeded && script->strict()) { michael@0: // XXX This observably calls ToString(propval). We should convert to michael@0: // PropertyKey and use that to delete, and to report an error if michael@0: // necessary! michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, propval, &id)) michael@0: goto error; michael@0: obj->reportNotConfigurable(cx, id); michael@0: goto error; michael@0: } michael@0: michael@0: MutableHandleValue res = REGS.stackHandleAt(-2); michael@0: res.setBoolean(succeeded); michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_DELELEM) michael@0: michael@0: CASE(JSOP_TOID) michael@0: { michael@0: /* michael@0: * Increment or decrement requires use to lookup the same property twice, michael@0: * but we need to avoid the observable stringification the second time. michael@0: * There must be an object value below the id, which will not be popped. michael@0: */ michael@0: RootedValue &objval = rootValue0, &idval = rootValue1; michael@0: objval = REGS.sp[-2]; michael@0: idval = REGS.sp[-1]; michael@0: michael@0: MutableHandleValue res = REGS.stackHandleAt(-1); michael@0: if (!ToIdOperation(cx, script, REGS.pc, objval, idval, res)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_TOID) michael@0: michael@0: CASE(JSOP_TYPEOFEXPR) michael@0: CASE(JSOP_TYPEOF) michael@0: { michael@0: REGS.sp[-1].setString(TypeOfOperation(REGS.sp[-1], cx->runtime())); michael@0: } michael@0: END_CASE(JSOP_TYPEOF) michael@0: michael@0: CASE(JSOP_VOID) michael@0: REGS.sp[-1].setUndefined(); michael@0: END_CASE(JSOP_VOID) michael@0: michael@0: CASE(JSOP_THIS) michael@0: if (!ComputeThis(cx, REGS.fp())) michael@0: goto error; michael@0: PUSH_COPY(REGS.fp()->thisValue()); michael@0: END_CASE(JSOP_THIS) michael@0: michael@0: CASE(JSOP_GETPROP) michael@0: CASE(JSOP_GETXPROP) michael@0: CASE(JSOP_LENGTH) michael@0: CASE(JSOP_CALLPROP) michael@0: { michael@0: michael@0: MutableHandleValue lval = REGS.stackHandleAt(-1); michael@0: if (!GetPropertyOperation(cx, REGS.fp(), script, REGS.pc, lval, lval)) michael@0: goto error; michael@0: michael@0: TypeScript::Monitor(cx, script, REGS.pc, lval); michael@0: assertSameCompartmentDebugOnly(cx, lval); michael@0: } michael@0: END_CASE(JSOP_GETPROP) michael@0: michael@0: CASE(JSOP_SETINTRINSIC) michael@0: { michael@0: HandleValue value = REGS.stackHandleAt(-1); michael@0: michael@0: if (!SetIntrinsicOperation(cx, script, REGS.pc, value)) michael@0: goto error; michael@0: michael@0: REGS.sp[-2] = REGS.sp[-1]; michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_SETINTRINSIC) michael@0: michael@0: CASE(JSOP_SETGNAME) michael@0: CASE(JSOP_SETNAME) michael@0: { michael@0: RootedObject &scope = rootObject0; michael@0: scope = ®S.sp[-2].toObject(); michael@0: michael@0: HandleValue value = REGS.stackHandleAt(-1); michael@0: michael@0: if (!SetNameOperation(cx, script, REGS.pc, scope, value)) michael@0: goto error; michael@0: michael@0: REGS.sp[-2] = REGS.sp[-1]; michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_SETNAME) michael@0: michael@0: CASE(JSOP_SETPROP) michael@0: { michael@0: HandleValue lval = REGS.stackHandleAt(-2); michael@0: HandleValue rval = REGS.stackHandleAt(-1); michael@0: michael@0: if (!SetPropertyOperation(cx, script, REGS.pc, lval, rval)) michael@0: goto error; michael@0: michael@0: REGS.sp[-2] = REGS.sp[-1]; michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_SETPROP) michael@0: michael@0: CASE(JSOP_GETELEM) michael@0: CASE(JSOP_CALLELEM) michael@0: { michael@0: MutableHandleValue lval = REGS.stackHandleAt(-2); michael@0: HandleValue rval = REGS.stackHandleAt(-1); michael@0: MutableHandleValue res = REGS.stackHandleAt(-2); michael@0: michael@0: bool done = false; michael@0: if (!GetElemOptimizedArguments(cx, REGS.fp(), lval, rval, res, &done)) michael@0: goto error; michael@0: michael@0: if (!done) { michael@0: if (!GetElementOperation(cx, JSOp(*REGS.pc), lval, rval, res)) michael@0: goto error; michael@0: } michael@0: michael@0: TypeScript::Monitor(cx, script, REGS.pc, res); michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_GETELEM) michael@0: michael@0: CASE(JSOP_SETELEM) michael@0: { michael@0: RootedObject &obj = rootObject0; michael@0: FETCH_OBJECT(cx, -3, obj); michael@0: RootedId &id = rootId0; michael@0: FETCH_ELEMENT_ID(-2, id); michael@0: Value &value = REGS.sp[-1]; michael@0: if (!SetObjectElementOperation(cx, obj, id, value, script->strict())) michael@0: goto error; michael@0: REGS.sp[-3] = value; michael@0: REGS.sp -= 2; michael@0: } michael@0: END_CASE(JSOP_SETELEM) michael@0: michael@0: CASE(JSOP_EVAL) michael@0: { michael@0: CallArgs args = CallArgsFromSp(GET_ARGC(REGS.pc), REGS.sp); michael@0: if (REGS.fp()->scopeChain()->global().valueIsEval(args.calleev())) { michael@0: if (!DirectEval(cx, args)) michael@0: goto error; michael@0: } else { michael@0: if (!Invoke(cx, args)) michael@0: goto error; michael@0: } michael@0: REGS.sp = args.spAfterCall(); michael@0: TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]); michael@0: } michael@0: END_CASE(JSOP_EVAL) michael@0: michael@0: CASE(JSOP_SPREADNEW) michael@0: CASE(JSOP_SPREADCALL) michael@0: if (REGS.fp()->hasPushedSPSFrame()) michael@0: cx->runtime()->spsProfiler.updatePC(script, REGS.pc); michael@0: /* FALL THROUGH */ michael@0: michael@0: CASE(JSOP_SPREADEVAL) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 3); michael@0: RootedObject &aobj = rootObject0; michael@0: aobj = ®S.sp[-1].toObject(); michael@0: michael@0: uint32_t length = aobj->as().length(); michael@0: michael@0: if (length > ARGS_LENGTH_MAX) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: *REGS.pc == JSOP_SPREADNEW ? JSMSG_TOO_MANY_CON_SPREADARGS michael@0: : JSMSG_TOO_MANY_FUN_SPREADARGS); michael@0: goto error; michael@0: } michael@0: michael@0: InvokeArgs args(cx); michael@0: michael@0: if (!args.init(length)) michael@0: return false; michael@0: michael@0: args.setCallee(REGS.sp[-3]); michael@0: args.setThis(REGS.sp[-2]); michael@0: michael@0: if (!GetElements(cx, aobj, length, args.array())) michael@0: goto error; michael@0: michael@0: switch (*REGS.pc) { michael@0: case JSOP_SPREADNEW: michael@0: if (!InvokeConstructor(cx, args)) michael@0: goto error; michael@0: break; michael@0: case JSOP_SPREADCALL: michael@0: if (!Invoke(cx, args)) michael@0: goto error; michael@0: break; michael@0: case JSOP_SPREADEVAL: michael@0: if (REGS.fp()->scopeChain()->global().valueIsEval(args.calleev())) { michael@0: if (!DirectEval(cx, args)) michael@0: goto error; michael@0: } else { michael@0: if (!Invoke(cx, args)) michael@0: goto error; michael@0: } michael@0: break; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad spread opcode"); michael@0: } michael@0: michael@0: REGS.sp -= 2; michael@0: REGS.sp[-1] = args.rval(); michael@0: TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]); michael@0: } michael@0: END_CASE(JSOP_SPREADCALL) michael@0: michael@0: CASE(JSOP_FUNAPPLY) michael@0: { michael@0: CallArgs args = CallArgsFromSp(GET_ARGC(REGS.pc), REGS.sp); michael@0: if (!GuardFunApplyArgumentsOptimization(cx, REGS.fp(), args.calleev(), args.array(), michael@0: args.length())) michael@0: goto error; michael@0: /* FALL THROUGH */ michael@0: } michael@0: michael@0: CASE(JSOP_NEW) michael@0: CASE(JSOP_CALL) michael@0: CASE(JSOP_FUNCALL) michael@0: { michael@0: if (REGS.fp()->hasPushedSPSFrame()) michael@0: cx->runtime()->spsProfiler.updatePC(script, REGS.pc); michael@0: JS_ASSERT(REGS.stackDepth() >= 2 + GET_ARGC(REGS.pc)); michael@0: CallArgs args = CallArgsFromSp(GET_ARGC(REGS.pc), REGS.sp); michael@0: michael@0: bool construct = (*REGS.pc == JSOP_NEW); michael@0: michael@0: RootedFunction &fun = rootFunction0; michael@0: RootedScript &funScript = rootScript0; michael@0: bool isFunction = IsFunctionObject(args.calleev(), fun.address()); michael@0: michael@0: /* michael@0: * Some builtins are marked as clone-at-callsite to increase precision of michael@0: * TI and JITs. michael@0: */ michael@0: if (isFunction && fun->isInterpreted()) { michael@0: funScript = fun->getOrCreateScript(cx); michael@0: if (!funScript) michael@0: goto error; michael@0: if (funScript->shouldCloneAtCallsite()) { michael@0: fun = CloneFunctionAtCallsite(cx, fun, script, REGS.pc); michael@0: if (!fun) michael@0: goto error; michael@0: args.setCallee(ObjectValue(*fun)); michael@0: } michael@0: } michael@0: michael@0: /* Don't bother trying to fast-path calls to scripted non-constructors. */ michael@0: if (!isFunction || !fun->isInterpretedConstructor()) { michael@0: if (construct) { michael@0: if (!InvokeConstructor(cx, args)) michael@0: goto error; michael@0: } else { michael@0: if (!Invoke(cx, args)) michael@0: goto error; michael@0: } michael@0: Value *newsp = args.spAfterCall(); michael@0: TypeScript::Monitor(cx, script, REGS.pc, newsp[-1]); michael@0: REGS.sp = newsp; michael@0: ADVANCE_AND_DISPATCH(JSOP_CALL_LENGTH); michael@0: } michael@0: michael@0: InitialFrameFlags initial = construct ? INITIAL_CONSTRUCT : INITIAL_NONE; michael@0: bool newType = UseNewType(cx, script, REGS.pc); michael@0: michael@0: TypeMonitorCall(cx, args, construct); michael@0: michael@0: #ifdef JS_ION michael@0: { michael@0: InvokeState state(cx, args, initial); michael@0: if (newType) michael@0: state.setUseNewType(); michael@0: michael@0: if (!newType && jit::IsIonEnabled(cx)) { michael@0: jit::MethodStatus status = jit::CanEnter(cx, state); michael@0: if (status == jit::Method_Error) michael@0: goto error; michael@0: if (status == jit::Method_Compiled) { michael@0: jit::IonExecStatus exec = jit::IonCannon(cx, state); michael@0: CHECK_BRANCH(); michael@0: REGS.sp = args.spAfterCall(); michael@0: interpReturnOK = !IsErrorStatus(exec); michael@0: goto jit_return; michael@0: } michael@0: } michael@0: michael@0: if (jit::IsBaselineEnabled(cx)) { michael@0: jit::MethodStatus status = jit::CanEnterBaselineMethod(cx, state); michael@0: if (status == jit::Method_Error) michael@0: goto error; michael@0: if (status == jit::Method_Compiled) { michael@0: jit::IonExecStatus exec = jit::EnterBaselineMethod(cx, state); michael@0: CHECK_BRANCH(); michael@0: REGS.sp = args.spAfterCall(); michael@0: interpReturnOK = !IsErrorStatus(exec); michael@0: goto jit_return; michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: funScript = fun->nonLazyScript(); michael@0: if (!activation.pushInlineFrame(args, funScript, initial)) michael@0: goto error; michael@0: michael@0: if (newType) michael@0: REGS.fp()->setUseNewType(); michael@0: michael@0: SET_SCRIPT(REGS.fp()->script()); michael@0: michael@0: uint32_t scriptLogId = TraceLogCreateTextId(logger, script); michael@0: TraceLogStartEvent(logger, scriptLogId); michael@0: TraceLogStartEvent(logger, TraceLogger::Interpreter); michael@0: michael@0: if (!REGS.fp()->prologue(cx)) michael@0: goto error; michael@0: if (MOZ_UNLIKELY(cx->compartment()->debugMode())) { michael@0: switch (ScriptDebugPrologue(cx, REGS.fp(), REGS.pc)) { michael@0: case JSTRAP_CONTINUE: michael@0: break; michael@0: case JSTRAP_RETURN: michael@0: ForcedReturn(cx, REGS); michael@0: goto successful_return_continuation; michael@0: case JSTRAP_THROW: michael@0: case JSTRAP_ERROR: michael@0: goto error; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad ScriptDebugPrologue status"); michael@0: } michael@0: } michael@0: michael@0: /* Load first op and dispatch it (safe since JSOP_RETRVAL). */ michael@0: ADVANCE_AND_DISPATCH(0); michael@0: } michael@0: michael@0: CASE(JSOP_SETCALL) michael@0: { michael@0: JS_ALWAYS_FALSE(SetCallOperation(cx)); michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_SETCALL) michael@0: michael@0: CASE(JSOP_IMPLICITTHIS) michael@0: { michael@0: RootedPropertyName &name = rootName0; michael@0: name = script->getName(REGS.pc); michael@0: michael@0: RootedObject &scopeObj = rootObject0; michael@0: scopeObj = REGS.fp()->scopeChain(); michael@0: michael@0: RootedObject &scope = rootObject1; michael@0: if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &scope)) michael@0: goto error; michael@0: michael@0: RootedValue &v = rootValue0; michael@0: if (!ComputeImplicitThis(cx, scope, &v)) michael@0: goto error; michael@0: PUSH_COPY(v); michael@0: } michael@0: END_CASE(JSOP_IMPLICITTHIS) michael@0: michael@0: CASE(JSOP_GETGNAME) michael@0: CASE(JSOP_NAME) michael@0: { michael@0: RootedValue &rval = rootValue0; michael@0: michael@0: if (!NameOperation(cx, REGS.fp(), REGS.pc, &rval)) michael@0: goto error; michael@0: michael@0: PUSH_COPY(rval); michael@0: TypeScript::Monitor(cx, script, REGS.pc, rval); michael@0: } michael@0: END_CASE(JSOP_NAME) michael@0: michael@0: CASE(JSOP_GETINTRINSIC) michael@0: { michael@0: RootedValue &rval = rootValue0; michael@0: michael@0: if (!GetIntrinsicOperation(cx, REGS.pc, &rval)) michael@0: goto error; michael@0: michael@0: PUSH_COPY(rval); michael@0: TypeScript::Monitor(cx, script, REGS.pc, rval); michael@0: } michael@0: END_CASE(JSOP_GETINTRINSIC) michael@0: michael@0: CASE(JSOP_UINT16) michael@0: PUSH_INT32((int32_t) GET_UINT16(REGS.pc)); michael@0: END_CASE(JSOP_UINT16) michael@0: michael@0: CASE(JSOP_UINT24) michael@0: PUSH_INT32((int32_t) GET_UINT24(REGS.pc)); michael@0: END_CASE(JSOP_UINT24) michael@0: michael@0: CASE(JSOP_INT8) michael@0: PUSH_INT32(GET_INT8(REGS.pc)); michael@0: END_CASE(JSOP_INT8) michael@0: michael@0: CASE(JSOP_INT32) michael@0: PUSH_INT32(GET_INT32(REGS.pc)); michael@0: END_CASE(JSOP_INT32) michael@0: michael@0: CASE(JSOP_DOUBLE) michael@0: { michael@0: double dbl; michael@0: LOAD_DOUBLE(0, dbl); michael@0: PUSH_DOUBLE(dbl); michael@0: } michael@0: END_CASE(JSOP_DOUBLE) michael@0: michael@0: CASE(JSOP_STRING) michael@0: PUSH_STRING(script->getAtom(REGS.pc)); michael@0: END_CASE(JSOP_STRING) michael@0: michael@0: CASE(JSOP_OBJECT) michael@0: { michael@0: RootedObject &ref = rootObject0; michael@0: ref = script->getObject(REGS.pc); michael@0: if (JS::CompartmentOptionsRef(cx).cloneSingletons(cx)) { michael@0: JSObject *obj = js::DeepCloneObjectLiteral(cx, ref, js::MaybeSingletonObject); michael@0: if (!obj) michael@0: goto error; michael@0: PUSH_OBJECT(*obj); michael@0: } else { michael@0: JS::CompartmentOptionsRef(cx).setSingletonsAsValues(); michael@0: PUSH_OBJECT(*ref); michael@0: } michael@0: } michael@0: END_CASE(JSOP_OBJECT) michael@0: michael@0: CASE(JSOP_REGEXP) michael@0: { michael@0: /* michael@0: * Push a regexp object cloned from the regexp literal object mapped by the michael@0: * bytecode at pc. michael@0: */ michael@0: JSObject *obj = CloneRegExpObject(cx, script->getRegExp(REGS.pc)); michael@0: if (!obj) michael@0: goto error; michael@0: PUSH_OBJECT(*obj); michael@0: } michael@0: END_CASE(JSOP_REGEXP) michael@0: michael@0: CASE(JSOP_ZERO) michael@0: PUSH_INT32(0); michael@0: END_CASE(JSOP_ZERO) michael@0: michael@0: CASE(JSOP_ONE) michael@0: PUSH_INT32(1); michael@0: END_CASE(JSOP_ONE) michael@0: michael@0: CASE(JSOP_NULL) michael@0: PUSH_NULL(); michael@0: END_CASE(JSOP_NULL) michael@0: michael@0: CASE(JSOP_FALSE) michael@0: PUSH_BOOLEAN(false); michael@0: END_CASE(JSOP_FALSE) michael@0: michael@0: CASE(JSOP_TRUE) michael@0: PUSH_BOOLEAN(true); michael@0: END_CASE(JSOP_TRUE) michael@0: michael@0: CASE(JSOP_TABLESWITCH) michael@0: { michael@0: jsbytecode *pc2 = REGS.pc; michael@0: int32_t len = GET_JUMP_OFFSET(pc2); michael@0: michael@0: /* michael@0: * ECMAv2+ forbids conversion of discriminant, so we will skip to the michael@0: * default case if the discriminant isn't already an int jsval. (This michael@0: * opcode is emitted only for dense int-domain switches.) michael@0: */ michael@0: const Value &rref = *--REGS.sp; michael@0: int32_t i; michael@0: if (rref.isInt32()) { michael@0: i = rref.toInt32(); michael@0: } else { michael@0: /* Use mozilla::NumberEqualsInt32 to treat -0 (double) as 0. */ michael@0: if (!rref.isDouble() || !NumberEqualsInt32(rref.toDouble(), &i)) michael@0: ADVANCE_AND_DISPATCH(len); michael@0: } michael@0: michael@0: pc2 += JUMP_OFFSET_LEN; michael@0: int32_t low = GET_JUMP_OFFSET(pc2); michael@0: pc2 += JUMP_OFFSET_LEN; michael@0: int32_t high = GET_JUMP_OFFSET(pc2); michael@0: michael@0: i -= low; michael@0: if ((uint32_t)i < (uint32_t)(high - low + 1)) { michael@0: pc2 += JUMP_OFFSET_LEN + JUMP_OFFSET_LEN * i; michael@0: int32_t off = (int32_t) GET_JUMP_OFFSET(pc2); michael@0: if (off) michael@0: len = off; michael@0: } michael@0: ADVANCE_AND_DISPATCH(len); michael@0: } michael@0: michael@0: CASE(JSOP_ARGUMENTS) michael@0: JS_ASSERT(!REGS.fp()->fun()->hasRest()); michael@0: if (!script->ensureHasAnalyzedArgsUsage(cx)) michael@0: goto error; michael@0: if (script->needsArgsObj()) { michael@0: ArgumentsObject *obj = ArgumentsObject::createExpected(cx, REGS.fp()); michael@0: if (!obj) michael@0: goto error; michael@0: PUSH_COPY(ObjectValue(*obj)); michael@0: } else { michael@0: PUSH_COPY(MagicValue(JS_OPTIMIZED_ARGUMENTS)); michael@0: } michael@0: END_CASE(JSOP_ARGUMENTS) michael@0: michael@0: CASE(JSOP_RUNONCE) michael@0: { michael@0: if (!RunOnceScriptPrologue(cx, script)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_RUNONCE) michael@0: michael@0: CASE(JSOP_REST) michael@0: { michael@0: RootedObject &rest = rootObject0; michael@0: rest = REGS.fp()->createRestParameter(cx); michael@0: if (!rest) michael@0: goto error; michael@0: PUSH_COPY(ObjectValue(*rest)); michael@0: } michael@0: END_CASE(JSOP_REST) michael@0: michael@0: CASE(JSOP_GETALIASEDVAR) michael@0: { michael@0: ScopeCoordinate sc = ScopeCoordinate(REGS.pc); michael@0: PUSH_COPY(REGS.fp()->aliasedVarScope(sc).aliasedVar(sc)); michael@0: TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]); michael@0: } michael@0: END_CASE(JSOP_GETALIASEDVAR) michael@0: michael@0: CASE(JSOP_SETALIASEDVAR) michael@0: { michael@0: ScopeCoordinate sc = ScopeCoordinate(REGS.pc); michael@0: ScopeObject &obj = REGS.fp()->aliasedVarScope(sc); michael@0: michael@0: // Avoid computing the name if no type updates are needed, as this may be michael@0: // expensive on scopes with large numbers of variables. michael@0: PropertyName *name = (obj.hasSingletonType() && !obj.hasLazyType()) michael@0: ? ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, REGS.pc) michael@0: : nullptr; michael@0: michael@0: obj.setAliasedVar(cx, sc, name, REGS.sp[-1]); michael@0: } michael@0: END_CASE(JSOP_SETALIASEDVAR) michael@0: michael@0: CASE(JSOP_GETARG) michael@0: { michael@0: unsigned i = GET_ARGNO(REGS.pc); michael@0: if (script->argsObjAliasesFormals()) michael@0: PUSH_COPY(REGS.fp()->argsObj().arg(i)); michael@0: else michael@0: PUSH_COPY(REGS.fp()->unaliasedFormal(i)); michael@0: } michael@0: END_CASE(JSOP_GETARG) michael@0: michael@0: CASE(JSOP_SETARG) michael@0: { michael@0: unsigned i = GET_ARGNO(REGS.pc); michael@0: if (script->argsObjAliasesFormals()) michael@0: REGS.fp()->argsObj().setArg(i, REGS.sp[-1]); michael@0: else michael@0: REGS.fp()->unaliasedFormal(i) = REGS.sp[-1]; michael@0: } michael@0: END_CASE(JSOP_SETARG) michael@0: michael@0: CASE(JSOP_GETLOCAL) michael@0: { michael@0: uint32_t i = GET_LOCALNO(REGS.pc); michael@0: PUSH_COPY_SKIP_CHECK(REGS.fp()->unaliasedLocal(i)); michael@0: michael@0: /* michael@0: * Skip the same-compartment assertion if the local will be immediately michael@0: * popped. We do not guarantee sync for dead locals when coming in from the michael@0: * method JIT, and a GETLOCAL followed by POP is not considered to be michael@0: * a use of the variable. michael@0: */ michael@0: if (REGS.pc[JSOP_GETLOCAL_LENGTH] != JSOP_POP) michael@0: assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); michael@0: } michael@0: END_CASE(JSOP_GETLOCAL) michael@0: michael@0: CASE(JSOP_SETLOCAL) michael@0: { michael@0: uint32_t i = GET_LOCALNO(REGS.pc); michael@0: REGS.fp()->unaliasedLocal(i) = REGS.sp[-1]; michael@0: } michael@0: END_CASE(JSOP_SETLOCAL) michael@0: michael@0: CASE(JSOP_DEFCONST) michael@0: CASE(JSOP_DEFVAR) michael@0: { michael@0: /* ES5 10.5 step 8 (with subsequent errata). */ michael@0: unsigned attrs = JSPROP_ENUMERATE; michael@0: if (!REGS.fp()->isEvalFrame()) michael@0: attrs |= JSPROP_PERMANENT; michael@0: if (*REGS.pc == JSOP_DEFCONST) michael@0: attrs |= JSPROP_READONLY; michael@0: michael@0: /* Step 8b. */ michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.fp()->varObj(); michael@0: michael@0: RootedPropertyName &name = rootName0; michael@0: name = script->getName(REGS.pc); michael@0: michael@0: if (!DefVarOrConstOperation(cx, obj, name, attrs)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_DEFVAR) michael@0: michael@0: CASE(JSOP_DEFFUN) michael@0: { michael@0: /* michael@0: * A top-level function defined in Global or Eval code (see ECMA-262 michael@0: * Ed. 3), or else a SpiderMonkey extension: a named function statement in michael@0: * a compound statement (not at the top statement level of global code, or michael@0: * at the top level of a function body). michael@0: */ michael@0: RootedFunction &fun = rootFunction0; michael@0: fun = script->getFunction(GET_UINT32_INDEX(REGS.pc)); michael@0: michael@0: if (!DefFunOperation(cx, script, REGS.fp()->scopeChain(), fun)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_DEFFUN) michael@0: michael@0: CASE(JSOP_LAMBDA) michael@0: { michael@0: /* Load the specified function object literal. */ michael@0: RootedFunction &fun = rootFunction0; michael@0: fun = script->getFunction(GET_UINT32_INDEX(REGS.pc)); michael@0: michael@0: JSObject *obj = Lambda(cx, fun, REGS.fp()->scopeChain()); michael@0: if (!obj) michael@0: goto error; michael@0: JS_ASSERT(obj->getProto()); michael@0: PUSH_OBJECT(*obj); michael@0: } michael@0: END_CASE(JSOP_LAMBDA) michael@0: michael@0: CASE(JSOP_LAMBDA_ARROW) michael@0: { michael@0: /* Load the specified function object literal. */ michael@0: RootedFunction &fun = rootFunction0; michael@0: fun = script->getFunction(GET_UINT32_INDEX(REGS.pc)); michael@0: RootedValue &thisv = rootValue0; michael@0: thisv = REGS.sp[-1]; michael@0: JSObject *obj = LambdaArrow(cx, fun, REGS.fp()->scopeChain(), thisv); michael@0: if (!obj) michael@0: goto error; michael@0: JS_ASSERT(obj->getProto()); michael@0: REGS.sp[-1].setObject(*obj); michael@0: } michael@0: END_CASE(JSOP_LAMBDA_ARROW) michael@0: michael@0: CASE(JSOP_CALLEE) michael@0: JS_ASSERT(REGS.fp()->isNonEvalFunctionFrame()); michael@0: PUSH_COPY(REGS.fp()->calleev()); michael@0: END_CASE(JSOP_CALLEE) michael@0: michael@0: CASE(JSOP_INITPROP_GETTER) michael@0: CASE(JSOP_INITPROP_SETTER) michael@0: { michael@0: RootedObject &obj = rootObject0; michael@0: RootedPropertyName &name = rootName0; michael@0: RootedObject &val = rootObject1; michael@0: michael@0: JS_ASSERT(REGS.stackDepth() >= 2); michael@0: obj = ®S.sp[-2].toObject(); michael@0: name = script->getName(REGS.pc); michael@0: val = ®S.sp[-1].toObject(); michael@0: michael@0: if (!InitGetterSetterOperation(cx, REGS.pc, obj, name, val)) michael@0: goto error; michael@0: michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_INITPROP_GETTER) michael@0: michael@0: CASE(JSOP_INITELEM_GETTER) michael@0: CASE(JSOP_INITELEM_SETTER) michael@0: { michael@0: RootedObject &obj = rootObject0; michael@0: RootedValue &idval = rootValue0; michael@0: RootedObject &val = rootObject1; michael@0: michael@0: JS_ASSERT(REGS.stackDepth() >= 3); michael@0: obj = ®S.sp[-3].toObject(); michael@0: idval = REGS.sp[-2]; michael@0: val = ®S.sp[-1].toObject(); michael@0: michael@0: if (!InitGetterSetterOperation(cx, REGS.pc, obj, idval, val)) michael@0: goto error; michael@0: michael@0: REGS.sp -= 2; michael@0: } michael@0: END_CASE(JSOP_INITELEM_GETTER) michael@0: michael@0: CASE(JSOP_HOLE) michael@0: PUSH_HOLE(); michael@0: END_CASE(JSOP_HOLE) michael@0: michael@0: CASE(JSOP_NEWINIT) michael@0: { michael@0: uint8_t i = GET_UINT8(REGS.pc); michael@0: JS_ASSERT(i == JSProto_Array || i == JSProto_Object); michael@0: michael@0: RootedObject &obj = rootObject0; michael@0: NewObjectKind newKind; michael@0: if (i == JSProto_Array) { michael@0: newKind = UseNewTypeForInitializer(script, REGS.pc, &ArrayObject::class_); michael@0: obj = NewDenseEmptyArray(cx, nullptr, newKind); michael@0: } else { michael@0: gc::AllocKind allocKind = GuessObjectGCKind(0); michael@0: newKind = UseNewTypeForInitializer(script, REGS.pc, &JSObject::class_); michael@0: obj = NewBuiltinClassInstance(cx, &JSObject::class_, allocKind, newKind); michael@0: } michael@0: if (!obj || !SetInitializerObjectType(cx, script, REGS.pc, obj, newKind)) michael@0: goto error; michael@0: michael@0: PUSH_OBJECT(*obj); michael@0: } michael@0: END_CASE(JSOP_NEWINIT) michael@0: michael@0: CASE(JSOP_NEWARRAY) michael@0: { michael@0: unsigned count = GET_UINT24(REGS.pc); michael@0: RootedObject &obj = rootObject0; michael@0: NewObjectKind newKind = UseNewTypeForInitializer(script, REGS.pc, &ArrayObject::class_); michael@0: obj = NewDenseAllocatedArray(cx, count, nullptr, newKind); michael@0: if (!obj || !SetInitializerObjectType(cx, script, REGS.pc, obj, newKind)) michael@0: goto error; michael@0: michael@0: PUSH_OBJECT(*obj); michael@0: } michael@0: END_CASE(JSOP_NEWARRAY) michael@0: michael@0: CASE(JSOP_NEWOBJECT) michael@0: { michael@0: RootedObject &baseobj = rootObject0; michael@0: baseobj = script->getObject(REGS.pc); michael@0: michael@0: RootedObject &obj = rootObject1; michael@0: NewObjectKind newKind = UseNewTypeForInitializer(script, REGS.pc, baseobj->getClass()); michael@0: obj = CopyInitializerObject(cx, baseobj, newKind); michael@0: if (!obj || !SetInitializerObjectType(cx, script, REGS.pc, obj, newKind)) michael@0: goto error; michael@0: michael@0: PUSH_OBJECT(*obj); michael@0: } michael@0: END_CASE(JSOP_NEWOBJECT) michael@0: michael@0: CASE(JSOP_ENDINIT) michael@0: { michael@0: /* FIXME remove JSOP_ENDINIT bug 588522 */ michael@0: JS_ASSERT(REGS.stackDepth() >= 1); michael@0: JS_ASSERT(REGS.sp[-1].isObject() || REGS.sp[-1].isUndefined()); michael@0: } michael@0: END_CASE(JSOP_ENDINIT) michael@0: michael@0: CASE(JSOP_MUTATEPROTO) michael@0: { michael@0: MOZ_ASSERT(REGS.stackDepth() >= 2); michael@0: michael@0: if (REGS.sp[-1].isObjectOrNull()) { michael@0: RootedObject &newProto = rootObject1; michael@0: rootObject1 = REGS.sp[-1].toObjectOrNull(); michael@0: michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.sp[-2].toObject(); michael@0: MOZ_ASSERT(obj->is()); michael@0: michael@0: bool succeeded; michael@0: if (!JSObject::setProto(cx, obj, newProto, &succeeded)) michael@0: goto error; michael@0: MOZ_ASSERT(succeeded); michael@0: } michael@0: michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_MUTATEPROTO); michael@0: michael@0: CASE(JSOP_INITPROP) michael@0: { michael@0: /* Load the property's initial value into rval. */ michael@0: JS_ASSERT(REGS.stackDepth() >= 2); michael@0: RootedValue &rval = rootValue0; michael@0: rval = REGS.sp[-1]; michael@0: michael@0: /* Load the object being initialized into lval/obj. */ michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.sp[-2].toObject(); michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: PropertyName *name = script->getName(REGS.pc); michael@0: michael@0: RootedId &id = rootId0; michael@0: id = NameToId(name); michael@0: michael@0: if (!DefineNativeProperty(cx, obj, id, rval, nullptr, nullptr, JSPROP_ENUMERATE)) michael@0: goto error; michael@0: michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_INITPROP); michael@0: michael@0: CASE(JSOP_INITELEM) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 3); michael@0: HandleValue val = REGS.stackHandleAt(-1); michael@0: HandleValue id = REGS.stackHandleAt(-2); michael@0: michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.sp[-3].toObject(); michael@0: michael@0: if (!InitElemOperation(cx, obj, id, val)) michael@0: goto error; michael@0: michael@0: REGS.sp -= 2; michael@0: } michael@0: END_CASE(JSOP_INITELEM) michael@0: michael@0: CASE(JSOP_INITELEM_ARRAY) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 2); michael@0: HandleValue val = REGS.stackHandleAt(-1); michael@0: michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.sp[-2].toObject(); michael@0: michael@0: JS_ASSERT(obj->is()); michael@0: michael@0: uint32_t index = GET_UINT24(REGS.pc); michael@0: if (!InitArrayElemOperation(cx, REGS.pc, obj, index, val)) michael@0: goto error; michael@0: michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_INITELEM_ARRAY) michael@0: michael@0: CASE(JSOP_INITELEM_INC) michael@0: { michael@0: JS_ASSERT(REGS.stackDepth() >= 3); michael@0: HandleValue val = REGS.stackHandleAt(-1); michael@0: michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.sp[-3].toObject(); michael@0: michael@0: uint32_t index = REGS.sp[-2].toInt32(); michael@0: if (!InitArrayElemOperation(cx, REGS.pc, obj, index, val)) michael@0: goto error; michael@0: michael@0: REGS.sp[-2].setInt32(index + 1); michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_INITELEM_INC) michael@0: michael@0: CASE(JSOP_SPREAD) michael@0: { michael@0: int32_t count = REGS.sp[-2].toInt32(); michael@0: RootedObject &arr = rootObject0; michael@0: arr = ®S.sp[-3].toObject(); michael@0: const Value iterable = REGS.sp[-1]; michael@0: ForOfIterator iter(cx); michael@0: RootedValue &iterVal = rootValue0; michael@0: iterVal.set(iterable); michael@0: if (!iter.init(iterVal)) michael@0: goto error; michael@0: while (true) { michael@0: bool done; michael@0: if (!iter.next(&iterVal, &done)) michael@0: goto error; michael@0: if (done) michael@0: break; michael@0: if (count == INT32_MAX) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_SPREAD_TOO_LARGE); michael@0: goto error; michael@0: } michael@0: if (!JSObject::defineElement(cx, arr, count++, iterVal, nullptr, nullptr, michael@0: JSPROP_ENUMERATE)) michael@0: goto error; michael@0: } michael@0: REGS.sp[-2].setInt32(count); michael@0: REGS.sp--; michael@0: } michael@0: END_CASE(JSOP_SPREAD) michael@0: michael@0: CASE(JSOP_GOSUB) michael@0: { michael@0: PUSH_BOOLEAN(false); michael@0: int32_t i = script->pcToOffset(REGS.pc) + JSOP_GOSUB_LENGTH; michael@0: int32_t len = GET_JUMP_OFFSET(REGS.pc); michael@0: PUSH_INT32(i); michael@0: ADVANCE_AND_DISPATCH(len); michael@0: } michael@0: michael@0: CASE(JSOP_RETSUB) michael@0: { michael@0: /* Pop [exception or hole, retsub pc-index]. */ michael@0: Value rval, lval; michael@0: POP_COPY_TO(rval); michael@0: POP_COPY_TO(lval); michael@0: JS_ASSERT(lval.isBoolean()); michael@0: if (lval.toBoolean()) { michael@0: /* michael@0: * Exception was pending during finally, throw it *before* we adjust michael@0: * pc, because pc indexes into script->trynotes. This turns out not to michael@0: * be necessary, but it seems clearer. And it points out a FIXME: michael@0: * 350509, due to Igor Bukanov. michael@0: */ michael@0: cx->setPendingException(rval); michael@0: goto error; michael@0: } michael@0: JS_ASSERT(rval.isInt32()); michael@0: michael@0: /* Increment the PC by this much. */ michael@0: int32_t len = rval.toInt32() - int32_t(script->pcToOffset(REGS.pc)); michael@0: ADVANCE_AND_DISPATCH(len); michael@0: } michael@0: michael@0: CASE(JSOP_EXCEPTION) michael@0: { michael@0: PUSH_NULL(); michael@0: MutableHandleValue res = REGS.stackHandleAt(-1); michael@0: if (!GetAndClearException(cx, res)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_EXCEPTION) michael@0: michael@0: CASE(JSOP_FINALLY) michael@0: CHECK_BRANCH(); michael@0: END_CASE(JSOP_FINALLY) michael@0: michael@0: CASE(JSOP_THROWING) michael@0: { michael@0: JS_ASSERT(!cx->isExceptionPending()); michael@0: Value v; michael@0: POP_COPY_TO(v); michael@0: cx->setPendingException(v); michael@0: } michael@0: END_CASE(JSOP_THROWING) michael@0: michael@0: CASE(JSOP_THROW) michael@0: { michael@0: CHECK_BRANCH(); michael@0: RootedValue &v = rootValue0; michael@0: POP_COPY_TO(v); michael@0: JS_ALWAYS_FALSE(Throw(cx, v)); michael@0: /* let the code at error try to catch the exception. */ michael@0: goto error; michael@0: } michael@0: michael@0: CASE(JSOP_INSTANCEOF) michael@0: { michael@0: RootedValue &rref = rootValue0; michael@0: rref = REGS.sp[-1]; michael@0: if (rref.isPrimitive()) { michael@0: js_ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS, -1, rref, js::NullPtr()); michael@0: goto error; michael@0: } michael@0: RootedObject &obj = rootObject0; michael@0: obj = &rref.toObject(); michael@0: bool cond = false; michael@0: if (!HasInstance(cx, obj, REGS.stackHandleAt(-2), &cond)) michael@0: goto error; michael@0: REGS.sp--; michael@0: REGS.sp[-1].setBoolean(cond); michael@0: } michael@0: END_CASE(JSOP_INSTANCEOF) michael@0: michael@0: CASE(JSOP_DEBUGGER) michael@0: { michael@0: JSTrapStatus st = JSTRAP_CONTINUE; michael@0: RootedValue rval(cx); michael@0: if (JSDebuggerHandler handler = cx->runtime()->debugHooks.debuggerHandler) michael@0: st = handler(cx, script, REGS.pc, rval.address(), cx->runtime()->debugHooks.debuggerHandlerData); michael@0: if (st == JSTRAP_CONTINUE) michael@0: st = Debugger::onDebuggerStatement(cx, &rval); michael@0: switch (st) { michael@0: case JSTRAP_ERROR: michael@0: goto error; michael@0: case JSTRAP_CONTINUE: michael@0: break; michael@0: case JSTRAP_RETURN: michael@0: REGS.fp()->setReturnValue(rval); michael@0: ForcedReturn(cx, REGS); michael@0: goto successful_return_continuation; michael@0: case JSTRAP_THROW: michael@0: cx->setPendingException(rval); michael@0: goto error; michael@0: default:; michael@0: } michael@0: } michael@0: END_CASE(JSOP_DEBUGGER) michael@0: michael@0: CASE(JSOP_PUSHBLOCKSCOPE) michael@0: { michael@0: StaticBlockObject &blockObj = script->getObject(REGS.pc)->as(); michael@0: michael@0: JS_ASSERT(blockObj.needsClone()); michael@0: // Clone block and push on scope chain. michael@0: if (!REGS.fp()->pushBlock(cx, blockObj)) michael@0: goto error; michael@0: } michael@0: END_CASE(JSOP_PUSHBLOCKSCOPE) michael@0: michael@0: CASE(JSOP_POPBLOCKSCOPE) michael@0: { michael@0: #ifdef DEBUG michael@0: // Pop block from scope chain. michael@0: JS_ASSERT(*(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH) == JSOP_DEBUGLEAVEBLOCK); michael@0: NestedScopeObject *scope = script->getStaticScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH); michael@0: JS_ASSERT(scope && scope->is()); michael@0: StaticBlockObject &blockObj = scope->as(); michael@0: JS_ASSERT(blockObj.needsClone()); michael@0: #endif michael@0: michael@0: // Pop block from scope chain. michael@0: REGS.fp()->popBlock(cx); michael@0: } michael@0: END_CASE(JSOP_POPBLOCKSCOPE) michael@0: michael@0: CASE(JSOP_DEBUGLEAVEBLOCK) michael@0: { michael@0: JS_ASSERT(script->getStaticScope(REGS.pc)); michael@0: JS_ASSERT(script->getStaticScope(REGS.pc)->is()); michael@0: michael@0: // FIXME: This opcode should not be necessary. The debugger shouldn't need michael@0: // help from bytecode to do its job. See bug 927782. michael@0: michael@0: if (MOZ_UNLIKELY(cx->compartment()->debugMode())) michael@0: DebugScopes::onPopBlock(cx, REGS.fp(), REGS.pc); michael@0: } michael@0: END_CASE(JSOP_DEBUGLEAVEBLOCK) michael@0: michael@0: CASE(JSOP_GENERATOR) michael@0: { michael@0: JS_ASSERT(!cx->isExceptionPending()); michael@0: REGS.fp()->initGeneratorFrame(); michael@0: REGS.pc += JSOP_GENERATOR_LENGTH; michael@0: JSObject *obj = js_NewGenerator(cx, REGS); michael@0: if (!obj) michael@0: goto error; michael@0: REGS.fp()->setReturnValue(ObjectValue(*obj)); michael@0: REGS.fp()->setYielding(); michael@0: interpReturnOK = true; michael@0: if (activation.entryFrame() != REGS.fp()) michael@0: goto inline_return; michael@0: goto exit; michael@0: } michael@0: michael@0: CASE(JSOP_YIELD) michael@0: JS_ASSERT(!cx->isExceptionPending()); michael@0: JS_ASSERT(REGS.fp()->isNonEvalFunctionFrame()); michael@0: if (cx->innermostGenerator()->state == JSGEN_CLOSING) { michael@0: RootedValue &val = rootValue0; michael@0: val.setObject(REGS.fp()->callee()); michael@0: js_ReportValueError(cx, JSMSG_BAD_GENERATOR_YIELD, JSDVG_SEARCH_STACK, val, js::NullPtr()); michael@0: goto error; michael@0: } michael@0: REGS.fp()->setReturnValue(REGS.sp[-1]); michael@0: REGS.fp()->setYielding(); michael@0: REGS.pc += JSOP_YIELD_LENGTH; michael@0: interpReturnOK = true; michael@0: goto exit; michael@0: michael@0: CASE(JSOP_ARRAYPUSH) michael@0: { michael@0: RootedObject &obj = rootObject0; michael@0: obj = ®S.sp[-1].toObject(); michael@0: if (!NewbornArrayPush(cx, obj, REGS.sp[-2])) michael@0: goto error; michael@0: REGS.sp -= 2; michael@0: } michael@0: END_CASE(JSOP_ARRAYPUSH) michael@0: michael@0: DEFAULT() michael@0: { michael@0: char numBuf[12]; michael@0: JS_snprintf(numBuf, sizeof numBuf, "%d", *REGS.pc); michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, michael@0: JSMSG_BAD_BYTECODE, numBuf); michael@0: goto error; michael@0: } michael@0: michael@0: } /* interpreter loop */ michael@0: michael@0: MOZ_ASSUME_UNREACHABLE("Interpreter loop exited via fallthrough"); michael@0: michael@0: error: michael@0: switch (HandleError(cx, REGS)) { michael@0: case SuccessfulReturnContinuation: michael@0: goto successful_return_continuation; michael@0: michael@0: case ErrorReturnContinuation: michael@0: interpReturnOK = false; michael@0: goto return_continuation; michael@0: michael@0: case CatchContinuation: michael@0: ADVANCE_AND_DISPATCH(0); michael@0: michael@0: case FinallyContinuation: michael@0: /* michael@0: * Push (true, exception) pair for finally to indicate that [retsub] michael@0: * should rethrow the exception. michael@0: */ michael@0: RootedValue &exception = rootValue0; michael@0: if (!cx->getPendingException(&exception)) { michael@0: interpReturnOK = false; michael@0: goto return_continuation; michael@0: } michael@0: PUSH_BOOLEAN(true); michael@0: PUSH_COPY(exception); michael@0: cx->clearPendingException(); michael@0: ADVANCE_AND_DISPATCH(0); michael@0: } michael@0: MOZ_ASSUME_UNREACHABLE("Invalid HandleError continuation"); michael@0: michael@0: exit: michael@0: if (MOZ_UNLIKELY(cx->compartment()->debugMode())) michael@0: interpReturnOK = ScriptDebugEpilogue(cx, REGS.fp(), REGS.pc, interpReturnOK); michael@0: if (!REGS.fp()->isYielding()) michael@0: REGS.fp()->epilogue(cx); michael@0: else michael@0: probes::ExitScript(cx, script, script->functionNonDelazifying(), michael@0: REGS.fp()->hasPushedSPSFrame()); michael@0: michael@0: gc::MaybeVerifyBarriers(cx, true); michael@0: michael@0: TraceLogStopEvent(logger); michael@0: TraceLogStopEvent(logger, scriptLogId); michael@0: michael@0: #ifdef JS_ION michael@0: /* michael@0: * This path is used when it's guaranteed the method can be finished michael@0: * inside the JIT. michael@0: */ michael@0: leave_on_safe_point: michael@0: #endif michael@0: michael@0: if (interpReturnOK) michael@0: state.setReturnValue(activation.entryFrame()->returnValue()); michael@0: michael@0: return interpReturnOK; michael@0: } michael@0: michael@0: bool michael@0: js::Throw(JSContext *cx, HandleValue v) michael@0: { michael@0: JS_ASSERT(!cx->isExceptionPending()); michael@0: cx->setPendingException(v); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: js::GetProperty(JSContext *cx, HandleValue v, HandlePropertyName name, MutableHandleValue vp) michael@0: { michael@0: if (name == cx->names().length) { michael@0: // Fast path for strings, arrays and arguments. michael@0: if (GetLengthProperty(v, vp)) michael@0: return true; michael@0: } michael@0: michael@0: RootedObject obj(cx, ToObjectFromStack(cx, v)); michael@0: if (!obj) michael@0: return false; michael@0: return JSObject::getProperty(cx, obj, obj, name, vp); michael@0: } michael@0: michael@0: bool michael@0: js::CallProperty(JSContext *cx, HandleValue v, HandlePropertyName name, MutableHandleValue vp) michael@0: { michael@0: if (!GetProperty(cx, v, name, vp)) michael@0: return false; michael@0: michael@0: #if JS_HAS_NO_SUCH_METHOD michael@0: if (MOZ_UNLIKELY(vp.isUndefined()) && v.isObject()) michael@0: { michael@0: RootedObject obj(cx, &v.toObject()); michael@0: if (!OnUnknownMethod(cx, obj, StringValue(name), vp)) michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::GetScopeName(JSContext *cx, HandleObject scopeChain, HandlePropertyName name, MutableHandleValue vp) michael@0: { michael@0: RootedShape shape(cx); michael@0: RootedObject obj(cx), pobj(cx); michael@0: if (!LookupName(cx, name, scopeChain, &obj, &pobj, &shape)) michael@0: return false; michael@0: michael@0: if (!shape) { michael@0: JSAutoByteString printable; michael@0: if (AtomToPrintableString(cx, name, &printable)) michael@0: js_ReportIsNotDefined(cx, printable.ptr()); michael@0: return false; michael@0: } michael@0: michael@0: return JSObject::getProperty(cx, obj, obj, name, vp); michael@0: } michael@0: michael@0: /* michael@0: * Alternate form for NAME opcodes followed immediately by a TYPEOF, michael@0: * which do not report an exception on (typeof foo == "undefined") tests. michael@0: */ michael@0: bool michael@0: js::GetScopeNameForTypeOf(JSContext *cx, HandleObject scopeChain, HandlePropertyName name, michael@0: MutableHandleValue vp) michael@0: { michael@0: RootedShape shape(cx); michael@0: RootedObject obj(cx), pobj(cx); michael@0: if (!LookupName(cx, name, scopeChain, &obj, &pobj, &shape)) michael@0: return false; michael@0: michael@0: if (!shape) { michael@0: vp.set(UndefinedValue()); michael@0: return true; michael@0: } michael@0: michael@0: return JSObject::getProperty(cx, obj, obj, name, vp); michael@0: } michael@0: michael@0: JSObject * michael@0: js::Lambda(JSContext *cx, HandleFunction fun, HandleObject parent) michael@0: { michael@0: MOZ_ASSERT(!fun->isArrow()); michael@0: michael@0: RootedObject clone(cx, CloneFunctionObjectIfNotSingleton(cx, fun, parent, TenuredObject)); michael@0: if (!clone) michael@0: return nullptr; michael@0: michael@0: MOZ_ASSERT(clone->global() == clone->global()); michael@0: return clone; michael@0: } michael@0: michael@0: JSObject * michael@0: js::LambdaArrow(JSContext *cx, HandleFunction fun, HandleObject parent, HandleValue thisv) michael@0: { michael@0: MOZ_ASSERT(fun->isArrow()); michael@0: michael@0: RootedObject clone(cx, CloneFunctionObjectIfNotSingleton(cx, fun, parent, TenuredObject)); michael@0: if (!clone) michael@0: return nullptr; michael@0: michael@0: MOZ_ASSERT(clone->as().isArrow()); michael@0: clone->as().setExtendedSlot(0, thisv); michael@0: michael@0: MOZ_ASSERT(clone->global() == clone->global()); michael@0: return clone; michael@0: } michael@0: michael@0: bool michael@0: js::DefFunOperation(JSContext *cx, HandleScript script, HandleObject scopeChain, michael@0: HandleFunction funArg) michael@0: { michael@0: /* michael@0: * If static link is not current scope, clone fun's object to link to the michael@0: * current scope via parent. We do this to enable sharing of compiled michael@0: * functions among multiple equivalent scopes, amortizing the cost of michael@0: * compilation over a number of executions. Examples include XUL scripts michael@0: * and event handlers shared among Firefox or other Mozilla app chrome michael@0: * windows, and user-defined JS functions precompiled and then shared among michael@0: * requests in server-side JS. michael@0: */ michael@0: RootedFunction fun(cx, funArg); michael@0: if (fun->isNative() || fun->environment() != scopeChain) { michael@0: fun = CloneFunctionObjectIfNotSingleton(cx, fun, scopeChain, TenuredObject); michael@0: if (!fun) michael@0: return false; michael@0: } else { michael@0: JS_ASSERT(script->compileAndGo()); michael@0: JS_ASSERT(!script->functionNonDelazifying()); michael@0: } michael@0: michael@0: /* michael@0: * We define the function as a property of the variable object and not the michael@0: * current scope chain even for the case of function expression statements michael@0: * and functions defined by eval inside let or with blocks. michael@0: */ michael@0: RootedObject parent(cx, scopeChain); michael@0: while (!parent->isVarObj()) michael@0: parent = parent->enclosingScope(); michael@0: michael@0: /* ES5 10.5 (NB: with subsequent errata). */ michael@0: RootedPropertyName name(cx, fun->atom()->asPropertyName()); michael@0: michael@0: RootedShape shape(cx); michael@0: RootedObject pobj(cx); michael@0: if (!JSObject::lookupProperty(cx, parent, name, &pobj, &shape)) michael@0: return false; michael@0: michael@0: RootedValue rval(cx, ObjectValue(*fun)); michael@0: michael@0: /* michael@0: * ECMA requires functions defined when entering Eval code to be michael@0: * impermanent. michael@0: */ michael@0: unsigned attrs = script->isActiveEval() michael@0: ? JSPROP_ENUMERATE michael@0: : JSPROP_ENUMERATE | JSPROP_PERMANENT; michael@0: michael@0: /* Steps 5d, 5f. */ michael@0: if (!shape || pobj != parent) { michael@0: return JSObject::defineProperty(cx, parent, name, rval, JS_PropertyStub, michael@0: JS_StrictPropertyStub, attrs); michael@0: } michael@0: michael@0: /* Step 5e. */ michael@0: JS_ASSERT(parent->isNative()); michael@0: if (parent->is()) { michael@0: if (shape->configurable()) { michael@0: return JSObject::defineProperty(cx, parent, name, rval, JS_PropertyStub, michael@0: JS_StrictPropertyStub, attrs); michael@0: } michael@0: michael@0: if (shape->isAccessorDescriptor() || !shape->writable() || !shape->enumerable()) { michael@0: JSAutoByteString bytes; michael@0: if (AtomToPrintableString(cx, name, &bytes)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_REDEFINE_PROP, michael@0: bytes.ptr()); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * Non-global properties, and global properties which we aren't simply michael@0: * redefining, must be set. First, this preserves their attributes. michael@0: * Second, this will produce warnings and/or errors as necessary if the michael@0: * specified Call object property is not writable (const). michael@0: */ michael@0: michael@0: /* Step 5f. */ michael@0: return JSObject::setProperty(cx, parent, parent, name, &rval, script->strict()); michael@0: } michael@0: michael@0: bool michael@0: js::SetCallOperation(JSContext *cx) michael@0: { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_LEFTSIDE_OF_ASS); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: js::GetAndClearException(JSContext *cx, MutableHandleValue res) michael@0: { michael@0: bool status = cx->getPendingException(res); michael@0: cx->clearPendingException(); michael@0: if (!status) michael@0: return false; michael@0: michael@0: // Allow interrupting deeply nested exception handling. michael@0: return CheckForInterrupt(cx); michael@0: } michael@0: michael@0: template michael@0: bool michael@0: js::SetProperty(JSContext *cx, HandleObject obj, HandleId id, const Value &value) michael@0: { michael@0: RootedValue v(cx, value); michael@0: return JSObject::setGeneric(cx, obj, obj, id, &v, strict); michael@0: } michael@0: michael@0: template bool js::SetProperty (JSContext *cx, HandleObject obj, HandleId id, const Value &value); michael@0: template bool js::SetProperty(JSContext *cx, HandleObject obj, HandleId id, const Value &value); michael@0: michael@0: template michael@0: bool michael@0: js::DeleteProperty(JSContext *cx, HandleValue v, HandlePropertyName name, bool *bp) michael@0: { michael@0: RootedObject obj(cx, ToObjectFromStack(cx, v)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (!JSObject::deleteProperty(cx, obj, name, bp)) michael@0: return false; michael@0: michael@0: if (strict && !*bp) { michael@0: obj->reportNotConfigurable(cx, NameToId(name)); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template bool js::DeleteProperty (JSContext *cx, HandleValue val, HandlePropertyName name, bool *bp); michael@0: template bool js::DeleteProperty(JSContext *cx, HandleValue val, HandlePropertyName name, bool *bp); michael@0: michael@0: template michael@0: bool michael@0: js::DeleteElement(JSContext *cx, HandleValue val, HandleValue index, bool *bp) michael@0: { michael@0: RootedObject obj(cx, ToObjectFromStack(cx, val)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (!JSObject::deleteByValue(cx, obj, index, bp)) michael@0: return false; michael@0: michael@0: if (strict && !*bp) { michael@0: // XXX This observably calls ToString(propval). We should convert to michael@0: // PropertyKey and use that to delete, and to report an error if michael@0: // necessary! michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, index, &id)) michael@0: return false; michael@0: obj->reportNotConfigurable(cx, id); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: template bool js::DeleteElement (JSContext *, HandleValue, HandleValue, bool *succeeded); michael@0: template bool js::DeleteElement(JSContext *, HandleValue, HandleValue, bool *succeeded); michael@0: michael@0: bool michael@0: js::GetElement(JSContext *cx, MutableHandleValue lref, HandleValue rref, MutableHandleValue vp) michael@0: { michael@0: return GetElementOperation(cx, JSOP_GETELEM, lref, rref, vp); michael@0: } michael@0: michael@0: bool michael@0: js::CallElement(JSContext *cx, MutableHandleValue lref, HandleValue rref, MutableHandleValue res) michael@0: { michael@0: return GetElementOperation(cx, JSOP_CALLELEM, lref, rref, res); michael@0: } michael@0: michael@0: bool michael@0: js::SetObjectElement(JSContext *cx, HandleObject obj, HandleValue index, HandleValue value, michael@0: bool strict) michael@0: { michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, index, &id)) michael@0: return false; michael@0: return SetObjectElementOperation(cx, obj, id, value, strict); michael@0: } michael@0: michael@0: bool michael@0: js::SetObjectElement(JSContext *cx, HandleObject obj, HandleValue index, HandleValue value, michael@0: bool strict, HandleScript script, jsbytecode *pc) michael@0: { michael@0: JS_ASSERT(pc); michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, index, &id)) michael@0: return false; michael@0: return SetObjectElementOperation(cx, obj, id, value, strict, script, pc); michael@0: } michael@0: michael@0: bool michael@0: js::InitElementArray(JSContext *cx, jsbytecode *pc, HandleObject obj, uint32_t index, HandleValue value) michael@0: { michael@0: return InitArrayElemOperation(cx, pc, obj, index, value); michael@0: } michael@0: michael@0: bool michael@0: js::AddValues(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) michael@0: { michael@0: return AddOperation(cx, lhs, rhs, res); michael@0: } michael@0: michael@0: bool michael@0: js::SubValues(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) michael@0: { michael@0: return SubOperation(cx, lhs, rhs, res); michael@0: } michael@0: michael@0: bool michael@0: js::MulValues(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) michael@0: { michael@0: return MulOperation(cx, lhs, rhs, res); michael@0: } michael@0: michael@0: bool michael@0: js::DivValues(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) michael@0: { michael@0: return DivOperation(cx, lhs, rhs, res); michael@0: } michael@0: michael@0: bool michael@0: js::ModValues(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) michael@0: { michael@0: return ModOperation(cx, lhs, rhs, res); michael@0: } michael@0: michael@0: bool michael@0: js::UrshValues(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) michael@0: { michael@0: return UrshOperation(cx, lhs, rhs, res); michael@0: } michael@0: michael@0: bool michael@0: js::DeleteNameOperation(JSContext *cx, HandlePropertyName name, HandleObject scopeObj, michael@0: MutableHandleValue res) michael@0: { michael@0: RootedObject scope(cx), pobj(cx); michael@0: RootedShape shape(cx); michael@0: if (!LookupName(cx, name, scopeObj, &scope, &pobj, &shape)) michael@0: return false; michael@0: michael@0: if (!scope) { michael@0: // Return true for non-existent names. michael@0: res.setBoolean(true); michael@0: return true; michael@0: } michael@0: michael@0: bool succeeded; michael@0: if (!JSObject::deleteProperty(cx, scope, name, &succeeded)) michael@0: return false; michael@0: res.setBoolean(succeeded); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::ImplicitThisOperation(JSContext *cx, HandleObject scopeObj, HandlePropertyName name, michael@0: MutableHandleValue res) michael@0: { michael@0: RootedObject obj(cx); michael@0: if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &obj)) michael@0: return false; michael@0: michael@0: return ComputeImplicitThis(cx, obj, res); michael@0: } michael@0: michael@0: bool michael@0: js::RunOnceScriptPrologue(JSContext *cx, HandleScript script) michael@0: { michael@0: JS_ASSERT(script->treatAsRunOnce()); michael@0: michael@0: if (!script->hasRunOnce()) { michael@0: script->setHasRunOnce(); michael@0: return true; michael@0: } michael@0: michael@0: // Force instantiation of the script's function's type to ensure the flag michael@0: // is preserved in type information. michael@0: if (!script->functionNonDelazifying()->getType(cx)) michael@0: return false; michael@0: michael@0: types::MarkTypeObjectFlags(cx, script->functionNonDelazifying(), michael@0: types::OBJECT_FLAG_RUNONCE_INVALIDATED); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::InitGetterSetterOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, HandleId id, michael@0: HandleObject val) michael@0: { michael@0: JS_ASSERT(val->isCallable()); michael@0: PropertyOp getter; michael@0: StrictPropertyOp setter; michael@0: unsigned attrs = JSPROP_ENUMERATE | JSPROP_SHARED; michael@0: michael@0: JSOp op = JSOp(*pc); michael@0: michael@0: if (op == JSOP_INITPROP_GETTER || op == JSOP_INITELEM_GETTER) { michael@0: getter = CastAsPropertyOp(val); michael@0: setter = JS_StrictPropertyStub; michael@0: attrs |= JSPROP_GETTER; michael@0: } else { michael@0: JS_ASSERT(op == JSOP_INITPROP_SETTER || op == JSOP_INITELEM_SETTER); michael@0: getter = JS_PropertyStub; michael@0: setter = CastAsStrictPropertyOp(val); michael@0: attrs |= JSPROP_SETTER; michael@0: } michael@0: michael@0: RootedValue scratch(cx); michael@0: return JSObject::defineGeneric(cx, obj, id, scratch, getter, setter, attrs); michael@0: } michael@0: michael@0: bool michael@0: js::InitGetterSetterOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, michael@0: HandlePropertyName name, HandleObject val) michael@0: { michael@0: RootedId id(cx, NameToId(name)); michael@0: return InitGetterSetterOperation(cx, pc, obj, id, val); michael@0: } michael@0: michael@0: bool michael@0: js::InitGetterSetterOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, HandleValue idval, michael@0: HandleObject val) michael@0: { michael@0: RootedId id(cx); michael@0: if (!ValueToId(cx, idval, &id)) michael@0: return false; michael@0: michael@0: return InitGetterSetterOperation(cx, pc, obj, id, val); michael@0: }