Source code

Revision control

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/WrappingOperations.h"
#include <string.h>
#include "jslibmath.h"
#include "jsmath.h"
#include "jsnum.h"
#include "builtin/Array.h"
#include "builtin/Eval.h"
#include "builtin/ModuleObject.h"
#include "builtin/Promise.h"
#include "jit/AtomicOperations.h"
#include "jit/BaselineJIT.h"
#include "jit/Ion.h"
#include "jit/IonAnalysis.h"
#include "jit/Jit.h"
#include "jit/JitRuntime.h"
#include "js/CharacterEncoding.h"
#include "js/experimental/JitInfo.h" // JSJitInfo
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::CheckRecursionLimit
#include "js/friend/WindowProxy.h" // js::IsWindowProxy
#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/FunctionFlags.h" // js::FunctionFlags
#include "vm/GeneratorObject.h"
#include "vm/Instrumentation.h"
#include "vm/Iteration.h"
#include "vm/JSAtom.h"
#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/Printer.h"
#include "vm/Scope.h"
#include "vm/Shape.h"
#include "vm/SharedStencil.h" // GCThingIndex
#include "vm/StringType.h"
#include "vm/ThrowMsgKind.h" // ThrowMsgKind
#include "vm/TraceLogging.h"
#include "builtin/Boolean-inl.h"
#include "debugger/DebugAPI-inl.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/GeckoProfiler-inl.h"
#include "vm/JSAtom-inl.h"
#include "vm/JSFunction-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();
}
}
bool js::Debug_CheckSelfHosted(JSContext* cx, HandleValue fun) {
#ifndef DEBUG
MOZ_CRASH("self-hosted checks should only be done in Debug builds");
#endif
RootedObject funObj(cx, UncheckedUnwrap(&fun.toObject()));
MOZ_ASSERT(funObj->as<JSFunction>().isSelfHostedOrIntrinsic());
// This is purely to police self-hosted code. There is no actual operation.
return true;
}
static inline bool GetPropertyOperation(JSContext* cx, InterpreterFrame* fp,
HandleScript script, jsbytecode* pc,
MutableHandleValue lval,
MutableHandleValue vp) {
JSOp op = JSOp(*pc);
if (op == JSOp::Length) {
if (IsOptimizedArguments(fp, lval)) {
vp.setInt32(fp->numActualArgs());
return true;
}
if (GetLengthProperty(lval, vp)) {
return true;
}
}
RootedPropertyName name(cx, script->getName(pc));
if (name == cx->names().callee && IsOptimizedArguments(fp, lval)) {
vp.setObject(fp->callee());
return true;
}
// Copy lval, because it might alias vp.
RootedValue v(cx, lval);
return GetProperty(cx, v, name, vp);
}
static inline bool GetNameOperation(JSContext* cx, InterpreterFrame* fp,
jsbytecode* pc, MutableHandleValue vp) {
RootedObject envChain(cx, fp->environmentChain());
RootedPropertyName name(cx, fp->script()->getName(pc));
/*
* Skip along the env chain to the enclosing global object. This is
* used for GNAME opcodes where the bytecode emitter has determined a
* name access must be on the global. It also insulates us from bugs
* in the emitter: type inference will assume that GNAME opcodes are
* accessing the global object, and the inferred behavior should match
* the actual behavior even if the id could be found on the env chain
* before the global object.
*/
if (IsGlobalOp(JSOp(*pc)) && !fp->script()->hasNonSyntacticScope()) {
envChain = &cx->global()->lexicalEnvironment();
}
/* Kludge to allow (typeof foo == "undefined") tests. */
JSOp op2 = JSOp(pc[JSOpLength_GetName]);
if (op2 == 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);
RootedPropertyName name(cx, script->getName(pc));
Rooted<PropertyResult> prop(cx);
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);
}
static bool SetPropertyOperation(JSContext* cx, JSOp op, HandleValue lval,
int lvalIndex, HandleId id, HandleValue rval) {
MOZ_ASSERT(op == JSOp::SetProp || op == JSOp::StrictSetProp);
RootedObject obj(cx,
ToObjectFromStackForPropertyAccess(cx, lval, lvalIndex, id));
if (!obj) {
return false;
}
ObjectOpResult result;
return SetProperty(cx, obj, id, rval, lval, result) &&
result.checkStrictModeError(cx, obj, id, op == JSOp::StrictSetProp);
}
JSFunction* js::MakeDefaultConstructor(JSContext* cx, HandleScript script,
jsbytecode* pc, HandleObject proto) {
JSOp op = JSOp(*pc);
bool derived = op == JSOp::DerivedConstructor;
MOZ_ASSERT(derived == !!proto);
// Get class name and source offsets from bytecode.
GCThingIndex atomIndex;
uint32_t classStartOffset = 0, classEndOffset = 0;
GetClassConstructorOperands(pc, &atomIndex, &classStartOffset,
&classEndOffset);
RootedAtom className(cx, script->getAtom(atomIndex));
// The empty atom is used as a sentinel to indicate no name is assigned. This
// happens when the name is computed by a dynamic SetFunctionName.
if (className == cx->names().empty) {
className = nullptr;
}
// Locate the self-hosted script that we will use as a template.
RootedPropertyName selfHostedName(
cx, derived ? cx->names().DefaultDerivedClassConstructor
: cx->names().DefaultBaseClassConstructor);
RootedFunction sourceFun(
cx, cx->runtime()->getUnclonedSelfHostedFunction(selfHostedName.get()));
MOZ_ASSERT(sourceFun);
RootedScript sourceScript(cx, sourceFun->nonLazyScript());
// Create the new class constructor function.
RootedFunction ctor(
cx, NewScriptedFunction(cx, sourceFun->nargs(),
FunctionFlags::INTERPRETED_CLASS_CTOR, className,
proto, gc::AllocKind::FUNCTION, TenuredObject));
if (!ctor) {
return nullptr;
}
// Override the source span needs for toString. Calling toString on a class
// constructor should return the class declaration, not the source for the
// (self-hosted) constructor function.
unsigned column;
unsigned line = PCToLineNumber(script, pc, &column);
SourceExtent classExtent = SourceExtent::makeClassExtent(
classStartOffset, classEndOffset, line, column);
// Clone bytecode from template function.
MOZ_ASSERT(sourceFun->enclosingScope()->is<GlobalScope>());
RootedScope enclosingScope(cx, &cx->global()->emptyGlobalScope());
Rooted<ScriptSourceObject*> sourceObject(cx, script->sourceObject());
if (!CloneScriptIntoFunction(cx, enclosingScope, ctor, sourceScript,
sourceObject, &classExtent)) {
return nullptr;
}
RootedScript ctorScript(cx, ctor->nonLazyScript());
// Even though the script was cloned from the self-hosted template, we cannot
// delazify back to a SelfHostedLazyScript. The script is no longer marked as
// SelfHosted either.
ctorScript->clearAllowRelazify();
DebugAPI::onNewScript(cx, ctorScript);
return ctor;
}
static JSObject* SuperFunOperation(JSObject* callee) {
MOZ_ASSERT(callee->as<JSFunction>().isClassConstructor());
MOZ_ASSERT(
callee->as<JSFunction>().baseScript()->isDerivedClassConstructor());
return callee->as<JSFunction>().staticPrototype();
}
static JSObject* HomeObjectSuperBase(JSContext* cx, JSObject* homeObj) {
MOZ_ASSERT(homeObj->is<PlainObject>() || homeObj->is<JSFunction>());
if (JSObject* superBase = homeObj->staticPrototype()) {
return superBase;
}
ThrowHomeObjectNotObject(cx);
return nullptr;
}
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());
return CreateThis(cx, callee, newTarget, GenericObject, args.mutableThisv());
}
static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool Interpret(JSContext* cx,
RunState& state);
InterpreterFrame* InvokeState::pushInterpreterFrame(JSContext* cx) {
return cx->interpreterStack().pushInvokeFrame(cx, args_, construct_);
}
InterpreterFrame* ExecuteState::pushInterpreterFrame(JSContext* cx) {
return cx->interpreterStack().pushExecuteFrame(cx, script_, newTargetValue_,
envChain_, evalInFrame_);
}
InterpreterFrame* RunState::pushInterpreterFrame(JSContext* cx) {
if (isInvoke()) {
return asInvoke()->pushInterpreterFrame(cx);
}
return asExecute()->pushInterpreterFrame(cx);
}
// 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) {
if (!CheckRecursionLimit(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);
startTime = ReallyNow();
}
auto timerEnd = mozilla::MakeScopeExit([&]() {
if (measuringTime) {
mozilla::TimeDuration delta = ReallyNow() - startTime;
cx->realm()->timers.executionTime += delta;
cx->setIsMeasuringExecutionTime(false);
// JS_TELEMETRY_RUN_TIME_US reporting was done here, but is temporarily
// disabled due to the crash in 1670348.
}
});
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 = Interpret(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) {
TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
AutoTraceLog traceLog(logger, TraceLogger_Call);
if (!CheckRecursionLimit(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.
*
* Exception: (new Object(Object)) returns the callee.
*/
MOZ_ASSERT_IF((!callee->is<JSFunction>() ||
callee->as<JSFunction>().native() != obj_construct),
args.rval().isObject() && callee != &args.rval().toObject());
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) {
MOZ_ASSERT(args.length() <= ARGS_LENGTH_MAX);
MOZ_ASSERT(!cx->zone()->types.activeAnalysis);
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 (construct != CONSTRUCT && fun->isClassConstructor()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CANT_CALL_CLASS_CONSTRUCTOR);
return false;
}
if (fun->isNative()) {
MOZ_ASSERT_IF(construct, !fun->isConstructor());
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;
}
bool ok = RunScript(cx, state);
MOZ_ASSERT_IF(ok && construct, args.rval().isObject());
return ok;
}
static bool InternalCall(JSContext* cx, const AnyInvokeArgs& args,
CallReason reason = CallReason::Call) {
MOZ_ASSERT(args.array() + args.length() == args.end(),
"must pass calling arguments to a calling attempt");
if (args.thisv().isObject()) {
// We must call the thisValue hook in case we are not called from the
// interpreter, where a prior bytecode has computed an appropriate
// |this| already. But don't do that if fval is a DOM function.
HandleValue fval = args.calleev();
if (!fval.isObject() || !fval.toObject().is<JSFunction>() ||
!fval.toObject().as<JSFunction>().isNative() ||
!fval.toObject().as<JSFunction>().hasJitInfo() ||
fval.toObject()
.as<JSFunction>()
.jitInfo()
->needsOuterizedThisObject()) {
JSObject* thisObj = GetThisObject(&args.thisv().toObject());
args.mutableThisv().setObject(*thisObj);
}
}
return InternalCallOrConstruct(cx, args, NO_CONSTRUCT, reason);
}
bool js::CallFromStack(JSContext* cx, const CallArgs& args) {
return InternalCall(cx, static_cast<const AnyInvokeArgs&>(args));
}
// ES7 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93
// 7.3.12 Call.
bool js::Call(JSContext* cx, HandleValue fval, HandleValue thisv,
const AnyInvokeArgs& args, MutableHandleValue rval,
CallReason reason) {
// Explicitly qualify these methods to bypass AnyInvokeArgs's deliberate
// shadowing.
args.CallArgs::setCallee(fval);
args.CallArgs::setThis(thisv);
if (!InternalCall(cx, args, reason)) {
return false;
}
rval.set(args.rval());
return true;
}
static bool InternalConstruct(JSContext* cx, const AnyConstructArgs& args) {
MOZ_ASSERT(args.array() + args.length() + 1 == args.end(),
"must pass constructing arguments to a construction attempt");
MOZ_ASSERT(!JSFunction::class_.getConstruct());
// Callers are responsible for enforcing these preconditions.
MOZ_ASSERT(IsConstructor(args.calleev()),
"trying to construct a value that isn't a constructor");
MOZ_ASSERT(IsConstructor(args.CallArgs::newTarget()),
"provided new.target value must be a constructor");
MOZ_ASSERT(args.thisv().isMagic(JS_IS_CONSTRUCTING) ||
args.thisv().isObject());
JSObject& callee = args.callee();
if (callee.is<JSFunction>()) {
RootedFunction fun(cx, &callee.as<JSFunction>());
if (fun->isNative()) {
return CallJSNativeConstructor(cx, fun->native(), args);
}
if (!InternalCallOrConstruct(cx, args, CONSTRUCT)) {
return false;
}
MOZ_ASSERT(args.CallArgs::rval().isObject());
return true;
}
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) {
if (!StackCheckIsConstructorCalleeNewTarget(cx, args.calleev(),
args.newTarget())) {
return false;
}
return InternalConstruct(cx, static_cast<const AnyConstructArgs&>(args));
}
bool js::Construct(JSContext* cx, HandleValue fval,
const AnyConstructArgs& args, HandleValue newTarget,
MutableHandleObject objp) {
MOZ_ASSERT(args.thisv().isMagic(JS_IS_CONSTRUCTING));
// Explicitly qualify to bypass AnyConstructArgs's deliberate shadowing.
args.CallArgs::setCallee(fval);
args.CallArgs::newTarget().set(newTarget);
if (!InternalConstruct(cx, args)) {
return false;
}
MOZ_ASSERT(args.CallArgs::rval().isObject());
objp.set(&args.CallArgs::rval().toObject());
return true;
}
bool js::InternalConstructWithProvidedThis(JSContext* cx, HandleValue fval,
HandleValue thisv,
const AnyConstructArgs& args,
HandleValue newTarget,
MutableHandleValue rval) {
args.CallArgs::setCallee(fval);
MOZ_ASSERT(thisv.isObject());
args.CallArgs::setThis(thisv);
args.CallArgs::newTarget().set(newTarget);
if (!InternalConstruct(cx, args)) {
return false;
}
rval.set(args.CallArgs::rval());
return true;
}
bool js::CallGetter(JSContext* cx, HandleValue thisv, HandleValue getter,
MutableHandleValue rval) {
// Invoke could result in another try to get or set the same id again, see
if (!CheckRecursionLimit(cx)) {
return false;
}
FixedInvokeArgs<0> args(cx);
return Call(cx, getter, thisv, args, rval, CallReason::Getter);
}
bool js::CallSetter(JSContext* cx, HandleValue thisv, HandleValue setter,
HandleValue v) {
if (!CheckRecursionLimit(cx)) {
return false;
}
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, HandleValue newTargetValue,
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, newTargetValue, 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, NullHandleValue,
NullFramePtr() /* evalInFrame */, rval);
}
/*
* ES6 (4-25-16) 12.10.4 InstanceofOperator
*/
extern bool JS::InstanceofOperator(JSContext* cx, HandleObject obj,
HandleValue v, bool* bp) {
/* Step 1. is handled by caller. */
/* Step 2. */
RootedValue hasInstance(cx);
RootedId id(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().hasInstance));
if (!GetProperty(cx, obj, obj, id, &hasInstance)) {
return false;
}
if (!hasInstance.isNullOrUndefined()) {
if (!IsCallable(hasInstance)) {
return ReportIsNotFunction(cx, hasInstance);
}
/* Step 3. */
RootedValue rval(cx);
if (!Call(cx, hasInstance, obj, v, &rval)) {
return false;
}
*bp = ToBoolean(rval);
return true;
}
/* Step 4. */
if (!obj->isCallable()) {
RootedValue val(cx, ObjectValue(*obj));
return ReportIsNotFunction(cx, val);
}
/* Step 5. */
return OrdinaryHasInstance(cx, obj, v, bp);
}
bool js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp) {
const JSClass* clasp = obj->getClass();
RootedValue local(cx, v);
if (JSHasInstanceOp hasInstance = clasp->getHasInstance()) {
return hasInstance(cx, obj, &local, bp);
}
return JS::InstanceofOperator(cx, obj, local, bp);
}
JSType js::TypeOfObject(JSObject* obj) {
if (EmulatesUndefined(obj)) {
return JSTYPE_UNDEFINED;
}
if (obj->isCallable()) {
return JSTYPE_FUNCTION;
}
return JSTYPE_OBJECT;
}
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());
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 NewObjectWithGivenProto<PlainObject>(cx, proto);
}
JSObject* js::FunWithProtoOperation(JSContext* cx, HandleFunction fun,
HandleObject parent, HandleObject proto) {
return CloneFunctionObjectIfNotSingleton(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<LexicalEnvironmentObject>();
}
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;
}
RootedScope scope(cx, ei.initialFrame().script()->innermostScope(pc));
#ifdef DEBUG
// A frame's environment chain cannot be unwound to anything enclosing the
// body scope of a script. This includes the parameter defaults
// environment and the decl env object. These environments, once pushed
// onto the environment chain, are expected to be there for the duration
// of the frame.
//
// Attempting to unwind to the parameter defaults code in a script is a
// bug; that section of code has no try-catch blocks.
JSScript* script = ei.initialFrame().script();
for (uint32_t i = 0; i < script->bodyScopeIndex(); i++) {
MOZ_ASSERT(scope != script->getScope(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();
}
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)
#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_IfNe == JSOpLength_IfEq);
static_assert(uint8_t(JSOp::IfNe) == uint8_t(JSOp::IfEq) + 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();
}
static MOZ_ALWAYS_INLINE bool AddOperation(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
MutableHandleValue res) {
if (lhs.isInt32() && rhs.isInt32()) {
int32_t l = lhs.toInt32(), r = rhs.toInt32();
int32_t t;
if (MOZ_LIKELY(SafeAdd(l, r, &t))) {
res.setInt32(t);
return true;
}
}
if (!ToPrimitive(cx, lhs)) {
return false;
}
if (!ToPrimitive(cx, rhs)) {
return false;
}
bool lIsString = lhs.isString();
bool rIsString = rhs.isString();
if (lIsString || rIsString) {
JSString* lstr;
if (lIsString) {
lstr = lhs.toString();
} else {
lstr = ToString<CanGC>(cx, lhs);
if (!lstr) {
return false;
}
}
JSString* rstr;
if (rIsString) {
rstr = rhs.toString();
} else {
// Save/restore lstr in case of GC activity under ToString.
lhs.setString(lstr);
rstr = ToString<CanGC>(cx, rhs);
if (!rstr) {
return false;
}
lstr = lhs.toString();
}
JSString* str = ConcatStrings<NoGC>(cx, lstr, rstr);
if (!str) {
RootedString nlstr(cx, lstr), nrstr(cx, rstr);
str = ConcatStrings<CanGC>(cx, nlstr, nrstr);
if (!str) {
return false;
}
}
res.setString(str);
return true;
}
if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
return false;
}
if (lhs.isBigInt() || rhs.isBigInt()) {
return BigInt::addValue(cx, lhs, rhs, res);
}
res.setNumber(lhs.toNumber() + rhs.toNumber());
return true;
}
static MOZ_ALWAYS_INLINE bool SubOperation(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
MutableHandleValue res) {
if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
return false;
}
if (lhs.isBigInt() || rhs.isBigInt()) {
return BigInt::subValue(cx, lhs, rhs, res);
}
res.setNumber(lhs.toNumber() - rhs.toNumber());
return true;
}
static MOZ_ALWAYS_INLINE bool MulOperation(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
MutableHandleValue res) {
if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
return false;
}
if (lhs.isBigInt() || rhs.isBigInt()) {
return BigInt::mulValue(cx, lhs, rhs, res);
}
res.setNumber(lhs.toNumber() * rhs.toNumber());
return true;
}
static MOZ_ALWAYS_INLINE bool DivOperation(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
MutableHandleValue res) {
if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
return false;
}
if (lhs.isBigInt() || rhs.isBigInt()) {
return BigInt::divValue(cx, lhs, rhs, res);
}
res.setNumber(NumberDiv(lhs.toNumber(), rhs.toNumber()));
return true;
}
static MOZ_ALWAYS_INLINE bool ModOperation(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
MutableHandleValue res) {
int32_t l, r;
if (lhs.isInt32() && rhs.isInt32() && (l = lhs.toInt32()) >= 0 &&
(r = rhs.toInt32()) > 0) {
int32_t mod = l % r;
res.setInt32(mod);
return true;
}
if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
return false;
}
if (lhs.isBigInt() || rhs.isBigInt()) {
return BigInt::modValue(cx, lhs, rhs, res);
}
res.setNumber(NumberMod(lhs.toNumber(), rhs.toNumber()));
return true;
}
static MOZ_ALWAYS_INLINE bool PowOperation(JSContext* cx,
MutableHandleValue lhs,
MutableHandleValue rhs,
MutableHandleValue res) {
if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) {
return false;
}
if (lhs.isBigInt() || rhs.isBigInt()) {
return BigInt::powValue(cx, lhs, rhs, res);
}
res.setNumber(ecmaPow(lhs.toNumber(), rhs.toNumber()));
return true;
}
static MOZ_ALWAYS_INLINE bool BitNotOperation(JSContext* cx,
MutableHandleValue in,
MutableHandleValue out) {
if (!ToInt32OrBigInt(cx, in)) {
return false;
}
if (in.isBigInt()) {
return BigInt::