Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/DebugOnly.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/WrappingOperations.h"
#include <string.h>
#include "jsapi.h"
#include "jslibmath.h"
#include "jsmath.h"
#include "jsnum.h"
#include "builtin/Array.h"
#include "builtin/Eval.h"
#include "builtin/ModuleObject.h"
#include "builtin/Object.h"
#include "builtin/Promise.h"
#include "gc/GC.h"
#include "jit/BaselineJIT.h"
#include "jit/Jit.h"
#include "jit/JitRuntime.h"
#include "js/experimental/JitInfo.h" // JSJitInfo
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
#include "js/friend/WindowProxy.h" // js::IsWindowProxy
#include "js/Printer.h"
#include "proxy/DeadObjectProxy.h"
#include "util/CheckedArithmetic.h"
#include "util/StringBuffer.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/BigIntType.h"
#include "vm/BytecodeUtil.h" // JSDVG_SEARCH_STACK
#include "vm/EqualityOperations.h" // js::StrictlyEqual
#include "vm/GeneratorObject.h"
#include "vm/Iteration.h"
#include "vm/JSAtomUtils.h" // AtomToPrintableString
#include "vm/JSContext.h"
#include "vm/JSFunction.h"
#include "vm/JSObject.h"
#include "vm/JSScript.h"
#include "vm/Opcodes.h"
#include "vm/PIC.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/Scope.h"
#include "vm/Shape.h"
#include "vm/SharedStencil.h" // GCThingIndex
#include "vm/StringType.h"
#include "vm/ThrowMsgKind.h" // ThrowMsgKind
#include "vm/Time.h"
#ifdef ENABLE_RECORD_TUPLE
# include "vm/RecordType.h"
# include "vm/TupleType.h"
#endif
#include "builtin/Boolean-inl.h"
#include "debugger/DebugAPI-inl.h"
#include "vm/ArgumentsObject-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/GeckoProfiler-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectOperations-inl.h"
#include "vm/PlainObject-inl.h" // js::CopyInitializerObject, js::CreateThis
#include "vm/Probes-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
using mozilla::DebugOnly;
using mozilla::NumberEqualsInt32;
using js::jit::JitScript;
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;
}
JSObject* js::BoxNonStrictThis(JSContext* cx, HandleValue thisv) {
MOZ_ASSERT(!thisv.isMagic());
if (thisv.isNullOrUndefined()) {
return cx->global()->lexicalEnvironment().thisObject();
}
if (thisv.isObject()) {
return &thisv.toObject();
}
return PrimitiveToObject(cx, thisv);
}
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()) {
res.set(frame.thisArgument());
return true;
}
MOZ_ASSERT(!frame.callee()->isSelfHostedBuiltin(),
"Self-hosted builtins must be strict");
RootedValue thisv(cx, frame.thisArgument());
// If there is a NSVO on environment chain, use it as basis for fallback
// global |this|. This gives a consistent definition of global lexical
// |this| between function and global contexts.
//
// NOTE: If only non-syntactic WithEnvironments are on the chain, we use the
// global lexical |this| value. This is for compatibility with the Subscript
// Loader.
if (frame.script()->hasNonSyntacticScope() && thisv.isNullOrUndefined()) {
RootedObject env(cx, frame.environmentChain());
while (true) {
if (IsNSVOLexicalEnvironment(env) || IsGlobalLexicalEnvironment(env)) {
res.setObject(*GetThisObjectOfLexical(env));
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.setObject(*GetThisObject(env));
return true;
}
env = env->enclosingEnvironment();
}
}
JSObject* obj = BoxNonStrictThis(cx, thisv);
if (!obj) {
return false;
}
res.setObject(*obj);
return true;
}
void js::GetNonSyntacticGlobalThis(JSContext* cx, HandleObject envChain,
MutableHandleValue res) {
RootedObject env(cx, envChain);
while (true) {
if (IsExtensibleLexicalEnvironment(env)) {
res.setObject(*GetThisObjectOfLexical(env));
return;
}
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.setObject(*GetThisObject(env));
return;
}
env = env->enclosingEnvironment();
}
}
#ifdef DEBUG
static bool IsSelfHostedOrKnownBuiltinCtor(JSFunction* fun, JSContext* cx) {
if (fun->isSelfHostedOrIntrinsic()) {
return true;
}
// GetBuiltinConstructor in MapGroupBy
if (fun == cx->global()->maybeGetConstructor(JSProto_Map)) {
return true;
}
// GetBuiltinConstructor in intlFallbackSymbol
if (fun == cx->global()->maybeGetConstructor(JSProto_Symbol)) {
return true;
}
// ConstructorForTypedArray in MergeSortTypedArray
if (fun == cx->global()->maybeGetConstructor(JSProto_Int8Array) ||
fun == cx->global()->maybeGetConstructor(JSProto_Uint8Array) ||
fun == cx->global()->maybeGetConstructor(JSProto_Int16Array) ||
fun == cx->global()->maybeGetConstructor(JSProto_Uint16Array) ||
fun == cx->global()->maybeGetConstructor(JSProto_Int32Array) ||
fun == cx->global()->maybeGetConstructor(JSProto_Uint32Array) ||
fun == cx->global()->maybeGetConstructor(JSProto_Float32Array) ||
fun == cx->global()->maybeGetConstructor(JSProto_Float64Array) ||
fun == cx->global()->maybeGetConstructor(JSProto_Uint8ClampedArray) ||
fun == cx->global()->maybeGetConstructor(JSProto_BigInt64Array) ||
fun == cx->global()->maybeGetConstructor(JSProto_BigUint64Array)) {
return true;
}
return false;
}
#endif // DEBUG
bool js::Debug_CheckSelfHosted(JSContext* cx, HandleValue funVal) {
#ifdef DEBUG
JSFunction* fun = &UncheckedUnwrap(&funVal.toObject())->as<JSFunction>();
MOZ_ASSERT(IsSelfHostedOrKnownBuiltinCtor(fun, cx),
"functions directly called inside self-hosted JS must be one of "
"selfhosted function, self-hosted intrinsic, or known built-in "
"constructor");
#else
MOZ_CRASH("self-hosted checks should only be done in Debug builds");
#endif
// This is purely to police self-hosted code. There is no actual operation.
return true;
}
static inline bool GetPropertyOperation(JSContext* cx,
Handle<PropertyName*> name,
HandleValue lval,
MutableHandleValue vp) {
if (name == cx->names().length && GetLengthProperty(lval, vp)) {
return true;
}
return GetProperty(cx, lval, name, vp);
}
static inline bool GetNameOperation(JSContext* cx, HandleObject envChain,
Handle<PropertyName*> name, JSOp nextOp,
MutableHandleValue vp) {
/* Kludge to allow (typeof foo == "undefined") tests. */
if (nextOp == JSOp::Typeof) {
return GetEnvironmentName<GetNameMode::TypeOf>(cx, envChain, name, vp);
}
return GetEnvironmentName<GetNameMode::Normal>(cx, envChain, name, vp);
}
bool js::GetImportOperation(JSContext* cx, HandleObject envChain,
HandleScript script, jsbytecode* pc,
MutableHandleValue vp) {
RootedObject env(cx), pobj(cx);
Rooted<PropertyName*> name(cx, script->getName(pc));
PropertyResult prop;
MOZ_ALWAYS_TRUE(LookupName(cx, name, envChain, &env, &pobj, &prop));
MOZ_ASSERT(env && env->is<ModuleEnvironmentObject>());
MOZ_ASSERT(env->as<ModuleEnvironmentObject>().hasImportBinding(name));
return FetchName<GetNameMode::Normal>(cx, env, pobj, name, prop, vp);
}
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;
}
static bool MaybeCreateThisForConstructor(JSContext* cx, const CallArgs& args) {
if (args.thisv().isObject()) {
return true;
}
RootedFunction callee(cx, &args.callee().as<JSFunction>());
RootedObject newTarget(cx, &args.newTarget().toObject());
MOZ_ASSERT(callee->hasBytecode());
if (!CreateThis(cx, callee, newTarget, GenericObject, args.mutableThisv())) {
return false;
}
// Ensure the callee still has a non-lazy script. We normally don't relazify
// in active compartments, but the .prototype lookup might have called the
// relazifyFunctions testing function that doesn't have this restriction.
return JSFunction::getOrCreateScript(cx, callee);
}
#ifdef ENABLE_RECORD_TUPLE
static bool AddRecordSpreadOperation(JSContext* cx, HandleValue recHandle,
HandleValue spreadeeHandle) {
MOZ_ASSERT(recHandle.toExtendedPrimitive().is<RecordType>());
RecordType* rec = &recHandle.toExtendedPrimitive().as<RecordType>();
RootedObject obj(cx, ToObjectOrGetObjectPayload(cx, spreadeeHandle));
RootedIdVector keys(cx);
if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &keys)) {
return false;
}
size_t len = keys.length();
RootedId propKey(cx);
RootedValue propValue(cx);
for (size_t i = 0; i < len; i++) {
propKey.set(keys[i]);
// Step 4.c.ii.1.
if (MOZ_UNLIKELY(!GetProperty(cx, obj, obj, propKey, &propValue))) {
return false;
}
if (MOZ_UNLIKELY(!rec->initializeNextProperty(cx, propKey, propValue))) {
return false;
}
}
return true;
}
#endif
InterpreterFrame* InvokeState::pushInterpreterFrame(JSContext* cx) {
return cx->interpreterStack().pushInvokeFrame(cx, args_, construct_);
}
InterpreterFrame* ExecuteState::pushInterpreterFrame(JSContext* cx) {
return cx->interpreterStack().pushExecuteFrame(cx, script_, envChain_,
evalInFrame_);
}
InterpreterFrame* RunState::pushInterpreterFrame(JSContext* cx) {
if (isInvoke()) {
return asInvoke()->pushInterpreterFrame(cx);
}
return asExecute()->pushInterpreterFrame(cx);
}
static MOZ_ALWAYS_INLINE bool MaybeEnterInterpreterTrampoline(JSContext* cx,
RunState& state) {
#ifdef NIGHTLY_BUILD
if (jit::JitOptions.emitInterpreterEntryTrampoline &&
cx->runtime()->hasJitRuntime()) {
js::jit::JitRuntime* jitRuntime = cx->runtime()->jitRuntime();
JSScript* script = state.script();
uint8_t* codeRaw = nullptr;
auto p = jitRuntime->getInterpreterEntryMap()->lookup(script);
if (p) {
codeRaw = p->value().raw();
} else {
js::jit::JitCode* code =
jitRuntime->generateEntryTrampolineForScript(cx, script);
if (!code) {
ReportOutOfMemory(cx);
return false;
}
js::jit::EntryTrampoline entry(cx, code);
if (!jitRuntime->getInterpreterEntryMap()->put(script, entry)) {
ReportOutOfMemory(cx);
return false;
}
codeRaw = code->raw();
}
MOZ_ASSERT(codeRaw, "Should have a valid trampoline here.");
// The C++ entry thunk is located at the vmInterpreterEntryOffset offset.
codeRaw += jitRuntime->vmInterpreterEntryOffset();
return js::jit::EnterInterpreterEntryTrampoline(codeRaw, cx, &state);
}
#endif
return Interpret(cx, state);
}
// 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) {
AutoCheckRecursionLimit recursion(cx);
if (!recursion.check(cx)) {
return false;
}
MOZ_ASSERT_IF(cx->runtime()->hasJitRuntime(),
!cx->runtime()->jitRuntime()->disallowArbitraryCode());
// Since any script can conceivably GC, make sure it's safe to do so.
cx->verifyIsSafeToGC();
MOZ_ASSERT(cx->realm() == state.script()->realm());
MOZ_DIAGNOSTIC_ASSERT(cx->realm()->isSystem() ||
cx->runtime()->allowContentJS());
if (!DebugAPI::checkNoExecute(cx, state.script())) {
return false;
}
GeckoProfilerEntryMarker marker(cx, state.script());
bool measuringTime = !cx->isMeasuringExecutionTime();
mozilla::TimeStamp startTime;
if (measuringTime) {
cx->setIsMeasuringExecutionTime(true);
cx->setIsExecuting(true);
startTime = mozilla::TimeStamp::Now();
}
auto timerEnd = mozilla::MakeScopeExit([&]() {
if (measuringTime) {
mozilla::TimeDuration delta = mozilla::TimeStamp::Now() - startTime;
cx->realm()->timers.executionTime += delta;
cx->setIsMeasuringExecutionTime(false);
cx->setIsExecuting(false);
}
});
jit::EnterJitStatus status = jit::MaybeEnterJit(cx, state);
switch (status) {
case jit::EnterJitStatus::Error:
return false;
case jit::EnterJitStatus::Ok:
return true;
case jit::EnterJitStatus::NotEntered:
break;
}
bool ok = MaybeEnterInterpreterTrampoline(cx, state);
return ok;
}
#ifdef _MSC_VER
# pragma optimize("", on)
#endif
STATIC_PRECONDITION_ASSUME(ubound(args.argv_) >= argc)
MOZ_ALWAYS_INLINE bool CallJSNative(JSContext* cx, Native native,
CallReason reason, const CallArgs& args) {
AutoCheckRecursionLimit recursion(cx);
if (!recursion.check(cx)) {
return false;
}
NativeResumeMode resumeMode = DebugAPI::onNativeCall(cx, args, reason);
if (resumeMode != NativeResumeMode::Continue) {
return resumeMode == NativeResumeMode::Override;
}
#ifdef DEBUG
bool alreadyThrowing = cx->isExceptionPending();
#endif
cx->check(args);
MOZ_ASSERT(!args.callee().is<ProxyObject>());
AutoRealm ar(cx, &args.callee());
bool ok = native(cx, args.length(), args.base());
if (ok) {
cx->check(args.rval());
MOZ_ASSERT_IF(!alreadyThrowing, !cx->isExceptionPending());
}
return ok;
}
STATIC_PRECONDITION(ubound(args.argv_) >= argc)
MOZ_ALWAYS_INLINE bool CallJSNativeConstructor(JSContext* cx, Native native,
const CallArgs& args) {
#ifdef DEBUG
RootedObject callee(cx, &args.callee());
#endif
MOZ_ASSERT(args.thisv().isMagic());
if (!CallJSNative(cx, native, CallReason::Call, args)) {
return false;
}
/*
* Native constructors must return non-primitive values on success.
* Although it is legal, if a constructor returns the callee, there is a
* 99.9999% chance it is a bug. If any valid code actually wants the
* constructor to return the callee, the assertion can be removed or
* (another) conjunct can be added to the antecedent.
*
* Exceptions:
* - (new Object(Object)) returns the callee.
* - The bound function construct hook can return an arbitrary object,
* including the callee.
*
* Also allow if this may be due to a debugger hook since fuzzing may let this
* happen.
*/
MOZ_ASSERT(args.rval().isObject());
MOZ_ASSERT_IF(!JS_IsNativeFunction(callee, obj_construct) &&
!callee->is<BoundFunctionObject>() &&
!cx->realm()->debuggerObservesNativeCall(),
args.rval() != ObjectValue(*callee));
return true;
}
/*
* 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,
CallReason reason /* = CallReason::Call */) {
MOZ_ASSERT(args.length() <= ARGS_LENGTH_MAX);
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().isConstructor());
if (!args.callee().isCallable()) {
return ReportIsNotFunction(cx, args.calleev(), skipForCallee, construct);
}
if (args.callee().is<ProxyObject>()) {
RootedObject proxy(cx, &args.callee());
return Proxy::call(cx, proxy, args);
}
JSNative call = args.callee().callHook();
MOZ_ASSERT(call, "isCallable without a callHook?");
return CallJSNative(cx, call, reason, args);
}
/* Invoke native functions. */
RootedFunction fun(cx, &args.callee().as<JSFunction>());
if (fun->isNativeFun()) {
MOZ_ASSERT_IF(construct, !fun->isConstructor());
JSNative native = fun->native();
if (!construct && args.ignoresReturnValue() && fun->hasJitInfo()) {
const JSJitInfo* jitInfo = fun->jitInfo();
if (jitInfo->type() == JSJitInfo::IgnoresReturnValueNative) {
native = jitInfo->ignoresReturnValueMethod;
}
}
return CallJSNative(cx, native, reason, args);
}
// Self-hosted builtins are considered native by the onNativeCall hook.
if (fun->isSelfHostedBuiltin()) {
NativeResumeMode resumeMode = DebugAPI::onNativeCall(cx, args, reason);
if (resumeMode != NativeResumeMode::Continue) {
return resumeMode == NativeResumeMode::Override;
}
}
if (!JSFunction::getOrCreateScript(cx, fun)) {
return false;
}
/* Run function until JSOp::RetRval, JSOp::Return or error. */
InvokeState state(cx, args, construct);
// Create |this| if we're constructing. Switch to the callee's realm to
// ensure this object has the correct realm.
AutoRealm ar(cx, state.script());
if (construct && !MaybeCreateThisForConstructor(cx, args)) {
return false;
}
// Calling class constructors throws an error from the callee's realm.
if (construct != CONSTRUCT && fun->isClassConstructor()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CANT_CALL_CLASS_CONSTRUCTOR);
return false;
}
bool ok = RunScript(cx, state);
MOZ_ASSERT_IF(ok && construct, args.rval().isObject());
return ok;
}
// Returns true if the callee needs an outerized |this| object. Outerization
// means passing the WindowProxy instead of the Window (a GlobalObject) because
// we must never expose the Window to script. This returns false only for DOM
// getters or setters.
static bool CalleeNeedsOuterizedThisObject(const Value& callee) {
if (!callee.isObject() || !callee.toObject().is<JSFunction>()) {
return true;
}
JSFunction& fun = callee.toObject().as<JSFunction>();
if (!fun.isNativeFun() || !fun.hasJitInfo()) {
return true;
}
return fun.jitInfo()->needsOuterizedThisObject();
}
static bool InternalCall(JSContext* cx, const AnyInvokeArgs& args,
CallReason reason) {
MOZ_ASSERT(args.array() + args.length() == args.end(),
"must pass calling arguments to a calling attempt");
#ifdef DEBUG
// The caller is responsible for calling GetThisObject if needed.
if (args.thisv().isObject()) {
JSObject* thisObj = &args.thisv().toObject();
MOZ_ASSERT_IF(CalleeNeedsOuterizedThisObject(args.calleev()),
GetThisObject(thisObj) == thisObj);
}
#endif
return InternalCallOrConstruct(cx, args, NO_CONSTRUCT, reason);
}
bool js::CallFromStack(JSContext* cx, const CallArgs& args,
CallReason reason /* = CallReason::Call */) {
return InternalCall(cx, static_cast<const AnyInvokeArgs&>(args), reason);
}
// ES7 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
// 7.3.12 Call.
bool js::Call(JSContext* cx, HandleValue fval, HandleValue thisv,
const AnyInvokeArgs& args, MutableHandleValue rval,
CallReason reason) {
// Explicitly qualify these methods to bypass AnyInvokeArgs's deliberate
// shadowing.
args.CallArgs::setCallee(fval);
args.CallArgs::setThis(thisv);
if (thisv.isObject()) {
// If |this| is a global object, it might be a Window and in that case we
// usually have to pass the WindowProxy instead.
JSObject* thisObj = &thisv.toObject();
if (thisObj->is<GlobalObject>()) {
if (CalleeNeedsOuterizedThisObject(fval)) {
args.mutableThisv().setObject(*GetThisObject(thisObj));
}
} else {
// Fast path: we don't have to do anything if the object isn't a global.
MOZ_ASSERT(GetThisObject(thisObj) == thisObj);
}
}
if (!InternalCall(cx, args, reason)) {
return false;
}
rval.set(args.rval());
return true;
}
static bool InternalConstruct(JSContext* cx, const AnyConstructArgs& args,
CallReason reason = CallReason::Call) {
MOZ_ASSERT(args.array() + args.length() + 1 == args.end(),
"must pass constructing arguments to a construction attempt");
MOZ_ASSERT(!FunctionClass.getConstruct());
MOZ_ASSERT(!ExtendedFunctionClass.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->isNativeFun()) {
return CallJSNativeConstructor(cx, fun->native(), args);
}
if (!InternalCallOrConstruct(cx, args, CONSTRUCT, reason)) {
return false;
}
MOZ_ASSERT(args.CallArgs::rval().isObject());
return true;
}
if (callee.is<ProxyObject>()) {
RootedObject proxy(cx, &callee);
return Proxy::construct(cx, proxy, args);
}
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,
CallReason reason /* CallReason::Call */) {
if (!StackCheckIsConstructorCalleeNewTarget(cx, args.calleev(),
args.newTarget())) {
return false;
}
return InternalConstruct(cx, static_cast<const AnyConstructArgs&>(args),
reason);
}
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) {
FixedInvokeArgs<0> args(cx);
return Call(cx, getter, thisv, args, rval, CallReason::Getter);
}
bool js::CallSetter(JSContext* cx, HandleValue thisv, HandleValue setter,
HandleValue v) {
FixedInvokeArgs<1> args(cx);
args[0].set(v);
RootedValue ignored(cx);
return Call(cx, setter, thisv, args, &ignored, CallReason::Setter);
}
bool js::ExecuteKernel(JSContext* cx, HandleScript script,
HandleObject envChainArg, AbstractFramePtr evalInFrame,
MutableHandleValue 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()) {
result.setUndefined();
return true;
}
probes::StartExecution(script);
ExecuteState state(cx, script, envChainArg, evalInFrame, result);
bool ok = RunScript(cx, state);
probes::StopExecution(script);
return ok;
}
bool js::Execute(JSContext* cx, HandleScript script, HandleObject envChain,
MutableHandleValue rval) {
/* The env chain is something we control, so we know it can't
have any outer objects on it. */
MOZ_ASSERT(!IsWindowProxy(envChain));
if (script->isModule()) {
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 {
cx->check(s);
MOZ_ASSERT_IF(!s->enclosingEnvironment(), s->is<GlobalObject>());
} while ((s = s->enclosingEnvironment()));
#endif
return ExecuteKernel(cx, script, envChain, NullFramePtr() /* evalInFrame */,
rval);
}
/*
* ES6 (4-25-16) 12.10.4 InstanceofOperator
*/
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, PropertyKey::Symbol(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);
}
JSType js::TypeOfObject(JSObject* obj) {
#ifdef ENABLE_RECORD_TUPLE
MOZ_ASSERT(!js::IsExtendedPrimitive(*obj));
#endif
AutoUnsafeCallWithABI unsafe;
if (EmulatesUndefined(obj)) {
return JSTYPE_UNDEFINED;
}
if (obj->isCallable()) {
return JSTYPE_FUNCTION;
}
return JSTYPE_OBJECT;
}
#ifdef ENABLE_RECORD_TUPLE
JSType TypeOfExtendedPrimitive(JSObject* obj) {
MOZ_ASSERT(js::IsExtendedPrimitive(*obj));
if (obj->is<RecordType>()) {
return JSTYPE_RECORD;
}
if (obj->is<TupleType>()) {
return JSTYPE_TUPLE;
}
MOZ_CRASH("Unknown ExtendedPrimitive");
}
#endif
JSType js::TypeOfValue(const Value& v) {
switch (v.type()) {
case ValueType::Double:
case ValueType::Int32:
return JSTYPE_NUMBER;
case ValueType::String:
return JSTYPE_STRING;
case ValueType::Null:
return JSTYPE_OBJECT;
case ValueType::Undefined:
return JSTYPE_UNDEFINED;
case ValueType::Object:
return TypeOfObject(&v.toObject());
#ifdef ENABLE_RECORD_TUPLE
case ValueType::ExtendedPrimitive:
return TypeOfExtendedPrimitive(&v.toExtendedPrimitive());
#endif
case ValueType::Boolean:
return JSTYPE_BOOLEAN;
case ValueType::BigInt:
return JSTYPE_BIGINT;
case ValueType::Symbol:
return JSTYPE_SYMBOL;
case ValueType::Magic:
case ValueType::PrivateGCThing:
break;
}
ReportBadValueTypeAndCrash(v);
}
bool js::CheckClassHeritageOperation(JSContext* cx, HandleValue heritage) {
if (IsConstructor(heritage)) {
return true;
}
if (heritage.isNull()) {
return true;
}
if (heritage.isObject()) {
ReportIsNotFunction(cx, heritage, 0, CONSTRUCT);
return false;
}
ReportValueError(cx, JSMSG_BAD_HERITAGE, -1, heritage, nullptr,
"not an object or null");
return false;
}
PlainObject* js::ObjectWithProtoOperation(JSContext* cx, HandleValue val) {
if (!val.isObjectOrNull()) {
ReportValueError(cx, JSMSG_NOT_OBJORNULL, -1, val, nullptr);
return nullptr;
}
RootedObject proto(cx, val.toObjectOrNull());
return NewPlainObjectWithProto(cx, proto);
}
JSObject* js::FunWithProtoOperation(JSContext* cx, HandleFunction fun,
HandleObject parent, HandleObject proto) {
return CloneFunctionReuseScript(cx, fun, parent, proto);
}
/*
* 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:
case ScopeKind::FunctionLexical:
case ScopeKind::ClassBody:
if (MOZ_UNLIKELY(cx->realm()->isDebuggee())) {
DebugEnvironments::onPopLexical(cx, ei);
}
if (ei.scope().hasEnvironment()) {
ei.initialFrame()
.popOffEnvironmentChain<ScopedLexicalEnvironmentObject>();
}
break;
case ScopeKind::With:
if (MOZ_UNLIKELY(cx->realm()->isDebuggee())) {
DebugEnvironments::onPopWith(ei.initialFrame());
}
ei.initialFrame().popOffEnvironmentChain<WithEnvironmentObject>();
break;
case ScopeKind::Function:
if (MOZ_UNLIKELY(cx->realm()->isDebuggee())) {
DebugEnvironments::onPopCall(cx, ei.initialFrame());
}
if (ei.scope().hasEnvironment()) {
ei.initialFrame().popOffEnvironmentChain<CallObject>();
}
break;
case ScopeKind::FunctionBodyVar:
case ScopeKind::StrictEval:
if (MOZ_UNLIKELY(cx->realm()->isDebuggee())) {
DebugEnvironments::onPopVar(cx, ei);
}
if (ei.scope().hasEnvironment()) {
ei.initialFrame().popOffEnvironmentChain<VarEnvironmentObject>();
}
break;
case ScopeKind::Module:
if (MOZ_UNLIKELY(cx->realm()->isDebuggee())) {
DebugEnvironments::onPopModule(cx, ei);
}
break;
case ScopeKind::Eval:
case ScopeKind::Global:
case ScopeKind::NonSyntactic:
break;
case ScopeKind::WasmInstance:
case ScopeKind::WasmFunction:
MOZ_CRASH("wasm is not interpreted");
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;
}
Rooted<Scope*> 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(GCThingIndex(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, const TryNote* tn) {
jsbytecode* pc = script->offsetToPC(tn->start);
if (tn->kind() == TryNoteKind::Catch || tn->kind() == TryNoteKind::Finally) {
pc -= JSOpLength_Try;
MOZ_ASSERT(JSOp(*pc) == JSOp::Try);
} else if (tn->kind() == TryNoteKind::Destructuring) {
pc -= JSOpLength_TryDestructuring;
MOZ_ASSERT(JSOp(*pc) == JSOp::TryDestructuring);
}
return pc;
}
static void SettleOnTryNote(JSContext* cx, const TryNote* 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()->offsetToPC(tn->start + tn->length);
regs.sp = regs.spForStackDepth(tn->stackDepth);
}
class InterpreterTryNoteFilter {
const InterpreterRegs& regs_;
public:
explicit InterpreterTryNoteFilter(const InterpreterRegs& regs)
: regs_(regs) {}
bool operator()(const TryNote* note) {
return note->stackDepth <= regs_.stackDepth();
}
};
class TryNoteIterInterpreter : public TryNoteIter<InterpreterTryNoteFilter> {
public:
TryNoteIterInterpreter(JSContext* cx, const InterpreterRegs& regs)
: TryNoteIter(cx, regs.fp()->script(), regs.pc,
InterpreterTryNoteFilter(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) {
const TryNote* tn = *tni;
switch (tn->kind()) {
case TryNoteKind::ForIn: {
Value* sp = regs.spForStackDepth(tn->stackDepth);
UnwindIteratorForUncatchableException(&sp[-1].toObject());
break;
}
default:
break;
}
}
}
enum HandleErrorContinuation {
SuccessfulReturnContinuation,
ErrorReturnContinuation,
CatchContinuation,
FinallyContinuation
};
static HandleErrorContinuation ProcessTryNotes(JSContext* cx,
EnvironmentIter& ei,
InterpreterRegs& regs) {
for (TryNoteIterInterpreter tni(cx, regs); !tni.done(); ++tni) {
const TryNote* tn = *tni;
switch (tn->kind()) {
case TryNoteKind::Catch:
/* Catch cannot intercept the closing of a generator. */
if (cx->isClosingGenerator()) {
break;
}
SettleOnTryNote(cx, tn, ei, regs);
return CatchContinuation;
case TryNoteKind::Finally:
SettleOnTryNote(cx, tn, ei, regs);
return FinallyContinuation;
case TryNoteKind::ForIn: {
/* This is similar to JSOp::EndIter in the interpreter loop. */
MOZ_ASSERT(tn->stackDepth <= regs.stackDepth());
Value* sp = regs.spForStackDepth(tn->stackDepth);
JSObject* obj = &sp[-1].toObject();
CloseIterator(obj);
break;
}
case TryNoteKind::Destructuring: {
// Whether the destructuring iterator is done is at the top of the
// stack. The iterator object is second from the top.
MOZ_ASSERT(tn->stackDepth > 1);
Value* sp = regs.spForStackDepth(tn->stackDepth);
RootedValue doneValue(cx, sp[-1]);
MOZ_RELEASE_ASSERT(!doneValue.isMagic());
bool done = ToBoolean(doneValue);
if (!done) {
RootedObject iterObject(cx, &sp[-2].toObject());
if (!IteratorCloseForException(cx, iterObject)) {
SettleOnTryNote(cx, tn, ei, regs);
return ErrorReturnContinuation;
}
}
break;
}
case TryNoteKind::ForOf:
case TryNoteKind::Loop:
break;
// TryNoteKind::ForOfIterClose is handled internally by the try note
// iterator.
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;
auto* genObj = GetGeneratorObjectForFrame(cx, frame);
genObj->setClosed(cx);
}
return ok;
}
static HandleErrorContinuation HandleError(JSContext* cx,
InterpreterRegs& regs) {
MOZ_ASSERT(regs.fp()->script()->containsPC(regs.pc));
MOZ_ASSERT(cx->realm() == regs.fp()->script()->realm());
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()) {
if (!DebugAPI::onExceptionUnwind(cx, regs.fp())) {
if (!cx->isExceptionPending()) {
goto again;
}
}
// Ensure that the debugger hasn't returned 'true' while clearing the
// exception state.
MOZ_ASSERT(cx->isExceptionPending());
}
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);
} else {
UnwindIteratorsForUncatchableException(cx, regs);
// We may be propagating a forced return from a debugger hook function.
if (MOZ_UNLIKELY(cx->isPropagatingForcedReturn())) {
cx->clearPropagatingForcedReturn();
ok = true;
}
}
ok = DebugAPI::onLeaveFrame(cx, regs.fp(), regs.pc, ok);
// 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); \
cx->debugOnlyCheck(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_BIGINT(b) REGS.sp++->setBigInt(b)
#define PUSH_STRING(s) \
do { \
REGS.sp++->setString(s); \
cx->debugOnlyCheck(REGS.sp[-1]); \
} while (0)
#define PUSH_OBJECT(obj) \
do { \
REGS.sp++->setObject(obj); \
cx->debugOnlyCheck(REGS.sp[-1]); \
} while (0)
#define PUSH_OBJECT_OR_NULL(obj) \
do { \
REGS.sp++->setObjectOrNull(obj); \
cx->debugOnlyCheck(REGS.sp[-1]); \
} while (0)
#ifdef ENABLE_RECORD_TUPLE
# define PUSH_EXTENDED_PRIMITIVE(obj) \
do { \
REGS.sp++->setExtendedPrimitive(obj); \
cx->debugOnlyCheck(REGS.sp[-1]); \
} while (0)
#endif
#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)
/*
* Same for JSOp::SetName and JSOp::SetProp, which differ only slightly but
* remain distinct for the decompiler.
*/
static_assert(JSOpLength_SetName == JSOpLength_SetProp);
/* See TRY_BRANCH_AFTER_COND. */
static_assert(JSOpLength_JumpIfTrue == JSOpLength_JumpIfFalse);
static_assert(uint8_t(JSOp::JumpIfTrue) == uint8_t(JSOp::JumpIfFalse) + 1);
/*
* Compute the implicit |this| value used by a call expression with an
* unqualified name reference. The environment the binding was found on is
* passed as argument, env.
*
* The implicit |this| is |undefined| for all environment types except
* WithEnvironmentObject. This is the case for |with(...) {...}| expressions or
* if the embedding uses a non-syntactic WithEnvironmentObject.
*
* NOTE: A non-syntactic WithEnvironmentObject may have a corresponding
* extensible LexicalEnviornmentObject, but it will not be considered as an
* implicit |this|. This is for compatibility with the Gecko subscript loader.
*/
static inline Value ComputeImplicitThis(JSObject* env) {
// Fast-path for GlobalObject
if (env->is<GlobalObject>()) {
return UndefinedValue();
}
// WithEnvironmentObjects have an actual implicit |this|
if (env->is<WithEnvironmentObject>()) {
return ObjectValue(*GetThisObjectOfWith(env));
}
// Debugger environments need special casing, as despite being
// non-syntactic, they wrap syntactic environments and should not be
// treated like other embedding-specific non-syntactic environments.
if (env->is<DebugEnvironmentProxy>()) {
return ComputeImplicitThis(&env->as<DebugEnvironmentProxy>().environment());
}
MOZ_ASSERT(env->is<EnvironmentObject>());
return UndefinedValue();
}
// BigInt proposal 3.2.4 Abstract Relational Comparison
// Returns Nothing when at least one operand is a NaN, or when
// ToNumeric or StringToBigInt can't interpret a string as a numeric
// value. (These cases correspond to a NaN result in the spec.)
// Otherwise, return a boolean to indicate whether lhs is less than
// rhs. The operands must be primitives; the caller is responsible for
// evaluating them in the correct order.
static MOZ_ALWAYS_INLINE bool LessThanImpl(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
mozilla::Maybe<bool>& res) {
// Steps 1 and 2 are performed by the caller.
// Step 3.
if (lhs.isString() && rhs.isString()) {
JSString* l = lhs.toString();
JSString* r = rhs.toString();
int32_t result;
if (!CompareStrings(cx, l, r, &result)) {
return false;
}
res = mozilla::Some(result < 0);
return true;
}
// Step 4a.
if (lhs.isBigInt() && rhs.isString()) {
return BigInt::lessThan(cx, lhs, rhs, res);
}
// Step 4b.
if (lhs.isString() && rhs.isBigInt()) {
return BigInt::lessThan(cx, lhs, rhs, res);
}
// Steps 4c and 4d.
if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
return false;
}
// Steps 4e-j.
if (lhs.isBigInt() || rhs.isBigInt()) {
return BigInt::lessThan(cx, lhs, rhs, res);
}
// Step 4e for Number operands.
MOZ_ASSERT(lhs.isNumber() && rhs.isNumber());
double lhsNum = lhs.toNumber();
double rhsNum = rhs.toNumber();
if (std::isnan(lhsNum) || std::isnan(rhsNum)) {
res = mozilla::Maybe<bool>(mozilla::Nothing());
return true;
}
res = mozilla::Some(lhsNum < rhsNum);
return true;
}
static MOZ_ALWAYS_INLINE bool LessThanOperation(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
bool* res) {
if (lhs.isInt32() && rhs.isInt32()) {
*res = lhs.toInt32() < rhs.toInt32();
return true;
}
if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) {
return false;
}
if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) {
return false;
}
mozilla::Maybe<bool> tmpResult;
if (!LessThanImpl(cx, lhs, rhs, tmpResult)) {
return false;
}
*res = tmpResult.valueOr(false);
return true;
}
static MOZ_ALWAYS_INLINE bool LessThanOrEqualOperation(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
bool* res) {
if (lhs.isInt32() && rhs.isInt32()) {
*res = lhs.toInt32() <= rhs.toInt32();
return true;
}
if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) {
return false;
}
if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) {
return false;
}
mozilla::Maybe<bool> tmpResult;
if (!LessThanImpl(cx, rhs, lhs, tmpResult)) {
return false;
}
*res = !tmpResult.valueOr(true);
return true;
}
static MOZ_ALWAYS_INLINE bool GreaterThanOperation(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
bool* res) {
if (lhs.isInt32() && rhs.isInt32()) {
*res = lhs.toInt32() > rhs.toInt32();
return true;
}
if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) {
return false;
}
if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) {
return false;
}
mozilla::Maybe<bool> tmpResult;
if (!LessThanImpl(cx, rhs, lhs, tmpResult)) {
return false;
}
*res = tmpResult.valueOr(false);
return true;
}
static MOZ_ALWAYS_INLINE bool GreaterThanOrEqualOperation(
JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) {
if (lhs.isInt32() && rhs.isInt32()) {
*res = lhs.toInt32() >= rhs.toInt32();
return true;
}
if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) {
return false;
}
if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) {
return false;
}
mozilla::Maybe<bool> tmpResult;
if (!LessThanImpl(cx, lhs, rhs, tmpResult)) {
return false;
}
*res = !tmpResult.valueOr(true);
return true;
}
static MOZ_ALWAYS_INLINE bool SetObjectElementOperation(
JSContext* cx, HandleObject obj, HandleId id, HandleValue value,
HandleValue receiver, bool strict) {
ObjectOpResult result;
return SetProperty(cx, obj, id, value, receiver, result) &&
result.checkStrictModeError(cx, obj, id, strict);
}
void js::ReportInNotObjectError(JSContext* cx, HandleValue lref,
HandleValue rref) {
auto uniqueCharsFromString = [](JSContext* cx,
HandleValue ref) -> UniqueChars {
static const size_t MaxStringLength = 16;
RootedString str(cx, ref.toString());
if (str->length() > MaxStringLength) {
JSStringBuilder buf(cx);
if (!buf.appendSubstring(str, 0, MaxStringLength)) {
return nullptr;
}
if (!buf.append("...")) {
return nullptr;
}
str = buf.finishString();
if (!str) {
return nullptr;
}
}
return QuoteString(cx, str, '"');
};
if (lref.isString() && rref.isString()) {
UniqueChars lbytes = uniqueCharsFromString(cx, lref);
if (!lbytes) {
return;
}
UniqueChars rbytes = uniqueCharsFromString(cx, rref);
if (!rbytes) {
return;
}
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_IN_STRING,
lbytes.get(), rbytes.get());
return;
}
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_IN_NOT_OBJECT,
InformalValueTypeName(rref));
}
bool MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER js::Interpret(JSContext* cx,
RunState& state) {
/*
* Define macros for an interpreter loop. Opcode dispatch is done by
* indirect goto (aka a threaded interpreter), which is technically
* non-standard but is supported by all of our supported compilers.
*/
#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
};
/*
* 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(JSOpLength_##OP);
/*
* 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->realm()->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 SET_SCRIPT(s) \
JS_BEGIN_MACRO \
script = (s); \
MOZ_ASSERT(cx->realm() == script->realm()); \
if (DebugAPI::hasAnyBreakpointsOrStepMode(script) || \
script->hasScriptCounts()) \
activation.enableInterruptsUnconditionally(); \
JS_END_MACRO
#define SANITY_CHECKS() \
JS_BEGIN_MACRO \
js::gc::MaybeVerifyBarriers(cx); \
JS_END_MACRO
// Verify that an uninitialized lexical is followed by a correct check op.
#ifdef DEBUG
# define ASSERT_UNINITIALIZED_ALIASED_LEXICAL(val) \
JS_BEGIN_MACRO \
if (IsUninitializedLexical(val)) { \
JSOp next = JSOp(*GetNextPc(REGS.pc)); \
MOZ_ASSERT(next == JSOp::CheckThis || next == JSOp::CheckReturn || \
next == JSOp::CheckThisReinit || \
next == JSOp::CheckAliasedLexical); \
} \
JS_END_MACRO
#else
# define ASSERT_UNINITIALIZED_ALIASED_LEXICAL(val) \
JS_BEGIN_MACRO \
/* nothing */ \
JS_END_MACRO
#endif
gc::MaybeVerifyBarriers(cx, true);
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());
/*
* 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);
RootedObject rootObject0(cx), rootObject1(cx);
RootedFunction rootFunction0(cx);
Rooted<JSAtom*> rootAtom0(cx);
Rooted<PropertyName*> rootName0(cx);
RootedId rootId0(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;
}
if (!DebugAPI::onEnterFrame(cx, activation.entryFrame())) {
goto error;
}
// 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->realm()->collectCoverageForDebug()) {
if (!script->initScriptCounts(cx)) {
goto error;
}
}
if (script->isDebuggee()) {
if (DebugAPI::stepModeEnabled(script)) {
if (!DebugAPI::onSingleStep(cx)) {
goto error;
}
moreInterrupts = true;
}
if (DebugAPI::hasAnyBreakpointsOrStepMode(script)) {
moreInterrupts = true;
}
if (DebugAPI::hasBreakpointsAt(script, REGS.pc)) {
if (!DebugAPI::onTrap(cx)) {
goto error;
}
}
}
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(Nop)
CASE(Try)
CASE(NopDestructuring)
CASE(NopIsAssignOp)
CASE(TryDestructuring) {
MOZ_ASSERT(GetBytecodeLength(REGS.pc) == 1);
ADVANCE_AND_DISPATCH(1);
}
CASE(JumpTarget)
COUNT_COVERAGE();
END_CASE(JumpTarget)
CASE(LoopHead) {
COUNT_COVERAGE();
// Attempt on-stack replacement into the Baseline Interpreter.
if (jit::IsBaselineInterpreterEnabled()) {
script->incWarmUpCounter();
jit::MethodStatus status =
jit::CanEnterBaselineInterpreterAtBranch(cx, REGS.fp());
if (status == jit::Method_Error) {
goto error;
}
if (status == jit::Method_Compiled) {
bool wasProfiler = REGS.fp()->hasPushedGeckoProfilerFrame();
jit::JitExecStatus maybeOsr;
{
GeckoProfilerBaselineOSRMarker osr(cx, wasProfiler);
maybeOsr =
jit::EnterBaselineInterpreterAtBranch(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 profiler frame pushed by the interpreter. (The compiled
// version of the function popped a copy of the frame pushed by the
// OSR trampoline.)
if (wasProfiler) {
cx->geckoProfiler().exit(cx, script);
}
if (activation.entryFrame() != REGS.fp()) {
goto jit_return_pop_frame;
}
goto leave_on_safe_point;
}
}
}
END_CASE(LoopHead)
CASE(Lineno)
END_CASE(Lineno)
CASE(ForceInterpreter) {
// Ensure pattern matching still works.
MOZ_ASSERT(script->hasForceInterpreterOp());
}
END_CASE(ForceInterpreter)
CASE(Undefined) { PUSH_UNDEFINED(); }
END_CASE(Undefined)
CASE(Pop) { REGS.sp--; }
END_CASE(Pop)
CASE(PopN) {
MOZ_ASSERT(GET_UINT16(REGS.pc) <= REGS.stackDepth());
REGS.sp -= GET_UINT16(REGS.pc);
}
END_CASE(PopN)
CASE(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(DupAt)
CASE(SetRval) { POP_RETURN_VALUE(); }
END_CASE(SetRval)
CASE(GetRval) { PUSH_COPY(REGS.fp()->returnValue()); }
END_CASE(GetRval)
CASE(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(EnterWith)
CASE(LeaveWith) {
REGS.fp()->popOffEnvironmentChain<WithEnvironmentObject>();
}
END_CASE(LeaveWith)
CASE(Return) {
POP_RETURN_VALUE();
/* FALL THROUGH */
}
CASE(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.)
if (MOZ_LIKELY(!frameHalfInitialized)) {
interpReturnOK =
DebugAPI::onLeaveFrame(cx, REGS.fp(), REGS.pc, interpReturnOK);
REGS.fp()->epilogue(cx, REGS.pc);
}
jit_return_pop_frame:
activation.popInlineFrame(REGS.fp());
{
JSScript* callerScript = REGS.fp()->script();
if (cx->realm() != callerScript->realm()) {
cx->leaveRealm(callerScript->realm());
}
SET_SCRIPT(callerScript);
}
jit_return:
MOZ_ASSERT(IsInvokePC(REGS.pc));
MOZ_ASSERT(cx->realm() == script->realm());
/* Resume execution in the calling frame. */
if (MOZ_LIKELY(interpReturnOK)) {
if (JSOp(*REGS.pc) == JSOp::Resume) {
ADVANCE_AND_DISPATCH(JSOpLength_Resume);
}
MOZ_ASSERT(GetBytecodeLength(REGS.pc) == JSOpLength_Call);
ADVANCE_AND_DISPATCH(JSOpLength_Call);
}
goto error;
} else {
// Stack should be empty for the outer frame, unless we executed the
// first |await| expression in an async function.
MOZ_ASSERT(REGS.stackDepth() == 0 ||
(JSOp(*REGS.pc) == JSOp::Await &&
!REGS.fp()->isResumedGenerator()));
}
goto exit;
}
CASE(Default) {
REGS.sp--;
/* FALL THROUGH */
}
CASE(Goto) { BRANCH(GET_JUMP_OFFSET(REGS.pc)); }
CASE(JumpIfFalse) {
bool cond = ToBoolean(REGS.stackHandleAt(-1));
REGS.sp--;
if (!cond) {
BRANCH(GET_JUMP_OFFSET(REGS.pc));
}
}
END_CASE(JumpIfFalse)
CASE(JumpIfTrue) {
bool cond = ToBoolean(REGS.stackHandleAt(-1));
REGS.sp--;
if (cond) {
BRANCH(GET_JUMP_OFFSET(REGS.pc));
}
}
END_CASE(JumpIfTrue)
CASE(Or) {
bool cond = ToBoolean(REGS.stackHandleAt(-1));
if (cond) {
ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc));
}
}
END_CASE(Or)
CASE(Coalesce) {
MutableHandleValue res = REGS.stackHandleAt(-1);
bool cond = !res.isNullOrUndefined();
if (cond) {
ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc));
}
}
END_CASE(Coalesce)
CASE(And) {
bool cond = ToBoolean(REGS.stackHandleAt(-1));
if (!cond) {
ADVANCE_AND_DISPATCH(GET_JUMP_OFFSET(REGS.pc));
}
}
END_CASE(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(GetBytecodeLength(REGS.pc) == 1); \
unsigned diff_ = \
(unsigned)GET_UINT8(REGS.pc) - (unsigned)JSOp::JumpIfFalse; \
if (diff_ <= 1) { \
REGS.sp -= (spdec); \
if ((cond) == (diff_ != 0)) { \
++REGS.pc; \
BRANCH(GET_JUMP_OFFSET(REGS.pc)); \
} \
ADVANCE_AND_DISPATCH(1 + JSOpLength_JumpIfFalse); \
} \
JS_END_MACRO
CASE(In) {
HandleValue rref = REGS.stackHandleAt(-1);
if (!rref.isObject()) {
HandleValue lref = REGS.stackHandleAt(-2);
ReportInNotObjectError(cx, lref, rref);
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(In)
CASE(HasOwn) {
HandleValue val = REGS.stackHandleAt(-1);
HandleValue idval = REGS.stackHandleAt(-2);
bool found;
if (!HasOwnProperty(cx, val, idval, &found)) {
goto error;
}
REGS.sp--;
REGS.sp[-1].setBoolean(found);
}
END_CASE(HasOwn)
CASE(CheckPrivateField) {
/* Load the object being initialized into lval/val. */
HandleValue val = REGS.stackHandleAt(-2);
HandleValue idval = REGS.stackHandleAt(-1);
bool result = false;
if (!CheckPrivateFieldOperation(cx, REGS.pc, val, idval, &result)) {
goto error;
}
PUSH_BOOLEAN(result);
}
END_CASE(CheckPrivateField)
CASE(NewPrivateName) {
ReservedRooted<JSAtom*> name(&rootAtom0, script->getAtom(REGS.pc));
auto* symbol = NewPrivateName(cx, name);
if (!symbol) {
goto error;
}
PUSH_SYMBOL(symbol);
}
END_CASE(NewPrivateName)
CASE(IsNullOrUndefined) {
bool b = REGS.sp[-1].isNullOrUndefined();
PUSH_BOOLEAN(b);
}
END_CASE(IsNullOrUndefined)
CASE(Iter) {
MOZ_ASSERT(REGS.stackDepth() >= 1);
HandleValue val = REGS.stackHandleAt(-1);
JSObject* iter = ValueToIterator(cx, val);
if (!iter) {
goto error;
}
REGS.sp[-1].setObject(*iter);
}
END_CASE(Iter)
CASE(MoreIter) {
MOZ_ASSERT(REGS.stackDepth() >= 1);
MOZ_ASSERT(REGS.sp[-1].isObject());
Value v = IteratorMore(&REGS.sp[-1].toObject());
PUSH_COPY(v);
}
END_CASE(MoreIter)
CASE(IsNoIter) {
bool b = REGS.sp[-1].isMagic(JS_NO_ITER_VALUE);
PUSH_BOOLEAN(b);
}
END_CASE(IsNoIter)
CASE(EndIter) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
CloseIterator(&REGS.sp[-2].toObject());
REGS.sp -= 2;
}
END_CASE(EndIter)
CASE(CloseIter) {
ReservedRooted<JSObject*> iter(&rootObject0, &REGS.sp[-1].toObject());
CompletionKind kind = CompletionKind(GET_UINT8(REGS.pc));
if (!CloseIterOperation(cx, iter, kind)) {
goto error;
}
REGS.sp--;
}
END_CASE(CloseIter)
CASE(OptimizeGetIterator) {
ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
MutableHandleValue rval = REGS.stackHandleAt(-1);
bool result;
if (!OptimizeGetIterator(cx, val, &result)) {
goto error;
}
rval.setBoolean(result);
}
END_CASE(OptimizeGetIterator)
CASE(IsGenClosing) {
bool b = REGS.sp[-1].isMagic(JS_GENERATOR_CLOSING);
PUSH_BOOLEAN(b);
}
END_CASE(IsGenClosing)
CASE(Dup) {
MOZ_ASSERT(REGS.stackDepth() >= 1);
const Value& rref = REGS.sp[-1];
PUSH_COPY(rref);
}
END_CASE(Dup)
CASE(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(Dup2)
CASE(Swap) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
Value& lref = REGS.sp[-2];
Value& rref = REGS.sp[-1];
lref.swap(rref);
}
END_CASE(Swap)
CASE(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(Pick)
CASE(Unpick) {
int i = GET_UINT8(REGS.pc);
MOZ_ASSERT(REGS.stackDepth() >= unsigned(i) + 1);
Value lval = REGS.sp[-1];
memmove(REGS.sp - i, REGS.sp - (i + 1), sizeof(Value) * i);
REGS.sp[-(i + 1)] = lval;
}
END_CASE(Unpick)
CASE(BindGName)
CASE(BindName) {
JSOp op = JSOp(*REGS.pc);
ReservedRooted<JSObject*> envChain(&rootObject0);
if (op == JSOp::BindName) {
envChain.set(REGS.fp()->environmentChain());
} else {
MOZ_ASSERT(!script->hasNonSyntacticScope());
envChain.set(&REGS.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(JSOpLength_BindName == JSOpLength_BindGName,
"We're sharing the END_CASE so the lengths better match");
}
END_CASE(BindName)
CASE(BindVar) {
JSObject* varObj = BindVarOperation(cx, REGS.fp()->environmentChain());
PUSH_OBJECT(*varObj);
}
END_CASE(BindVar)
CASE(BitOr) {
MutableHandleValue lhs = REGS.stackHandleAt(-2);
MutableHandleValue rhs = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!BitOrOperation(cx, lhs, rhs, res)) {
goto error;
}
REGS.sp--;
}
END_CASE(BitOr)
CASE(BitXor) {
MutableHandleValue lhs = REGS.stackHandleAt(-2);
MutableHandleValue rhs = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!BitXorOperation(cx, lhs, rhs, res)) {
goto error;
}
REGS.sp--;
}
END_CASE(BitXor)
CASE(BitAnd) {
MutableHandleValue lhs = REGS.stackHandleAt(-2);
MutableHandleValue rhs = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!BitAndOperation(cx, lhs, rhs, res)) {
goto error;
}
REGS.sp--;
}
END_CASE(BitAnd)
CASE(Eq) {
if (!LooseEqualityOp<true>(cx, REGS)) {
goto error;
}
}
END_CASE(Eq)
CASE(Ne) {
if (!LooseEqualityOp<false>(cx, REGS)) {
goto error;
}
}
END_CASE(Ne)
#define STRICT_EQUALITY_OP(OP, COND) \
JS_BEGIN_MACRO \
HandleValue lval = REGS.stackHandleAt(-2); \
HandleValue rval = REGS.stackHandleAt(-1); \
bool equal; \
if (!js::StrictlyEqual(cx, lval, rval, &equal)) { \
goto error; \
} \
(COND) = equal OP true; \
REGS.sp--; \
JS_END_MACRO
CASE(StrictEq) {
bool cond;
STRICT_EQUALITY_OP(==, cond);
REGS.sp[-1].setBoolean(cond);
}
END_CASE(StrictEq)
CASE(StrictNe) {
bool cond;
STRICT_EQUALITY_OP(!=, cond);
REGS.sp[-1].setBoolean(cond);
}
END_CASE(StrictNe)
#undef STRICT_EQUALITY_OP
CASE(Case) {
bool cond = REGS.sp[-1].toBoolean();
REGS.sp--;
if (cond) {
REGS.sp--;
BRANCH(GET_JUMP_OFFSET(REGS.pc));
}
}
END_CASE(Case)
CASE(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(Lt)
CASE(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(Le)
CASE(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(Gt)
CASE(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(Ge)
CASE(Lsh) {
MutableHandleValue lhs = REGS.stackHandleAt(-2);
MutableHandleValue rhs = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!BitLshOperation(cx, lhs, rhs, res)) {
goto error;
}
REGS.sp--;
}
END_CASE(Lsh)
CASE(Rsh) {
MutableHandleValue lhs = REGS.stackHandleAt(-2);
MutableHandleValue rhs = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!BitRshOperation(cx, lhs, rhs, res)) {
goto error;
}
REGS.sp--;
}
END_CASE(Rsh)
CASE(Ursh) {
MutableHandleValue lhs = REGS.stackHandleAt(-2);
MutableHandleValue rhs = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!UrshOperation(cx, lhs, rhs, res)) {
goto error;
}
REGS.sp--;
}
END_CASE(Ursh)
CASE(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(Add)
CASE(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(Sub)
CASE(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(Mul)
CASE(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(Div)
CASE(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(Mod)
CASE(Pow) {
ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]);
ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!PowOperation(cx, &lval, &rval, res)) {
goto error;
}
REGS.sp--;
}
END_CASE(Pow)
CASE(Not) {
bool cond = ToBoolean(REGS.stackHandleAt(-1));
REGS.sp--;
PUSH_BOOLEAN(!cond);
}
END_CASE(Not)
CASE(BitNot) {
MutableHandleValue val = REGS.stackHandleAt(-1);
if (!BitNotOperation(cx, val, val)) {
goto error;
}
}
END_CASE(BitNot)
CASE(Neg) {
MutableHandleValue val = REGS.stackHandleAt(-1);
if (!NegOperation(cx, val, val)) {
goto error;
}
}
END_CASE(Neg)
CASE(Pos) {
if (!ToNumber(cx, REGS.stackHandleAt(-1))) {
goto error;
}
}
END_CASE(Pos)
CASE(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(DelName)
CASE(DelProp)
CASE(StrictDelProp) {
static_assert(JSOpLength_DelProp == JSOpLength_StrictDelProp,
"delprop and strictdelprop must be the same size");
HandleValue val = REGS.stackHandleAt(-1);
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
bool res = false;
if (JSOp(*REGS.pc) == JSOp::StrictDelProp) {
if (!DelPropOperation<true>(cx, val, name, &res)) {
goto error;
}
} else {
if (!DelPropOperation<false>(cx, val, name, &res)) {
goto error;
}
}
REGS.sp[-1].setBoolean(res);
}
END_CASE(DelProp)
CASE(DelElem)
CASE(StrictDelElem) {
static_assert(JSOpLength_DelElem == JSOpLength_StrictDelElem,
"delelem and strictdelelem must be the same size");
HandleValue val = REGS.stackHandleAt(-2);
HandleValue propval = REGS.stackHandleAt(-1);
bool res = false;
if (JSOp(*REGS.pc) == JSOp::StrictDelElem) {
if (!DelElemOperation<true>(cx, val, propval, &res)) {
goto error;
}
} else {
if (!DelElemOperation<false>(cx, val, propval, &res)) {
goto error;
}
}
REGS.sp[-2].setBoolean(res);
REGS.sp--;
}
END_CASE(DelElem)
CASE(ToPropertyKey) {
ReservedRooted<Value> idval(&rootValue1, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-1);
if (!ToPropertyKeyOperation(cx, idval, res)) {
goto error;
}
}
END_CASE(ToPropertyKey)
CASE(TypeofExpr)
CASE(Typeof) {
REGS.sp[-1].setString(TypeOfOperation(REGS.sp[-1], cx->runtime()));
}
END_CASE(Typeof)
CASE(Void) { REGS.sp[-1].setUndefined(); }
END_CASE(Void)
CASE(FunctionThis) {
PUSH_NULL();
if (!GetFunctionThis(cx, REGS.fp(), REGS.stackHandleAt(-1))) {
goto error;
}
}
END_CASE(FunctionThis)
CASE(GlobalThis) {
MOZ_ASSERT(!script->hasNonSyntacticScope());
PUSH_OBJECT(*cx->global()->lexicalEnvironment().thisObject());
}
END_CASE(GlobalThis)
CASE(NonSyntacticGlobalThis) {
PUSH_NULL();
GetNonSyntacticGlobalThis(cx, REGS.fp()->environmentChain(),
REGS.stackHandleAt(-1));
}
END_CASE(NonSyntacticGlobalThis)
CASE(CheckIsObj) {
if (!REGS.sp[-1].isObject()) {
MOZ_ALWAYS_FALSE(
ThrowCheckIsObject(cx, CheckIsObjectKind(GET_UINT8(REGS.pc))));
goto error;
}
}
END_CASE(CheckIsObj)
CASE(CheckThis) {
if (REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) {
MOZ_ALWAYS_FALSE(ThrowUninitializedThis(cx));
goto error;
}
}
END_CASE(CheckThis)
CASE(CheckThisReinit) {
if (!REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) {
MOZ_ALWAYS_FALSE(ThrowInitializedThis(cx));
goto error;
}
}
END_CASE(CheckThisReinit)
CASE(CheckReturn) {
ReservedRooted<Value> thisv(&rootValue0, REGS.sp[-1]);
MutableHandleValue rval = REGS.stackHandleAt(-1);
if (!REGS.fp()->checkReturn(cx, thisv, rval)) {
goto error;
}
}
END_CASE(CheckReturn)
CASE(GetProp) {
ReservedRooted<Value> lval(&rootValue0, REGS.sp[-1]);
MutableHandleValue res = REGS.stackHandleAt(-1);
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
if (!GetPropertyOperation(cx, name, lval, res)) {
goto error;
}
cx->debugOnlyCheck(res);
}
END_CASE(GetProp)
CASE(GetPropSuper) {
ReservedRooted<Value> receiver(&rootValue0, REGS.sp[-2]);
HandleValue lval = REGS.stackHandleAt(-1);
MOZ_ASSERT(lval.isObjectOrNull());
MutableHandleValue rref = REGS.stackHandleAt(-2);
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
ReservedRooted<JSObject*> obj(&rootObject0);
obj = ToObjectFromStackForPropertyAccess(cx, lval, -1, name);
if (!obj) {
goto error;
}
if (!GetProperty(cx, obj, receiver, name, rref)) {
goto error;
}
cx->debugOnlyCheck(rref);
REGS.sp--;
}
END_CASE(GetPropSuper)
CASE(GetBoundName) {
ReservedRooted<JSObject*> env(&rootObject0, &REGS.sp[-1].toObject());
ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
MutableHandleValue rval = REGS.stackHandleAt(-1);
if (!GetNameBoundInEnvironment(cx, env, id, rval)) {
goto error;
}
cx->debugOnlyCheck(rval);
}
END_CASE(GetBoundName)
CASE(SetIntrinsic) {
HandleValue value = REGS.stackHandleAt(-1);
if (!SetIntrinsicOperation(cx, script, REGS.pc, value)) {
goto error;
}
}
END_CASE(SetIntrinsic)
CASE(SetGName)
CASE(StrictSetGName)
CASE(SetName)
CASE(StrictSetName) {
static_assert(JSOpLength_SetName == JSOpLength_StrictSetName,
"setname and strictsetname must be the same size");
static_assert(JSOpLength_SetGName == JSOpLength_StrictSetGName,
"setgname and strictsetgname must be the same size");
static_assert(JSOpLength_SetName == JSOpLength_SetGName,
"We're sharing the END_CASE so the lengths better match");
ReservedRooted<JSObject*> env(&rootObject0, &REGS.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(SetName)
CASE(SetProp)
CASE(StrictSetProp) {
static_assert(JSOpLength_SetProp == JSOpLength_StrictSetProp,
"setprop and strictsetprop must be the same size");
int lvalIndex = -2;
HandleValue lval = REGS.stackHandleAt(lvalIndex);
HandleValue rval = REGS.stackHandleAt(-1);
ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
bool strict = JSOp(*REGS.pc) == JSOp::StrictSetProp;
ReservedRooted<JSObject*> obj(&rootObject0);
obj = ToObjectFromStackForPropertyAccess(cx, lval, lvalIndex, id);
if (!obj) {
goto error;
}
if (!SetObjectElementOperation(cx, obj, id, rval, lval, strict)) {
goto error;
}
REGS.sp[-2] = REGS.sp[-1];
REGS.sp--;
}
END_CASE(SetProp)
CASE(SetPropSuper)
CASE(StrictSetPropSuper) {
static_assert(
JSOpLength_SetPropSuper == JSOpLength_StrictSetPropSuper,
"setprop-super and strictsetprop-super must be the same size");
HandleValue receiver = REGS.stackHandleAt(-3);
HandleValue lval = REGS.stackHandleAt(-2);
MOZ_ASSERT(lval.isObjectOrNull());
HandleValue rval = REGS.stackHandleAt(-1);
ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
bool strict = JSOp(*REGS.pc) == JSOp::StrictSetPropSuper;
ReservedRooted<JSObject*> obj(&rootObject0);
obj = ToObjectFromStackForPropertyAccess(cx, lval, -2, id);
if (!obj) {
goto error;
}
if (!SetObjectElementOperation(cx, obj, id, rval, receiver, strict)) {
goto error;
}
REGS.sp[-3] = REGS.sp[-1];
REGS.sp -= 2;
}
END_CASE(SetPropSuper)
CASE(GetElem) {
int lvalIndex = -2;
ReservedRooted<Value> lval(&rootValue0, REGS.sp[lvalIndex]);
HandleValue rval = REGS.stackHandleAt(-1);
MutableHandleValue res = REGS.stackHandleAt(-2);
if (!GetElementOperationWithStackIndex(cx, lval, lvalIndex, rval, res)) {
goto error;
}
REGS.sp--;
}
END_CASE(GetElem)
CASE(GetElemSuper) {
ReservedRooted<Value> receiver(&rootValue0, REGS.sp[-3]);
HandleValue index = REGS.stackHandleAt(-2);
HandleValue lval = REGS.stackHandleAt(-1);
MOZ_ASSERT(lval.isObjectOrNull());
MutableHandleValue res = REGS.stackHandleAt(-3);
ReservedRooted<JSObject*> obj(&rootObject0);
obj = ToObjectFromStackForPropertyAccess(cx, lval, -1, index);
if (!obj) {
goto error;
}
if (!GetObjectElementOperation(cx, JSOp(*REGS.pc), obj, receiver, index,
res)) {
goto error;
}
REGS.sp -= 2;
}
END_CASE(GetElemSuper)
CASE(SetElem)
CASE(StrictSetElem) {
static_assert(JSOpLength_SetElem == JSOpLength_StrictSetElem,
"setelem and strictsetelem must be the same size");
int receiverIndex = -3;
HandleValue receiver = REGS.stackHandleAt(receiverIndex);
HandleValue value = REGS.stackHandleAt(-1);
ReservedRooted<JSObject*> obj(&rootObject0);
obj = ToObjectFromStackForPropertyAccess(cx, receiver, receiverIndex,
REGS.stackHandleAt(-2));
if (!obj) {
goto error;
}
ReservedRooted<jsid> id(&rootId0);
FETCH_ELEMENT_ID(-2, id);
if (!SetObjectElementOperation(cx, obj, id, value, receiver,
JSOp(*REGS.pc) == JSOp::StrictSetElem)) {
goto error;
}
REGS.sp[-3] = value;
REGS.sp -= 2;
}
END_CASE(SetElem)
CASE(SetElemSuper)
CASE(StrictSetElemSuper) {
static_assert(
JSOpLength_SetElemSuper == JSOpLength_StrictSetElemSuper,
"setelem-super and strictsetelem-super must be the same size");
HandleValue receiver = REGS.stackHandleAt(-4);
HandleValue lval = REGS.stackHandleAt(-2);
MOZ_ASSERT(lval.isObjectOrNull());
HandleValue value = REGS.stackHandleAt(-1);
ReservedRooted<JSObject*> obj(&rootObject0);
obj = ToObjectFromStackForPropertyAccess(cx, lval, -2,
REGS.stackHandleAt(-3));
if (!obj) {
goto error;
}
ReservedRooted<jsid> id(&rootId0);
FETCH_ELEMENT_ID(-3, id);
bool strict = JSOp(*REGS.pc) == JSOp::StrictSetElemSuper;
if (!SetObjectElementOperation(cx, obj, id, value, receiver, strict)) {
goto error;
}
REGS.sp[-4] = value;
REGS.sp -= 3;
}
END_CASE(SetElemSuper)
CASE(Eval)
CASE(StrictEval) {
static_assert(JSOpLength_Eval == JSOpLength_StrictEval,
"eval and stricteval must be the same size");
CallArgs args = CallArgsFromSp(GET_ARGC(REGS.pc), REGS.sp);
if (cx->global()->valueIsEval(args.calleev())) {
if (!DirectEval(cx, args.get(0), args.rval())) {
goto error;
}
} else {
if (!CallFromStack(cx, args, CallReason::Call)) {
goto error;
}
}
REGS.sp = args.spAfterCall();
}
END_CASE(Eval)
CASE(SpreadNew)
CASE(SpreadCall)
CASE(SpreadSuperCall) {
if (REGS.fp()->hasPushedGeckoProfilerFrame()) {
cx->geckoProfiler().updatePC(cx, script, REGS.pc);
}
/* FALL THROUGH */
}
CASE(SpreadEval)
CASE(StrictSpreadEval) {
static_assert(JSOpLength_SpreadEval == JSOpLength_StrictSpreadEval,
"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(SpreadCall)
CASE(New)
CASE(NewContent)
CASE(Call)
CASE(CallContent)
CASE(CallIgnoresRv)
CASE(CallIter)
CASE(CallContentIter)
CASE(SuperCall) {
static_assert(JSOpLength_Call == JSOpLength_New,
"call and new must be the same size");
static_assert(JSOpLength_Call == JSOpLength_CallContent,
"call and call-content must be the same size");
static_assert(JSOpLength_Call == JSOpLength_CallIgnoresRv,
"call and call-ignores-rv must be the same size");
static_assert(JSOpLength_Call == JSOpLength_CallIter,
"call and calliter must be the same size");
static_assert(JSOpLength_Call == JSOpLength_CallContentIter,
"call and call-content-iter must be the same size");
static_assert(JSOpLength_Call == JSOpLength_SuperCall,
"call and supercall must be the same size");
if (REGS.fp()->hasPushedGeckoProfilerFrame()) {
cx->geckoProfiler().updatePC(cx, script, REGS.pc);
}
JSOp op = JSOp(*REGS.pc);
MaybeConstruct construct = MaybeConstruct(
op == JSOp::New || op == JSOp::NewContent || op == JSOp::SuperCall);
bool ignoresReturnValue = op == JSOp::CallIgnoresRv;
unsigned argStackSlots = GET_ARGC(REGS.pc) + construct;
MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc));
CallArgs args =
CallArgsFromSp(argStackSlots, REGS.sp, construct, ignoresReturnValue);
JSFunction* maybeFun;
bool isFunction = IsFunctionObject(args.calleev(), &maybeFun);
// Use the slow path if the callee is not an interpreted function, if we
// have to throw an exception, or if we might have to invoke the
// OnNativeCall hook for a self-hosted builtin.
if (!isFunction || !maybeFun->isInterpreted() ||
(construct && !maybeFun->isConstructor()) ||
(!construct && maybeFun->isClassConstructor()) ||
cx->realm()->debuggerObservesNativeCall()) {
if (construct) {
CallReason reason = op == JSOp::NewContent ? CallReason::CallContent
: CallReason::Call;
if (!ConstructFromStack(cx, args, reason)) {
goto error;
}
} else {
if ((op == JSOp::CallIter || op == JSOp::CallContentIter) &&
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;
}
CallReason reason =
(op == JSOp::CallContent || op == JSOp::CallContentIter)
? CallReason::CallContent
: CallReason::Call;
if (!CallFromStack(cx, args, reason)) {
goto error;
}
}
Value* newsp = args.spAfterCall();
REGS.sp = newsp;
ADVANCE_AND_DISPATCH(JSOpLength_Call);
}
{
MOZ_ASSERT(maybeFun);
ReservedRooted<JSFunction*> fun(&rootFunction0, maybeFun);
ReservedRooted<JSScript*> funScript(
&rootScript0, JSFunction::getOrCreateScript(cx, fun));
if (!funScript) {
goto error;
}
// Enter the callee's realm if this is a cross-realm call. Use
// MakeScopeExit to leave this realm on all error/JIT-return paths
// below.
const bool isCrossRealm = cx->realm() != funScript->realm();
if (isCrossRealm) {
cx->enterRealmOf(funScript);
}
auto leaveRealmGuard =
mozilla::MakeScopeExit([isCrossRealm, cx, &script] {
if (isCrossRealm) {
cx->leaveRealm(script->realm());
}
});
if (construct && !MaybeCreateThisForConstructor(cx, args)) {
goto error;
}
{
InvokeState state(cx, args, construct);
jit::EnterJitStatus status = jit::MaybeEnterJit(cx, state);
switch (status) {
case jit::EnterJitStatus::Error:
goto error;
case jit::EnterJitStatus::Ok:
interpReturnOK = true;
CHECK_BRANCH();
REGS.sp = args.spAfterCall();
goto jit_return;
case jit::EnterJitStatus::NotEntered:
break;
}
#ifdef NIGHTLY_BUILD
// If entry trampolines are enabled, call back into
// MaybeEnterInterpreterTrampoline so we can generate an
// entry trampoline for the new frame.
if (jit::JitOptions.emitInterpreterEntryTrampoline) {
if (MaybeEnterInterpreterTrampoline(cx, state)) {
interpReturnOK = true;
CHECK_BRANCH();
REGS.sp = args.spAfterCall();
goto jit_return;
}
goto error;
}
#endif
}
funScript = fun->nonLazyScript();
if (!activation.pushInlineFrame(args, funScript, construct)) {
goto error;
}
leaveRealmGuard.release(); // We leave the callee's realm when we
// call popInlineFrame.
}
SET_SCRIPT(REGS.fp()->script());
if (!REGS.fp()->prologue(cx)) {
goto prologue_error;
}
if (!DebugAPI::onEnterFrame(cx, REGS.fp())) {
goto error;
}
// 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(OptimizeSpreadCall) {
ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
MutableHandleValue rval = REGS.stackHandleAt(-1);
if (!OptimizeSpreadCall(cx, val, rval)) {
goto error;
}
}
END_CASE(OptimizeSpreadCall)
CASE(ThrowMsg) {
MOZ_ALWAYS_FALSE(ThrowMsgOperation(cx, GET_UINT8(REGS.pc)));
goto error;
}
END_CASE(ThrowMsg)
CASE(ImplicitThis) {
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);
}
END_CASE(ImplicitThis)
CASE(GetGName) {
ReservedRooted<Value> rval(&rootValue0);
ReservedRooted<JSObject*> env(&rootObject0,
&cx->global()->lexicalEnvironment());
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
MOZ_ASSERT(!script->hasNonSyntacticScope());
if (!GetNameOperation(cx, env, name, JSOp(REGS.pc[JSOpLength_GetGName]),
&rval)) {
goto error;
}
PUSH_COPY(rval);
}
END_CASE(GetGName)
CASE(GetName) {
ReservedRooted<Value> rval(&rootValue0);
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
if (!GetNameOperation(cx, REGS.fp()->environmentChain(), name,
JSOp(REGS.pc[JSOpLength_GetName]), &rval)) {
goto error;
}
PUSH_COPY(rval);
}
END_CASE(GetName)
CASE(GetImport) {
PUSH_NULL();
MutableHandleValue rval = REGS.stackHandleAt(-1);
HandleObject envChain = REGS.fp()->environmentChain();
if (!GetImportOperation(cx, envChain, script, REGS.pc, rval)) {
goto error;
}
}
END_CASE(GetImport)
CASE(GetIntrinsic) {
ReservedRooted<Value> rval(&rootValue0);
if (!GetIntrinsicOperation(cx, script, REGS.pc, &rval)) {
goto error;
}
PUSH_COPY(rval);
}
END_CASE(GetIntrinsic)
CASE(Uint16) { PUSH_INT32((int32_t)GET_UINT16(REGS.pc)); }
END_CASE(Uint16)
CASE(Uint24) { PUSH_INT32((int32_t)GET_UINT24(REGS.pc)); }
END_CASE(Uint24)
CASE(Int8) { PUSH_INT32(GET_INT8(REGS.pc)); }
END_CASE(Int8)
CASE(Int32) { PUSH_INT32(GET_INT32(REGS.pc)); }
END_CASE(Int32)
CASE(Double) { PUSH_COPY(GET_INLINE_VALUE(REGS.pc)); }
END_CASE(Double)
CASE(String) { PUSH_STRING(script->getString(REGS.pc)); }
END_CASE(String)
CASE(ToString) {
MutableHandleValue oper = REGS.stackHandleAt(-1);
if (!oper.isString()) {
JSString* operString = ToString<CanGC>(cx, oper);
if (!operString) {
goto error;
}
oper.setString(operString);
}
}
END_CASE(ToString)
CASE(Symbol) {
PUSH_SYMBOL(cx->wellKnownSymbols().get(GET_UINT8(REGS.pc)));
}
END_CASE(Symbol)
CASE(Object) {
MOZ_ASSERT(script->treatAsRunOnce());
PUSH_OBJECT(*script->getObject(REGS.pc));
}
END_CASE(Object)
CASE(CallSiteObj) {
JSObject* cso = script->getObject(REGS.pc);
MOZ_ASSERT(!cso->as<ArrayObject>().isExtensible());
MOZ_ASSERT(cso->as<ArrayObject>().containsPure(cx->names().raw));
PUSH_OBJECT(*cso);
}
END_CASE(CallSiteObj)
CASE(RegExp) {
/*
* Push a regexp object cloned from the regexp literal object mapped by
* the bytecode at pc.
*/
ReservedRooted<JSObject*> re(&rootObject0, script->getRegExp(REGS.pc));
JSObject* obj = CloneRegExpObject(cx, re.as<RegExpObject>());
if (!obj) {
goto error;
}
PUSH_OBJECT(*obj);
}
END_CASE(RegExp)
CASE(Zero) { PUSH_INT32(0); }
END_CASE(Zero)
CASE(One) { PUSH_INT32(1); }
END_CASE(One)
CASE(Null) { PUSH_NULL(); }
END_CASE(Null)
CASE(False) { PUSH_BOOLEAN(false); }
END_CASE(False)
CASE(True) { PUSH_BOOLEAN(true); }
END_CASE(True)
CASE(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 = uint32_t(i) - uint32_t(low);
if (uint32_t(i) < uint32_t(high - low + 1)) {
len = script->tableSwitchCaseOffset(REGS.pc, uint32_t(i)) -
script->pcToOffset(REGS.pc);
}
ADVANCE_AND_DISPATCH(len);
}
CASE(Arguments) {
MOZ_ASSERT(script->needsArgsObj());
ArgumentsObject* obj = ArgumentsObject::createExpected(cx, REGS.fp());
if (!obj) {
goto error;
}
PUSH_COPY(ObjectValue(*obj));
}
END_CASE(Arguments)
CASE(Rest) {
ReservedRooted<JSObject*> rest(&rootObject0,
REGS.fp()->createRestParameter(cx));
if (!rest) {
goto error;
}
PUSH_COPY(ObjectValue(*rest));
}
END_CASE(Rest)
CASE(GetAliasedVar) {
EnvironmentCoordinate ec = EnvironmentCoordinate(REGS.pc);
ReservedRooted<Value> val(
&rootValue0, REGS.fp()->aliasedEnvironment(ec).aliasedBinding(ec));
ASSERT_UNINITIALIZED_ALIASED_LEXICAL(val);
PUSH_COPY(val);
}
END_CASE(GetAliasedVar)
CASE(GetAliasedDebugVar) {
EnvironmentCoordinate ec = EnvironmentCoordinate(REGS.pc);
ReservedRooted<Value> val(
&rootValue0,
REGS.fp()->aliasedEnvironmentMaybeDebug(ec).aliasedBinding(ec));
ASSERT_UNINITIALIZED_ALIASED_LEXICAL(val);
PUSH_COPY(val);
}
END_CASE(GetAliasedVar)
CASE(SetAliasedVar) {
EnvironmentCoordinate ec = EnvironmentCoordinate(REGS.pc);
EnvironmentObject& obj = REGS.fp()->aliasedEnvironment(ec);
MOZ_ASSERT(!IsUninitializedLexical(obj.aliasedBinding(ec)));
obj.setAliasedBinding(ec, REGS.sp[-1]);
}
END_CASE(SetAliasedVar)
CASE(ThrowSetConst) {
ReportRuntimeLexicalError(cx, JSMSG_BAD_CONST_ASSIGN, script, REGS.pc);
goto error;
}
END_CASE(ThrowSetConst)
CASE(CheckLexical) {
if (REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) {
ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, script,
REGS.pc);
goto error;
}
}
END_CASE(CheckLexical)
CASE(CheckAliasedLexical) {
if (REGS.sp[-1].isMagic(JS_UNINITIALIZED_LEXICAL)) {
ReportRuntimeLexicalError(cx, JSMSG_UNINITIALIZED_LEXICAL, script,
REGS.pc);
goto error;
}
}
END_CASE(CheckAliasedLexical)
CASE(InitLexical) {
uint32_t i = GET_LOCALNO(REGS.pc);
REGS.fp()->unaliasedLocal(i) = REGS.sp[-1];
}
END_CASE(InitLexical)
CASE(InitAliasedLexical) {
EnvironmentCoordinate ec = EnvironmentCoordinate(REGS.pc);
EnvironmentObject& obj = REGS.fp()->aliasedEnvironment(ec);
obj.setAliasedBinding(ec, REGS.sp[-1]);
}
END_CASE(InitAliasedLexical)
CASE(InitGLexical) {
ExtensibleLexicalEnvironmentObject* lexicalEnv;
if (script->hasNonSyntacticScope()) {
lexicalEnv = &REGS.fp()->extensibleLexicalEnvironment();
} else {
lexicalEnv = &cx->global()->lexicalEnvironment();
}
HandleValue value = REGS.stackHandleAt(-1);
InitGlobalLexicalOperation(cx, lexicalEnv, script, REGS.pc, value);
}
END_CASE(InitGLexical)
CASE(Uninitialized) { PUSH_MAGIC(JS_UNINITIALIZED_LEXICAL); }
END_CASE(Uninitialized)
CASE(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(GetArg)
CASE(GetFrameArg) {
uint32_t i = GET_ARGNO(REGS.pc);
PUSH_COPY(REGS.fp()->unaliasedFormal(i, DONT_CHECK_ALIASING));
}
END_CASE(GetFrameArg)
CASE(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(SetArg)
CASE(GetLocal) {
uint32_t i = GET_LOCALNO(REGS.pc);
PUSH_COPY_SKIP_CHECK(REGS.fp()->unaliasedLocal(i));
#ifdef DEBUG
if (IsUninitializedLexical(REGS.sp[-1])) {
JSOp next = JSOp(*GetNextPc(REGS.pc));
MOZ_ASSERT(next == JSOp::CheckThis || next == JSOp::CheckReturn ||
next == JSOp::CheckThisReinit || next == JSOp::CheckLexical);
}
/*
* 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 (JSOp(REGS.pc[JSOpLength_GetLocal]) != JSOp::Pop) {
cx->debugOnlyCheck(REGS.sp[-1]);
}
#endif
}
END_CASE(GetLocal)
CASE(SetLocal) {
uint32_t i = GET_LOCALNO(REGS.pc);
MOZ_ASSERT(!IsUninitializedLexical(REGS.fp()->unaliasedLocal(i)));
REGS.fp()->unaliasedLocal(i) = REGS.sp[-1];
}
END_CASE(SetLocal)
CASE(ArgumentsLength) {
MOZ_ASSERT(!script->needsArgsObj());
PUSH_INT32(REGS.fp()->numActualArgs());
}
END_CASE(ArgumentsLength)
CASE(GetActualArg) {
MOZ_ASSERT(!script->needsArgsObj());
uint32_t index = REGS.sp[-1].toInt32();
REGS.sp[-1] = REGS.fp()->unaliasedActual(index);
}
END_CASE(GetActualArg)
CASE(GlobalOrEvalDeclInstantiation) {
GCThingIndex lastFun = GET_GCTHING_INDEX(REGS.pc);
HandleObject env = REGS.fp()->environmentChain();
if (!GlobalOrEvalDeclInstantiation(cx, env, script, lastFun)) {
goto error;
}
}
END_CASE(GlobalOrEvalDeclInstantiation)
CASE(Lambda) {
/* Load the specified function object literal. */
ReservedRooted<JSFunction*> fun(&rootFunction0,
script->getFunction(REGS.pc));
JSObject* obj = Lambda(cx, fun, REGS.fp()->environmentChain());
if (!obj) {
goto error;
}
MOZ_ASSERT(obj->staticPrototype());
PUSH_OBJECT(*obj);
}
END_CASE(Lambda)
CASE(ToAsyncIter) {
ReservedRooted<Value> nextMethod(&rootValue0, REGS.sp[-1]);
ReservedRooted<JSObject*> iter(&rootObject1, &REGS.sp[-2].toObject());
JSObject* asyncIter = CreateAsyncFromSyncIterator(cx, iter, nextMethod);
if (!asyncIter) {
goto error;
}
REGS.sp--;
REGS.sp[-1].setObject(*asyncIter);
}
END_CASE(ToAsyncIter)
CASE(CanSkipAwait) {
ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]);
bool canSkip;
if (!CanSkipAwait(cx, val, &canSkip)) {
goto error;
}
PUSH_BOOLEAN(canSkip);
}
END_CASE(CanSkipAwait)
CASE(MaybeExtractAwaitValue) {
MutableHandleValue val = REGS.stackHandleAt(-2);
ReservedRooted<Value> canSkip(&rootValue0, REGS.sp[-1]);
if (canSkip.toBoolean()) {
if (!ExtractAwaitValue(cx, val, val)) {
goto error;
}
}
}
END_CASE(MaybeExtractAwaitValue)
CASE(AsyncAwait) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
ReservedRooted<JSObject*> gen(&rootObject1, &REGS.sp[-1].toObject());
ReservedRooted<Value> value(&rootValue0, REGS.sp[-2]);
JSObject* promise =
AsyncFunctionAwait(cx, gen.as<AsyncFunctionGeneratorObject>(), value);
if (!promise) {
goto error;
}
REGS.sp--;
REGS.sp[-1].setObject(*promise);
}
END_CASE(AsyncAwait)
CASE(AsyncResolve) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
ReservedRooted<JSObject*> gen(&rootObject1, &REGS.sp[-1].toObject());
ReservedRooted<Value> value(&rootValue0, REGS.sp[-2]);
JSObject* promise = AsyncFunctionResolve(
cx, gen.as<AsyncFunctionGeneratorObject>(), value);
if (!promise) {
goto error;
}
REGS.sp--;
REGS.sp[-1].setObject(*promise);
}
END_CASE(AsyncResolve)
CASE(AsyncReject) {
MOZ_ASSERT(REGS.stackDepth() >= 3);
ReservedRooted<JSObject*> gen(&rootObject1, &REGS.sp[-1].toObject());
ReservedRooted<Value> stack(&rootValue0, REGS.sp[-2]);
ReservedRooted<Value> reason(&rootValue1, REGS.sp[-3]);
JSObject* promise = AsyncFunctionReject(
cx, gen.as<AsyncFunctionGeneratorObject>(), reason, stack);
if (!promise) {
goto error;
}
REGS.sp -= 2;
REGS.sp[-1].setObject(*promise);
}
END_CASE(AsyncReject)
CASE(SetFunName) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
FunctionPrefixKind prefixKind = FunctionPrefixKind(GET_UINT8(REGS.pc));
ReservedRooted<Value> name(&rootValue0, REGS.sp[-1]);
ReservedRooted<JSFunction*> fun(&rootFunction0,
&REGS.sp[-2].toObject().as<JSFunction>());
if (!SetFunctionName(cx, fun, name, prefixKind)) {
goto error;
}
REGS.sp--;
}
END_CASE(SetFunName)
CASE(Callee) {
MOZ_ASSERT(REGS.fp()->isFunctionFrame());
PUSH_COPY(REGS.fp()->calleev());
}
END_CASE(Callee)
CASE(InitPropGetter)
CASE(InitHiddenPropGetter)
CASE(InitPropSetter)
CASE(InitHiddenPropSetter) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-2].toObject());
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
ReservedRooted<JSObject*> val(&rootObject1, &REGS.sp[-1].toObject());
if (!InitPropGetterSetterOperation(cx, REGS.pc, obj, name, val)) {
goto error;
}
REGS.sp--;
}
END_CASE(InitPropGetter)
CASE(InitElemGetter)
CASE(InitHiddenElemGetter)
CASE(InitElemSetter)
CASE(InitHiddenElemSetter) {
MOZ_ASSERT(REGS.stackDepth() >= 3);
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-3].toObject());
ReservedRooted<Value> idval(&rootValue0, REGS.sp[-2]);
ReservedRooted<JSObject*> val(&rootObject1, &REGS.sp[-1].toObject());
if (!InitElemGetterSetterOperation(cx, REGS.pc, obj, idval, val)) {
goto error;
}
REGS.sp -= 2;
}
END_CASE(InitElemGetter)
CASE(Hole) { PUSH_MAGIC(JS_ELEMENTS_HOLE); }
END_CASE(Hole)
CASE(NewInit) {
JSObject* obj = NewObjectOperation(cx, script, REGS.pc);
if (!obj) {
goto error;
}
PUSH_OBJECT(*obj);
}
END_CASE(NewInit)
CASE(NewArray) {
uint32_t length = GET_UINT32(REGS.pc);
ArrayObject* obj = NewArrayOperation(cx, length);
if (!obj) {
goto error;
}
PUSH_OBJECT(*obj);
}
END_CASE(NewArray)
CASE(NewObject) {
JSObject* obj = NewObjectOperation(cx, script, REGS.pc);
if (!obj) {
goto error;
}
PUSH_OBJECT(*obj);
}
END_CASE(NewObject)
CASE(MutateProto) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
if (REGS.sp[-1].isObjectOrNull()) {
ReservedRooted<JSObject*> newProto(&rootObject1,
REGS.sp[-1].toObjectOrNull());
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-2].toObject());
MOZ_ASSERT(obj->is<PlainObject>());
if (!SetPrototype(cx, obj, newProto)) {
goto error;
}
}
REGS.sp--;
}
END_CASE(MutateProto)
CASE(InitProp)
CASE(InitLockedProp)
CASE(InitHiddenProp) {
static_assert(JSOpLength_InitProp == JSOpLength_InitLockedProp,
"initprop and initlockedprop must be the same size");
static_assert(JSOpLength_InitProp == JSOpLength_InitHiddenProp,
"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, &REGS.sp[-2].toObject());
ReservedRooted<PropertyName*> name(&rootName0, script->getName(REGS.pc));
if (!InitPropertyOperation(cx, REGS.pc, obj, name, rval)) {
goto error;
}
REGS.sp--;
}
END_CASE(InitProp)
CASE(InitElem)
CASE(InitHiddenElem)
CASE(InitLockedElem) {
MOZ_ASSERT(REGS.stackDepth() >= 3);
HandleValue val = REGS.stackHandleAt(-1);
HandleValue id = REGS.stackHandleAt(-2);
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-3].toObject());
if (!InitElemOperation(cx, REGS.pc, obj, id, val)) {
goto error;
}
REGS.sp -= 2;
}
END_CASE(InitElem)
CASE(InitElemArray) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
HandleValue val = REGS.stackHandleAt(-1);
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-2].toObject());
InitElemArrayOperation(cx, REGS.pc, obj.as<ArrayObject>(), val);
REGS.sp--;
}
END_CASE(InitElemArray)
CASE(InitElemInc) {
MOZ_ASSERT(REGS.stackDepth() >= 3);
HandleValue val = REGS.stackHandleAt(-1);
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-3].toObject());
uint32_t index = REGS.sp[-2].toInt32();
if (!InitElemIncOperation(cx, obj.as<ArrayObject>(), index, val)) {
goto error;
}
REGS.sp[-2].setInt32(index + 1);
REGS.sp--;
}
END_CASE(InitElemInc)
#ifdef ENABLE_RECORD_TUPLE
CASE(InitRecord) {
uint32_t length = GET_UINT32(REGS.pc);
RecordType* rec = RecordType::createUninitialized(cx, length);
if (!rec) {
goto error;
}
PUSH_EXTENDED_PRIMITIVE(*rec);
}
END_CASE(InitRecord)
CASE(AddRecordProperty) {
MOZ_ASSERT(REGS.stackDepth() >= 3);
ReservedRooted<JSObject*> rec(&rootObject0,
&REGS.sp[-3].toExtendedPrimitive());
MOZ_ASSERT(rec->is<RecordType>());
ReservedRooted<Value> key(&rootValue0, REGS.sp[-2]);
ReservedRooted<jsid> id(&rootId0);
if (!JS_ValueToId(cx, key, &id)) {
goto error;
}
if (!rec->as<RecordType>().initializeNextProperty(
cx, id, REGS.stackHandleAt(-1))) {
goto error;
}
REGS.sp -= 2;
}
END_CASE(AddRecordProperty)
CASE(AddRecordSpread) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
if (!AddRecordSpreadOperation(cx, REGS.stackHandleAt(-2),
REGS.stackHandleAt(-1))) {
goto error;
}
REGS.sp--;
}
END_CASE(AddRecordSpread)
CASE(FinishRecord) {
MOZ_ASSERT(REGS.stackDepth() >= 1);
RecordType* rec = &REGS.sp[-1].toExtendedPrimitive().as<RecordType>();
if (!rec->finishInitialization(cx)) {
goto error;
}
}
END_CASE(FinishRecord)
CASE(InitTuple) {
uint32_t length = GET_UINT32(REGS.pc);
TupleType* tup = TupleType::createUninitialized(cx, length);
if (!tup) {
goto error;
}
PUSH_EXTENDED_PRIMITIVE(*tup);
}
END_CASE(InitTuple)
CASE(AddTupleElement) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
ReservedRooted<JSObject*> tup(&rootObject0,
&REGS.sp[-2].toExtendedPrimitive());
HandleValue val = REGS.stackHandleAt(-1);
if (!tup->as<TupleType>().initializeNextElement(cx, val)) {
goto error;
}
REGS.sp--;
}
END_CASE(AddTupleElement)
CASE(FinishTuple) {
MOZ_ASSERT(REGS.stackDepth() >= 1);
TupleType& tup = REGS.sp[-1].toExtendedPrimitive().as<TupleType>();
tup.finishInitialization(cx);
}
END_CASE(FinishTuple)
#endif
CASE(Exception) {
PUSH_NULL();
MutableHandleValue res = REGS.stackHandleAt(-1);
if (!GetAndClearException(cx, res)) {
goto error;
}
}
END_CASE(Exception)
CASE(ExceptionAndStack) {
ReservedRooted<Value> stack(&rootValue0);
if (!cx->getPendingExceptionStack(&stack)) {
goto error;
}
PUSH_NULL();
MutableHandleValue res = REGS.stackHandleAt(-1);
if (!GetAndClearException(cx, res)) {
goto error;
}
PUSH_COPY(stack);
}
END_CASE(ExceptionAndStack)
CASE(Finally) { CHECK_BRANCH(); }
END_CASE(Finally)
CASE(Throw) {
CHECK_BRANCH();
ReservedRooted<Value> v(&rootValue0);
POP_COPY_TO(v);
MOZ_ALWAYS_FALSE(ThrowOperation(cx, v));
/* let the code at error try to catch the exception. */
goto error;
}
CASE(ThrowWithStack) {
CHECK_BRANCH();
ReservedRooted<Value> v(&rootValue0);
ReservedRooted<Value> stack(&rootValue1);
POP_COPY_TO(stack);
POP_COPY_TO(v);
MOZ_ALWAYS_FALSE(ThrowWithStackOperation(cx, v, stack));
/* let the code at error try to catch the exception. */
goto error;
}
CASE(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 (!InstanceofOperator(cx, obj, REGS.stackHandleAt(-2), &cond)) {
goto error;
}
REGS.sp--;
REGS.sp[-1].setBoolean(cond);
}
END_CASE(Instanceof)
CASE(Debugger) {
if (!DebugAPI::onDebuggerStatement(cx, REGS.fp())) {
goto error;
}
}
END_CASE(Debugger)
CASE(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(PushLexicalEnv)
CASE(PopLexicalEnv) {
#ifdef DEBUG
Scope* scope = script->lookupScope(REGS.pc);
MOZ_ASSERT(scope);
MOZ_ASSERT(scope->is<LexicalScope>() || scope->is<ClassBodyScope>());
MOZ_ASSERT_IF(scope->is<LexicalScope>(),
scope->as<LexicalScope>().hasEnvironment());
MOZ_ASSERT_IF(scope->is<ClassBodyScope>(),
scope->as<ClassBodyScope>().hasEnvironment());
#endif
if (MOZ_UNLIKELY(cx->realm()->isDebuggee())) {
DebugEnvironments::onPopLexical(cx, REGS.fp(), REGS.pc);
}
// Pop block from scope chain.
REGS.fp()->popOffEnvironmentChain<LexicalEnvironmentObject>();
}
END_CASE(PopLexicalEnv)
CASE(DebugLeaveLexicalEnv) {
#ifdef DEBUG
Scope* scope = script->lookupScope(REGS.pc);
MOZ_ASSERT(scope);
MOZ_ASSERT(scope->is<LexicalScope>() || scope->is<ClassBodyScope>());
MOZ_ASSERT_IF(scope->is<LexicalScope>(),
!scope->as<LexicalScope>().hasEnvironment());
MOZ_ASSERT_IF(scope->is<ClassBodyScope>(),
!scope->as<ClassBodyScope>().hasEnvironment());
#endif
// 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->realm()->isDebuggee())) {
DebugEnvironments::onPopLexical(cx, REGS.fp(), REGS.pc);
}
}
END_CASE(DebugLeaveLexicalEnv)
CASE(FreshenLexicalEnv) {
#ifdef DEBUG
Scope* scope = script->getScope(REGS.pc);
auto envChain = REGS.fp()->environmentChain();
auto* envScope = &envChain->as<BlockLexicalEnvironmentObject>().scope();
MOZ_ASSERT(scope == envScope);
#endif
if (!REGS.fp()->freshenLexicalEnvironment(cx, REGS.pc)) {
goto error;
}
}
END_CASE(FreshenLexicalEnv)
CASE(RecreateLexicalEnv) {
#ifdef DEBUG
Scope* scope = script->getScope(REGS.pc);
auto envChain = REGS.fp()->environmentChain();
auto* envScope = &envChain->as<BlockLexicalEnvironmentObject>().scope();
MOZ_ASSERT(scope == envScope);
#endif
if (!REGS.fp()->recreateLexicalEnvironment(cx, REGS.pc)) {
goto error;
}
}
END_CASE(RecreateLexicalEnv)
CASE(PushClassBodyEnv) {
ReservedRooted<Scope*> scope(&rootScope0, script->getScope(REGS.pc));
if (!REGS.fp()->pushClassBodyEnvironment(cx,
scope.as<ClassBodyScope>())) {
goto error;
}
}
END_CASE(PushClassBodyEnv)
CASE(PushVarEnv) {
ReservedRooted<Scope*> scope(&rootScope0, script->getScope(REGS.pc));
if (!REGS.fp()->pushVarEnvironment(cx, scope)) {
goto error;
}
}
END_CASE(PushVarEnv)
CASE(Generator) {
MOZ_ASSERT(!cx->isExceptionPending());
MOZ_ASSERT(REGS.stackDepth() == 0);
JSObject* obj = AbstractGeneratorObject::createFromFrame(cx, REGS.fp());
if (!obj) {
goto error;
}
PUSH_OBJECT(*obj);
}
END_CASE(Generator)
CASE(InitialYield) {
MOZ_ASSERT(!cx->isExceptionPending());
MOZ_ASSERT_IF(script->isModule() && script->isAsync(),
REGS.fp()->isModuleFrame());
MOZ_ASSERT_IF(!script->isModule() && script->isAsync(),
REGS.fp()->isFunctionFrame());
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-1].toObject());
POP_RETURN_VALUE();
MOZ_ASSERT(REGS.stackDepth() == 0);
if (!AbstractGeneratorObject::suspend(cx, obj, REGS.fp(), REGS.pc,
script->nfixed())) {
goto error;
}
goto successful_return_continuation;
}
CASE(Yield)
CASE(Await) {
MOZ_ASSERT(!cx->isExceptionPending());
MOZ_ASSERT_IF(script->isModule() && script->isAsync(),
REGS.fp()->isModuleFrame());
MOZ_ASSERT_IF(!script->isModule() && script->isAsync(),
REGS.fp()->isFunctionFrame());
ReservedRooted<JSObject*> obj(&rootObject0, &REGS.sp[-1].toObject());
if (!AbstractGeneratorObject::suspend(
cx, obj, REGS.fp(), REGS.pc,
script->nfixed() + REGS.stackDepth() - 2)) {
goto error;
}
REGS.sp--;
POP_RETURN_VALUE();
goto successful_return_continuation;
}
CASE(ResumeKind) {
GeneratorResumeKind resumeKind = ResumeKindFromPC(REGS.pc);
PUSH_INT32(int32_t(resumeKind));
}
END_CASE(ResumeKind)
CASE(CheckResumeKind) {
int32_t kindInt = REGS.sp[-1].toInt32();
GeneratorResumeKind resumeKind = IntToResumeKind(kindInt);
if (MOZ_UNLIKELY(resumeKind != GeneratorResumeKind::Next)) {
ReservedRooted<Value> val(&rootValue0, REGS.sp[-3]);
Rooted<AbstractGeneratorObject*> gen(
cx, &REGS.sp[-2].toObject().as<AbstractGeneratorObject>());
MOZ_ALWAYS_FALSE(GeneratorThrowOrReturn(cx, activation.regs().fp(), gen,
val, resumeKind));
goto error;
}
REGS.sp -= 2;
}
END_CASE(CheckResumeKind)
CASE(Resume) {
{
Rooted<AbstractGeneratorObject*> gen(
cx, &REGS.sp[-3].toObject().as<AbstractGeneratorObject>());
ReservedRooted<Value> val(&rootValue0, REGS.sp[-2]);
ReservedRooted<Value> resumeKindVal(&rootValue1, REGS.sp[-1]);
// popInlineFrame expects there to be an additional value on the stack
// to pop off, so leave "gen" on the stack.
REGS.sp -= 1;
if (!AbstractGeneratorObject::resume(cx, activation, gen, val,
resumeKindVal)) {
goto error;
}
JSScript* generatorScript = REGS.fp()->script();
if (cx->realm() != generatorScript->realm()) {
cx->enterRealmOf(generatorScript);
}
SET_SCRIPT(generatorScript);
if (!probes::EnterScript(cx, generatorScript,
generatorScript->function(), REGS.fp())) {
goto error;
}
if (!DebugAPI::onResumeFrame(cx, REGS.fp())) {
if (cx->isPropagatingForcedReturn()) {
MOZ_ASSERT_IF(
REGS.fp()
->callee()
.isGenerator(), // as opposed to an async function
gen->isClosed());
}
goto error;
}
}
ADVANCE_AND_DISPATCH(0);
}
CASE(AfterYield) {
// AbstractGeneratorObject::resume takes care of setting the frame's
// debuggee flag.
MOZ_ASSERT_IF(REGS.fp()->script()->isDebuggee(), REGS.fp()->isDebuggee());
COUNT_COVERAGE();
}
END_CASE(AfterYield)
CASE(FinalYieldRval) {
ReservedRooted<JSObject*> gen(&rootObject0, &REGS.sp[-1].toObject());
REGS.sp--;
AbstractGeneratorObject::finalSuspend(cx, gen);
goto successful_return_continuation;
}
CASE(CheckClassHeritage) {
HandleValue heritage = REGS.stackHandleAt(-1);
if (!CheckClassHeritageOperation(cx, heritage)) {
goto error;
}
}
END_CASE(CheckClassHeritage)
CASE(BuiltinObject) {
auto kind = BuiltinObjectKind(GET_UINT8(REGS.pc));
JSObject* builtin = BuiltinObjectOperation(cx, kind);
if (!builtin) {
goto error;
}
PUSH_OBJECT(*builtin);
}
END_CASE(BuiltinObject)
CASE(FunWithProto) {
ReservedRooted<JSObject*> proto(&rootObject1, &REGS.sp[-1].toObject());
/* Load the specified function object literal. */
ReservedRooted<JSFunction*> fun(&rootFunction0,
script->getFunction(REGS.pc));
JSObject* obj =
FunWithProtoOperation(cx, fun, REGS.fp()->environmentChain(), proto);
if (!obj) {
goto error;
}
REGS.sp[-1].setObject(*obj);
}
END_CASE(FunWithProto)
CASE(ObjWithProto) {
JSObject* obj = ObjectWithProtoOperation(cx, REGS.stackHandleAt(-1));
if (!obj) {
goto error;
}
REGS.sp[-1].setObject(*obj);
}
END_CASE(ObjWithProto)
CASE(InitHomeObject) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
/* Load the function to be initialized */
JSFunction* func = &REGS.sp[-2].toObject().as<JSFunction>();
MOZ_ASSERT(func->allowSuperProperty());
/* Load the home object */
JSObject* obj = &REGS.sp[-1].toObject();
MOZ_ASSERT(obj->is<PlainObject>() || obj->is<JSFunction>());
func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT,
ObjectValue(*obj));
REGS.sp--;
}
END_CASE(InitHomeObject)
CASE(SuperBase) {
JSFunction& superEnvFunc = REGS.sp[-1].toObject().as<JSFunction>();
MOZ_ASSERT(superEnvFunc.allowSuperProperty());
MOZ_ASSERT(superEnvFunc.baseScript()->needsHomeObject());
const Value& homeObjVal = superEnvFunc.getExtendedSlot(
FunctionExtended::METHOD_HOMEOBJECT_SLOT);
JSObject* homeObj = &homeObjVal.toObject();
JSObject* superBase = HomeObjectSuperBase(homeObj);
REGS.sp[-1].setObjectOrNull(superBase);
}
END_CASE(SuperBase)
CASE(NewTarget) {
PUSH_COPY(REGS.fp()->newTarget());
MOZ_ASSERT(REGS.sp[-1].isObject() || REGS.sp[-1].isUndefined());
}
END_CASE(NewTarget)
CASE(ImportMeta) {
JSObject* metaObject = ImportMetaOperation(cx, script);
if (!metaObject) {
goto error;
}
PUSH_OBJECT(*metaObject);
}
END_CASE(ImportMeta)
CASE(DynamicImport) {
ReservedRooted<Value> options(&rootValue0, REGS.sp[-1]);
REGS.sp--;
ReservedRooted<Value> specifier(&rootValue1);
POP_COPY_TO(specifier);
JSObject* promise =
StartDynamicModuleImport(cx, script, specifier, options);
if (!promise) goto error;
PUSH_OBJECT(*promise);
}
END_CASE(DynamicImport)
CASE(EnvCallee) {
uint8_t numHops = GET_UINT8(REGS.pc);
JSObject* env = &REGS.fp()->environmentChain()->as<EnvironmentObject>();
for (unsigned i = 0; i < numHops; i++) {
env = &env->as<EnvironmentObject>().enclosingEnvironment();
}
PUSH_OBJECT(env->as<CallObject>().callee());
}
END_CASE(EnvCallee)
CASE(SuperFun) {
JSObject* superEnvFunc = &REGS.sp[-1].toObject();
JSObject* superFun = SuperFunOperation(superEnvFunc);
REGS.sp[-1].setObjectOrNull(superFun);
}
END_CASE(SuperFun)
CASE(CheckObjCoercible) {
ReservedRooted<Value> checkVal(&rootValue0, REGS.sp[-1]);
if (checkVal.isNullOrUndefined()) {
MOZ_ALWAYS_FALSE(ThrowObjectCoercible(cx, checkVal));
goto error;
}
}
END_CASE(CheckObjCoercible)
CASE(DebugCheckSelfHosted) {
#ifdef DEBUG
ReservedRooted<Value> checkVal(&rootValue0, REGS.sp[-1]);
if (!Debug_CheckSelfHosted(cx, checkVal)) {
goto error;
}
#endif
}
END_CASE(DebugCheckSelfHosted)
CASE(IsConstructing) { PUSH_MAGIC(JS_IS_CONSTRUCTING); }
END_CASE(IsConstructing)
CASE(Inc) {
MutableHandleValue val = REGS.stackHandleAt(-1);
if (!IncOperation(cx, val, val)) {
goto error;
}
}
END_CASE(Inc)
CASE(Dec) {
MutableHandleValue val = REGS.stackHandleAt(-1);
if (!DecOperation(cx, val, val)) {
goto error;
}
}
END_CASE(Dec)
CASE(ToNumeric) {
if (!ToNumeric(cx, REGS.stackHandleAt(-1))) {
goto error;
}
}
END_CASE(ToNumeric)
CASE(BigInt) { PUSH_BIGINT(script->getBigInt(REGS.pc)); }
END_CASE(BigInt)
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 (exception, stack, true) triple for finally to indicate that we
* should rethrow the exception.
*/
ReservedRooted<Value> exception(&rootValue0);
ReservedRooted<Value> exceptionStack(&rootValue1);
if (!cx->getPendingException(&exception) ||
!cx->getPendingExceptionStack(&exceptionStack)) {
interpReturnOK = false;
goto return_continuation;
}
PUSH_COPY(exception);
PUSH_COPY(exceptionStack);
PUSH_BOOLEAN(true);
cx->clearPendingException();
}
ADVANCE_AND_DISPATCH(0);
}
MOZ_CRASH("Invalid HandleError continuation");
exit:
if (MOZ_LIKELY(!frameHalfInitialized)) {
interpReturnOK =
DebugAPI::onLeaveFrame(cx, REGS.fp(), REGS.pc, interpReturnOK);
REGS.fp()->epilogue(cx, REGS.pc);
}
gc::MaybeVerifyBarriers(cx, true);
/*
* 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::ThrowOperation(JSContext* cx, HandleValue v) {
MOZ_ASSERT(!cx->isExceptionPending());
cx->setPendingException(v, ShouldCaptureStack::Maybe);
return false;
}
bool js::ThrowWithStackOperation(JSContext* cx, HandleValue v,
HandleValue stack) {
MOZ_ASSERT(!cx->isExceptionPending());
MOZ_ASSERT(stack.isObjectOrNull());
// Use a normal throw when no stack was recorded.
if (!stack.isObject()) {
return ThrowOperation(cx, v);
}
MOZ_ASSERT(UncheckedUnwrap(&stack.toObject())->is<SavedFrame>() ||
IsDeadProxyObject(&stack.toObject()));
Rooted<SavedFrame*> stackObj(cx,
stack.toObject().maybeUnwrapIf<SavedFrame>());
cx->setPendingException(v, stackObj);
return false;
}
bool js::GetPendingExceptionStack(JSContext* cx, MutableHandleValue vp) {
MOZ_ASSERT(cx->isExceptionPending());
return cx->getPendingExceptionStack(vp);
}
bool js::GetProperty(JSContext* cx, HandleValue v, Handle<PropertyName*> 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()) {
JSObject* proto;
switch (v.type()) {
case ValueType::Double:
case ValueType::Int32:
proto = GlobalObject::getOrCreateNumberPrototype(cx, cx->global());
break;
case ValueType::Boolean:
proto = GlobalObject::getOrCreateBooleanPrototype(cx, cx->global());
break;
case ValueType::String:
proto = GlobalObject::getOrCreateStringPrototype(cx, cx->global());
break;
case ValueType::Symbol:
proto = GlobalObject::getOrCreateSymbolPrototype(cx, cx->global());
break;
case ValueType::BigInt:
proto = GlobalObject::getOrCreateBigIntPrototype(cx, cx->global());
break;
#ifdef ENABLE_RECORD_TUPLE
case ValueType::ExtendedPrimitive: {
RootedObject obj(cx, &v.toExtendedPrimitive());
RootedId id(cx, NameToId(name));
return ExtendedPrimitiveGetProperty(cx, obj, v, id, vp);
}
#endif
case ValueType::Undefined:
case ValueType::Null:
case ValueType::Magic:
case ValueType::PrivateGCThing:
case ValueType::Object:
MOZ_CRASH("unexpected type");
}
if (!proto) {
return false;
}
if (GetPropertyPure(cx, proto, NameToId(name), vp.address())) {
return true;
}
}
RootedValue receiver(cx, v);
RootedObject obj(
cx, ToObjectFromStackForPropertyAccess(cx, v, JSDVG_SEARCH_STACK, name));
if (!obj) {
return false;
}
return GetProperty(cx, obj, receiver, name, vp);
}
JSObject* js::Lambda(JSContext* cx, HandleFunction fun, HandleObject parent) {
JSFunction* clone;
if (fun->isNativeFun()) {
MOZ_ASSERT(IsAsmJSModule(fun));
clone = CloneAsmJSModuleFunction(cx, fun);
} else {
RootedObject proto(cx, fun->staticPrototype());
clone = CloneFunctionReuseScript(cx, fun, parent, proto);
}
if (!clone) {
return nullptr;
}
MOZ_ASSERT(fun->global() == clone->global());
return clone;
}
JSObject* js::BindVarOperation(JSContext* cx, JSObject* envChain) {
// Note: BindVarOperation has an unused cx argument because the JIT callVM
// machinery requires this.
return &GetVariablesObject(envChain);
}
JSObject* js::ImportMetaOperation(JSContext* cx, HandleScript script) {
RootedObject module(cx, GetModuleObjectForScript(script));
return GetOrCreateModuleMetaObject(cx, module);
}
JSObject* js::BuiltinObjectOperation(JSContext* cx, BuiltinObjectKind kind) {
return GetOrCreateBuiltinObject(cx, kind);
}
bool js::ThrowMsgOperation(JSContext* cx, const unsigned throwMsgKind) {
auto errorNum = ThrowMsgKindToErrNum(ThrowMsgKind(throwMsgKind));
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNum);
return false;
}
bool js::GetAndClearExceptionAndStack(JSContext* cx, MutableHandleValue res,
MutableHandle<SavedFrame*> stack) {
if (!cx->getPendingException(res)) {
return false;
}
stack.set(cx->getPendingExceptionStack());
cx->clearPendingException();
// Allow interrupting deeply nested exception handling.
return CheckForInterrupt(cx);
}
bool js::GetAndClearException(JSContext* cx, MutableHandleValue res) {
Rooted<SavedFrame*> stack(cx);
return GetAndClearExceptionAndStack(cx, res, &stack);
}
template <bool strict>
bool js::DelPropOperation(JSContext* cx, HandleValue val,
Handle<PropertyName*> name, bool* res) {
const int valIndex = -1;
RootedObject obj(cx,
ToObjectFromStackForPropertyAccess(cx, val, valIndex, name));
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);
}
*res = true;
} else {
*res = result.ok();
}
return true;
}
template bool js::DelPropOperation<true>(JSContext* cx, HandleValue val,
Handle<PropertyName*> name, bool* res);
template bool js::DelPropOperation<false>(JSContext* cx, HandleValue val,
Handle<PropertyName*> name,
bool* res);
template <bool strict>
bool js::DelElemOperation(JSContext* cx, HandleValue val, HandleValue index,
bool* res) {
const int valIndex = -2;
RootedObject obj(
cx, ToObjectFromStackForPropertyAccess(cx, val, valIndex, index));
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);
}
*res = true;
} else {
*res = result.ok();
}
return true;
}
template bool js::DelElemOperation<true>(JSContext*, HandleValue, HandleValue,
bool*);
template bool js::DelElemOperation<false>(JSContext*, HandleValue, HandleValue,
bool*);
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::SetObjectElementWithReceiver(JSContext* cx, HandleObject obj,
HandleValue index, HandleValue value,
HandleValue receiver, bool strict) {
RootedId id(cx);
if (!ToPropertyKey(cx, index, &id)) {
return false;
}
return SetObjectElementOperation(cx, obj, id, value, receiver, strict);
}
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::PowValues(JSContext* cx, MutableHandleValue lhs,
MutableHandleValue rhs, MutableHandleValue res) {
return PowOperation(cx, lhs, rhs, res);
}
bool js::BitNot(JSContext* cx, MutableHandleValue in, MutableHandleValue res) {
return BitNotOperation(cx, in, res);
}
bool js::BitXor(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs,
MutableHandleValue res) {
return BitXorOperation(cx, lhs, rhs, res);
}
bool js::BitOr(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs,
MutableHandleValue res) {
return BitOrOperation(cx, lhs, rhs, res);
}
bool js::BitAnd(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs,
MutableHandleValue res) {
return BitAndOperation(cx, lhs, rhs, res);
}
bool js::BitLsh(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs,
MutableHandleValue res) {
return BitLshOperation(cx, lhs, rhs, res);
}
bool js::BitRsh(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs,
MutableHandleValue res) {
return BitRshOperation(cx, lhs, rhs, res);
}
bool js::UrshValues(JSContext* cx, MutableHandleValue lhs,
MutableHandleValue rhs, MutableHandleValue res) {
return UrshOperation(cx, lhs, rhs, res);
}
bool js::LessThan(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs,
bool* res) {
return LessThanOperation(cx, lhs, rhs, res);
}
bool js::LessThanOrEqual(JSContext* cx, MutableHandleValue lhs,
MutableHandleValue rhs, bool* res) {
return LessThanOrEqualOperation(cx, lhs, rhs, res);
}
bool js::GreaterThan(JSContext* cx, MutableHandleValue lhs,
MutableHandleValue rhs, bool* res) {
return GreaterThanOperation(cx, lhs, rhs, res);
}
bool js::GreaterThanOrEqual(JSContext* cx, MutableHandleValue lhs,
MutableHandleValue rhs, bool* res) {
return GreaterThanOrEqualOperation(cx, lhs, rhs, res);
}
bool js::DeleteNameOperation(JSContext* cx, Handle<PropertyName*> name,
HandleObject scopeObj, MutableHandleValue res) {
RootedObject scope(cx), pobj(cx);
PropertyResult prop;
if (!LookupName(cx, name, scopeObj, &scope, &pobj, &prop)) {
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->as<GlobalObject>().removeFromVarNames(name);
}
}
return true;
}
bool js::ImplicitThisOperation(JSContext* cx, HandleObject scopeObj,
Handle<PropertyName*> name,
MutableHandleValue res) {
RootedObject obj(cx);
if (!LookupNameWithGlobalDefault(cx, name, scopeObj, &obj)) {
return false;
}
res.set(ComputeImplicitThis(obj));
return true;
}
unsigned js::GetInitDataPropAttrs(JSOp op) {
switch (op) {
case JSOp::InitProp:
case JSOp::InitElem:
return JSPROP_ENUMERATE;
case JSOp::InitLockedProp:
case JSOp::InitLockedElem:
return JSPROP_PERMANENT | JSPROP_READONLY;
case JSOp::InitHiddenProp:
case JSOp::InitHiddenElem:
// Non-enumerable, but writable and configurable
return 0;
default:;
}
MOZ_CRASH("Unknown data initprop");
}
static bool InitGetterSetterOperation(JSContext* cx, jsbytecode* pc,
HandleObject obj, HandleId id,
HandleObject val) {
MOZ_ASSERT(val->isCallable());
JSOp op = JSOp(*pc);
unsigned attrs = 0;
if (!IsHiddenInitOp(op)) {
attrs |= JSPROP_ENUMERATE;
}
if (op == JSOp::InitPropGetter || op == JSOp::InitElemGetter ||
op == JSOp::InitHiddenPropGetter || op == JSOp::InitHiddenElemGetter) {
return DefineAccessorProperty(cx, obj, id, val, nullptr, attrs);
}
MOZ_ASSERT(op == JSOp::InitPropSetter || op == JSOp::InitElemSetter ||
op == JSOp::InitHiddenPropSetter ||
op == JSOp::InitHiddenElemSetter);
return DefineAccessorProperty(cx, obj, id, nullptr, val, attrs);
}
bool js::InitPropGetterSetterOperation(JSContext* cx, jsbytecode* pc,
HandleObject obj,
Handle<PropertyName*> name,
HandleObject val) {
RootedId id(cx, NameToId(name));
return InitGetterSetterOperation(cx, pc, obj, id, val);
}
bool js::InitElemGetterSetterOperation(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) {
Rooted<ArrayObject*> 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 (!callee.toObject().isCallable()) {
return ReportIsNotFunction(cx, callee, 2 + constructing,
constructing ? CONSTRUCT : NO_CONSTRUCT);
}
// The object must be an array with dense elements and no holes. Baseline's
// optimized spread call stubs rely on this.
MOZ_ASSERT(IsPackedArray(aobj));
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;
}
}
}
return true;
}
static bool OptimizeArrayIteration(JSContext* cx, HandleObject obj,
bool* optimized) {
*optimized = false;
// 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
// * %ArrayIteratorPrototype%.return is not defined
// * return is nowhere on the proto chain
if (!IsPackedArray(obj)) {
return true;
}
ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx);
if (!stubChain) {
return false;
}
if (!stubChain->tryOptimizeArray(cx, obj.as<ArrayObject>(), optimized)) {
return false;
}
return true;
}
static bool OptimizeArgumentsSpreadCall(JSContext* cx, HandleObject obj,
MutableHandleValue result) {
MOZ_ASSERT(result.isUndefined());
// Optimize spread call by skipping the spread operation when the following
// conditions are met:
// * the argument is an arguments object
// * the arguments object has no deleted elements
// * arguments.length is not overridden
// * arguments[@@iterator] is not overridden
// * %ArrayIteratorPrototype%.next is not modified
if (!obj->is<ArgumentsObject>()) {
return true;
}
Handle<ArgumentsObject*> args = obj.as<ArgumentsObject>();
if (args->hasOverriddenElement() || args->hasOverriddenLength() ||
args->hasOverriddenIterator()) {
return true;
}
ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx);
if (!stubChain) {
return false;
}
bool optimized;
if (!stubChain->tryOptimizeArrayIteratorNext(cx, &optimized)) {
return false;
}
if (!optimized) {
return true;
}
auto* array = ArrayFromArgumentsObject(cx, args);
if (!array) {
return false;
}
result.setObject(*array);
return true;
}
bool js::OptimizeSpreadCall(JSContext* cx, HandleValue arg,
MutableHandleValue result) {
// This function returns |undefined| if the spread operation can't be
// optimized.
result.setUndefined();
if (!arg.isObject()) {
return true;
}
RootedObject obj(cx, &arg.toObject());
bool optimized;
if (!OptimizeArrayIteration(cx, obj, &optimized)) {
return false;
}
if (optimized) {
result.setObject(*obj);
return true;
}
if (!OptimizeArgumentsSpreadCall(cx, obj, result)) {
return false;
}
if (result.isObject()) {
return true;
}
MOZ_ASSERT(result.isUndefined());
return true;
}
bool js::OptimizeGetIterator(JSContext* cx, HandleValue arg, bool* result) {
// This function returns |false| if the iteration can't be optimized.
*result = false;
if (!arg.isObject()) {
return true;
}
RootedObject obj(cx, &arg.toObject());
bool optimized;
if (!OptimizeArrayIteration(cx, obj, &optimized)) {
return false;
}
if (optimized) {
*result = true;
return true;
}
MOZ_ASSERT(!*result);
return true;
}
ArrayObject* js::ArrayFromArgumentsObject(JSContext* cx,
Handle<ArgumentsObject*> args) {
MOZ_ASSERT(!args->hasOverriddenLength());
MOZ_ASSERT(!args->hasOverriddenElement());
uint32_t length = args->initialLength();
auto* array = NewDenseFullyAllocatedArray(cx, length);
if (!array) {
return nullptr;
}
array->setDenseInitializedLength(length);
for (uint32_t index = 0; index < length; index++) {
const Value& v = args->element(index);
array->initDenseElement(index, v);
}
return array;
}
JSObject* js::NewObjectOperation(JSContext* cx, HandleScript script,
const jsbytecode* pc) {
if (JSOp(*pc) == JSOp::NewObject) {
Rooted<SharedShape*> shape(cx, script->getShape(pc));
return PlainObject::createWithShape(cx, shape);
}
MOZ_ASSERT(JSOp(*pc) == JSOp::NewInit);
return NewPlainObject(cx);
}
JSObject* js::NewPlainObjectBaselineFallback(JSContext* cx,
Handle<SharedShape*> shape,
gc::AllocKind allocKind,
gc::AllocSite* site) {
MOZ_ASSERT(shape->getObjectClass() == &PlainObject::class_);
mozilla::Maybe<AutoRealm> ar;
if (cx->realm() != shape->realm()) {
MOZ_ASSERT(cx->compartment() == shape->compartment());
ar.emplace(cx, shape);
}
gc::Heap initialHeap = site->initialHeap();
return NativeObject::create<PlainObject>(cx, allocKind, initialHeap, shape,
site);
}
JSObject* js::NewPlainObjectOptimizedFallback(JSContext* cx,
Handle<SharedShape*> shape,
gc::AllocKind allocKind,
gc::Heap initialHeap) {
MOZ_ASSERT(shape->getObjectClass() == &PlainObject::class_);
mozilla::Maybe<AutoRealm> ar;
if (cx->realm() != shape->realm()) {
MOZ_ASSERT(cx->compartment() == shape->compartment());
ar.emplace(cx, shape);
}
gc::AllocSite* site = cx->zone()->optimizedAllocSite();
return NativeObject::create<PlainObject>(cx, allocKind, initialHeap, shape,
site);
}
ArrayObject* js::NewArrayOperation(
JSContext* cx, uint32_t length,
NewObjectKind newKind /* = GenericObject */) {
return NewDenseFullyAllocatedArray(cx, length, newKind);
}
ArrayObject* js::NewArrayObjectBaselineFallback(JSContext* cx, uint32_t length,
gc::AllocKind allocKind,
gc::AllocSite* site) {
NewObjectKind newKind =
site->initialHeap() == gc::Heap::Tenured ? TenuredObject : GenericObject;
ArrayObject* array = NewDenseFullyAllocatedArray(cx, length, newKind, site);
// It's important that we allocate an object with the alloc kind we were
// expecting so that a new arena gets allocated if the current arena for that
// kind is full.
MOZ_ASSERT_IF(array && array->isTenured(),
array->asTenured().getAllocKind() == allocKind);
return array;
}
ArrayObject* js::NewArrayObjectOptimizedFallback(JSContext* cx, uint32_t length,
gc::AllocKind allocKind,
NewObjectKind newKind) {
gc::AllocSite* site = cx->zone()->optimizedAllocSite();
ArrayObject* array = NewDenseFullyAllocatedArray(cx, length, newKind, site);
// It's important that we allocate an object with the alloc kind we were
// expecting so that a new arena gets allocated if the current arena for that
// kind is full.
MOZ_ASSERT_IF(array && array->isTenured(),
array->asTenured().getAllocKind() == allocKind);
return array;
}
void js::ReportRuntimeLexicalError(JSContext* cx, unsigned errorNumber,
HandleId id) {
MOZ_ASSERT(errorNumber == JSMSG_UNINITIALIZED_LEXICAL ||
errorNumber == JSMSG_BAD_CONST_ASSIGN);
if (UniqueChars printable =
IdToPrintableUTF8(cx, id, IdToPrintableBehavior::IdIsIdentifier)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber,
printable.get());
}
}
void js::ReportRuntimeLexicalError(JSContext* cx, unsigned errorNumber,
Handle<PropertyName*> 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::GetImport);
Rooted<PropertyName*> name(cx);
if (IsLocalOp(op)) {
name = FrameSlotName(script, pc)->asPropertyName();
} else if (IsAliasedVarOp(op)) {
name = EnvironmentCoordinateNameSlow(script, pc);
} else {
MOZ_ASSERT(IsAtomOp(op));
name = script->getName(pc);
}
ReportRuntimeLexicalError(cx, errorNumber, name);
}
void js::ReportRuntimeRedeclaration(JSContext* cx, Handle<PropertyName*> name,
const char* redeclKind) {
if (UniqueChars printable = AtomToPrintableString(cx, name)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_REDECLARED_VAR, redeclKind,
printable.get());
}
}
bool js::ThrowCheckIsObject(JSContext* cx, CheckIsObjectKind kind) {
switch (kind) {
case CheckIsObjectKind::IteratorNext:
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "next");
break;
case CheckIsObjectKind::IteratorReturn:
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "return");
break;
case CheckIsObjectKind::IteratorThrow:
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_ITER_METHOD_RETURNED_PRIMITIVE, "throw");
break;
case CheckIsObjectKind::GetIterator:
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_GET_ITER_RETURNED_PRIMITIVE);
break;
case CheckIsObjectKind::GetAsyncIterator:
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE);
break;
#ifdef ENABLE_DECORATORS
case CheckIsObjectKind::DecoratorReturn:
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_DECORATOR_INVALID_RETURN_TYPE);
break;
#endif
default:
MOZ_CRASH("Unknown kind");
}
return false;
}
bool js::ThrowUninitializedThis(JSContext* cx) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_UNINITIALIZED_THIS);
return false;
}
bool js::ThrowInitializedThis(JSContext* cx) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_REINIT_THIS);
return false;
}
bool js::ThrowObjectCoercible(JSContext* cx, HandleValue value) {
MOZ_ASSERT(value.isNullOrUndefined());
ReportIsNullOrUndefinedForPropertyAccess(cx, value, JSDVG_SEARCH_STACK);
return false;
}
bool js::SetPropertySuper(JSContext* cx, HandleValue lval, HandleValue receiver,
Handle<PropertyName*> name, HandleValue rval,
bool strict) {
MOZ_ASSERT(lval.isObjectOrNull());
RootedObject obj(cx, ToObjectFromStackForPropertyAccess(
cx, lval, JSDVG_SEARCH_STACK, name));
if (!obj) {
return false;
}
RootedId id(cx, NameToId(name));
return SetObjectElementOperation(cx, obj, id, rval, receiver, strict);
}
bool js::SetElementSuper(JSContext* cx, HandleValue lval, HandleValue receiver,
HandleValue index, HandleValue rval, bool strict) {
MOZ_ASSERT(lval.isObjectOrNull());
RootedObject obj(cx, ToObjectFromStackForPropertyAccess(
cx, lval, JSDVG_SEARCH_STACK, index));
if (!obj) {
return false;
}
return SetObjectElementWithReceiver(cx, obj, index, rval, receiver, strict);
}
bool js::LoadAliasedDebugVar(JSContext* cx, JSObject* env, jsbytecode* pc,
MutableHandleValue result) {
EnvironmentCoordinate ec(pc);
for (unsigned i = ec.hops(); i; i--) {
if (env->is<EnvironmentObject>()) {
env = &env->as<EnvironmentObject>().enclosingEnvironment();
} else {
MOZ_ASSERT(env->is<DebugEnvironmentProxy>());
env = &env->as<DebugEnvironmentProxy>().enclosingEnvironment();
}
}
EnvironmentObject& finalEnv =
env->is<EnvironmentObject>()
? env->as<EnvironmentObject>()
: env->as<DebugEnvironmentProxy>().environment();
result.set(finalEnv.aliasedBinding(ec));
return true;
}
bool js::CloseIterOperation(JSContext* cx, HandleObject iter,
CompletionKind kind) {
// Steps 1-2 are implicit.
// Step 3
RootedValue returnMethod(cx);
bool innerResult =
GetProperty(cx, iter, iter, cx->names().return_, &returnMethod);
// Step 4
RootedValue result(cx);
if (innerResult) {
// Step 4b
if (returnMethod.isNullOrUndefined()) {
return true;
}
// Step 4c
if (IsCallable(returnMethod)) {
RootedValue thisVal(cx, ObjectValue(*iter));
innerResult = Call(cx, returnMethod, thisVal, &result);
} else {
innerResult = ReportIsNotFunction(cx, returnMethod);
}
}
// Step 5
if (kind == CompletionKind::Throw) {
// If we close an iterator while unwinding for an exception,
// the initial exception takes priority over any exception thrown
// while closing the iterator.
if (cx->isExceptionPending()) {
cx->clearPendingException();
}
return true;
}
// Step 6
if (!innerResult) {
return false;
}
// Step 7
if (!result.isObject()) {
return ThrowCheckIsObject(cx, CheckIsObjectKind::IteratorReturn);
}
// Step 8
return true;
}