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/. */
#include "vm/EnvironmentObject-inl.h"
#include "mozilla/Maybe.h"
#include "builtin/Array.h"
#include "builtin/ModuleObject.h"
#include "js/EnvironmentChain.h" // JS::EnvironmentChain
#include "js/Exception.h"
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy
#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_HasProperty, JS_HasPropertyById
#include "vm/ArgumentsObject.h"
#include "vm/BytecodeIterator.h"
#include "vm/BytecodeLocation.h"
#include "vm/GeneratorObject.h" // js::GetGeneratorObjectForEnvironment
#include "vm/GlobalObject.h"
#include "vm/JSObject.h"
#include "vm/ProxyObject.h"
#include "vm/Realm.h"
#include "vm/Scope.h"
#include "vm/Shape.h"
#include "wasm/WasmDebug.h"
#include "wasm/WasmDebugFrame.h"
#include "wasm/WasmInstance.h"
#include "gc/Marking-inl.h"
#include "gc/StableCellHasher-inl.h"
#include "vm/BytecodeIterator-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
/*****************************************************************************/
/*
* Return a shape representing the static scope containing the variable
* accessed by the ALIASEDVAR op at 'pc'.
*/
static SharedShape* EnvironmentCoordinateToEnvironmentShape(JSScript* script,
jsbytecode* pc) {
MOZ_ASSERT(JOF_OPTYPE(JSOp(*pc)) == JOF_ENVCOORD);
ScopeIter si(script->innermostScope(pc));
uint32_t hops = EnvironmentCoordinate(pc).hops();
while (true) {
MOZ_ASSERT(!si.done());
if (si.hasSyntacticEnvironment()) {
if (!hops) {
break;
}
hops--;
}
si++;
}
return si.environmentShape();
}
PropertyName* js::EnvironmentCoordinateNameSlow(JSScript* script,
jsbytecode* pc) {
SharedShape* shape = EnvironmentCoordinateToEnvironmentShape(script, pc);
EnvironmentCoordinate ec(pc);
SharedShapePropertyIter<NoGC> iter(shape);
while (iter->slot() != ec.slot()) {
iter++;
}
jsid id = iter->key();
/* Beware nameless destructuring formal. */
if (!id.isAtom()) {
return script->runtimeFromAnyThread()->commonNames->empty_;
}
return id.toAtom()->asPropertyName();
}
/*****************************************************************************/
template <typename T>
static T* CreateEnvironmentObject(JSContext* cx, Handle<SharedShape*> shape,
gc::Heap heap) {
static_assert(std::is_base_of_v<EnvironmentObject, T>,
"T must be an EnvironmentObject");
// All environment objects can be background-finalized.
gc::AllocKind allocKind = gc::GetGCObjectKind(shape->numFixedSlots());
MOZ_ASSERT(CanChangeToBackgroundAllocKind(allocKind, &T::class_));
allocKind = gc::ForegroundToBackgroundAllocKind(allocKind);
return NativeObject::create<T>(cx, allocKind, heap, shape);
}
// Helper function for simple environment objects that don't need the overloads
// above.
template <typename T>
static T* CreateEnvironmentObject(JSContext* cx, Handle<SharedShape*> shape,
NewObjectKind newKind = GenericObject) {
gc::Heap heap = GetInitialHeap(newKind, &T::class_);
return CreateEnvironmentObject<T>(cx, shape, heap);
}
CallObject* CallObject::createWithShape(JSContext* cx,
Handle<SharedShape*> shape) {
return CreateEnvironmentObject<CallObject>(cx, shape);
}
/*
* Create a CallObject for a JSScript that is not initialized to any particular
* callsite. This object can either be initialized (with an enclosing scope and
* callee) or used as a template for jit compilation.
*/
CallObject* CallObject::create(JSContext* cx, HandleScript script,
HandleObject enclosing, gc::Heap heap) {
Rooted<SharedShape*> shape(
cx, script->bodyScope()->as<FunctionScope>().environmentShape());
MOZ_ASSERT(shape->getObjectClass() == &class_);
// The JITs assume the result is nursery allocated unless we collected the
// nursery, so don't change |heap| here.
auto* callObj = CreateEnvironmentObject<CallObject>(cx, shape, heap);
if (!callObj) {
return nullptr;
}
if (enclosing) {
callObj->initEnclosingEnvironment(enclosing);
}
return callObj;
}
CallObject* CallObject::createTemplateObject(JSContext* cx, HandleScript script,
HandleObject enclosing) {
return create(cx, script, enclosing, gc::Heap::Tenured);
}
CallObject* CallObject::create(JSContext* cx, AbstractFramePtr frame) {
MOZ_ASSERT(frame.isFunctionFrame());
cx->check(frame);
RootedObject envChain(cx, frame.environmentChain());
RootedFunction callee(cx, frame.callee());
RootedScript script(cx, callee->nonLazyScript());
gc::Heap heap = gc::Heap::Default;
CallObject* callobj = create(cx, script, envChain, heap);
if (!callobj) {
return nullptr;
}
callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee));
return callobj;
}
template <class EnvT>
EnvT* FindEnclosingEnv(JSObject* env) {
for (;;) {
if (env->is<EnvT>()) {
break;
} else if (env->is<EnvironmentObject>()) {
env = &env->as<EnvironmentObject>().enclosingEnvironment();
} else if (env->is<DebugEnvironmentProxy>()) {
EnvironmentObject& unwrapped =
env->as<DebugEnvironmentProxy>().environment();
if (unwrapped.is<EnvT>()) {
env = &unwrapped;
break;
}
env = &env->as<DebugEnvironmentProxy>().enclosingEnvironment();
} else {
MOZ_ASSERT(env->is<GlobalObject>());
return nullptr;
}
}
return &env->as<EnvT>();
}
CallObject* CallObject::find(JSObject* env) {
return FindEnclosingEnv<CallObject>(env);
}
ModuleEnvironmentObject* ModuleEnvironmentObject::find(JSObject* env) {
return FindEnclosingEnv<ModuleEnvironmentObject>(env);
}
CallObject* CallObject::createHollowForDebug(JSContext* cx,
HandleFunction callee) {
MOZ_ASSERT(!callee->needsCallObject());
RootedScript script(cx, callee->nonLazyScript());
Rooted<FunctionScope*> scope(cx, &script->bodyScope()->as<FunctionScope>());
Rooted<SharedShape*> shape(cx, EmptyEnvironmentShape<CallObject>(cx));
if (!shape) {
return nullptr;
}
Rooted<CallObject*> callobj(cx, createWithShape(cx, shape));
if (!callobj) {
return nullptr;
}
// This environment's enclosing link is never used: the
// DebugEnvironmentProxy that refers to this scope carries its own
// enclosing link, which is what Debugger uses to construct the tree of
// Debugger.Environment objects.
callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
callobj->initFixedSlot(CALLEE_SLOT, ObjectValue(*callee));
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(script)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, callobj, id, optimizedOut)) {
return nullptr;
}
}
return callobj;
}
const JSClass CallObject::class_ = {
"Call",
JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS),
};
/*****************************************************************************/
/* static */
VarEnvironmentObject* VarEnvironmentObject::createInternal(
JSContext* cx, Handle<SharedShape*> shape, HandleObject enclosing,
gc::Heap heap) {
MOZ_ASSERT(shape->getObjectClass() == &class_);
auto* env = CreateEnvironmentObject<VarEnvironmentObject>(cx, shape, heap);
if (!env) {
return nullptr;
}
MOZ_ASSERT(!env->inDictionaryMode());
if (enclosing) {
env->initEnclosingEnvironment(enclosing);
}
return env;
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::create(JSContext* cx,
Handle<Scope*> scope,
HandleObject enclosing,
gc::Heap heap) {
MOZ_ASSERT(scope->is<EvalScope>() || scope->is<VarScope>());
Rooted<SharedShape*> shape(cx, scope->environmentShape());
auto* env = createInternal(cx, shape, enclosing, heap);
if (!env) {
return nullptr;
}
env->initScope(scope);
return env;
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::createForFrame(
JSContext* cx, Handle<Scope*> scope, AbstractFramePtr frame) {
#ifdef DEBUG
if (frame.isEvalFrame()) {
MOZ_ASSERT(scope->is<EvalScope>() && scope == frame.script()->bodyScope());
MOZ_ASSERT_IF(frame.isInterpreterFrame(),
cx->interpreterFrame() == frame.asInterpreterFrame());
MOZ_ASSERT_IF(frame.isInterpreterFrame(),
cx->interpreterRegs().pc == frame.script()->code());
} else {
MOZ_ASSERT(frame.environmentChain());
MOZ_ASSERT_IF(
frame.callee()->needsCallObject(),
&frame.environmentChain()->as<CallObject>().callee() == frame.callee());
}
#endif
RootedObject envChain(cx, frame.environmentChain());
return create(cx, scope, envChain, gc::Heap::Default);
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::createHollowForDebug(
JSContext* cx, Handle<Scope*> scope) {
MOZ_ASSERT(scope->is<VarScope>() || scope->kind() == ScopeKind::StrictEval);
MOZ_ASSERT(!scope->hasEnvironment());
Rooted<SharedShape*> shape(cx,
EmptyEnvironmentShape<VarEnvironmentObject>(cx));
if (!shape) {
return nullptr;
}
// This environment's enclosing link is never used: the
// DebugEnvironmentProxy that refers to this scope carries its own
// enclosing link, which is what Debugger uses to construct the tree of
// Debugger.Environment objects.
RootedObject enclosingEnv(cx, &cx->global()->lexicalEnvironment());
Rooted<VarEnvironmentObject*> env(
cx, createInternal(cx, shape, enclosingEnv, gc::Heap::Default));
if (!env) {
return nullptr;
}
RootedValue optimizedOut(cx, MagicValue(JS_OPTIMIZED_OUT));
RootedId id(cx);
for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
id = NameToId(bi.name()->asPropertyName());
if (!SetProperty(cx, env, id, optimizedOut)) {
return nullptr;
}
}
env->initScope(scope);
return env;
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::createTemplateObject(
JSContext* cx, Handle<VarScope*> scope) {
return create(cx, scope, nullptr, gc::Heap::Tenured);
}
/* static */
VarEnvironmentObject* VarEnvironmentObject::createWithoutEnclosing(
JSContext* cx, Handle<VarScope*> scope) {
return create(cx, scope, nullptr, gc::Heap::Default);
}
const JSClass VarEnvironmentObject::class_ = {
"Var",
JSCLASS_HAS_RESERVED_SLOTS(VarEnvironmentObject::RESERVED_SLOTS),
};
/*****************************************************************************/
const ObjectOps ModuleEnvironmentObject::objectOps_ = {
ModuleEnvironmentObject::lookupProperty, // lookupProperty
nullptr, // defineProperty
ModuleEnvironmentObject::hasProperty, // hasProperty
ModuleEnvironmentObject::getProperty, // getProperty
ModuleEnvironmentObject::setProperty, // setProperty
ModuleEnvironmentObject::
getOwnPropertyDescriptor, // getOwnPropertyDescriptor
ModuleEnvironmentObject::deleteProperty, // deleteProperty
nullptr, // getElements
nullptr, // funToString
};
const JSClassOps ModuleEnvironmentObject::classOps_ = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
ModuleEnvironmentObject::newEnumerate, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
nullptr, // finalize
nullptr, // call
nullptr, // construct
nullptr, // trace
};
const JSClass ModuleEnvironmentObject::class_ = {
"ModuleEnvironmentObject",
JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS),
&ModuleEnvironmentObject::classOps_,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
&ModuleEnvironmentObject::objectOps_,
};
/* static */
ModuleEnvironmentObject* ModuleEnvironmentObject::create(
JSContext* cx, Handle<ModuleObject*> module) {
RootedScript script(cx, module->script());
Rooted<SharedShape*> shape(
cx, script->bodyScope()->as<ModuleScope>().environmentShape());
MOZ_ASSERT(shape->getObjectClass() == &class_);
Rooted<ModuleEnvironmentObject*> env(
cx, CreateEnvironmentObject<ModuleEnvironmentObject>(cx, shape,
TenuredObject));
if (!env) {
return nullptr;
}
env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));
// Initialize this early so that we can manipulate the env object without
// causing assertions.
env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
// Initialize all lexical bindings and imports as uninitialized. Imports
// get uninitialized because they have a special TDZ for cyclic imports.
for (BindingIter bi(script); bi; bi++) {
BindingLocation loc = bi.location();
if (loc.kind() == BindingLocation::Kind::Environment &&
BindingKindIsLexical(bi.kind())) {
env->initSlot(loc.slot(), MagicValue(JS_UNINITIALIZED_LEXICAL));
}
}
// It is not be possible to add or remove bindings from a module environment
// after this point as module code is always strict.
#ifdef DEBUG
for (ShapePropertyIter<NoGC> iter(env->shape()); !iter.done(); iter++) {
MOZ_ASSERT(!iter->configurable());
}
MOZ_ASSERT(env->hasFlag(ObjectFlag::NotExtensible));
MOZ_ASSERT(!env->inDictionaryMode());
#endif
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
env->initSlot(ModuleEnvironmentObject::DISPOSABLE_RESOURCE_STACK_SLOT,
UndefinedValue());
#endif
return env;
}
#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT
static ArrayObject* initialiseAndSetDisposeCapabilityHelper(
JSContext* cx, JS::Handle<EnvironmentObject*> env, uint32_t slot) {
JS::Value slotData = env->getReservedSlot(slot);
ArrayObject* disposablesList = nullptr;
if (slotData.isUndefined()) {
disposablesList = NewDenseEmptyArray(cx);
if (!disposablesList) {
return nullptr;
}
env->setReservedSlot(slot, ObjectValue(*disposablesList));
} else {
disposablesList = &slotData.toObject().as<ArrayObject>();
}
return disposablesList;
}
ArrayObject* DisposableEnvironmentObject::getOrCreateDisposeCapability(
JSContext* cx) {
Rooted<DisposableEnvironmentObject*> env(cx, this);
return initialiseAndSetDisposeCapabilityHelper(
cx, env, DISPOSABLE_RESOURCE_STACK_SLOT);
}
// TODO: The get & clear disposables function can be merged. (bug 1907736)
JS::Value DisposableEnvironmentObject::getDisposables() {
return getReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT);
}
void DisposableEnvironmentObject::clearDisposables() {
setReservedSlot(DISPOSABLE_RESOURCE_STACK_SLOT, UndefinedValue());
}
#endif
/* static */
ModuleEnvironmentObject* ModuleEnvironmentObject::createSynthetic(
JSContext* cx, Handle<ModuleObject*> module) {
Rooted<SharedShape*> shape(cx,
CreateEnvironmentShapeForSyntheticModule(
cx, &class_, JSSLOT_FREE(&class_), module));
if (!shape) {
return nullptr;
}
MOZ_ASSERT(shape->getObjectClass() == &class_);
Rooted<ModuleEnvironmentObject*> env(
cx, CreateEnvironmentObject<ModuleEnvironmentObject>(cx, shape,
TenuredObject));
if (!env) {
return nullptr;
}
env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));
// Initialize this early so that we can manipulate the env object without
// causing assertions.
env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
// It is not be possible to add or remove bindings from a module environment
// after this point as module code is always strict.
#ifdef DEBUG
for (ShapePropertyIter<NoGC> iter(env->shape()); !iter.done(); iter++) {
MOZ_ASSERT(!iter->configurable());
}
MOZ_ASSERT(env->hasFlag(ObjectFlag::NotExtensible));
MOZ_ASSERT(!env->inDictionaryMode());
#endif
return env;
}
ModuleObject& ModuleEnvironmentObject::module() const {
return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>();
}
IndirectBindingMap& ModuleEnvironmentObject::importBindings() const {
return module().importBindings();
}
bool ModuleEnvironmentObject::createImportBinding(JSContext* cx,
Handle<JSAtom*> importName,
Handle<ModuleObject*> module,
Handle<JSAtom*> localName) {
RootedId importNameId(cx, AtomToId(importName));
RootedId localNameId(cx, AtomToId(localName));
Rooted<ModuleEnvironmentObject*> env(cx, &module->initialEnvironment());
if (!importBindings().put(cx, importNameId, env, localNameId)) {
return false;
}
return true;
}
bool ModuleEnvironmentObject::hasImportBinding(Handle<PropertyName*> name) {
return importBindings().has(NameToId(name));
}
bool ModuleEnvironmentObject::lookupImport(
jsid name, ModuleEnvironmentObject** envOut,
mozilla::Maybe<PropertyInfo>* propOut) {
return importBindings().lookup(name, envOut, propOut);
}
/* static */
bool ModuleEnvironmentObject::lookupProperty(JSContext* cx, HandleObject obj,
HandleId id,
MutableHandleObject objp,
PropertyResult* propp) {
const IndirectBindingMap& bindings =
obj->as<ModuleEnvironmentObject>().importBindings();
mozilla::Maybe<PropertyInfo> propInfo;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &propInfo)) {
objp.set(env);
propp->setNativeProperty(*propInfo);
return true;
}
Rooted<NativeObject*> target(cx, &obj->as<NativeObject>());
if (!NativeLookupOwnProperty<CanGC>(cx, target, id, propp)) {
return false;
}
objp.set(obj);
return true;
}
/* static */
bool ModuleEnvironmentObject::hasProperty(JSContext* cx, HandleObject obj,
HandleId id, bool* foundp) {
if (obj->as<ModuleEnvironmentObject>().importBindings().has(id)) {
*foundp = true;
return true;
}
Rooted<NativeObject*> self(cx, &obj->as<NativeObject>());
return NativeHasProperty(cx, self, id, foundp);
}
/* static */
bool ModuleEnvironmentObject::getProperty(JSContext* cx, HandleObject obj,
HandleValue receiver, HandleId id,
MutableHandleValue vp) {
const IndirectBindingMap& bindings =
obj->as<ModuleEnvironmentObject>().importBindings();
mozilla::Maybe<PropertyInfo> prop;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &prop)) {
vp.set(env->getSlot(prop->slot()));
return true;
}
Rooted<NativeObject*> self(cx, &obj->as<NativeObject>());
return NativeGetProperty(cx, self, receiver, id, vp);
}
/* static */
bool ModuleEnvironmentObject::setProperty(JSContext* cx, HandleObject obj,
HandleId id, HandleValue v,
HandleValue receiver,
JS::ObjectOpResult& result) {
Rooted<ModuleEnvironmentObject*> self(cx,
&obj->as<ModuleEnvironmentObject>());
if (self->importBindings().has(id)) {
return result.failReadOnly();
}
return NativeSetProperty<Qualified>(cx, self, id, v, receiver, result);
}
/* static */
bool ModuleEnvironmentObject::getOwnPropertyDescriptor(
JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
const IndirectBindingMap& bindings =
obj->as<ModuleEnvironmentObject>().importBindings();
mozilla::Maybe<PropertyInfo> prop;
ModuleEnvironmentObject* env;
if (bindings.lookup(id, &env, &prop)) {
desc.set(mozilla::Some(PropertyDescriptor::Data(
env->getSlot(prop->slot()),
{JS::PropertyAttribute::Enumerable, JS::PropertyAttribute::Writable})));
return true;
}
Rooted<NativeObject*> self(cx, &obj->as<NativeObject>());
return NativeGetOwnPropertyDescriptor(cx, self, id, desc);
}
/* static */
bool ModuleEnvironmentObject::deleteProperty(JSContext* cx, HandleObject obj,
HandleId id,
ObjectOpResult& result) {
return result.failCantDelete();
}
/* static */
bool ModuleEnvironmentObject::newEnumerate(JSContext* cx, HandleObject obj,
MutableHandleIdVector properties,
bool enumerableOnly) {
Rooted<ModuleEnvironmentObject*> self(cx,
&obj->as<ModuleEnvironmentObject>());
const IndirectBindingMap& bs(self->importBindings());
MOZ_ASSERT(properties.length() == 0);
size_t count = bs.count() + self->slotSpan() - RESERVED_SLOTS;
if (!properties.reserve(count)) {
ReportOutOfMemory(cx);
return false;
}
bs.forEachExportedName([&](jsid name) { properties.infallibleAppend(name); });
for (ShapePropertyIter<NoGC> iter(self->shape()); !iter.done(); iter++) {
properties.infallibleAppend(iter->key());
}
MOZ_ASSERT(properties.length() == count);
return true;
}
/*****************************************************************************/
const JSClass WasmInstanceEnvironmentObject::class_ = {
"WasmInstance",
JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceEnvironmentObject::RESERVED_SLOTS),
};
/* static */
WasmInstanceEnvironmentObject*
WasmInstanceEnvironmentObject::createHollowForDebug(
JSContext* cx, Handle<WasmInstanceScope*> scope) {
Rooted<SharedShape*> shape(
cx, EmptyEnvironmentShape<WasmInstanceEnvironmentObject>(cx));
if (!shape) {
return nullptr;
}
auto* env = CreateEnvironmentObject<WasmInstanceEnvironmentObject>(cx, shape);
if (!env) {
return nullptr;
}
env->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
env->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
return env;
}
/*****************************************************************************/
const JSClass WasmFunctionCallObject::class_ = {
"WasmCall",
JSCLASS_HAS_RESERVED_SLOTS(WasmFunctionCallObject::RESERVED_SLOTS),
};
/* static */
WasmFunctionCallObject* WasmFunctionCallObject::createHollowForDebug(
JSContext* cx, HandleObject enclosing, Handle<WasmFunctionScope*> scope) {
Rooted<SharedShape*> shape(cx,
EmptyEnvironmentShape<WasmFunctionCallObject>(cx));
if (!shape) {
return nullptr;
}
auto* callobj = CreateEnvironmentObject<WasmFunctionCallObject>(cx, shape);
if (!callobj) {
return nullptr;
}
callobj->initEnclosingEnvironment(enclosing);
callobj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
return callobj;
}
/*****************************************************************************/
JSObject* js::GetThisObject(JSObject* obj) {
// Use the WindowProxy if the global is a Window, as Window must never be
// exposed to script.
if (obj->is<GlobalObject>()) {
return ToWindowProxyIfWindow(obj);
}
// We should not expose any environments except NSVOs to script. The NSVO is
// pretending to be the global object in this case.
MOZ_ASSERT_IF(obj->is<EnvironmentObject>(),
obj->is<NonSyntacticVariablesObject>());
return obj;
}
WithEnvironmentObject* WithEnvironmentObject::create(
JSContext* cx, HandleObject object, HandleObject enclosing,
Handle<WithScope*> scope, JS::SupportUnscopables supportUnscopables) {
Rooted<SharedShape*> shape(cx,
EmptyEnvironmentShape<WithEnvironmentObject>(cx));
if (!shape) {
return nullptr;
}
auto* obj = CreateEnvironmentObject<WithEnvironmentObject>(cx, shape);
if (!obj) {
return nullptr;
}
JSObject* thisObj = GetThisObject(object);
obj->initEnclosingEnvironment(enclosing);
obj->initReservedSlot(OBJECT_SLOT, ObjectValue(*object));
obj->initReservedSlot(THIS_SLOT, ObjectValue(*thisObj));
if (scope) {
MOZ_ASSERT(supportUnscopables == JS::SupportUnscopables::Yes,
"with-statements must support Symbol.unscopables");
obj->initReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT,
PrivateGCThingValue(scope));
} else {
Value v = BooleanValue(supportUnscopables == JS::SupportUnscopables::Yes);
obj->initReservedSlot(SCOPE_OR_SUPPORT_UNSCOPABLES_SLOT, v);
}
return obj;
}
WithEnvironmentObject* WithEnvironmentObject::createNonSyntactic(
JSContext* cx, HandleObject object, HandleObject enclosing,
JS::SupportUnscopables supportUnscopables) {
return create(cx, object, enclosing, nullptr, supportUnscopables);
}
static inline bool IsUnscopableDotName(JSContext* cx, HandleId id) {
return id.isAtom(cx->names().dot_this_) ||
id.isAtom(cx->names().dot_newTarget_);
}
#ifdef DEBUG
static bool IsInternalDotName(JSContext* cx, HandleId id) {
return id.isAtom(cx->names().dot_this_) ||
id.isAtom(cx->names().dot_generator_) ||
id.isAtom(cx->names().dot_initializers_) ||
id.isAtom(cx->names().dot_fieldKeys_) ||
id.isAtom(cx->names().dot_staticInitializers_) ||
id.isAtom(cx->names().dot_staticFieldKeys_) ||
id.isAtom(cx->names().dot_args_) ||
id.isAtom(cx->names().dot_newTarget_) ||
id.isAtom(cx->names().star_namespace_star_);
}
#endif
/* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */
static bool CheckUnscopables(JSContext* cx, HandleObject obj, HandleId id,
bool* scopable) {
RootedId unscopablesId(
cx, PropertyKey::Symbol(cx->wellKnownSymbols().unscopables));
RootedValue v(cx);
if (!GetProperty(cx, obj, obj, unscopablesId, &v)) {
return false;
}
if (v.isObject()) {
RootedObject unscopablesObj(cx, &v.toObject());
if (!GetProperty(cx, unscopablesObj, unscopablesObj, id, &v)) {
return false;
}
*scopable = !ToBoolean(v);
} else {
*scopable = true;
}
return true;
}
static bool with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleObject objp,
PropertyResult* propp) {
// SpiderMonkey-specific: consider the internal '.this' and '.newTarget' names
// to be unscopable.
if (IsUnscopableDotName(cx, id)) {
objp.set(nullptr);
propp->setNotFound();
return true;
}
// Other internal dot-names shouldn't even end up in with-environments.
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
if (!LookupProperty(cx, actual, id, objp, propp)) {
return false;
}
if (propp->isFound()) {
bool scopable = true;
if (obj->as<WithEnvironmentObject>().supportUnscopables() &&
!CheckUnscopables(cx, actual, id, &scopable)) {
return false;
}
if (!scopable) {
objp.set(nullptr);
propp->setNotFound();
}
}
return true;
}
static bool with_DefineProperty(JSContext* cx, HandleObject obj, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
return DefineProperty(cx, actual, id, desc, result);
}
static bool with_HasProperty(JSContext* cx, HandleObject obj, HandleId id,
bool* foundp) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
// ES 8.1.1.2.1 step 3-5.
if (!HasProperty(cx, actual, id, foundp)) {
return false;
}
if (!*foundp || !obj->as<WithEnvironmentObject>().supportUnscopables()) {
return true;
}
// Steps 7-10. (Step 6 is a no-op.)
return CheckUnscopables(cx, actual, id, foundp);
}
static bool with_GetProperty(JSContext* cx, HandleObject obj,
HandleValue receiver, HandleId id,
MutableHandleValue vp) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
RootedValue actualReceiver(cx, receiver);
if (receiver.isObject() && &receiver.toObject() == obj) {
actualReceiver.setObject(*actual);
}
return GetProperty(cx, actual, actualReceiver, id, vp);
}
static bool with_SetProperty(JSContext* cx, HandleObject obj, HandleId id,
HandleValue v, HandleValue receiver,
ObjectOpResult& result) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
RootedValue actualReceiver(cx, receiver);
if (receiver.isObject() && &receiver.toObject() == obj) {
actualReceiver.setObject(*actual);
}
return SetProperty(cx, actual, id, v, actualReceiver, result);
}
static bool with_GetOwnPropertyDescriptor(
JSContext* cx, HandleObject obj, HandleId id,
MutableHandle<mozilla::Maybe<PropertyDescriptor>> desc) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
return GetOwnPropertyDescriptor(cx, actual, id, desc);
}
static bool with_DeleteProperty(JSContext* cx, HandleObject obj, HandleId id,
ObjectOpResult& result) {
MOZ_ASSERT(!IsInternalDotName(cx, id));
RootedObject actual(cx, &obj->as<WithEnvironmentObject>().object());
return DeleteProperty(cx, actual, id, result);
}
static const ObjectOps WithEnvironmentObjectOps = {
with_LookupProperty, // lookupProperty
with_DefineProperty, // defineProperty
with_HasProperty, // hasProperty
with_GetProperty, // getProperty
with_SetProperty, // setProperty
with_GetOwnPropertyDescriptor, // getOwnPropertyDescriptor
with_DeleteProperty, // deleteProperty
nullptr, // getElements
nullptr, // funToString
};
const JSClass WithEnvironmentObject::class_ = {
"With",
JSCLASS_HAS_RESERVED_SLOTS(WithEnvironmentObject::RESERVED_SLOTS),
JS_NULL_CLASS_OPS,
JS_NULL_CLASS_SPEC,
JS_NULL_CLASS_EXT,
&WithEnvironmentObjectOps,
};
/* static */
NonSyntacticVariablesObject* NonSyntacticVariablesObject::create(
JSContext* cx) {
Rooted<SharedShape*> shape(
cx, EmptyEnvironmentShape<NonSyntacticVariablesObject>(cx));
if (!shape) {
return nullptr;
}
Rooted<NonSyntacticVariablesObject*> obj(
cx, CreateEnvironmentObject<NonSyntacticVariablesObject>(cx, shape,
TenuredObject));
if (!obj) {
return nullptr;
}
// An NVSO holds both variables qualified with `var` and those that are not.
MOZ_ASSERT(obj->isUnqualifiedVarObj());
MOZ_ASSERT(obj->isQualifiedVarObj());
obj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
return obj;
}
const JSClass NonSyntacticVariablesObject::class_ = {
"NonSyntacticVariablesObject",
JSCLASS_HAS_RESERVED_SLOTS(NonSyntacticVariablesObject::RESERVED_SLOTS),
};
NonSyntacticLexicalEnvironmentObject* js::CreateNonSyntacticEnvironmentChain(
JSContext* cx, const JS::EnvironmentChain& envChain) {
// Callers are responsible for segregating the NonSyntactic case from simple
// compilation cases.