diff options
Diffstat (limited to 'js/src/vm/Interpreter.cpp')
-rw-r--r-- | js/src/vm/Interpreter.cpp | 5087 |
1 files changed, 5087 insertions, 0 deletions
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp new file mode 100644 index 000000000..3ca379d01 --- /dev/null +++ b/js/src/vm/Interpreter.cpp @@ -0,0 +1,5087 @@ +/* -*- 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 <string.h> + +#include "jsarray.h" +#include "jsatom.h" +#include "jscntxt.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsiter.h" +#include "jslibmath.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsopcode.h" +#include "jsprf.h" +#include "jsscript.h" +#include "jsstr.h" + +#include "builtin/Eval.h" +#include "jit/AtomicOperations.h" +#include "jit/BaselineJIT.h" +#include "jit/Ion.h" +#include "jit/IonAnalysis.h" +#include "vm/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 <bool Eq> +static MOZ_ALWAYS_INLINE bool +LooseEqualityOp(JSContext* cx, InterpreterRegs& regs) +{ + HandleValue rval = regs.stackHandleAt(-1); + HandleValue lval = regs.stackHandleAt(-2); + bool cond; + if (!LooselyEqual(cx, lval, rval, &cond)) + return false; + cond = (cond == Eq); + regs.sp--; + regs.sp[-1].setBoolean(cond); + return true; +} + +bool +js::BoxNonStrictThis(JSContext* cx, HandleValue thisv, MutableHandleValue vp) +{ + /* + * Check for SynthesizeFrame poisoning and fast constructors which + * didn't check their callee properly. + */ + MOZ_ASSERT(!thisv.isMagic()); + + if (thisv.isNullOrUndefined()) { + vp.set(GetThisValue(cx->global())); + return true; + } + + if (thisv.isObject()) { + vp.set(thisv); + return true; + } + + JSObject* obj = PrimitiveToObject(cx, thisv); + if (!obj) + return false; + + vp.setObject(*obj); + return true; +} + +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<LexicalEnvironmentObject>().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<GlobalObject>()); + 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<JSFunction>().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<true>(cx, envRoot, pobjRoot, nameRoot, shapeRoot, vp); + + return FetchName<false>(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<ModuleEnvironmentObject>()); + MOZ_ASSERT(env->as<ModuleEnvironmentObject>().hasImportBinding(name)); + return FetchName<false>(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(); + if (derived) + ctor->setHasRest(); + + 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<JSFunction>().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<JSFunction>())) { + MOZ_ASSERT_IF(construct, !args.callee().constructHook()); + JSNative call = args.callee().callHook(); + if (!call) + return ReportIsNotFunction(cx, args.calleev(), skipForCallee, construct); + return CallJSNative(cx, call, args); + } + + /* Invoke native functions. */ + JSFunction* fun = &args.callee().as<JSFunction>(); + if (construct != CONSTRUCT && fun->isClassConstructor()) { + JS_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<JSFunction>() || + !fval.toObject().as<JSFunction>().isNative() || + !fval.toObject().as<JSFunction>().jitInfo() || + fval.toObject().as<JSFunction>().jitInfo()->needsOuterizedThisObject()) + { + JSObject* thisObj = &args.thisv().toObject(); + args.mutableThisv().set(GetThisValue(thisObj)); + } + } + + return InternalCallOrConstruct(cx, args, NO_CONSTRUCT); +} + +bool +js::CallFromStack(JSContext* cx, const CallArgs& args) +{ + return InternalCall(cx, static_cast<const AnyInvokeArgs&>(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<JSFunction>()) { + RootedFunction fun(cx, &callee.as<JSFunction>()); + + 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<const AnyConstructArgs&>(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<GlobalObject>() || + 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<GlobalObject>()); + } while ((s = s->enclosingEnvironment())); +#endif + + return ExecuteKernel(cx, script, *envChain, NullValue(), + NullFramePtr() /* evalInFrame */, rval); +} + +/* + * ES6 (4-25-16) 12.10.4 InstanceofOperator + */ +extern bool +js::InstanceOfOperator(JSContext* cx, HandleObject obj, HandleValue v, bool* bp) +{ + /* Step 1. is handled by caller. */ + + /* Step 2. */ + 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 3. */ + RootedValue rval(cx); + if (!Call(cx, hasInstance, obj, v, &rval)) + return false; + *bp = ToBoolean(rval); + return true; + } + + /* Step 4. */ + if (!obj->isCallable()) { + RootedValue val(cx, ObjectValue(*obj)); + return ReportIsNotFunction(cx, val); + } + + /* Step 5. */ + 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<WithScope*> 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<LexicalEnvironmentObject>(); + break; + case ScopeKind::With: + if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) + DebugEnvironments::onPopWith(ei.initialFrame()); + ei.initialFrame().popOffEnvironmentChain<WithEnvironmentObject>(); + break; + case ScopeKind::Function: + if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) + DebugEnvironments::onPopCall(cx, ei.initialFrame()); + if (ei.scope().hasEnvironment()) + ei.initialFrame().popOffEnvironmentChain<CallObject>(); + 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<VarEnvironmentObject>(); + 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); + } + 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<InterpreterFrameStackDepthOp> +{ + public: + TryNoteIterInterpreter(JSContext* cx, const InterpreterRegs& regs) + : TryNoteIter(cx, regs.fp()->script(), regs.pc, InterpreterFrameStackDepthOp(regs)) + { } +}; + +static void +UnwindIteratorsForUncatchableException(JSContext* cx, const InterpreterRegs& regs) +{ + // c.f. the regular (catchable) TryNoteIterInterpreter loop in + // ProcessTryNotes. + for (TryNoteIterInterpreter tni(cx, regs); !tni.done(); ++tni) { + JSTryNote* tn = *tni; + if (tn->kind == JSTRY_FOR_IN) { + Value* sp = regs.spForStackDepth(tn->stackDepth); + UnwindIteratorForUncatchableException(cx, &sp[-1].toObject()); + } + } +} + +enum HandleErrorContinuation +{ + SuccessfulReturnContinuation, + ErrorReturnContinuation, + CatchContinuation, + FinallyContinuation +}; + +static HandleErrorContinuation +ProcessTryNotes(JSContext* cx, EnvironmentIter& ei, InterpreterRegs& regs) +{ + for (TryNoteIterInterpreter tni(cx, regs); !tni.done(); ++tni) { + JSTryNote* tn = *tni; + + switch (tn->kind) { + case JSTRY_CATCH: + /* Catch cannot intercept the closing of a generator. */ + if (cx->isClosingGenerator()) + break; + SettleOnTryNote(cx, tn, ei, regs); + return CatchContinuation; + + case JSTRY_FINALLY: + SettleOnTryNote(cx, tn, ei, regs); + return FinallyContinuation; + + case JSTRY_FOR_IN: { + /* This is similar to JSOP_ENDITER in the interpreter loop. */ + DebugOnly<jsbytecode*> pc = regs.fp()->script()->main() + tn->start + tn->length; + MOZ_ASSERT(JSOp(*pc) == JSOP_ENDITER); + Value* sp = regs.spForStackDepth(tn->stackDepth); + RootedObject obj(cx, &sp[-1].toObject()); + if (!UnwindIteratorForException(cx, obj)) { + // We should only settle on the note only if + // UnwindIteratorForException itself threw, as + // onExceptionUnwind should be called anew with the new + // location of the throw (the iterator). Indeed, we must + // settle to avoid infinitely handling the same exception. + SettleOnTryNote(cx, tn, ei, regs); + return ErrorReturnContinuation; + } + break; + } + + case JSTRY_FOR_OF: + case JSTRY_LOOP: + break; + + default: + MOZ_CRASH("Invalid try note"); + } + } + + return SuccessfulReturnContinuation; +} + +bool +js::HandleClosingGeneratorReturn(JSContext* cx, AbstractFramePtr frame, bool ok) +{ + /* + * Propagate the exception or error to the caller unless the exception + * is an asynchronous return from a generator. + */ + if (cx->isClosingGenerator()) { + cx->clearPendingException(); + ok = true; + SetReturnValueForClosingGenerator(cx, frame); + } + return ok; +} + +static HandleErrorContinuation +HandleError(JSContext* cx, InterpreterRegs& regs) +{ + MOZ_ASSERT(regs.fp()->script()->containsPC(regs.pc)); + + if (regs.fp()->script()->hasScriptCounts()) { + PCCounts* counts = regs.fp()->script()->getThrowCounts(regs.pc); + // If we failed to allocate, then skip the increment and continue to + // handle the exception. + if (counts) + counts->numExec()++; + } + + 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<GlobalObject>()) + 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<CanGC>(cx, lhs); + if (!lstr) + return false; + } + + JSString* rstr; + if (rIsString) { + rstr = rhs.toString(); + } else { + // Save/restore lstr in case of GC activity under ToString. + lhs.setString(lstr); + rstr = ToString<CanGC>(cx, rhs); + if (!rstr) + return false; + lstr = lhs.toString(); + } + JSString* str = ConcatStrings<NoGC>(cx, lstr, rstr); + if (!str) { + RootedString nlstr(cx, lstr), nrstr(cx, rstr); + str = ConcatStrings<CanGC>(cx, nlstr, nrstr); + if (!str) + return false; + } + res.setString(str); + } else { + double l, r; + if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r)) + return false; + res.setNumber(l + r); + } + + return true; +} + +static MOZ_ALWAYS_INLINE bool +SubOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +{ + double d1, d2; + if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + return false; + res.setNumber(d1 - d2); + return true; +} + +static MOZ_ALWAYS_INLINE bool +MulOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +{ + double d1, d2; + if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + return false; + res.setNumber(d1 * d2); + return true; +} + +static MOZ_ALWAYS_INLINE bool +DivOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +{ + double d1, d2; + if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + return false; + res.setNumber(NumberDiv(d1, d2)); + return true; +} + +static MOZ_ALWAYS_INLINE bool +ModOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +{ + int32_t l, r; + if (lhs.isInt32() && rhs.isInt32() && + (l = lhs.toInt32()) >= 0 && (r = rhs.toInt32()) > 0) { + int32_t mod = l % r; + res.setInt32(mod); + return true; + } + + double d1, d2; + if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + return false; + + res.setNumber(NumberMod(d1, d2)); + return true; +} + +static MOZ_ALWAYS_INLINE bool +SetObjectElementOperation(JSContext* cx, HandleObject obj, 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<NativeObject>().getDenseInitializedLength(); + int32_t i = JSID_TO_INT(id); + if ((uint32_t)i >= length) { + // Annotate script if provided with information (e.g. baseline) + if (script && script->hasBaselineScript() && 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<FunctionScope>()) { + JSFunction& callee = ei.environment().as<CallObject>().callee(); + + // Arrow functions don't have the information we're looking for, + // their enclosing scopes do. Nevertheless, they might have call + // objects. Skip them to find what we came for. + if (callee.isArrow()) + continue; + + return callee; + } + } + MOZ_CRASH("unexpected env chain for GetSuperEnvFunction"); +} + + +/* + * As an optimization, the interpreter creates a handful of reserved Rooted<T> + * variables at the beginning, thus inserting them into the Rooted list once + * upon entry. ReservedRooted "borrows" a reserved Rooted variable and uses it + * within a local scope, resetting the value to nullptr (or the appropriate + * equivalent for T) at scope end. This avoids inserting/removing the Rooted + * from the rooter list, while preventing stale values from being kept alive + * unnecessarily. + */ + +template<typename T> +class ReservedRootedBase { +}; + +template<typename T> +class ReservedRooted : public ReservedRootedBase<T> +{ + Rooted<T>* savedRoot; + + public: + ReservedRooted(Rooted<T>* root, const T& ptr) : savedRoot(root) { + *root = ptr; + } + + explicit ReservedRooted(Rooted<T>* root) : savedRoot(root) { + *root = JS::GCPolicy<T>::initial(); + } + + ~ReservedRooted() { + *savedRoot = JS::GCPolicy<T>::initial(); + } + + void set(const T& p) const { *savedRoot = p; } + operator Handle<T>() { return *savedRoot; } + operator Rooted<T>&() { return *savedRoot; } + MutableHandle<T> operator&() { return &*savedRoot; } + + DECLARE_NONPOINTER_ACCESSOR_METHODS(savedRoot->get()) + DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(savedRoot->get()) + DECLARE_POINTER_CONSTREF_OPS(T) + DECLARE_POINTER_ASSIGN_OPS(ReservedRooted, T) +}; + +template <> +class ReservedRootedBase<Value> : public ValueOperations<ReservedRooted<Value>> +{}; + +template <> +class ReservedRootedBase<Scope*> : public ScopeCastOperation<ReservedRooted<Scope*>> +{}; + +static MOZ_NEVER_INLINE bool +Interpret(JSContext* cx, RunState& state) +{ +/* + * Define macros for an interpreter loop. Opcode dispatch may be either by a + * switch statement or by indirect goto (aka a threaded interpreter), depending + * on compiler support. + * + * Threaded interpretation appears to be well-supported by GCC 3 and higher. + * IBM's C compiler when run with the right options (e.g., -qlanglvl=extended) + * also supports threading. Ditto the SunPro C compiler. + */ +#if (defined(__GNUC__) || \ + (__IBMC__ >= 700 && defined __IBM_COMPUTED_GOTO) || \ + __SUNPRO_C >= 0x570) +// Non-standard but faster indirect-goto-based dispatch. +# define INTERPRETER_LOOP() +# define CASE(OP) label_##OP: +# define DEFAULT() label_default: +# define DISPATCH_TO(OP) goto* addresses[(OP)] + +# define LABEL(X) (&&label_##X) + + // Use addresses instead of offsets to optimize for runtime speed over + // load-time relocation overhead. + static const void* const addresses[EnableInterruptsPseudoOpcode + 1] = { +# define OPCODE_LABEL(op, ...) LABEL(op), + FOR_EACH_OPCODE(OPCODE_LABEL) +# undef OPCODE_LABEL +# define TRAILING_LABEL(v) \ + ((v) == EnableInterruptsPseudoOpcode \ + ? LABEL(EnableInterruptsPseudoOpcode) \ + : LABEL(default)), + FOR_EACH_TRAILING_UNUSED_OPCODE(TRAILING_LABEL) +# undef TRAILING_LABEL + }; +#else +// Portable switch-based dispatch. +# define INTERPRETER_LOOP() the_switch: switch (switchOp) +# define CASE(OP) case OP: +# define DEFAULT() default: +# define DISPATCH_TO(OP) \ + JS_BEGIN_MACRO \ + switchOp = (OP); \ + goto the_switch; \ + JS_END_MACRO + + // This variable is effectively a parameter to the_switch. + jsbytecode switchOp; +#endif + + /* + * Increment REGS.pc by N, load the opcode at that position, + * and jump to the code to execute it. + * + * When Debugger puts a script in single-step mode, all js::Interpret + * invocations that might be presently running that script must have + * interrupts enabled. It's not practical to simply check + * script->stepModeEnabled() at each point some callee could have changed + * it, because there are so many places js::Interpret could possibly cause + * JavaScript to run: each place an object might be coerced to a primitive + * or a number, for example. So instead, we expose a simple mechanism to + * let Debugger tweak the affected js::Interpret frames when an onStep + * handler is added: calling activation.enableInterruptsUnconditionally() + * will enable interrupts, and activation.opMask() is or'd with the opcode + * to implement a simple alternate dispatch. + */ +#define ADVANCE_AND_DISPATCH(N) \ + JS_BEGIN_MACRO \ + REGS.pc += (N); \ + SANITY_CHECKS(); \ + DISPATCH_TO(*REGS.pc | activation.opMask()); \ + JS_END_MACRO + + /* + * Shorthand for the common sequence at the end of a fixed-size opcode. + */ +#define END_CASE(OP) ADVANCE_AND_DISPATCH(OP##_LENGTH); + + /* + * Prepare to call a user-supplied branch handler, and abort the script + * if it returns false. + */ +#define CHECK_BRANCH() \ + JS_BEGIN_MACRO \ + if (!CheckForInterrupt(cx)) \ + goto error; \ + JS_END_MACRO + + /* + * This is a simple wrapper around ADVANCE_AND_DISPATCH which also does + * a CHECK_BRANCH() if n is not positive, which possibly indicates that it + * is the backedge of a loop. + */ +#define BRANCH(n) \ + JS_BEGIN_MACRO \ + int32_t nlen = (n); \ + if (nlen <= 0) \ + CHECK_BRANCH(); \ + ADVANCE_AND_DISPATCH(nlen); \ + JS_END_MACRO + + /* + * 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 + +#define SANITY_CHECKS() \ + JS_BEGIN_MACRO \ + js::gc::MaybeVerifyBarriers(cx); \ + JS_END_MACRO + + gc::MaybeVerifyBarriers(cx, true); + MOZ_ASSERT(!cx->zone()->types.activeAnalysis); + + InterpreterFrame* entryFrame = state.pushInterpreterFrame(cx); + if (!entryFrame) + return false; + + ActivationEntryMonitor entryMonitor(cx, entryFrame); + InterpreterActivation activation(state, cx, entryFrame); + + /* The script is used frequently, so keep a local copy. */ + RootedScript script(cx); + SET_SCRIPT(REGS.fp()->script()); + + TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerEvent scriptEvent(logger, TraceLogger_Scripts, script); + TraceLogStartEvent(logger, scriptEvent); + TraceLogStartEvent(logger, TraceLogger_Interpreter); + + /* + * Pool of rooters for use in this interpreter frame. References to these + * are used for local variables within interpreter cases. This avoids + * creating new rooters each time an interpreter case is entered, and also + * correctness pitfalls due to incorrect compilation of destructor calls + * around computed gotos. + */ + RootedValue rootValue0(cx), rootValue1(cx); + RootedString rootString0(cx), rootString1(cx); + RootedObject rootObject0(cx), rootObject1(cx), rootObject2(cx); + RootedNativeObject rootNativeObject0(cx); + RootedFunction rootFunction0(cx); + RootedPropertyName rootName0(cx); + RootedId rootId0(cx); + RootedShape rootShape0(cx); + RootedScript rootScript0(cx); + Rooted<Scope*> rootScope0(cx); + DebugOnly<uint32_t> 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. */ + SANITY_CHECKS(); + DISPATCH_TO(op); +} + +/* Various 1-byte no-ops. */ +CASE(JSOP_NOP) +CASE(JSOP_NOP_DESTRUCTURING) +CASE(JSOP_UNUSED182) +CASE(JSOP_UNUSED183) +CASE(JSOP_UNUSED187) +CASE(JSOP_UNUSED192) +CASE(JSOP_UNUSED209) +CASE(JSOP_UNUSED210) +CASE(JSOP_UNUSED211) +CASE(JSOP_UNUSED219) +CASE(JSOP_UNUSED220) +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<Value> val(&rootValue0, REGS.sp[-1]); + REGS.sp--; + ReservedRooted<Scope*> scope(&rootScope0, script->getScope(REGS.pc)); + + if (!EnterWithOperation(cx, REGS.fp(), val, scope.as<WithScope>())) + goto error; +} +END_CASE(JSOP_ENTERWITH) + +CASE(JSOP_LEAVEWITH) + REGS.fp()->popOffEnvironmentChain<WithEnvironmentObject>(); +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<JSObject*> obj(&rootObject0, &rref.toObject()); + ReservedRooted<jsid> id(&rootId0); + FETCH_ELEMENT_ID(-2, id); + if (!HasProperty(cx, obj, id, &found)) + goto error; + } + TRY_BRANCH_AFTER_COND(found, 2); + REGS.sp--; + REGS.sp[-1].setBoolean(found); +} +END_CASE(JSOP_IN) + +CASE(JSOP_ITER) +{ + MOZ_ASSERT(REGS.stackDepth() >= 1); + uint8_t flags = GET_UINT8(REGS.pc); + HandleValue val = REGS.stackHandleAt(-1); + ReservedRooted<JSObject*> 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<JSObject*> obj(&rootObject0, ®S.sp[-2].toObject()); + if (!IteratorMore(cx, obj, REGS.stackHandleAt(-1))) + goto error; +} +END_CASE(JSOP_MOREITER) + +CASE(JSOP_ISNOITER) +{ + bool b = REGS.sp[-1].isMagic(JS_NO_ITER_VALUE); + PUSH_BOOLEAN(b); +} +END_CASE(JSOP_ISNOITER) + +CASE(JSOP_ENDITER) +{ + MOZ_ASSERT(REGS.stackDepth() >= 1); + COUNT_COVERAGE(); + ReservedRooted<JSObject*> obj(&rootObject0, ®S.sp[-1].toObject()); + bool ok = CloseIterator(cx, obj); + REGS.sp--; + if (!ok) + goto error; +} +END_CASE(JSOP_ENDITER) + +CASE(JSOP_DUP) +{ + MOZ_ASSERT(REGS.stackDepth() >= 1); + const Value& rref = REGS.sp[-1]; + PUSH_COPY(rref); +} +END_CASE(JSOP_DUP) + +CASE(JSOP_DUP2) +{ + MOZ_ASSERT(REGS.stackDepth() >= 2); + const Value& lref = REGS.sp[-2]; + const Value& rref = REGS.sp[-1]; + PUSH_COPY(lref); + PUSH_COPY(rref); +} +END_CASE(JSOP_DUP2) + +CASE(JSOP_SWAP) +{ + MOZ_ASSERT(REGS.stackDepth() >= 2); + Value& lref = REGS.sp[-2]; + Value& rref = REGS.sp[-1]; + lref.swap(rref); +} +END_CASE(JSOP_SWAP) + +CASE(JSOP_PICK) +{ + unsigned i = GET_UINT8(REGS.pc); + MOZ_ASSERT(REGS.stackDepth() >= i + 1); + Value lval = REGS.sp[-int(i + 1)]; + memmove(REGS.sp - (i + 1), REGS.sp - i, sizeof(Value) * i); + REGS.sp[-1] = lval; +} +END_CASE(JSOP_PICK) + +CASE(JSOP_BINDGNAME) +CASE(JSOP_BINDNAME) +{ + JSOp op = JSOp(*REGS.pc); + ReservedRooted<JSObject*> envChain(&rootObject0); + if (op == JSOP_BINDNAME || script->hasNonSyntacticScope()) + envChain.set(REGS.fp()->environmentChain()); + else + envChain.set(®S.fp()->global().lexicalEnvironment()); + ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc)); + + /* Assigning to an undeclared name adds a property to the global object. */ + ReservedRooted<JSObject*> 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<true>(cx, REGS)) + goto error; +END_CASE(JSOP_EQ) + +CASE(JSOP_NE) + if (!LooseEqualityOp<false>(cx, REGS)) + goto error; +END_CASE(JSOP_NE) + +#define STRICT_EQUALITY_OP(OP, COND) \ + JS_BEGIN_MACRO \ + HandleValue lval = REGS.stackHandleAt(-2); \ + HandleValue rval = REGS.stackHandleAt(-1); \ + bool equal; \ + if (!StrictlyEqual(cx, lval, rval, &equal)) \ + goto error; \ + (COND) = equal OP true; \ + REGS.sp--; \ + JS_END_MACRO + +CASE(JSOP_STRICTEQ) +{ + bool cond; + STRICT_EQUALITY_OP(==, cond); + REGS.sp[-1].setBoolean(cond); +} +END_CASE(JSOP_STRICTEQ) + +CASE(JSOP_STRICTNE) +{ + bool cond; + STRICT_EQUALITY_OP(!=, cond); + REGS.sp[-1].setBoolean(cond); +} +END_CASE(JSOP_STRICTNE) + +CASE(JSOP_CASE) +{ + bool cond; + STRICT_EQUALITY_OP(==, cond); + if (cond) { + REGS.sp--; + BRANCH(GET_JUMP_OFFSET(REGS.pc)); + } +} +END_CASE(JSOP_CASE) + +#undef STRICT_EQUALITY_OP + +CASE(JSOP_LT) +{ + bool cond; + MutableHandleValue lval = REGS.stackHandleAt(-2); + MutableHandleValue rval = REGS.stackHandleAt(-1); + if (!LessThanOperation(cx, lval, rval, &cond)) + goto error; + TRY_BRANCH_AFTER_COND(cond, 2); + REGS.sp[-2].setBoolean(cond); + REGS.sp--; +} +END_CASE(JSOP_LT) + +CASE(JSOP_LE) +{ + bool cond; + MutableHandleValue lval = REGS.stackHandleAt(-2); + MutableHandleValue rval = REGS.stackHandleAt(-1); + if (!LessThanOrEqualOperation(cx, lval, rval, &cond)) + goto error; + TRY_BRANCH_AFTER_COND(cond, 2); + REGS.sp[-2].setBoolean(cond); + REGS.sp--; +} +END_CASE(JSOP_LE) + +CASE(JSOP_GT) +{ + bool cond; + MutableHandleValue lval = REGS.stackHandleAt(-2); + MutableHandleValue rval = REGS.stackHandleAt(-1); + if (!GreaterThanOperation(cx, lval, rval, &cond)) + goto error; + TRY_BRANCH_AFTER_COND(cond, 2); + REGS.sp[-2].setBoolean(cond); + REGS.sp--; +} +END_CASE(JSOP_GT) + +CASE(JSOP_GE) +{ + bool cond; + MutableHandleValue lval = REGS.stackHandleAt(-2); + MutableHandleValue rval = REGS.stackHandleAt(-1); + if (!GreaterThanOrEqualOperation(cx, lval, rval, &cond)) + goto error; + TRY_BRANCH_AFTER_COND(cond, 2); + REGS.sp[-2].setBoolean(cond); + REGS.sp--; +} +END_CASE(JSOP_GE) + +#define SIGNED_SHIFT_OP(OP) \ + JS_BEGIN_MACRO \ + int32_t i, j; \ + if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \ + goto error; \ + if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \ + goto error; \ + i = i OP (j & 31); \ + REGS.sp--; \ + REGS.sp[-1].setInt32(i); \ + JS_END_MACRO + +CASE(JSOP_LSH) + SIGNED_SHIFT_OP(<<); +END_CASE(JSOP_LSH) + +CASE(JSOP_RSH) + SIGNED_SHIFT_OP(>>); +END_CASE(JSOP_RSH) + +#undef SIGNED_SHIFT_OP + +CASE(JSOP_URSH) +{ + HandleValue lval = REGS.stackHandleAt(-2); + HandleValue rval = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!UrshOperation(cx, lval, rval, res)) + goto error; + REGS.sp--; +} +END_CASE(JSOP_URSH) + +CASE(JSOP_ADD) +{ + MutableHandleValue lval = REGS.stackHandleAt(-2); + MutableHandleValue rval = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!AddOperation(cx, lval, rval, res)) + goto error; + REGS.sp--; +} +END_CASE(JSOP_ADD) + +CASE(JSOP_SUB) +{ + ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); + ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!SubOperation(cx, lval, rval, res)) + goto error; + REGS.sp--; +} +END_CASE(JSOP_SUB) + +CASE(JSOP_MUL) +{ + ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); + ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!MulOperation(cx, lval, rval, res)) + goto error; + REGS.sp--; +} +END_CASE(JSOP_MUL) + +CASE(JSOP_DIV) +{ + ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); + ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!DivOperation(cx, lval, rval, res)) + goto error; + REGS.sp--; +} +END_CASE(JSOP_DIV) + +CASE(JSOP_MOD) +{ + ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); + ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!ModOperation(cx, lval, rval, res)) + goto error; + REGS.sp--; +} +END_CASE(JSOP_MOD) + +CASE(JSOP_POW) +{ + ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); + ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!math_pow_handle(cx, lval, rval, res)) + goto error; + REGS.sp--; +} +END_CASE(JSOP_POW) + +CASE(JSOP_NOT) +{ + bool cond = ToBoolean(REGS.stackHandleAt(-1)); + REGS.sp--; + PUSH_BOOLEAN(!cond); +} +END_CASE(JSOP_NOT) + +CASE(JSOP_BITNOT) +{ + int32_t i; + HandleValue value = REGS.stackHandleAt(-1); + if (!BitNot(cx, value, &i)) + goto error; + REGS.sp[-1].setInt32(i); +} +END_CASE(JSOP_BITNOT) + +CASE(JSOP_NEG) +{ + ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-1); + if (!NegOperation(cx, script, REGS.pc, val, res)) + goto error; +} +END_CASE(JSOP_NEG) + +CASE(JSOP_POS) + if (!ToNumber(cx, REGS.stackHandleAt(-1))) + goto error; +END_CASE(JSOP_POS) + +CASE(JSOP_DELNAME) +{ + ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc)); + ReservedRooted<JSObject*> 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<jsid> id(&rootId0, NameToId(script->getName(REGS.pc))); + ReservedRooted<JSObject*> obj(&rootObject0); + FETCH_OBJECT(cx, -1, obj); + + ObjectOpResult result; + if (!DeleteProperty(cx, obj, id, result)) + goto error; + if (!result && JSOp(*REGS.pc) == JSOP_STRICTDELPROP) { + result.reportError(cx, obj, id); + goto error; + } + MutableHandleValue res = REGS.stackHandleAt(-1); + res.setBoolean(result.ok()); +} +END_CASE(JSOP_DELPROP) + +CASE(JSOP_DELELEM) +CASE(JSOP_STRICTDELELEM) +{ + static_assert(JSOP_DELELEM_LENGTH == JSOP_STRICTDELELEM_LENGTH, + "delelem and strictdelelem must be the same size"); + /* Fetch the left part and resolve it to a non-null object. */ + ReservedRooted<JSObject*> obj(&rootObject0); + FETCH_OBJECT(cx, -2, obj); + + ReservedRooted<Value> propval(&rootValue0, REGS.sp[-1]); + + ObjectOpResult result; + ReservedRooted<jsid> id(&rootId0); + if (!ToPropertyKey(cx, propval, &id)) + goto error; + if (!DeleteProperty(cx, obj, id, result)) + goto error; + if (!result && JSOp(*REGS.pc) == JSOP_STRICTDELELEM) { + result.reportError(cx, obj, id); + goto error; + } + + MutableHandleValue res = REGS.stackHandleAt(-2); + res.setBoolean(result.ok()); + REGS.sp--; +} +END_CASE(JSOP_DELELEM) + +CASE(JSOP_TOID) +{ + /* + * Increment or decrement requires use to lookup the same property twice, + * but we need to avoid the observable stringification the second time. + * There must be an object value below the id, which will not be popped. + */ + ReservedRooted<Value> idval(&rootValue1, REGS.sp[-1]); + MutableHandleValue res = REGS.stackHandleAt(-1); + if (!ToIdOperation(cx, script, REGS.pc, idval, res)) + goto error; +} +END_CASE(JSOP_TOID) + +CASE(JSOP_TYPEOFEXPR) +CASE(JSOP_TYPEOF) +{ + REGS.sp[-1].setString(TypeOfOperation(REGS.sp[-1], cx->runtime())); +} +END_CASE(JSOP_TYPEOF) + +CASE(JSOP_VOID) + REGS.sp[-1].setUndefined(); +END_CASE(JSOP_VOID) + +CASE(JSOP_FUNCTIONTHIS) + PUSH_NULL(); + if (!GetFunctionThis(cx, REGS.fp(), REGS.stackHandleAt(-1))) + goto error; +END_CASE(JSOP_FUNCTIONTHIS) + +CASE(JSOP_GLOBALTHIS) +{ + if (script->hasNonSyntacticScope()) { + PUSH_NULL(); + if (!GetNonSyntacticGlobalThis(cx, REGS.fp()->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_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<JSObject*> receiver(&rootObject0); + FETCH_OBJECT(cx, -2, receiver); + ReservedRooted<JSObject*> obj(&rootObject1, ®S.sp[-1].toObject()); + MutableHandleValue rref = REGS.stackHandleAt(-2); + + if (!GetProperty(cx, obj, receiver, script->getName(REGS.pc), rref)) + goto error; + + REGS.sp--; +} +END_CASE(JSOP_GETPROP_SUPER) + +CASE(JSOP_GETXPROP) +{ + ReservedRooted<JSObject*> obj(&rootObject0, ®S.sp[-1].toObject()); + ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc))); + MutableHandleValue rval = REGS.stackHandleAt(-1); + if (!GetPropertyForNameLookup(cx, obj, id, rval)) + goto error; + + TypeScript::Monitor(cx, script, REGS.pc, rval); + assertSameCompartmentDebugOnly(cx, rval); +} +END_CASE(JSOP_GETXPROP) + +CASE(JSOP_SETINTRINSIC) +{ + HandleValue value = REGS.stackHandleAt(-1); + + if (!SetIntrinsicOperation(cx, script, REGS.pc, value)) + goto error; +} +END_CASE(JSOP_SETINTRINSIC) + +CASE(JSOP_SETGNAME) +CASE(JSOP_STRICTSETGNAME) +CASE(JSOP_SETNAME) +CASE(JSOP_STRICTSETNAME) +{ + static_assert(JSOP_SETNAME_LENGTH == JSOP_STRICTSETNAME_LENGTH, + "setname and strictsetname must be the same size"); + static_assert(JSOP_SETGNAME_LENGTH == JSOP_STRICTSETGNAME_LENGTH, + "setganem adn strictsetgname must be the same size"); + static_assert(JSOP_SETNAME_LENGTH == JSOP_SETGNAME_LENGTH, + "We're sharing the END_CASE so the lengths better match"); + + ReservedRooted<JSObject*> 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<jsid> id(&rootId0, NameToId(script->getName(REGS.pc))); + if (!SetPropertyOperation(cx, JSOp(*REGS.pc), lval, id, rval)) + goto error; + + REGS.sp[-2] = REGS.sp[-1]; + REGS.sp--; +} +END_CASE(JSOP_SETPROP) + +CASE(JSOP_SETPROP_SUPER) +CASE(JSOP_STRICTSETPROP_SUPER) +{ + static_assert(JSOP_SETPROP_SUPER_LENGTH == JSOP_STRICTSETPROP_SUPER_LENGTH, + "setprop-super and strictsetprop-super must be the same size"); + + + ReservedRooted<Value> receiver(&rootValue0, REGS.sp[-3]); + ReservedRooted<JSObject*> obj(&rootObject0, ®S.sp[-2].toObject()); + ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); + ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc))); + + ObjectOpResult result; + if (!SetProperty(cx, obj, id, rval, receiver, result)) + goto error; + + bool strict = JSOp(*REGS.pc) == JSOP_STRICTSETPROP_SUPER; + if (!result.checkStrictErrorOrWarning(cx, obj, id, strict)) + goto error; + + REGS.sp[-3] = REGS.sp[-1]; + REGS.sp -= 2; +} +END_CASE(JSOP_SETPROP_SUPER) + +CASE(JSOP_GETELEM) +CASE(JSOP_CALLELEM) +{ + MutableHandleValue lval = REGS.stackHandleAt(-2); + HandleValue rval = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + + bool done = false; + if (!GetElemOptimizedArguments(cx, REGS.fp(), lval, rval, res, &done)) + goto error; + + if (!done) { + if (!GetElementOperation(cx, JSOp(*REGS.pc), lval, rval, res)) + goto error; + } + + TypeScript::Monitor(cx, script, REGS.pc, res); + REGS.sp--; +} +END_CASE(JSOP_GETELEM) + +CASE(JSOP_GETELEM_SUPER) +{ + HandleValue rval = REGS.stackHandleAt(-3); + ReservedRooted<JSObject*> receiver(&rootObject0); + FETCH_OBJECT(cx, -2, receiver); + ReservedRooted<JSObject*> obj(&rootObject1, ®S.sp[-1].toObject()); + + MutableHandleValue res = REGS.stackHandleAt(-3); + + // Since we have asserted that obj has to be an object, it cannot be + // either optimized arguments, or indeed any primitive. This simplifies + // our task some. + if (!GetObjectElementOperation(cx, JSOp(*REGS.pc), obj, receiver, rval, res)) + goto error; + + TypeScript::Monitor(cx, script, REGS.pc, res); + REGS.sp -= 2; +} +END_CASE(JSOP_GETELEM_SUPER) + +CASE(JSOP_SETELEM) +CASE(JSOP_STRICTSETELEM) +{ + static_assert(JSOP_SETELEM_LENGTH == JSOP_STRICTSETELEM_LENGTH, + "setelem and strictsetelem must be the same size"); + HandleValue receiver = REGS.stackHandleAt(-3); + ReservedRooted<JSObject*> obj(&rootObject0); + obj = ToObjectFromStack(cx, receiver); + if (!obj) + goto error; + ReservedRooted<jsid> 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<jsid> id(&rootId0); + FETCH_ELEMENT_ID(-4, id); + ReservedRooted<Value> receiver(&rootValue0, REGS.sp[-3]); + ReservedRooted<JSObject*> 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<JSFunction*> fun(&rootFunction0, maybeFun); + ReservedRooted<JSScript*> funScript(&rootScript0, fun->getOrCreateScript(cx)); + if (!funScript) + goto error; + + bool createSingleton = ObjectGroup::useSingletonForNewObject(cx, script, REGS.pc); + + TypeMonitorCall(cx, args, construct); + + mozilla::Maybe<InvokeState> 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<Value> 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<PropertyName*> name(&rootName0, script->getName(REGS.pc)); + ReservedRooted<JSObject*> envObj(&rootObject0, REGS.fp()->environmentChain()); + ReservedRooted<JSObject*> 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<Value> rval(&rootValue0); + if (!GetNameOperation(cx, REGS.fp(), REGS.pc, &rval)) + goto error; + + PUSH_COPY(rval); + TypeScript::Monitor(cx, script, REGS.pc, rval); + static_assert(JSOP_GETNAME_LENGTH == JSOP_GETGNAME_LENGTH, + "We're sharing the END_CASE so the lengths better match"); +} +END_CASE(JSOP_GETNAME) + +CASE(JSOP_GETIMPORT) +{ + PUSH_NULL(); + MutableHandleValue rval = REGS.stackHandleAt(-1); + if (!GetImportOperation(cx, REGS.fp(), REGS.pc, rval)) + goto error; + + TypeScript::Monitor(cx, script, REGS.pc, rval); +} +END_CASE(JSOP_GETIMPORT) + +CASE(JSOP_GETINTRINSIC) +{ + ReservedRooted<Value> rval(&rootValue0); + if (!GetIntrinsicOperation(cx, REGS.pc, &rval)) + goto error; + + PUSH_COPY(rval); + TypeScript::Monitor(cx, script, REGS.pc, rval); +} +END_CASE(JSOP_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<CanGC>(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<JSObject*> 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<JSObject*> cso(&rootObject0, script->getObject(REGS.pc)); + ReservedRooted<JSObject*> raw(&rootObject1, script->getObject(GET_UINT32_INDEX(REGS.pc) + 1)); + ReservedRooted<Value> 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<JSObject*> 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<Value> 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<Value> 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<Value> 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<JSObject*> obj(&rootObject0, ®S.fp()->varObj()); + ReservedRooted<PropertyName*> 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<JSFunction*> fun(&rootFunction0, ®S.sp[-1].toObject().as<JSFunction>()); + 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<JSFunction*> 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<JSFunction*> fun(&rootFunction0, script->getFunction(GET_UINT32_INDEX(REGS.pc))); + ReservedRooted<Value> 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<JSFunction*> unwrapped(&rootFunction0, + ®S.sp[-1].toObject().as<JSFunction>()); + JSObject* wrapped = WrapAsyncFunction(cx, unwrapped); + if (!wrapped) + goto error; + + REGS.sp[-1].setObject(*wrapped); +} +END_CASE(JSOP_TOASYNC) + +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<JSObject*> obj(&rootObject0, ®S.sp[-2].toObject()); + ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc)); + ReservedRooted<JSObject*> 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<JSObject*> obj(&rootObject0, ®S.sp[-3].toObject()); + ReservedRooted<Value> idval(&rootValue0, REGS.sp[-2]); + ReservedRooted<JSObject*> 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) +CASE(JSOP_SPREADCALLARRAY) +{ + 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<JSObject*> baseobj(&rootObject0, ObjectGroup::getOrFixupCopyOnWriteObject(cx, script, REGS.pc)); + if (!baseobj) + goto error; + + ReservedRooted<JSObject*> obj(&rootObject1, NewDenseCopyOnWriteArray(cx, ((RootedObject&)(baseobj)).as<ArrayObject>(), 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<JSObject*> newProto(&rootObject1, REGS.sp[-1].toObjectOrNull()); + ReservedRooted<JSObject*> obj(&rootObject0, ®S.sp[-2].toObject()); + MOZ_ASSERT(obj->is<PlainObject>()); + + 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<Value> rval(&rootValue0, REGS.sp[-1]); + + /* Load the object being initialized into lval/obj. */ + ReservedRooted<JSObject*> 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<JSObject*> 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<JSObject*> 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<JSObject*> 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<Value> v(&rootValue0); + POP_COPY_TO(v); + MOZ_ALWAYS_TRUE(ThrowingOperation(cx, v)); +} +END_CASE(JSOP_THROWING) + +CASE(JSOP_THROW) +{ + CHECK_BRANCH(); + ReservedRooted<Value> 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<Value> rref(&rootValue0, REGS.sp[-1]); + if (HandleValue(rref).isPrimitive()) { + ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS, -1, rref, nullptr); + goto error; + } + ReservedRooted<JSObject*> 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*> scope(&rootScope0, script->getScope(REGS.pc)); + + // Create block environment and push on scope chain. + if (!REGS.fp()->pushLexicalEnvironment(cx, scope.as<LexicalScope>())) + 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<LexicalScope>()); + MOZ_ASSERT(scope->as<LexicalScope>().hasEnvironment()); +#endif + + if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) + DebugEnvironments::onPopLexical(cx, REGS.fp(), REGS.pc); + + // Pop block from scope chain. + REGS.fp()->popOffEnvironmentChain<LexicalEnvironmentObject>(); +} +END_CASE(JSOP_POPLEXICALENV) + +CASE(JSOP_DEBUGLEAVELEXICALENV) +{ + MOZ_ASSERT(script->lookupScope(REGS.pc)); + MOZ_ASSERT(script->lookupScope(REGS.pc)->is<LexicalScope>()); + MOZ_ASSERT(!script->lookupScope(REGS.pc)->as<LexicalScope>().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*> 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<VarScope>()); + MOZ_ASSERT(scope->as<VarScope>().hasEnvironment()); +#endif + + if (MOZ_UNLIKELY(cx->compartment()->isDebuggee())) + DebugEnvironments::onPopVar(cx, REGS.fp(), REGS.pc); + + REGS.fp()->popOffEnvironmentChain<VarEnvironmentObject>(); +} +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<JSObject*> 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<JSObject*> 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<JSObject*> gen(&rootObject0, ®S.sp[-2].toObject()); + ReservedRooted<Value> 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<JSObject*> 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<JSObject*> 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<Value> val(&rootValue0, REGS.sp[-1]); + + ReservedRooted<Value> objProto(&rootValue1); + ReservedRooted<JSObject*> 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<JSObject*> proto(&rootObject1, ®S.sp[-1].toObject()); + + /* Load the specified function object literal. */ + ReservedRooted<JSFunction*> 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<JSObject*> proto(&rootObject0, REGS.sp[-1].toObjectOrNull()); + + JSObject* obj = NewObjectWithGivenProto<PlainObject>(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<JSFunction*> func(&rootFunction0, ®S.sp[-1].toObject().as<JSFunction>()); + MOZ_ASSERT(func->allowSuperProperty()); + + /* Load the home object */ + ReservedRooted<JSObject*> obj(&rootObject0); + obj = ®S.sp[int(-2 - skipOver)].toObject(); + MOZ_ASSERT(obj->is<PlainObject>() || obj->is<UnboxedPlainObject>() || obj->is<JSFunction>()); + + 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<JSObject*> homeObj(&rootObject0, &homeObjVal.toObject()); + ReservedRooted<JSObject*> 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<JSObject*> superEnvFunc(&rootObject0, &GetSuperEnvFunction(cx, REGS)); + MOZ_ASSERT(superEnvFunc->as<JSFunction>().isClassConstructor()); + MOZ_ASSERT(superEnvFunc->as<JSFunction>().nonLazyScript()->isDerivedClassConstructor()); + + ReservedRooted<JSObject*> superFun(&rootObject1); + + if (!GetPrototype(cx, superEnvFunc, &superFun)) + goto error; + + ReservedRooted<Value> 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<JSObject*> 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<Value> checkVal(&rootValue0, REGS.sp[-1]); + if (checkVal.isNullOrUndefined() && !ToObjectFromStack(cx, checkVal)) + goto error; +} +END_CASE(JSOP_CHECKOBJCOERCIBLE) + +CASE(JSOP_DEBUGCHECKSELFHOSTED) +{ +#ifdef DEBUG + ReservedRooted<Value> 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<Value> 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); + } + + gc::MaybeVerifyBarriers(cx, true); + + 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<JSFunction>().isArrow()); + clone->as<JSFunction>().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->name()->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<GlobalObject>() ? 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<DebugEnvironmentProxy>()); + if (parent->is<GlobalObject>()) { + 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 strict> +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<true> (JSContext* cx, HandleValue val, HandlePropertyName name, + bool* bp); +template bool js::DeletePropertyJit<false>(JSContext* cx, HandleValue val, HandlePropertyName name, + bool* bp); + +template <bool strict> +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<true> (JSContext*, HandleValue, HandleValue, bool* succeeded); +template bool js::DeleteElementJit<false>(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<GlobalObject>()) + 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<ArrayObject>()); + 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<JSFunction>()) && !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<ArrayObject>(), 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->maybeUnboxedLayout()) + group->maybeUnboxedLayout()->setAllocationSite(script, pc); + } + + if (group->shouldPreTenure() || group->maybePreliminaryObjects()) + newKind = TenuredObject; + + if (group->maybeUnboxedLayout()) + return UnboxedPlainObject::create(cx, group, newKind); + } + + RootedObject obj(cx); + + if (*pc == JSOP_NEWOBJECT) { + RootedPlainObject baseObject(cx, &script->getObject(pc)->as<PlainObject>()); + obj = CopyInitializerObject(cx, baseObject, newKind); + } else { + MOZ_ASSERT(*pc == JSOP_NEWINIT); + MOZ_ASSERT(GET_UINT8(pc) == JSProto_Object); + obj = NewBuiltinClassInstance<PlainObject>(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<PlainObject>(), 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; + + if (group->maybeUnboxedLayout()) + return UnboxedArrayObject::create(cx, group, length, newKind); + } + + ArrayObject* obj = NewDenseFullyAllocatedArray(cx, length, nullptr, newKind); + if (!obj) + return nullptr; + + if (newKind == SingletonObject) { + MOZ_ASSERT(obj->isSingleton()); + } else { + obj->setGroup(group); + + if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects()) + preliminaryObjects->registerNewObject(obj); + } + + return obj; +} + +JSObject* +js::NewArrayOperationWithTemplate(JSContext* cx, HandleObject templateObject) +{ + MOZ_ASSERT(!templateObject->isSingleton()); + + NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject; + + if (templateObject->is<UnboxedArrayObject>()) { + uint32_t length = templateObject->as<UnboxedArrayObject>().length(); + RootedObjectGroup group(cx, templateObject->group()); + return UnboxedArrayObject::create(cx, group, length, newKind); + } + + ArrayObject* obj = NewDenseFullyAllocatedArray(cx, templateObject->as<ArrayObject>().length(), + nullptr, newKind); + if (!obj) + return nullptr; + + MOZ_ASSERT(obj->lastProperty() == templateObject->as<ArrayObject>().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()->name()->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_NEXT_RETURNED_PRIMITIVE); + break; + case CheckIsObjectKind::GetIterator: + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_GET_ITER_RETURNED_PRIMITIVE); + 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<FunctionScope>()) { + fun = si.scope()->as<FunctionScope>().canonicalFunction(); + break; + } + } + MOZ_ASSERT(fun); + } + + if (fun->isDerivedClassConstructor()) { + const char* name = "anonymous"; + JSAutoByteString str; + if (fun->name()) { + if (!AtomToPrintableString(cx, fun->name(), &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; +} |