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