/* -*- 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 "mozilla/Sprintf.h" #include #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/AsyncFunction.h" #include "vm/Debugger.h" #include "vm/GeneratorObject.h" #include "vm/Opcodes.h" #include "vm/Scope.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/EnvironmentObject-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Probes-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 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; } bool js::GetFunctionThis(JSContext* cx, AbstractFramePtr frame, MutableHandleValue res) { MOZ_ASSERT(frame.isFunctionFrame()); MOZ_ASSERT(!frame.callee()->isArrow()); if (frame.thisArgument().isObject() || frame.callee()->strict() || frame.callee()->isSelfHostedBuiltin()) { res.set(frame.thisArgument()); return true; } RootedValue thisv(cx, frame.thisArgument()); return BoxNonStrictThis(cx, thisv, res); } bool js::GetNonSyntacticGlobalThis(JSContext* cx, HandleObject envChain, MutableHandleValue res) { RootedObject env(cx, envChain); while (true) { if (IsExtensibleLexicalEnvironment(env)) { res.set(env->as().thisValue()); return true; } if (!env->enclosingEnvironment()) { // This can only happen in Debugger eval frames: in that case we // don't always have a global lexical env, see EvaluateInEnv. MOZ_ASSERT(env->is()); res.set(GetThisValue(env)); return true; } env = env->enclosingEnvironment(); } return true; } bool js::Debug_CheckSelfHosted(JSContext* cx, HandleValue fun) { #ifndef DEBUG MOZ_CRASH("self-hosted checks should only be done in Debug builds"); #endif RootedObject funObj(cx, UncheckedUnwrap(&fun.toObject())); MOZ_ASSERT(funObj->as().isSelfHostedOrIntrinsic()); // This is purely to police self-hosted code. There is no actual operation. 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->environmentChain(); PropertyName* name = fp->script()->getName(pc); /* * Skip along the env 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 env chain * before the global object. */ if (IsGlobalOp(JSOp(*pc)) && !fp->script()->hasNonSyntacticScope()) obj = &obj->global().lexicalEnvironment(); Shape* shape = nullptr; JSObject* env = nullptr; JSObject* pobj = nullptr; if (LookupNameNoGC(cx, name, obj, &env, &pobj, &shape)) { if (FetchNameNoGC(pobj, shape, vp)) return true; } RootedObject objRoot(cx, obj), envRoot(cx), pobjRoot(cx); RootedPropertyName nameRoot(cx, name); RootedShape shapeRoot(cx); if (!LookupName(cx, nameRoot, objRoot, &envRoot, &pobjRoot, &shapeRoot)) return false; /* Kludge to allow (typeof foo == "undefined") tests. */ JSOp op2 = JSOp(pc[JSOP_GETNAME_LENGTH]); if (op2 == JSOP_TYPEOF) return FetchName(cx, envRoot, pobjRoot, nameRoot, shapeRoot, vp); return FetchName(cx, envRoot, pobjRoot, nameRoot, shapeRoot, vp); } static inline bool GetImportOperation(JSContext* cx, InterpreterFrame* fp, jsbytecode* pc, MutableHandleValue vp) { RootedObject obj(cx, fp->environmentChain()), env(cx), pobj(cx); RootedPropertyName name(cx, fp->script()->getName(pc)); RootedShape shape(cx); MOZ_ALWAYS_TRUE(LookupName(cx, name, obj, &env, &pobj, &shape)); MOZ_ASSERT(env && env->is()); MOZ_ASSERT(env->as().hasImportBinding(name)); return FetchName(cx, env, 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; ObjectOpResult result; return SetProperty(cx, obj, id, rval, lval, 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); PropertyName* lookup = derived ? cx->names().DefaultDerivedClassConstructor : cx->names().DefaultBaseClassConstructor; RootedPropertyName selfHostedName(cx, lookup); RootedAtom name(cx, atom == cx->names().empty ? nullptr : atom); RootedFunction ctor(cx); if (!cx->runtime()->createLazySelfHostedFunctionClone(cx, selfHostedName, name, /* nargs = */ !!derived, proto, TenuredObject, &ctor)) { return nullptr; } ctor->setIsConstructor(); ctor->setIsClassConstructor(); MOZ_ASSERT(ctor->infallibleIsDefaultClassConstructor(cx)); return ctor; } 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 (callee->isBoundFunction()) { invoke.args().setThis(MagicValue(JS_UNINITIALIZED_LEXICAL)); } else if (script()->isDerivedClassConstructor()) { MOZ_ASSERT(callee->as().isClassConstructor()); invoke.args().setThis(MagicValue(JS_UNINITIALIZED_LEXICAL)); } else { MOZ_ASSERT(invoke.args().thisv().isMagic(JS_IS_CONSTRUCTING)); 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_, construct_); } InterpreterFrame* ExecuteState::pushInterpreterFrame(JSContext* cx) { return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, newTargetValue_, envChain_, 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); // Since any script can conceivably GC, make sure it's safe to do so. cx->runtime()->gc.verifyIsSafeToGC(); if (!Debugger::checkNoExecute(cx, state.script())) return false; #if defined(MOZ_HAVE_RDTSC) js::AutoStopwatch stopwatch(cx); #endif // 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. * * Note: This function DOES NOT call GetThisValue to munge |args.thisv()| if * necessary. The caller (usually the interpreter) must have performed * this step already! */ bool js::InternalCallOrConstruct(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()); 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())) { 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(); if (construct != CONSTRUCT && fun->isClassConstructor()) { JS_ReportErrorNumberASCII(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, construct); // 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; } static bool InternalCall(JSContext* cx, const AnyInvokeArgs& args) { MOZ_ASSERT(args.array() + args.length() == args.end(), "must pass calling arguments to a calling attempt"); 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. HandleValue fval = args.calleev(); if (!fval.isObject() || !fval.toObject().is() || !fval.toObject().as().isNative() || !fval.toObject().as().jitInfo() || fval.toObject().as().jitInfo()->needsOuterizedThisObject()) { JSObject* thisObj = &args.thisv().toObject(); args.mutableThisv().set(GetThisValue(thisObj)); } } return InternalCallOrConstruct(cx, args, NO_CONSTRUCT); } bool js::CallFromStack(JSContext* cx, const CallArgs& args) { return InternalCall(cx, static_cast(args)); } // ES7 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 7.3.12 Call. bool js::Call(JSContext* cx, HandleValue fval, HandleValue thisv, const AnyInvokeArgs& args, MutableHandleValue rval) { // Explicitly qualify these methods to bypass AnyInvokeArgs's deliberate // shadowing. args.CallArgs::setCallee(fval); args.CallArgs::setThis(thisv); if (!InternalCall(cx, args)) return false; rval.set(args.rval()); return true; } static bool InternalConstruct(JSContext* cx, const AnyConstructArgs& args) { MOZ_ASSERT(args.array() + args.length() + 1 == args.end(), "must pass constructing arguments to a construction attempt"); MOZ_ASSERT(!JSFunction::class_.getConstruct()); // 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.CallArgs::newTarget()), "provided new.target value must be a constructor"); MOZ_ASSERT(args.thisv().isMagic(JS_IS_CONSTRUCTING) || args.thisv().isObject()); JSObject& callee = args.callee(); if (callee.is()) { RootedFunction fun(cx, &callee.as()); if (fun->isNative()) return CallJSNativeConstructor(cx, fun->native(), args); if (!InternalCallOrConstruct(cx, args, CONSTRUCT)) return false; MOZ_ASSERT(args.CallArgs::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; } bool js::ConstructFromStack(JSContext* cx, const CallArgs& args) { if (!StackCheckIsConstructorCalleeNewTarget(cx, args.calleev(), args.newTarget())) return false; return InternalConstruct(cx, static_cast(args)); } bool js::Construct(JSContext* cx, HandleValue fval, const AnyConstructArgs& args, HandleValue newTarget, MutableHandleObject objp) { MOZ_ASSERT(args.thisv().isMagic(JS_IS_CONSTRUCTING)); // Explicitly qualify to bypass AnyConstructArgs's deliberate shadowing. args.CallArgs::setCallee(fval); args.CallArgs::newTarget().set(newTarget); if (!InternalConstruct(cx, args)) return false; MOZ_ASSERT(args.CallArgs::rval().isObject()); objp.set(&args.CallArgs::rval().toObject()); return true; } bool js::InternalConstructWithProvidedThis(JSContext* cx, HandleValue fval, HandleValue thisv, const AnyConstructArgs& args, HandleValue newTarget, MutableHandleValue rval) { args.CallArgs::setCallee(fval); MOZ_ASSERT(thisv.isObject()); args.CallArgs::setThis(thisv); args.CallArgs::newTarget().set(newTarget); if (!InternalConstruct(cx, args)) return false; rval.set(args.CallArgs::rval()); return true; } bool js::CallGetter(JSContext* cx, HandleValue thisv, HandleValue getter, 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); FixedInvokeArgs<0> args(cx); return Call(cx, getter, thisv, args, rval); } bool js::CallSetter(JSContext* cx, HandleValue thisv, HandleValue setter, HandleValue v) { JS_CHECK_RECURSION(cx, return false); FixedInvokeArgs<1> args(cx); args[0].set(v); RootedValue ignored(cx); return Call(cx, setter, thisv, args, &ignored); } bool js::ExecuteKernel(JSContext* cx, HandleScript script, JSObject& envChainArg, const Value& newTargetValue, AbstractFramePtr evalInFrame, Value* result) { MOZ_ASSERT_IF(script->isGlobalCode(), IsGlobalLexicalEnvironment(&envChainArg) || !IsSyntacticEnvironment(&envChainArg)); #ifdef DEBUG RootedObject terminatingEnv(cx, &envChainArg); while (IsSyntacticEnvironment(terminatingEnv)) terminatingEnv = terminatingEnv->enclosingEnvironment(); MOZ_ASSERT(terminatingEnv->is() || script->hasNonSyntacticScope()); #endif if (script->treatAsRunOnce()) { if (script->hasRunOnce()) { JS_ReportErrorASCII(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, envChainArg, evalInFrame, result); bool ok = RunScript(cx, state); probes::StopExecution(script); return ok; } bool js::Execute(JSContext* cx, HandleScript script, JSObject& envChainArg, Value* rval) { /* The env chain is something we control, so we know it can't have any outer objects on it. */ RootedObject envChain(cx, &envChainArg); MOZ_ASSERT(!IsWindowProxy(envChain)); if (script->module()) { MOZ_RELEASE_ASSERT(envChain == script->module()->environment(), "Module scripts can only be executed in the module's environment"); } else { MOZ_RELEASE_ASSERT(IsGlobalLexicalEnvironment(envChain) || script->hasNonSyntacticScope(), "Only global scripts with non-syntactic envs can be executed with " "interesting envchains"); } /* Ensure the env chain is all same-compartment and terminates in a global. */ #ifdef DEBUG JSObject* s = envChain; do { assertSameCompartment(cx, s); MOZ_ASSERT_IF(!s->enclosingEnvironment(), s->is()); } while ((s = s->enclosingEnvironment())); #endif return ExecuteKernel(cx, script, *envChain, NullValue(), NullFramePtr() /* evalInFrame */, rval); } /* * ES6 12.9.4 InstanceofOperator */ extern bool JS::InstanceofOperator(JSContext* cx, HandleObject obj, HandleValue v, bool* bp) { /* Step 1. is handled by caller. */ /* Step 2-3. */ RootedValue hasInstance(cx); RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().hasInstance)); if (!GetProperty(cx, obj, obj, id, &hasInstance)) return false; if (!hasInstance.isNullOrUndefined()) { if (!IsCallable(hasInstance)) return ReportIsNotFunction(cx, hasInstance); /* Step 4. */ RootedValue rval(cx); if (!Call(cx, hasInstance, obj, v, &rval)) return false; *bp = ToBoolean(rval); return true; } /* Step 5. */ if (!obj->isCallable()) { RootedValue val(cx, ObjectValue(*obj)); return ReportIsNotFunction(cx, val); } /* Step 6. */ return OrdinaryHasInstance(cx, obj, v, bp); } bool js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp) { const Class* clasp = obj->getClass(); RootedValue local(cx, v); if (JSHasInstanceOp hasInstance = clasp->getHasInstance()) return hasInstance(cx, obj, &local, bp); return JS::InstanceofOperator(cx, obj, local, bp); } 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 environment 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, Handle scope) { RootedObject obj(cx); if (val.isObject()) { obj = &val.toObject(); } else { obj = ToObject(cx, val); if (!obj) return false; } RootedObject envChain(cx, frame.environmentChain()); WithEnvironmentObject* withobj = WithEnvironmentObject::create(cx, obj, envChain, scope); if (!withobj) return false; frame.pushOnEnvironmentChain(*withobj); return true; } static void PopEnvironment(JSContext* cx, EnvironmentIter& ei) { switch (ei.scope().kind()) { case ScopeKind::Lexical: case ScopeKind::SimpleCatch: case ScopeKind::Catch: case ScopeKind::NamedLambda: case ScopeKind::StrictNamedLambda: if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugEnvironments::onPopLexical(cx, ei); if (ei.scope().hasEnvironment()) ei.initialFrame().popOffEnvironmentChain(); break; case ScopeKind::With: if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugEnvironments::onPopWith(ei.initialFrame()); ei.initialFrame().popOffEnvironmentChain(); break; case ScopeKind::Function: if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugEnvironments::onPopCall(cx, ei.initialFrame()); if (ei.scope().hasEnvironment()) ei.initialFrame().popOffEnvironmentChain(); break; case ScopeKind::FunctionBodyVar: case ScopeKind::ParameterExpressionVar: case ScopeKind::StrictEval: if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugEnvironments::onPopVar(cx, ei); if (ei.scope().hasEnvironment()) ei.initialFrame().popOffEnvironmentChain(); break; case ScopeKind::Eval: case ScopeKind::Global: case ScopeKind::NonSyntactic: case ScopeKind::Module: break; } } // Unwind environment chain and iterator to match the env corresponding to // the given bytecode position. void js::UnwindEnvironment(JSContext* cx, EnvironmentIter& ei, jsbytecode* pc) { if (!ei.withinInitialFrame()) return; RootedScope scope(cx, ei.initialFrame().script()->innermostScope(pc)); #ifdef DEBUG // A frame's environment chain cannot be unwound to anything enclosing the // body scope of a script. This includes the parameter defaults // environment and the decl env object. These environments, once pushed // onto the environment chain, are expected to be there for the duration // of the frame. // // Attempting to unwind to the parameter defaults code in a script is a // bug; that section of code has no try-catch blocks. JSScript* script = ei.initialFrame().script(); for (uint32_t i = 0; i < script->bodyScopeIndex(); i++) MOZ_ASSERT(scope != script->getScope(i)); #endif for (; ei.maybeScope() != scope; ei++) PopEnvironment(cx, ei); } // Unwind all environments. 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::UnwindAllEnvironmentsInFrame(JSContext* cx, EnvironmentIter& ei) { for (; ei.withinInitialFrame(); ei++) PopEnvironment(cx, ei); } // Compute the pc needed to unwind the environment 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::UnwindEnvironmentToTryPc(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); } else if (tn->kind == JSTRY_DESTRUCTURING_ITERCLOSE) { pc -= JSOP_TRY_DESTRUCTURING_ITERCLOSE_LENGTH; MOZ_ASSERT(*pc == JSOP_TRY_DESTRUCTURING_ITERCLOSE); } return pc; } static bool ForcedReturn(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs, bool frameOk = true) { bool ok = Debugger::onLeaveFrame(cx, regs.fp(), regs.pc, frameOk); // 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) { EnvironmentIter ei(cx, regs.fp(), regs.pc); return ForcedReturn(cx, ei, regs); } static void SettleOnTryNote(JSContext* cx, JSTryNote* tn, EnvironmentIter& ei, InterpreterRegs& regs) { // Unwind the environment to the beginning of the JSOP_TRY. UnwindEnvironment(cx, ei, UnwindEnvironmentToTryPc(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 { 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, EnvironmentIter& ei, InterpreterRegs& regs) { bool inForOfIterClose = false; 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; // If IteratorClose due to abnormal completion threw inside a // for-of loop, it is not catchable by try statements inside of // the for-of loop. // // This is handled by this weirdness in the exception handler // instead of in bytecode because it is hard to do so in bytecode: // // 1. IteratorClose emitted due to abnormal completion (break, // throw, return) are emitted inline, at the source location of // the break, throw, or return statement. For example: // // for (x of iter) { // try { return; } catch (e) { } // } // // From the try-note nesting's perspective, the IteratorClose // resulting from |return| is covered by the inner try, when it // should not be. // // 2. Try-catch notes cannot be disjoint. That is, we can't have // multiple notes with disjoint pc ranges jumping to the same // catch block. if (inForOfIterClose) break; SettleOnTryNote(cx, tn, ei, regs); return CatchContinuation; case JSTRY_FINALLY: // See note above. if (inForOfIterClose) break; SettleOnTryNote(cx, tn, ei, regs); return FinallyContinuation; case JSTRY_FOR_IN: { /* This is similar to JSOP_ENDITER in the interpreter loop. */ DebugOnly 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, ei, regs); return ErrorReturnContinuation; } break; } case JSTRY_DESTRUCTURING_ITERCLOSE: { // Whether the destructuring iterator is done is at the top of the // stack. The iterator object is second from the top. MOZ_ASSERT(tn->stackDepth > 1); Value* sp = regs.spForStackDepth(tn->stackDepth); RootedValue doneValue(cx, sp[-1]); bool done = ToBoolean(doneValue); if (!done) { RootedObject iterObject(cx, &sp[-2].toObject()); if (!IteratorCloseForException(cx, iterObject)) { SettleOnTryNote(cx, tn, ei, regs); return ErrorReturnContinuation; } } break; } case JSTRY_FOR_OF_ITERCLOSE: inForOfIterClose = true; break; case JSTRY_FOR_OF: inForOfIterClose = false; break; 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()++; } EnvironmentIter ei(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, ei, regs)) return ErrorReturnContinuation; return SuccessfulReturnContinuation; default: MOZ_CRASH("Bad Debugger::onExceptionUnwind status"); } } HandleErrorContinuation res = ProcessTryNotes(cx, ei, 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(), regs.pc, 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, ei, 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. 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_MAGIC(magic) REGS.sp++->setMagic(magic) #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 env 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 LexicalEnvironment or Call class (this * is what IsCacheableEnvironment tests). Such objects-as-envs must be * censored with undefined. * * Otherwise, we bind |this| to the result of GetThisValue(). Only names inside * |with| statements and embedding-specific environment 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 (obj->is()) return UndefinedValue(); if (IsCacheableEnvironment(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(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(cx, rhs); if (!rstr) return false; lstr = lhs.toString(); } JSString* str = ConcatStrings(cx, lstr, rstr); if (!str) { RootedString nlstr(cx, lstr), nrstr(cx, rstr); str = ConcatStrings(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, HandleId id, HandleValue value, HandleValue receiver, 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().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() && IsSetElemPC(pc)) script->baselineScript()->noteArrayWriteHole(script->pcToOffset(pc)); } } if (obj->isNative() && !JSID_IS_INT(id) && !obj->setHadElementsAccess(cx)) return false; ObjectOpResult result; return SetProperty(cx, obj, id, value, 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) { JSObject* env = regs.fp()->environmentChain(); Scope* scope = regs.fp()->script()->innermostScope(regs.pc); for (EnvironmentIter ei(cx, env, scope); ei; ei++) { if (ei.hasSyntacticEnvironment() && ei.scope().is()) { JSFunction& callee = ei.environment().as().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 env chain for GetSuperEnvFunction"); } /* * As an optimization, the interpreter creates a handful of reserved Rooted * 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 class ReservedRootedBase { }; template class ReservedRooted : public ReservedRootedBase { Rooted* savedRoot; public: ReservedRooted(Rooted* root, const T& ptr) : savedRoot(root) { *root = ptr; } explicit ReservedRooted(Rooted* root) : savedRoot(root) { *root = JS::GCPolicy::initial(); } ~ReservedRooted() { *savedRoot = JS::GCPolicy::initial(); } void set(const T& p) const { *savedRoot = p; } operator Handle() { return *savedRoot; } operator Rooted&() { return *savedRoot; } MutableHandle 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 : public ValueOperations> {}; template <> class ReservedRootedBase : public ScopeCastOperation> {}; 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); \ 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 /* * Initialize code coverage vectors. */ #define INIT_COVERAGE() \ JS_BEGIN_MACRO \ if (!script->hasScriptCounts()) { \ if (cx->compartment()->collectCoverageForDebug()) { \ if (!script->initScriptCounts(cx)) \ goto error; \ } \ } \ JS_END_MACRO /* * Increment the code coverage counter associated with the given pc. */ #define COUNT_COVERAGE_PC(PC) \ JS_BEGIN_MACRO \ if (script->hasScriptCounts()) { \ PCCounts* counts = script->maybeGetPCCounts(PC); \ MOZ_ASSERT(counts); \ counts->numExec()++; \ } \ JS_END_MACRO #define COUNT_COVERAGE_MAIN() \ JS_BEGIN_MACRO \ jsbytecode* main = script->main(); \ if (!BytecodeIsJumpTarget(JSOp(*main))) \ COUNT_COVERAGE_PC(main); \ JS_END_MACRO #define COUNT_COVERAGE() \ JS_BEGIN_MACRO \ MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*REGS.pc))); \ COUNT_COVERAGE_PC(REGS.pc); \ 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 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); Rooted rootScope0(cx); DebugOnly blockDepth; /* State communicated between non-local jumps: */ bool interpReturnOK; bool frameHalfInitialized; if (!activation.entryFrame()->prologue(cx)) goto prologue_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"); } // Increment the coverage for the main entry point. INIT_COVERAGE(); COUNT_COVERAGE_MAIN(); // 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()->collectCoverageForDebug()) { if (!script->initScriptCounts(cx)) goto error; } 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. */ DISPATCH_TO(op); } /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) CASE(JSOP_UNUSED126) CASE(JSOP_UNUSED192) CASE(JSOP_UNUSED209) CASE(JSOP_UNUSED210) CASE(JSOP_UNUSED211) CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE) CASE(JSOP_UNUSED221) CASE(JSOP_UNUSED222) CASE(JSOP_UNUSED223) CASE(JSOP_CONDSWITCH) { MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1); ADVANCE_AND_DISPATCH(1); } CASE(JSOP_TRY) CASE(JSOP_JUMPTARGET) CASE(JSOP_LOOPHEAD) { MOZ_ASSERT(CodeSpec[*REGS.pc].length == 1); COUNT_COVERAGE(); ADVANCE_AND_DISPATCH(1); } CASE(JSOP_LABEL) END_CASE(JSOP_LABEL) CASE(JSOP_LOOPENTRY) COUNT_COVERAGE(); // 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 val(&rootValue0, REGS.sp[-1]); REGS.sp--; ReservedRooted scope(&rootScope0, script->getScope(REGS.pc)); if (!EnterWithOperation(cx, REGS.fp(), val, scope.as())) goto error; } END_CASE(JSOP_ENTERWITH) CASE(JSOP_LEAVEWITH) REGS.fp()->popOffEnvironmentChain(); 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: frameHalfInitialized = false; prologue_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); if (MOZ_LIKELY(!frameHalfInitialized)) { interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), REGS.pc, interpReturnOK); REGS.fp()->epilogue(cx, REGS.pc); } 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 obj(&rootObject0, &rref.toObject()); ReservedRooted 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); HandleValue val = REGS.stackHandleAt(-1); ReservedRooted iter(&rootObject0); iter.set(ValueToIterator(cx, flags, val)); if (!iter) goto error; REGS.sp[-1].setObject(*iter); } END_CASE(JSOP_ITER) CASE(JSOP_MOREITER) { MOZ_ASSERT(REGS.stackDepth() >= 1); MOZ_ASSERT(REGS.sp[-1].isObject()); PUSH_NULL(); ReservedRooted 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); COUNT_COVERAGE(); ReservedRooted obj(&rootObject0, ®S.sp[-1].toObject()); bool ok = CloseIterator(cx, obj); REGS.sp--; if (!ok) goto error; } END_CASE(JSOP_ENDITER) CASE(JSOP_ISGENCLOSING) { bool b = REGS.sp[-1].isMagic(JS_GENERATOR_CLOSING); PUSH_BOOLEAN(b); } END_CASE(JSOP_ISGENCLOSING) 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_UNPICK) { int i = GET_UINT8(REGS.pc); MOZ_ASSERT(REGS.stackDepth() >= unsigned(i) + 1); Value lval = REGS.sp[-1]; memmove(REGS.sp - i, REGS.sp - (i + 1), sizeof(Value) * i); REGS.sp[-(i + 1)] = lval; } END_CASE(JSOP_UNPICK) CASE(JSOP_BINDGNAME) CASE(JSOP_BINDNAME) { JSOp op = JSOp(*REGS.pc); ReservedRooted envChain(&rootObject0); if (op == JSOP_BINDNAME || script->hasNonSyntacticScope()) envChain.set(REGS.fp()->environmentChain()); else envChain.set(®S.fp()->global().lexicalEnvironment()); ReservedRooted name(&rootName0, script->getName(REGS.pc)); /* Assigning to an undeclared name adds a property to the global object. */ ReservedRooted env(&rootObject1); if (!LookupNameUnqualified(cx, name, envChain, &env)) goto error; PUSH_OBJECT(*env); static_assert(JSOP_BINDNAME_LENGTH == JSOP_BINDGNAME_LENGTH, "We're sharing the END_CASE so the lengths better match"); } END_CASE(JSOP_BINDNAME) CASE(JSOP_BINDVAR) { PUSH_OBJECT(REGS.fp()->varObj()); } END_CASE(JSOP_BINDVAR) #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(cx, REGS)) goto error; END_CASE(JSOP_EQ) CASE(JSOP_NE) if (!LooseEqualityOp(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 lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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 lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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 lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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 lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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 lval(&rootValue0, REGS.sp[-2]); ReservedRooted 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 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 name(&rootName0, script->getName(REGS.pc)); ReservedRooted envObj(&rootObject0, REGS.fp()->environmentChain()); PUSH_BOOLEAN(true); MutableHandleValue res = REGS.stackHandleAt(-1); if (!DeleteNameOperation(cx, name, envObj, 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 id(&rootId0, NameToId(script->getName(REGS.pc))); ReservedRooted 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 obj(&rootObject0); FETCH_OBJECT(cx, -2, obj); ReservedRooted propval(&rootValue0, REGS.sp[-1]); ObjectOpResult result; ReservedRooted 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 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()->environmentChain(), REGS.stackHandleAt(-1))) goto error; } else { PUSH_COPY(cx->global()->lexicalEnvironment().thisValue()); } } END_CASE(JSOP_GLOBALTHIS) CASE(JSOP_CHECKISOBJ) { if (!REGS.sp[-1].isObject()) { MOZ_ALWAYS_FALSE(ThrowCheckIsObject(cx, CheckIsObjectKind(GET_UINT8(REGS.pc)))); goto error; } } END_CASE(JSOP_CHECKISOBJ) CASE(JSOP_CHECKISCALLABLE) { if (!IsCallable(REGS.sp[-1])) { MOZ_ALWAYS_FALSE(ThrowCheckIsCallable(cx, CheckIsCallableKind(GET_UINT8(REGS.pc)))); goto error; } } END_CASE(JSOP_CHECKISCALLABLE) 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_ReportErrorNumberASCII(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 receiver(&rootObject0); FETCH_OBJECT(cx, -2, receiver); ReservedRooted 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 obj(&rootObject0, ®S.sp[-1].toObject()); ReservedRooted 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 env(&rootObject0, ®S.sp[-2].toObject()); HandleValue value = REGS.stackHandleAt(-1); if (!SetNameOperation(cx, script, REGS.pc, env, 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 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 receiver(&rootValue0, REGS.sp[-3]); ReservedRooted obj(&rootObject0, ®S.sp[-2].toObject()); ReservedRooted rval(&rootValue1, REGS.sp[-1]); ReservedRooted 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 receiver(&rootObject0); FETCH_OBJECT(cx, -2, receiver); ReservedRooted 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"); HandleValue receiver = REGS.stackHandleAt(-3); ReservedRooted obj(&rootObject0); obj = ToObjectFromStack(cx, receiver); if (!obj) goto error; ReservedRooted id(&rootId0); FETCH_ELEMENT_ID(-2, id); HandleValue value = REGS.stackHandleAt(-1); if (!SetObjectElementOperation(cx, obj, id, value, receiver, *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 id(&rootId0); FETCH_ELEMENT_ID(-4, id); ReservedRooted receiver(&rootValue0, REGS.sp[-3]); ReservedRooted obj(&rootObject1, ®S.sp[-2].toObject()); HandleValue value = REGS.stackHandleAt(-1); bool strict = JSOp(*REGS.pc) == JSOP_STRICTSETELEM_SUPER; if (!SetObjectElementOperation(cx, obj, id, value, receiver, 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()->environmentChain()->global().valueIsEval(args.calleev())) { if (!DirectEval(cx, args.get(0), args.rval())) goto error; } else { if (!CallFromStack(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); MaybeConstruct construct = MaybeConstruct(*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 (!CallFromStack(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 fun(&rootFunction0, maybeFun); ReservedRooted funScript(&rootScript0, fun->getOrCreateScript(cx)); if (!funScript) goto error; bool createSingleton = ObjectGroup::useSingletonForNewObject(cx, script, REGS.pc); TypeMonitorCall(cx, args, construct); mozilla::Maybe state; state.emplace(cx, args, construct); 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()); interpReturnOK = !IsErrorStatus(exec); if (interpReturnOK) CHECK_BRANCH(); REGS.sp = args.spAfterCall(); 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()); interpReturnOK = !IsErrorStatus(exec); if (interpReturnOK) CHECK_BRANCH(); REGS.sp = args.spAfterCall(); goto jit_return; } } state.reset(); funScript = fun->nonLazyScript(); if (!activation.pushInlineFrame(args, funScript, construct)) 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 prologue_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"); } // Increment the coverage for the main entry point. INIT_COVERAGE(); COUNT_COVERAGE_MAIN(); /* Load first op and dispatch it (safe since JSOP_RETRVAL). */ ADVANCE_AND_DISPATCH(0); } CASE(JSOP_OPTIMIZE_SPREADCALL) { ReservedRooted val(&rootValue0, REGS.sp[-1]); bool optimized = false; if (!OptimizeSpreadCall(cx, val, &optimized)) goto error; PUSH_BOOLEAN(optimized); } END_CASE(JSOP_OPTIMIZE_SPREADCALL) 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 name(&rootName0, script->getName(REGS.pc)); ReservedRooted envObj(&rootObject0, REGS.fp()->environmentChain()); ReservedRooted env(&rootObject1); if (!LookupNameWithGlobalDefault(cx, name, envObj, &env)) goto error; Value v = ComputeImplicitThis(env); 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 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 rval(&rootValue0); if (!GetIntrinsicOperation(cx, REGS.pc, &rval)) goto error; PUSH_COPY(rval); TypeScript::Monitor(cx, script, REGS.pc, rval); } END_CASE(JSOP_GETINTRINSIC) CASE(JSOP_UINT16) PUSH_INT32((int32_t) GET_UINT16(REGS.pc)); END_CASE(JSOP_UINT16) CASE(JSOP_UINT24) PUSH_INT32((int32_t) GET_UINT24(REGS.pc)); END_CASE(JSOP_UINT24) CASE(JSOP_INT8) PUSH_INT32(GET_INT8(REGS.pc)); END_CASE(JSOP_INT8) CASE(JSOP_INT32) PUSH_INT32(GET_INT32(REGS.pc)); END_CASE(JSOP_INT32) CASE(JSOP_DOUBLE) { double dbl; LOAD_DOUBLE(0, dbl); PUSH_DOUBLE(dbl); } END_CASE(JSOP_DOUBLE) CASE(JSOP_STRING) PUSH_STRING(script->getAtom(REGS.pc)); END_CASE(JSOP_STRING) CASE(JSOP_TOSTRING) { MutableHandleValue oper = REGS.stackHandleAt(-1); if (!oper.isString()) { JSString* operString = ToString(cx, oper); if (!operString) goto error; oper.setString(operString); } } END_CASE(JSOP_TOSTRING) CASE(JSOP_SYMBOL) PUSH_SYMBOL(cx->wellKnownSymbols().get(GET_UINT8(REGS.pc))); END_CASE(JSOP_SYMBOL) CASE(JSOP_OBJECT) { ReservedRooted ref(&rootObject0, script->getObject(REGS.pc)); if (cx->compartment()->creationOptions().cloneSingletons()) { JSObject* obj = DeepCloneObjectLiteral(cx, ref, TenuredObject); if (!obj) goto error; PUSH_OBJECT(*obj); } else { cx->compartment()->behaviors().setSingletonsAsValues(); PUSH_OBJECT(*ref); } } END_CASE(JSOP_OBJECT) CASE(JSOP_CALLSITEOBJ) { ReservedRooted cso(&rootObject0, script->getObject(REGS.pc)); ReservedRooted raw(&rootObject1, script->getObject(GET_UINT32_INDEX(REGS.pc) + 1)); ReservedRooted rawValue(&rootValue0, ObjectValue(*raw)); if (!ProcessCallSiteObjOperation(cx, cso, raw, rawValue)) goto error; PUSH_OBJECT(*cso); } END_CASE(JSOP_CALLSITEOBJ) CASE(JSOP_REGEXP) { /* * Push a regexp object cloned from the regexp literal object mapped by the * bytecode at pc. */ JSObject* obj = CloneRegExpObject(cx, script->getRegExp(REGS.pc)); if (!obj) goto error; PUSH_OBJECT(*obj); } END_CASE(JSOP_REGEXP) CASE(JSOP_ZERO) PUSH_INT32(0); END_CASE(JSOP_ZERO) CASE(JSOP_ONE) PUSH_INT32(1); END_CASE(JSOP_ONE) CASE(JSOP_NULL) PUSH_NULL(); END_CASE(JSOP_NULL) CASE(JSOP_FALSE) PUSH_BOOLEAN(false); END_CASE(JSOP_FALSE) CASE(JSOP_TRUE) PUSH_BOOLEAN(true); END_CASE(JSOP_TRUE) CASE(JSOP_TABLESWITCH) { jsbytecode* pc2 = REGS.pc; int32_t len = GET_JUMP_OFFSET(pc2); /* * ECMAv2+ forbids conversion of discriminant, so we will skip to the * default case if the discriminant isn't already an int jsval. (This * opcode is emitted only for dense int-domain switches.) */ const Value& rref = *--REGS.sp; int32_t i; if (rref.isInt32()) { i = rref.toInt32(); } else { /* Use mozilla::NumberEqualsInt32 to treat -0 (double) as 0. */ if (!rref.isDouble() || !NumberEqualsInt32(rref.toDouble(), &i)) ADVANCE_AND_DISPATCH(len); } pc2 += JUMP_OFFSET_LEN; int32_t low = GET_JUMP_OFFSET(pc2); pc2 += JUMP_OFFSET_LEN; int32_t high = GET_JUMP_OFFSET(pc2); i -= low; if ((uint32_t)i < (uint32_t)(high - low + 1)) { pc2 += JUMP_OFFSET_LEN + JUMP_OFFSET_LEN * i; int32_t off = (int32_t) GET_JUMP_OFFSET(pc2); if (off) len = off; } ADVANCE_AND_DISPATCH(len); } CASE(JSOP_ARGUMENTS) if (!script->ensureHasAnalyzedArgsUsage(cx)) goto error; if (script->needsArgsObj()) { ArgumentsObject* obj = ArgumentsObject::createExpected(cx, REGS.fp()); if (!obj) goto error; PUSH_COPY(ObjectValue(*obj)); } else { PUSH_COPY(MagicValue(JS_OPTIMIZED_ARGUMENTS)); } END_CASE(JSOP_ARGUMENTS) CASE(JSOP_RUNONCE) { if (!RunOnceScriptPrologue(cx, script)) goto error; } END_CASE(JSOP_RUNONCE) CASE(JSOP_REST) { ReservedRooted rest(&rootObject0, REGS.fp()->createRestParameter(cx)); if (!rest) goto error; PUSH_COPY(ObjectValue(*rest)); } END_CASE(JSOP_REST) CASE(JSOP_GETALIASEDVAR) { EnvironmentCoordinate ec = EnvironmentCoordinate(REGS.pc); ReservedRooted val(&rootValue0, REGS.fp()->aliasedEnvironment(ec).aliasedBinding(ec)); #ifdef DEBUG // Only the .this slot can hold the TDZ MagicValue. if (IsUninitializedLexical(val)) { PropertyName* name = EnvironmentCoordinateName(cx->caches.envCoordinateNameCache, script, REGS.pc); MOZ_ASSERT(name == cx->names().dotThis); JSOp next = JSOp(*GetNextPc(REGS.pc)); MOZ_ASSERT(next == JSOP_CHECKTHIS || next == JSOP_CHECKRETURN || next == JSOP_CHECKTHISREINIT); } #endif PUSH_COPY(val); TypeScript::Monitor(cx, script, REGS.pc, REGS.sp[-1]); } END_CASE(JSOP_GETALIASEDVAR) CASE(JSOP_SETALIASEDVAR) { EnvironmentCoordinate ec = EnvironmentCoordinate(REGS.pc); EnvironmentObject& obj = REGS.fp()->aliasedEnvironment(ec); SetAliasedVarOperation(cx, script, REGS.pc, obj, ec, REGS.sp[-1], CheckTDZ); } END_CASE(JSOP_SETALIASEDVAR) CASE(JSOP_THROWSETCONST) CASE(JSOP_THROWSETALIASEDCONST) CASE(JSOP_THROWSETCALLEE) { ReportRuntimeConstAssignment(cx, script, REGS.pc); goto error; } END_CASE(JSOP_THROWSETCONST) CASE(JSOP_CHECKLEXICAL) { uint32_t i = GET_LOCALNO(REGS.pc); ReservedRooted val(&rootValue0, REGS.fp()->unaliasedLocal(i)); if (!CheckUninitializedLexical(cx, script, REGS.pc, val)) goto error; } END_CASE(JSOP_CHECKLEXICAL) CASE(JSOP_INITLEXICAL) { uint32_t i = GET_LOCALNO(REGS.pc); REGS.fp()->unaliasedLocal(i) = REGS.sp[-1]; } END_CASE(JSOP_INITLEXICAL) CASE(JSOP_CHECKALIASEDLEXICAL) { EnvironmentCoordinate ec = EnvironmentCoordinate(REGS.pc); ReservedRooted val(&rootValue0, REGS.fp()->aliasedEnvironment(ec).aliasedBinding(ec)); if (!CheckUninitializedLexical(cx, script, REGS.pc, val)) goto error; } END_CASE(JSOP_CHECKALIASEDLEXICAL) CASE(JSOP_INITALIASEDLEXICAL) { EnvironmentCoordinate ec = EnvironmentCoordinate(REGS.pc); EnvironmentObject& obj = REGS.fp()->aliasedEnvironment(ec); SetAliasedVarOperation(cx, script, REGS.pc, obj, ec, REGS.sp[-1], DontCheckTDZ); } END_CASE(JSOP_INITALIASEDLEXICAL) CASE(JSOP_INITGLEXICAL) { LexicalEnvironmentObject* lexicalEnv; if (script->hasNonSyntacticScope()) lexicalEnv = ®S.fp()->extensibleLexicalEnvironment(); else lexicalEnv = &cx->global()->lexicalEnvironment(); HandleValue value = REGS.stackHandleAt(-1); InitGlobalLexicalOperation(cx, lexicalEnv, script, REGS.pc, value); } END_CASE(JSOP_INITGLEXICAL) CASE(JSOP_UNINITIALIZED) PUSH_MAGIC(JS_UNINITIALIZED_LEXICAL); END_CASE(JSOP_UNINITIALIZED) CASE(JSOP_GETARG) { unsigned i = GET_ARGNO(REGS.pc); if (script->argsObjAliasesFormals()) PUSH_COPY(REGS.fp()->argsObj().arg(i)); else PUSH_COPY(REGS.fp()->unaliasedFormal(i)); } END_CASE(JSOP_GETARG) CASE(JSOP_SETARG) { unsigned i = GET_ARGNO(REGS.pc); if (script->argsObjAliasesFormals()) REGS.fp()->argsObj().setArg(i, REGS.sp[-1]); else REGS.fp()->unaliasedFormal(i) = REGS.sp[-1]; } END_CASE(JSOP_SETARG) CASE(JSOP_GETLOCAL) { uint32_t i = GET_LOCALNO(REGS.pc); PUSH_COPY_SKIP_CHECK(REGS.fp()->unaliasedLocal(i)); #ifdef DEBUG // Derived class constructors store the TDZ Value in the .this slot // before a super() call. if (IsUninitializedLexical(REGS.sp[-1])) { MOZ_ASSERT(script->isDerivedClassConstructor()); JSOp next = JSOp(*GetNextPc(REGS.pc)); MOZ_ASSERT(next == JSOP_CHECKTHIS || next == JSOP_CHECKRETURN || next == JSOP_CHECKTHISREINIT); } #endif /* * Skip the same-compartment assertion if the local will be immediately * popped. We do not guarantee sync for dead locals when coming in from the * method JIT, and a GETLOCAL followed by POP is not considered to be * a use of the variable. */ if (REGS.pc[JSOP_GETLOCAL_LENGTH] != JSOP_POP) assertSameCompartmentDebugOnly(cx, REGS.sp[-1]); } END_CASE(JSOP_GETLOCAL) CASE(JSOP_SETLOCAL) { uint32_t i = GET_LOCALNO(REGS.pc); MOZ_ASSERT(!IsUninitializedLexical(REGS.fp()->unaliasedLocal(i))); REGS.fp()->unaliasedLocal(i) = REGS.sp[-1]; } END_CASE(JSOP_SETLOCAL) CASE(JSOP_DEFVAR) { /* ES5 10.5 step 8 (with subsequent errata). */ unsigned attrs = JSPROP_ENUMERATE; if (!REGS.fp()->isEvalFrame()) attrs |= JSPROP_PERMANENT; /* Step 8b. */ ReservedRooted obj(&rootObject0, ®S.fp()->varObj()); ReservedRooted name(&rootName0, script->getName(REGS.pc)); if (!DefVarOperation(cx, obj, name, attrs)) goto error; } END_CASE(JSOP_DEFVAR) CASE(JSOP_DEFCONST) CASE(JSOP_DEFLET) { LexicalEnvironmentObject* lexicalEnv; JSObject* varObj; if (script->hasNonSyntacticScope()) { lexicalEnv = ®S.fp()->extensibleLexicalEnvironment(); varObj = ®S.fp()->varObj(); } else { lexicalEnv = &cx->global()->lexicalEnvironment(); varObj = cx->global(); } if (!DefLexicalOperation(cx, lexicalEnv, varObj, script, REGS.pc)) goto error; } END_CASE(JSOP_DEFLET) CASE(JSOP_DEFFUN) { /* * A top-level function defined in Global or Eval code (see ECMA-262 * Ed. 3), or else a SpiderMonkey extension: a named function statement in * a compound statement (not at the top statement level of global code, or * at the top level of a function body). */ ReservedRooted fun(&rootFunction0, ®S.sp[-1].toObject().as()); if (!DefFunOperation(cx, script, REGS.fp()->environmentChain(), fun)) goto error; REGS.sp--; } END_CASE(JSOP_DEFFUN) CASE(JSOP_LAMBDA) { /* Load the specified function object literal. */ ReservedRooted fun(&rootFunction0, script->getFunction(GET_UINT32_INDEX(REGS.pc))); JSObject* obj = Lambda(cx, fun, REGS.fp()->environmentChain()); if (!obj) goto error; MOZ_ASSERT(obj->staticPrototype()); PUSH_OBJECT(*obj); } END_CASE(JSOP_LAMBDA) CASE(JSOP_LAMBDA_ARROW) { /* Load the specified function object literal. */ ReservedRooted fun(&rootFunction0, script->getFunction(GET_UINT32_INDEX(REGS.pc))); ReservedRooted newTarget(&rootValue1, REGS.sp[-1]); JSObject* obj = LambdaArrow(cx, fun, REGS.fp()->environmentChain(), newTarget); if (!obj) goto error; MOZ_ASSERT(obj->staticPrototype()); REGS.sp[-1].setObject(*obj); } END_CASE(JSOP_LAMBDA_ARROW) CASE(JSOP_TOASYNC) { ReservedRooted unwrapped(&rootFunction0, ®S.sp[-1].toObject().as()); JSObject* wrapped = WrapAsyncFunction(cx, unwrapped); if (!wrapped) goto error; REGS.sp[-1].setObject(*wrapped); } END_CASE(JSOP_TOASYNC) CASE(JSOP_SETFUNNAME) { MOZ_ASSERT(REGS.stackDepth() >= 2); FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(REGS.pc)); ReservedRooted name(&rootValue0, REGS.sp[-1]); ReservedRooted fun(&rootFunction0, ®S.sp[-2].toObject().as()); if (!SetFunctionNameIfNoOwnName(cx, fun, name, prefixKind)) goto error; REGS.sp--; } END_CASE(JSOP_SETFUNNAME) CASE(JSOP_CALLEE) MOZ_ASSERT(REGS.fp()->isFunctionFrame()); PUSH_COPY(REGS.fp()->calleev()); END_CASE(JSOP_CALLEE) CASE(JSOP_INITPROP_GETTER) CASE(JSOP_INITHIDDENPROP_GETTER) CASE(JSOP_INITPROP_SETTER) CASE(JSOP_INITHIDDENPROP_SETTER) { MOZ_ASSERT(REGS.stackDepth() >= 2); ReservedRooted obj(&rootObject0, ®S.sp[-2].toObject()); ReservedRooted name(&rootName0, script->getName(REGS.pc)); ReservedRooted val(&rootObject1, ®S.sp[-1].toObject()); if (!InitGetterSetterOperation(cx, REGS.pc, obj, name, val)) goto error; REGS.sp--; } END_CASE(JSOP_INITPROP_GETTER) CASE(JSOP_INITELEM_GETTER) CASE(JSOP_INITHIDDENELEM_GETTER) CASE(JSOP_INITELEM_SETTER) CASE(JSOP_INITHIDDENELEM_SETTER) { MOZ_ASSERT(REGS.stackDepth() >= 3); ReservedRooted obj(&rootObject0, ®S.sp[-3].toObject()); ReservedRooted idval(&rootValue0, REGS.sp[-2]); ReservedRooted val(&rootObject1, ®S.sp[-1].toObject()); if (!InitGetterSetterOperation(cx, REGS.pc, obj, idval, val)) goto error; REGS.sp -= 2; } END_CASE(JSOP_INITELEM_GETTER) CASE(JSOP_HOLE) PUSH_MAGIC(JS_ELEMENTS_HOLE); END_CASE(JSOP_HOLE) CASE(JSOP_NEWINIT) { uint8_t i = GET_UINT8(REGS.pc); MOZ_ASSERT(i == JSProto_Array || i == JSProto_Object); JSObject* obj; if (i == JSProto_Array) obj = NewArrayOperation(cx, script, REGS.pc, 0); else obj = NewObjectOperation(cx, script, REGS.pc); if (!obj) goto error; PUSH_OBJECT(*obj); } END_CASE(JSOP_NEWINIT) CASE(JSOP_NEWARRAY) { uint32_t length = GET_UINT32(REGS.pc); JSObject* obj = NewArrayOperation(cx, script, REGS.pc, length); if (!obj) goto error; PUSH_OBJECT(*obj); } END_CASE(JSOP_NEWARRAY) CASE(JSOP_NEWARRAY_COPYONWRITE) { ReservedRooted baseobj(&rootObject0, ObjectGroup::getOrFixupCopyOnWriteObject(cx, script, REGS.pc)); if (!baseobj) goto error; ReservedRooted obj(&rootObject1, NewDenseCopyOnWriteArray(cx, ((RootedObject&)(baseobj)).as(), gc::DefaultHeap)); if (!obj) goto error; PUSH_OBJECT(*obj); } END_CASE(JSOP_NEWARRAY_COPYONWRITE) CASE(JSOP_NEWOBJECT) { JSObject* obj = NewObjectOperation(cx, script, REGS.pc); if (!obj) goto error; PUSH_OBJECT(*obj); } END_CASE(JSOP_NEWOBJECT) CASE(JSOP_MUTATEPROTO) { MOZ_ASSERT(REGS.stackDepth() >= 2); if (REGS.sp[-1].isObjectOrNull()) { ReservedRooted newProto(&rootObject1, REGS.sp[-1].toObjectOrNull()); ReservedRooted obj(&rootObject0, ®S.sp[-2].toObject()); MOZ_ASSERT(obj->is()); if (!SetPrototype(cx, obj, newProto)) goto error; } REGS.sp--; } END_CASE(JSOP_MUTATEPROTO) CASE(JSOP_INITPROP) CASE(JSOP_INITLOCKEDPROP) CASE(JSOP_INITHIDDENPROP) { static_assert(JSOP_INITPROP_LENGTH == JSOP_INITLOCKEDPROP_LENGTH, "initprop and initlockedprop must be the same size"); static_assert(JSOP_INITPROP_LENGTH == JSOP_INITHIDDENPROP_LENGTH, "initprop and inithiddenprop must be the same size"); /* Load the property's initial value into rval. */ MOZ_ASSERT(REGS.stackDepth() >= 2); ReservedRooted rval(&rootValue0, REGS.sp[-1]); /* Load the object being initialized into lval/obj. */ ReservedRooted obj(&rootObject0, ®S.sp[-2].toObject()); PropertyName* name = script->getName(REGS.pc); RootedId& id = rootId0; id = NameToId(name); if (!InitPropertyOperation(cx, JSOp(*REGS.pc), obj, id, rval)) goto error; REGS.sp--; } END_CASE(JSOP_INITPROP) CASE(JSOP_INITELEM) CASE(JSOP_INITHIDDENELEM) { MOZ_ASSERT(REGS.stackDepth() >= 3); HandleValue val = REGS.stackHandleAt(-1); HandleValue id = REGS.stackHandleAt(-2); ReservedRooted obj(&rootObject0, ®S.sp[-3].toObject()); if (!InitElemOperation(cx, REGS.pc, obj, id, val)) goto error; REGS.sp -= 2; } END_CASE(JSOP_INITELEM) CASE(JSOP_INITELEM_ARRAY) { MOZ_ASSERT(REGS.stackDepth() >= 2); HandleValue val = REGS.stackHandleAt(-1); ReservedRooted obj(&rootObject0, ®S.sp[-2].toObject()); uint32_t index = GET_UINT32(REGS.pc); if (!InitArrayElemOperation(cx, REGS.pc, obj, index, val)) goto error; REGS.sp--; } END_CASE(JSOP_INITELEM_ARRAY) CASE(JSOP_INITELEM_INC) { MOZ_ASSERT(REGS.stackDepth() >= 3); HandleValue val = REGS.stackHandleAt(-1); ReservedRooted obj(&rootObject0, ®S.sp[-3].toObject()); uint32_t index = REGS.sp[-2].toInt32(); if (!InitArrayElemOperation(cx, REGS.pc, obj, index, val)) goto error; REGS.sp[-2].setInt32(index + 1); REGS.sp--; } END_CASE(JSOP_INITELEM_INC) CASE(JSOP_GOSUB) { PUSH_BOOLEAN(false); int32_t i = script->pcToOffset(REGS.pc) + JSOP_GOSUB_LENGTH; int32_t len = GET_JUMP_OFFSET(REGS.pc); PUSH_INT32(i); ADVANCE_AND_DISPATCH(len); } CASE(JSOP_RETSUB) { /* Pop [exception or hole, retsub pc-index]. */ Value rval, lval; POP_COPY_TO(rval); POP_COPY_TO(lval); MOZ_ASSERT(lval.isBoolean()); if (lval.toBoolean()) { /* * Exception was pending during finally, throw it *before* we adjust * pc, because pc indexes into script->trynotes. This turns out not to * be necessary, but it seems clearer. And it points out a FIXME: * 350509, due to Igor Bukanov. */ cx->setPendingException(rval); goto error; } MOZ_ASSERT(rval.isInt32()); /* Increment the PC by this much. */ int32_t len = rval.toInt32() - int32_t(script->pcToOffset(REGS.pc)); ADVANCE_AND_DISPATCH(len); } CASE(JSOP_EXCEPTION) { PUSH_NULL(); MutableHandleValue res = REGS.stackHandleAt(-1); if (!GetAndClearException(cx, res)) goto error; } END_CASE(JSOP_EXCEPTION) CASE(JSOP_FINALLY) CHECK_BRANCH(); END_CASE(JSOP_FINALLY) CASE(JSOP_THROWING) { ReservedRooted v(&rootValue0); POP_COPY_TO(v); MOZ_ALWAYS_TRUE(ThrowingOperation(cx, v)); } END_CASE(JSOP_THROWING) CASE(JSOP_THROW) { CHECK_BRANCH(); ReservedRooted v(&rootValue0); POP_COPY_TO(v); JS_ALWAYS_FALSE(Throw(cx, v)); /* let the code at error try to catch the exception. */ goto error; } CASE(JSOP_INSTANCEOF) { ReservedRooted rref(&rootValue0, REGS.sp[-1]); if (HandleValue(rref).isPrimitive()) { ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS, -1, rref, nullptr); goto error; } ReservedRooted obj(&rootObject0, &rref.toObject()); bool cond = false; if (!HasInstance(cx, obj, REGS.stackHandleAt(-2), &cond)) goto error; REGS.sp--; REGS.sp[-1].setBoolean(cond); } END_CASE(JSOP_INSTANCEOF) CASE(JSOP_DEBUGGER) { RootedValue rval(cx); switch (Debugger::onDebuggerStatement(cx, REGS.fp())) { case JSTRAP_ERROR: goto error; case JSTRAP_CONTINUE: break; case JSTRAP_RETURN: if (!ForcedReturn(cx, REGS)) goto error; goto successful_return_continuation; case JSTRAP_THROW: goto error; default:; } } END_CASE(JSOP_DEBUGGER) CASE(JSOP_PUSHLEXICALENV) { ReservedRooted scope(&rootScope0, script->getScope(REGS.pc)); // Create block environment and push on scope chain. if (!REGS.fp()->pushLexicalEnvironment(cx, scope.as())) goto error; } END_CASE(JSOP_PUSHLEXICALENV) CASE(JSOP_POPLEXICALENV) { #ifdef DEBUG // Pop block from scope chain. Scope* scope = script->lookupScope(REGS.pc); MOZ_ASSERT(scope); MOZ_ASSERT(scope->is()); MOZ_ASSERT(scope->as().hasEnvironment()); #endif if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugEnvironments::onPopLexical(cx, REGS.fp(), REGS.pc); // Pop block from scope chain. REGS.fp()->popOffEnvironmentChain(); } END_CASE(JSOP_POPLEXICALENV) CASE(JSOP_DEBUGLEAVELEXICALENV) { MOZ_ASSERT(script->lookupScope(REGS.pc)); MOZ_ASSERT(script->lookupScope(REGS.pc)->is()); MOZ_ASSERT(!script->lookupScope(REGS.pc)->as().hasEnvironment()); // FIXME: This opcode should not be necessary. The debugger shouldn't need // help from bytecode to do its job. See bug 927782. if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugEnvironments::onPopLexical(cx, REGS.fp(), REGS.pc); } END_CASE(JSOP_DEBUGLEAVELEXICALENV) CASE(JSOP_FRESHENLEXICALENV) { if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugEnvironments::onPopLexical(cx, REGS.fp(), REGS.pc); if (!REGS.fp()->freshenLexicalEnvironment(cx)) goto error; } END_CASE(JSOP_FRESHENLEXICALENV) CASE(JSOP_RECREATELEXICALENV) { if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugEnvironments::onPopLexical(cx, REGS.fp(), REGS.pc); if (!REGS.fp()->recreateLexicalEnvironment(cx)) goto error; } END_CASE(JSOP_RECREATELEXICALENV) CASE(JSOP_PUSHVARENV) { ReservedRooted scope(&rootScope0, script->getScope(REGS.pc)); if (!REGS.fp()->pushVarEnvironment(cx, scope)) goto error; } END_CASE(JSOP_PUSHVARENV) CASE(JSOP_POPVARENV) { #ifdef DEBUG Scope* scope = script->lookupScope(REGS.pc); MOZ_ASSERT(scope); MOZ_ASSERT(scope->is()); MOZ_ASSERT(scope->as().hasEnvironment()); #endif if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) DebugEnvironments::onPopVar(cx, REGS.fp(), REGS.pc); REGS.fp()->popOffEnvironmentChain(); } END_CASE(JSOP_POPVARENV) CASE(JSOP_GENERATOR) { MOZ_ASSERT(!cx->isExceptionPending()); MOZ_ASSERT(REGS.stackDepth() == 0); JSObject* obj = GeneratorObject::create(cx, REGS.fp()); if (!obj) goto error; PUSH_OBJECT(*obj); } END_CASE(JSOP_GENERATOR) CASE(JSOP_INITIALYIELD) { MOZ_ASSERT(!cx->isExceptionPending()); MOZ_ASSERT(REGS.fp()->isFunctionFrame()); ReservedRooted obj(&rootObject0, ®S.sp[-1].toObject()); POP_RETURN_VALUE(); MOZ_ASSERT(REGS.stackDepth() == 0); if (!GeneratorObject::initialSuspend(cx, obj, REGS.fp(), REGS.pc)) goto error; goto successful_return_continuation; } CASE(JSOP_YIELD) { MOZ_ASSERT(!cx->isExceptionPending()); MOZ_ASSERT(REGS.fp()->isFunctionFrame()); ReservedRooted obj(&rootObject0, ®S.sp[-1].toObject()); if (!GeneratorObject::normalSuspend(cx, obj, REGS.fp(), REGS.pc, REGS.spForStackDepth(0), REGS.stackDepth() - 2)) { goto error; } REGS.sp--; POP_RETURN_VALUE(); goto successful_return_continuation; } CASE(JSOP_RESUME) { { ReservedRooted gen(&rootObject0, ®S.sp[-2].toObject()); ReservedRooted val(&rootValue0, REGS.sp[-1]); // popInlineFrame expects there to be an additional value on the stack // to pop off, so leave "gen" on the stack. GeneratorObject::ResumeKind resumeKind = GeneratorObject::getResumeKind(REGS.pc); bool ok = GeneratorObject::resume(cx, activation, gen, val, resumeKind); SET_SCRIPT(REGS.fp()->script()); TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); TraceLoggerEvent scriptEvent(logger, TraceLogger_Scripts, script); TraceLogStartEvent(logger, scriptEvent); TraceLogStartEvent(logger, TraceLogger_Interpreter); if (!ok) goto error; } ADVANCE_AND_DISPATCH(0); } CASE(JSOP_DEBUGAFTERYIELD) { // No-op in the interpreter, as GeneratorObject::resume takes care of // fixing up InterpreterFrames. MOZ_ASSERT_IF(REGS.fp()->script()->isDebuggee(), REGS.fp()->isDebuggee()); } END_CASE(JSOP_DEBUGAFTERYIELD) CASE(JSOP_FINALYIELDRVAL) { ReservedRooted gen(&rootObject0, ®S.sp[-1].toObject()); REGS.sp--; if (!GeneratorObject::finalSuspend(cx, gen)) { interpReturnOK = false; goto return_continuation; } goto successful_return_continuation; } CASE(JSOP_ARRAYPUSH) { ReservedRooted obj(&rootObject0, ®S.sp[-1].toObject()); if (!NewbornArrayPush(cx, obj, REGS.sp[-2])) goto error; REGS.sp -= 2; } END_CASE(JSOP_ARRAYPUSH) CASE(JSOP_CLASSHERITAGE) { ReservedRooted val(&rootValue0, REGS.sp[-1]); ReservedRooted objProto(&rootValue1); ReservedRooted funcProto(&rootObject0); if (val.isNull()) { objProto = NullValue(); if (!GetBuiltinPrototype(cx, JSProto_Function, &funcProto)) goto error; } else { if (!IsConstructor(val)) { ReportIsNotFunction(cx, val, 0, CONSTRUCT); goto error; } funcProto = &val.toObject(); if (!GetProperty(cx, funcProto, funcProto, cx->names().prototype, &objProto)) goto error; if (!objProto.isObjectOrNull()) { ReportValueError(cx, JSMSG_PROTO_NOT_OBJORNULL, -1, objProto, nullptr); goto error; } } REGS.sp[-1].setObject(*funcProto); PUSH_COPY(objProto); } END_CASE(JSOP_CLASSHERITAGE) CASE(JSOP_FUNWITHPROTO) { ReservedRooted proto(&rootObject1, ®S.sp[-1].toObject()); /* Load the specified function object literal. */ ReservedRooted fun(&rootFunction0, script->getFunction(GET_UINT32_INDEX(REGS.pc))); JSObject* obj = CloneFunctionObjectIfNotSingleton(cx, fun, REGS.fp()->environmentChain(), proto, GenericObject); if (!obj) goto error; REGS.sp[-1].setObject(*obj); } END_CASE(JSOP_FUNWITHPROTO) CASE(JSOP_OBJWITHPROTO) { ReservedRooted proto(&rootObject0, REGS.sp[-1].toObjectOrNull()); JSObject* obj = NewObjectWithGivenProto(cx, proto); if (!obj) goto error; REGS.sp[-1].setObject(*obj); } END_CASE(JSOP_OBJWITHPROTO) CASE(JSOP_INITHOMEOBJECT) { unsigned skipOver = GET_UINT8(REGS.pc); MOZ_ASSERT(REGS.stackDepth() >= skipOver + 2); /* Load the function to be initialized */ ReservedRooted func(&rootFunction0, ®S.sp[-1].toObject().as()); MOZ_ASSERT(func->allowSuperProperty()); /* Load the home object */ ReservedRooted obj(&rootObject0); obj = ®S.sp[int(-2 - skipOver)].toObject(); MOZ_ASSERT(obj->is() || obj->is()); func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, ObjectValue(*obj)); } END_CASE(JSOP_INITHOMEOBJECT) CASE(JSOP_SUPERBASE) { JSFunction& superEnvFunc = GetSuperEnvFunction(cx, REGS); MOZ_ASSERT(superEnvFunc.allowSuperProperty()); MOZ_ASSERT(superEnvFunc.nonLazyScript()->needsHomeObject()); const Value& homeObjVal = superEnvFunc.getExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT); ReservedRooted homeObj(&rootObject0, &homeObjVal.toObject()); ReservedRooted superBase(&rootObject1); if (!GetPrototype(cx, homeObj, &superBase)) goto error; if (!superBase) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, "null", "object"); goto error; } PUSH_OBJECT(*superBase); } END_CASE(JSOP_SUPERBASE) CASE(JSOP_NEWTARGET) PUSH_COPY(REGS.fp()->newTarget()); MOZ_ASSERT(REGS.sp[-1].isObject() || REGS.sp[-1].isUndefined()); END_CASE(JSOP_NEWTARGET) CASE(JSOP_SUPERFUN) { ReservedRooted superEnvFunc(&rootObject0, &GetSuperEnvFunction(cx, REGS)); MOZ_ASSERT(superEnvFunc->as().isClassConstructor()); MOZ_ASSERT(superEnvFunc->as().nonLazyScript()->isDerivedClassConstructor()); ReservedRooted superFun(&rootObject1); if (!GetPrototype(cx, superEnvFunc, &superFun)) goto error; ReservedRooted superFunVal(&rootValue0, UndefinedValue()); if (!superFun) superFunVal = NullValue(); else if (!superFun->isConstructor()) superFunVal = ObjectValue(*superFun); if (superFunVal.isObjectOrNull()) { ReportIsNotFunction(cx, superFunVal, JSDVG_IGNORE_STACK, CONSTRUCT); goto error; } PUSH_OBJECT(*superFun); } END_CASE(JSOP_SUPERFUN) CASE(JSOP_DERIVEDCONSTRUCTOR) { MOZ_ASSERT(REGS.sp[-1].isObject()); ReservedRooted proto(&rootObject0, ®S.sp[-1].toObject()); JSFunction* constructor = MakeDefaultConstructor(cx, JSOp(*REGS.pc), script->getAtom(REGS.pc), proto); if (!constructor) goto error; REGS.sp[-1].setObject(*constructor); } END_CASE(JSOP_DERIVEDCONSTRUCTOR) CASE(JSOP_CLASSCONSTRUCTOR) { JSFunction* constructor = MakeDefaultConstructor(cx, JSOp(*REGS.pc), script->getAtom(REGS.pc), nullptr); if (!constructor) goto error; PUSH_OBJECT(*constructor); } END_CASE(JSOP_CLASSCONSTRUCTOR) CASE(JSOP_CHECKOBJCOERCIBLE) { ReservedRooted checkVal(&rootValue0, REGS.sp[-1]); if (checkVal.isNullOrUndefined() && !ToObjectFromStack(cx, checkVal)) goto error; } END_CASE(JSOP_CHECKOBJCOERCIBLE) CASE(JSOP_DEBUGCHECKSELFHOSTED) { #ifdef DEBUG ReservedRooted checkVal(&rootValue0, REGS.sp[-1]); if (!Debug_CheckSelfHosted(cx, checkVal)) goto error; #endif } END_CASE(JSOP_DEBUGCHECKSELFHOSTED) CASE(JSOP_IS_CONSTRUCTING) PUSH_MAGIC(JS_IS_CONSTRUCTING); END_CASE(JSOP_IS_CONSTRUCTING) DEFAULT() { char numBuf[12]; SprintfLiteral(numBuf, "%d", *REGS.pc); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_BYTECODE, numBuf); goto error; } } /* interpreter loop */ MOZ_CRASH("Interpreter loop exited via fallthrough"); error: switch (HandleError(cx, REGS)) { case SuccessfulReturnContinuation: goto successful_return_continuation; case ErrorReturnContinuation: interpReturnOK = false; goto return_continuation; case CatchContinuation: ADVANCE_AND_DISPATCH(0); case FinallyContinuation: { /* * Push (true, exception) pair for finally to indicate that [retsub] * should rethrow the exception. */ ReservedRooted exception(&rootValue0); if (!cx->getPendingException(&exception)) { interpReturnOK = false; goto return_continuation; } PUSH_BOOLEAN(true); PUSH_COPY(exception); cx->clearPendingException(); } ADVANCE_AND_DISPATCH(0); } MOZ_CRASH("Invalid HandleError continuation"); exit: if (MOZ_LIKELY(!frameHalfInitialized)) { interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), REGS.pc, interpReturnOK); REGS.fp()->epilogue(cx, REGS.pc); } TraceLogStopEvent(logger, TraceLogger_Engine); TraceLogStopEvent(logger, scriptEvent); /* * This path is used when it's guaranteed the method can be finished * inside the JIT. */ leave_on_safe_point: if (interpReturnOK) state.setReturnValue(activation.entryFrame()->returnValue()); return interpReturnOK; prologue_error: interpReturnOK = false; frameHalfInitialized = true; goto prologue_return_continuation; } bool js::Throw(JSContext* cx, HandleValue v) { MOZ_ASSERT(!cx->isExceptionPending()); cx->setPendingException(v); return false; } bool js::ThrowingOperation(JSContext* cx, HandleValue v) { // Like js::Throw, but returns |true| instead of |false| to continue // execution instead of calling the (JIT) exception handler. MOZ_ASSERT(!cx->isExceptionPending()); cx->setPendingException(v); return true; } bool js::GetProperty(JSContext* cx, HandleValue v, HandlePropertyName name, MutableHandleValue vp) { if (name == cx->names().length) { // Fast path for strings, arrays and arguments. if (GetLengthProperty(v, vp)) return true; } // Optimize common cases like (2).toString() or "foo".valueOf() to not // create a wrapper object. if (v.isPrimitive() && !v.isNullOrUndefined()) { NativeObject* proto; if (v.isNumber()) { proto = GlobalObject::getOrCreateNumberPrototype(cx, cx->global()); } else if (v.isString()) { proto = GlobalObject::getOrCreateStringPrototype(cx, cx->global()); } else if (v.isBoolean()) { proto = GlobalObject::getOrCreateBooleanPrototype(cx, cx->global()); } else { MOZ_ASSERT(v.isSymbol()); proto = GlobalObject::getOrCreateSymbolPrototype(cx, cx->global()); } if (!proto) return false; if (GetPropertyPure(cx, proto, NameToId(name), vp.address())) return true; } RootedValue receiver(cx, v); RootedObject obj(cx, ToObjectFromStack(cx, v)); if (!obj) return false; return GetProperty(cx, obj, receiver, name, vp); } bool js::GetEnvironmentName(JSContext* cx, HandleObject envChain, HandlePropertyName name, MutableHandleValue vp) { RootedShape shape(cx); RootedObject obj(cx), pobj(cx); if (!LookupName(cx, name, envChain, &obj, &pobj, &shape)) return false; if (!shape) return ReportIsNotDefined(cx, name); if (!GetProperty(cx, obj, obj, name, vp)) return false; // We do our own explicit checking for |this| if (name == cx->names().dotThis) return true; // See note in FetchName. return CheckUninitializedLexical(cx, name, vp); } /* * Alternate form for NAME opcodes followed immediately by a TYPEOF, * which do not report an exception on (typeof foo == "undefined") tests. */ bool js::GetEnvironmentNameForTypeOf(JSContext* cx, HandleObject envChain, HandlePropertyName name, MutableHandleValue vp) { RootedShape shape(cx); RootedObject obj(cx), pobj(cx); if (!LookupName(cx, name, envChain, &obj, &pobj, &shape)) return false; if (!shape) { vp.set(UndefinedValue()); return true; } if (!GetProperty(cx, obj, obj, name, vp)) return false; // See note in FetchName. return CheckUninitializedLexical(cx, name, vp); } JSObject* js::Lambda(JSContext* cx, HandleFunction fun, HandleObject parent) { MOZ_ASSERT(!fun->isArrow()); RootedObject clone(cx, CloneFunctionObjectIfNotSingleton(cx, fun, parent)); if (!clone) return nullptr; MOZ_ASSERT(fun->global() == clone->global()); return clone; } JSObject* js::LambdaArrow(JSContext* cx, HandleFunction fun, HandleObject parent, HandleValue newTargetv) { MOZ_ASSERT(fun->isArrow()); RootedObject clone(cx, CloneFunctionObjectIfNotSingleton(cx, fun, parent, nullptr, TenuredObject)); if (!clone) return nullptr; MOZ_ASSERT(clone->as().isArrow()); clone->as().setExtendedSlot(0, newTargetv); MOZ_ASSERT(fun->global() == clone->global()); return clone; } bool js::DefFunOperation(JSContext* cx, HandleScript script, HandleObject envChain, HandleFunction fun) { /* * We define the function as a property of the variable object and not the * current scope chain even for the case of function expression statements * and functions defined by eval inside let or with blocks. */ RootedObject parent(cx, envChain); while (!parent->isQualifiedVarObj()) parent = parent->enclosingEnvironment(); /* ES5 10.5 (NB: with subsequent errata). */ RootedPropertyName name(cx, fun->explicitName()->asPropertyName()); RootedShape shape(cx); RootedObject pobj(cx); if (!LookupProperty(cx, parent, name, &pobj, &shape)) return false; RootedValue rval(cx, ObjectValue(*fun)); /* * ECMA requires functions defined when entering Eval code to be * impermanent. */ unsigned attrs = script->isActiveEval() ? JSPROP_ENUMERATE : JSPROP_ENUMERATE | JSPROP_PERMANENT; /* Steps 5d, 5f. */ if (!shape || pobj != parent) { if (!DefineProperty(cx, parent, name, rval, nullptr, nullptr, attrs)) return false; return parent->is() ? parent->compartment()->addToVarNames(cx, name) : true; } /* * Step 5e. * * A DebugEnvironmentProxy is okay here, and sometimes necessary. If * Debugger.Frame.prototype.eval defines a function with the same name as an * extant variable in the frame, the DebugEnvironmentProxy takes care of storing * the function in the stack frame (for non-aliased variables) or on the * scope object (for aliased). */ MOZ_ASSERT(parent->isNative() || parent->is()); if (parent->is()) { if (shape->configurable()) { if (!DefineProperty(cx, parent, name, rval, nullptr, nullptr, attrs)) return false; } else { MOZ_ASSERT(shape->isDataDescriptor()); MOZ_ASSERT(shape->writable()); MOZ_ASSERT(shape->enumerable()); } // Careful: the presence of a shape, even one appearing to derive from // a variable declaration, doesn't mean it's in [[VarNames]]. if (!parent->compartment()->addToVarNames(cx, name)) return false; } /* * Non-global properties, and global properties which we aren't simply * redefining, must be set. First, this preserves their attributes. * Second, this will produce warnings and/or errors as necessary if the * specified Call object property is not writable (const). */ /* Step 5f. */ RootedId id(cx, NameToId(name)); return PutProperty(cx, parent, id, rval, script->strict()); } bool js::ThrowMsgOperation(JSContext* cx, const unsigned errorNum) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNum); return false; } bool js::GetAndClearException(JSContext* cx, MutableHandleValue res) { if (!cx->getPendingException(res)) return false; cx->clearPendingException(); // Allow interrupting deeply nested exception handling. return CheckForInterrupt(cx); } template bool js::DeletePropertyJit(JSContext* cx, HandleValue v, HandlePropertyName name, bool* bp) { RootedObject obj(cx, ToObjectFromStack(cx, v)); if (!obj) return false; RootedId id(cx, NameToId(name)); ObjectOpResult result; if (!DeleteProperty(cx, obj, id, result)) return false; if (strict) { if (!result) return result.reportError(cx, obj, id); *bp = true; } else { *bp = result.ok(); } return true; } template bool js::DeletePropertyJit (JSContext* cx, HandleValue val, HandlePropertyName name, bool* bp); template bool js::DeletePropertyJit(JSContext* cx, HandleValue val, HandlePropertyName name, bool* bp); template bool js::DeleteElementJit(JSContext* cx, HandleValue val, HandleValue index, bool* bp) { RootedObject obj(cx, ToObjectFromStack(cx, val)); if (!obj) return false; RootedId id(cx); if (!ToPropertyKey(cx, index, &id)) return false; ObjectOpResult result; if (!DeleteProperty(cx, obj, id, result)) return false; if (strict) { if (!result) return result.reportError(cx, obj, id); *bp = true; } else { *bp = result.ok(); } return true; } template bool js::DeleteElementJit (JSContext*, HandleValue, HandleValue, bool* succeeded); template bool js::DeleteElementJit(JSContext*, HandleValue, HandleValue, bool* succeeded); bool js::GetElement(JSContext* cx, MutableHandleValue lref, HandleValue rref, MutableHandleValue vp) { return GetElementOperation(cx, JSOP_GETELEM, lref, rref, vp); } bool js::CallElement(JSContext* cx, MutableHandleValue lref, HandleValue rref, MutableHandleValue res) { return GetElementOperation(cx, JSOP_CALLELEM, lref, rref, res); } bool js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleValue value, bool strict) { RootedId id(cx); if (!ToPropertyKey(cx, index, &id)) return false; RootedValue receiver(cx, ObjectValue(*obj)); return SetObjectElementOperation(cx, obj, id, value, receiver, strict); } bool js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleValue value, bool strict, HandleScript script, jsbytecode* pc) { MOZ_ASSERT(pc); RootedId id(cx); if (!ToPropertyKey(cx, index, &id)) return false; RootedValue receiver(cx, ObjectValue(*obj)); return SetObjectElementOperation(cx, obj, id, value, receiver, strict, script, pc); } bool js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index, HandleValue value, HandleValue receiver, bool strict, HandleScript script, jsbytecode* pc) { MOZ_ASSERT(pc); RootedId id(cx); if (!ToPropertyKey(cx, index, &id)) return false; return SetObjectElementOperation(cx, obj, id, value, receiver, strict, script, pc); } bool js::InitElementArray(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t index, HandleValue value) { return InitArrayElemOperation(cx, pc, obj, index, value); } bool js::AddValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { return AddOperation(cx, lhs, rhs, res); } bool js::SubValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { return SubOperation(cx, lhs, rhs, res); } bool js::MulValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { return MulOperation(cx, lhs, rhs, res); } bool js::DivValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { return DivOperation(cx, lhs, rhs, res); } bool js::ModValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { return ModOperation(cx, lhs, rhs, res); } bool js::UrshValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { return UrshOperation(cx, lhs, rhs, res); } bool js::AtomicIsLockFree(JSContext* cx, HandleValue in, int* out) { int i; if (!ToInt32(cx, in, &i)) return false; *out = js::jit::AtomicOperations::isLockfree(i); return true; } bool js::DeleteNameOperation(JSContext* cx, HandlePropertyName name, HandleObject scopeObj, MutableHandleValue res) { RootedObject scope(cx), pobj(cx); RootedShape shape(cx); if (!LookupName(cx, name, scopeObj, &scope, &pobj, &shape)) return false; if (!scope) { // Return true for non-existent names. res.setBoolean(true); return true; } ObjectOpResult result; RootedId id(cx, NameToId(name)); if (!DeleteProperty(cx, scope, id, result)) return false; bool status = result.ok(); res.setBoolean(status); if (status) { // Deleting a name from the global object removes it from [[VarNames]]. if (pobj == scope && scope->is()) scope->compartment()->removeFromVarNames(name); } return true; } bool js::ImplicitThisOperation(JSContext* cx, HandleObject scopeObj, HandlePropertyName name, MutableHandleValue res) { RootedObject obj(cx); if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &obj)) return false; res.set(ComputeImplicitThis(obj)); return true; } bool js::RunOnceScriptPrologue(JSContext* cx, HandleScript script) { MOZ_ASSERT(script->treatAsRunOnce()); if (!script->hasRunOnce()) { script->setHasRunOnce(); return true; } // Force instantiation of the script's function's group to ensure the flag // is preserved in type information. if (!script->functionNonDelazifying()->getGroup(cx)) return false; MarkObjectGroupFlags(cx, script->functionNonDelazifying(), OBJECT_FLAG_RUNONCE_INVALIDATED); return true; } unsigned js::GetInitDataPropAttrs(JSOp op) { switch (op) { case JSOP_INITPROP: return JSPROP_ENUMERATE; case JSOP_INITLOCKEDPROP: return JSPROP_PERMANENT | JSPROP_READONLY; case JSOP_INITHIDDENPROP: // Non-enumerable, but writable and configurable return 0; default:; } MOZ_CRASH("Unknown data initprop"); } bool js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleId id, HandleObject val) { MOZ_ASSERT(val->isCallable()); GetterOp getter; SetterOp setter; unsigned attrs = JSPROP_SHARED; JSOp op = JSOp(*pc); if (!IsHiddenInitOp(op)) attrs |= JSPROP_ENUMERATE; if (op == JSOP_INITPROP_GETTER || op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENPROP_GETTER || op == JSOP_INITHIDDENELEM_GETTER) { getter = CastAsGetterOp(val); setter = nullptr; attrs |= JSPROP_GETTER; } else { MOZ_ASSERT(op == JSOP_INITPROP_SETTER || op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENPROP_SETTER || op == JSOP_INITHIDDENELEM_SETTER); getter = nullptr; setter = CastAsSetterOp(val); attrs |= JSPROP_SETTER; } RootedValue scratch(cx); return DefineProperty(cx, obj, id, scratch, getter, setter, attrs); } bool js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandlePropertyName name, HandleObject val) { RootedId id(cx, NameToId(name)); return InitGetterSetterOperation(cx, pc, obj, id, val); } bool js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleValue idval, HandleObject val) { RootedId id(cx); if (!ToPropertyKey(cx, idval, &id)) return false; return InitGetterSetterOperation(cx, pc, obj, id, val); } bool js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue thisv, HandleValue callee, HandleValue arr, HandleValue newTarget, MutableHandleValue res) { RootedArrayObject aobj(cx, &arr.toObject().as()); uint32_t length = aobj->length(); JSOp op = JSOp(*pc); bool constructing = op == JSOP_SPREADNEW || op == JSOP_SPREADSUPERCALL; // {Construct,Invoke}Args::init does this too, but this gives us a better // error message. if (length > ARGS_LENGTH_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, constructing ? JSMSG_TOO_MANY_CON_SPREADARGS : JSMSG_TOO_MANY_FUN_SPREADARGS); return false; } // Do our own checks for the callee being a function, as Invoke uses the // expression decompiler to decompile the callee stack operand based on // the number of arguments. Spread operations have the callee at sp - 3 // when not constructing, and sp - 4 when constructing. if (callee.isPrimitive()) { return ReportIsNotFunction(cx, callee, 2 + constructing, constructing ? CONSTRUCT : NO_CONSTRUCT); } if (MOZ_UNLIKELY(!callee.toObject().is()) && !callee.toObject().callHook()) { return ReportIsNotFunction(cx, callee, 2 + constructing, constructing ? CONSTRUCT : NO_CONSTRUCT); } #ifdef DEBUG // The object must be an array with dense elements and no holes. Baseline's // optimized spread call stubs rely on this. MOZ_ASSERT(!aobj->isIndexed()); MOZ_ASSERT(aobj->getDenseInitializedLength() == aobj->length()); for (size_t i = 0; i < aobj->length(); i++) MOZ_ASSERT(!aobj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)); #endif if (constructing) { if (!StackCheckIsConstructorCalleeNewTarget(cx, callee, newTarget)) return false; ConstructArgs cargs(cx); if (!cargs.init(cx, length)) return false; if (!GetElements(cx, aobj, length, cargs.array())) return false; RootedObject obj(cx); if (!Construct(cx, callee, cargs, newTarget, &obj)) return false; res.setObject(*obj); } else { InvokeArgs args(cx); if (!args.init(cx, length)) return false; if (!GetElements(cx, aobj, length, args.array())) return false; if ((op == JSOP_SPREADEVAL || op == JSOP_STRICTSPREADEVAL) && cx->global()->valueIsEval(callee)) { if (!DirectEval(cx, args.get(0), res)) return false; } else { MOZ_ASSERT(op == JSOP_SPREADCALL || op == JSOP_SPREADEVAL || op == JSOP_STRICTSPREADEVAL, "bad spread opcode"); if (!Call(cx, callee, thisv, args, res)) return false; } } TypeScript::Monitor(cx, script, pc, res); return true; } bool js::OptimizeSpreadCall(JSContext* cx, HandleValue arg, bool* optimized) { // Optimize spread call by skipping spread operation when following // conditions are met: // * the argument is an array // * the array has no hole // * array[@@iterator] is not modified // * the array's prototype is Array.prototype // * Array.prototype[@@iterator] is not modified // * %ArrayIteratorPrototype%.next is not modified if (!arg.isObject()) { *optimized = false; return true; } RootedObject obj(cx, &arg.toObject()); if (!IsPackedArray(obj)) { *optimized = false; return true; } ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx); if (!stubChain) return false; return stubChain->tryOptimizeArray(cx, obj.as(), optimized); } JSObject* js::NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc, NewObjectKind newKind /* = GenericObject */) { MOZ_ASSERT(newKind != SingletonObject); RootedObjectGroup group(cx); if (ObjectGroup::useSingletonForAllocationSite(script, pc, JSProto_Object)) { newKind = SingletonObject; } else { group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Object); if (!group) return nullptr; if (group->maybePreliminaryObjects()) { group->maybePreliminaryObjects()->maybeAnalyze(cx, group); } if (group->shouldPreTenure() || group->maybePreliminaryObjects()) newKind = TenuredObject; } RootedPlainObject obj(cx); if (*pc == JSOP_NEWOBJECT) { RootedPlainObject baseObject(cx, &script->getObject(pc)->as()); obj = CopyInitializerObject(cx, baseObject, newKind); } else { MOZ_ASSERT(*pc == JSOP_NEWINIT); MOZ_ASSERT(GET_UINT8(pc) == JSProto_Object); obj = NewBuiltinClassInstance(cx, newKind); } if (!obj) return nullptr; if (newKind == SingletonObject) { if (!JSObject::setSingleton(cx, obj)) return nullptr; } else { obj->setGroup(group); if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects()) preliminaryObjects->registerNewObject(obj); } return obj; } JSObject* js::NewObjectOperationWithTemplate(JSContext* cx, HandleObject templateObject) { // This is an optimized version of NewObjectOperation for use when the // object is not a singleton and has had its preliminary objects analyzed, // with the template object a copy of the object to create. MOZ_ASSERT(!templateObject->isSingleton()); NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject; if (templateObject->group()->maybeUnboxedLayout()) { RootedObjectGroup group(cx, templateObject->group()); return UnboxedPlainObject::create(cx, group, newKind); } JSObject* obj = CopyInitializerObject(cx, templateObject.as(), newKind); if (!obj) return nullptr; obj->setGroup(templateObject->group()); return obj; } JSObject* js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32_t length, NewObjectKind newKind /* = GenericObject */) { MOZ_ASSERT(newKind != SingletonObject); RootedObjectGroup group(cx); if (ObjectGroup::useSingletonForAllocationSite(script, pc, JSProto_Array)) { newKind = SingletonObject; } else { group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array); if (!group) return nullptr; if (group->maybePreliminaryObjects()) group->maybePreliminaryObjects()->maybeAnalyze(cx, group); if (group->shouldPreTenure() || group->maybePreliminaryObjects()) newKind = TenuredObject; } ArrayObject* obj = NewDenseFullyAllocatedArray(cx, length, nullptr, newKind); if (!obj) return nullptr; if (newKind == SingletonObject) { MOZ_ASSERT(obj->isSingleton()); } else { obj->setGroup(group); } return obj; } JSObject* js::NewArrayOperationWithTemplate(JSContext* cx, HandleObject templateObject) { MOZ_ASSERT(!templateObject->isSingleton()); NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject; ArrayObject* obj = NewDenseFullyAllocatedArray(cx, templateObject->as().length(), nullptr, newKind); if (!obj) return nullptr; MOZ_ASSERT(obj->lastProperty() == templateObject->as().lastProperty()); obj->setGroup(templateObject->group()); return obj; } void js::ReportRuntimeLexicalError(JSContext* cx, unsigned errorNumber, HandleId id) { MOZ_ASSERT(errorNumber == JSMSG_UNINITIALIZED_LEXICAL || errorNumber == JSMSG_BAD_CONST_ASSIGN); JSAutoByteString printable; if (ValueToPrintable(cx, IdToValue(id), &printable)) JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, errorNumber, printable.ptr()); } void js::ReportRuntimeLexicalError(JSContext* cx, unsigned errorNumber, HandlePropertyName name) { RootedId id(cx, NameToId(name)); ReportRuntimeLexicalError(cx, errorNumber, id); } void js::ReportRuntimeLexicalError(JSContext* cx, unsigned errorNumber, HandleScript script, jsbytecode* pc) { JSOp op = JSOp(*pc); MOZ_ASSERT(op == JSOP_CHECKLEXICAL || op == JSOP_CHECKALIASEDLEXICAL || op == JSOP_THROWSETCONST || op == JSOP_THROWSETALIASEDCONST || op == JSOP_THROWSETCALLEE || op == JSOP_GETIMPORT); RootedPropertyName name(cx); if (op == JSOP_THROWSETCALLEE) { name = script->functionNonDelazifying()->explicitName()->asPropertyName(); } else if (IsLocalOp(op)) { name = FrameSlotName(script, pc)->asPropertyName(); } else if (IsAtomOp(op)) { name = script->getName(pc); } else { MOZ_ASSERT(IsAliasedVarOp(op)); name = EnvironmentCoordinateName(cx->caches.envCoordinateNameCache, script, pc); } ReportRuntimeLexicalError(cx, errorNumber, name); } void js::ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name, const char* redeclKind) { JSAutoByteString printable; if (AtomToPrintableString(cx, name, &printable)) { JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_REDECLARED_VAR, redeclKind, printable.ptr()); } } bool js::ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind) { switch (kind) { case CheckIsObjectKind::IteratorNext: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next"); break; case CheckIsObjectKind::IteratorReturn: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "return"); break; case CheckIsObjectKind::IteratorThrow: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "throw"); break; case CheckIsObjectKind::GetIterator: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_GET_ITER_RETURNED_PRIMITIVE); break; default: MOZ_CRASH("Unknown kind"); } return false; } bool js::ThrowCheckIsCallable(JSContext* cx, CheckIsCallableKind kind) { switch (kind) { case CheckIsCallableKind::IteratorReturn: JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_RETURN_NOT_CALLABLE); break; default: MOZ_CRASH("Unknown kind"); } return false; } bool js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame) { RootedFunction fun(cx); if (frame.isFunctionFrame()) { fun = frame.callee(); } else { Scope* startingScope; if (frame.isDebuggerEvalFrame()) { AbstractFramePtr evalInFramePrev = frame.asInterpreterFrame()->evalInFramePrev(); startingScope = evalInFramePrev.script()->bodyScope(); } else { MOZ_ASSERT(frame.isEvalFrame()); MOZ_ASSERT(frame.script()->isDirectEvalInFunction()); startingScope = frame.script()->enclosingScope(); } for (ScopeIter si(startingScope); si; si++) { if (si.scope()->is()) { fun = si.scope()->as().canonicalFunction(); break; } } MOZ_ASSERT(fun); } if (fun->isDerivedClassConstructor()) { const char* name = "anonymous"; JSAutoByteString str; if (fun->explicitName()) { if (!AtomToPrintableString(cx, fun->explicitName(), &str)) return false; name = str.ptr(); } JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_THIS, name); return false; } MOZ_ASSERT(fun->isArrow()); JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_THIS_ARROW); return false; }