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
/*
* 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 <string.h>
#include "jsapi.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/StringBuilder.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/BigIntType.h"
#include "vm/BytecodeUtil.h" // JSDVG_SEARCH_STACK
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
# include "vm/ErrorObject.h"
#endif
#include "vm/EqualityOperations.h" // js::StrictlyEqual
#include "vm/GeneratorObject.h"
#include "vm/Iteration.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/Scope.h"
#include "vm/Shape.h"
#include "vm/SharedStencil.h" // GCThingIndex
#include "vm/StringType.h"
#include "vm/ThrowMsgKind.h" // ThrowMsgKind
#include "vm/TypeofEqOperand.h" // TypeofEqOperand
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
# include "vm/UsingHint.h"
#endif
#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"
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
# include "vm/DisposableRecord-inl.h"
#endif
#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;
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);
}
static bool IsNSVOLexicalEnvironment(JSObject* env) {
return env->is<LexicalEnvironmentObject>() &&
env->as<LexicalEnvironmentObject>()
.enclosingEnvironment()
.is<NonSyntacticVariablesObject>();
}
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()) {
JSObject* env = frame.environmentChain();
while (true) {
if (IsNSVOLexicalEnvironment(env) ||
env->is<GlobalLexicalEnvironmentObject>()) {
auto* obj = env->as<ExtensibleLexicalEnvironmentObject>().thisObject();
res.setObject(*obj);
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) {
JSObject* env = envChain;
while (true) {
if (env->is<ExtensibleLexicalEnvironmentObject>()) {
auto* obj = env->as<ExtensibleLexicalEnvironmentObject>().thisObject();
res.setObject(*obj);
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 GetLengthProperty(const Value& lval, MutableHandleValue vp) {
/* Optimize length accesses on strings, arrays, and arguments. */
if (lval.isString()) {
vp.setInt32(lval.toString()->length());
return true;
}
if (lval.isObject()) {
JSObject* obj = &lval.toObject();
if (obj->is<ArrayObject>()) {
vp.setNumber(obj->as<ArrayObject>().length());
return true;
}
if (obj->is<ArgumentsObject>()) {
ArgumentsObject* argsobj = &obj->as<ArgumentsObject>();
if (!argsobj->hasOverriddenLength()) {
uint32_t length = argsobj->initialLength();
MOZ_ASSERT(length < INT32_MAX);
vp.setInt32(int32_t(length));
return true;
}
}
}
return false;
}
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 (IsTypeOfNameOp(nextOp)) {
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
// 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(),
envChainArg->is<GlobalLexicalEnvironmentObject>() ||
!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(
envChain->is<GlobalLexicalEnvironmentObject>() ||
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: