#include "frontend/ObjectEmitter.h"
#include "mozilla/Assertions.h"
#include "frontend/BytecodeEmitter.h"
#include "frontend/IfEmitter.h"
#include "frontend/SharedContext.h"
#include "frontend/SourceNotes.h"
#include "gc/AllocKind.h"
#include "js/Id.h"
#include "js/Value.h"
#include "vm/BytecodeUtil.h"
#include "vm/JSContext.h"
#include "vm/NativeObject.h"
#include "vm/ObjectGroup.h"
#include "vm/Opcodes.h"
#include "vm/Runtime.h"
#include "gc/ObjectKind-inl.h"
#include "vm/JSAtom-inl.h"
#include "vm/JSObject-inl.h"
using namespace js;
using namespace js::frontend;
using mozilla::Maybe;
PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce)
: bce_(bce), obj_(bce->cx) {}
bool PropertyEmitter::prepareForProtoValue(const Maybe<uint32_t>& keyPos) {
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
propertyState_ == PropertyState::Init);
if (keyPos) {
if (!bce_->updateSourceCoordNotes(*keyPos)) {
return false;
}
}
#ifdef DEBUG
propertyState_ = PropertyState::ProtoValue;
#endif
return true;
}
bool PropertyEmitter::emitMutateProto() {
MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue);
if (!bce_->emit1(JSOP_MUTATEPROTO)) {
return false;
}
obj_ = nullptr;
#ifdef DEBUG
propertyState_ = PropertyState::Init;
#endif
return true;
}
bool PropertyEmitter::prepareForSpreadOperand(
const Maybe<uint32_t>& spreadPos) {
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
propertyState_ == PropertyState::Init);
if (spreadPos) {
if (!bce_->updateSourceCoordNotes(*spreadPos)) {
return false;
}
}
if (!bce_->emit1(JSOP_DUP)) {
return false;
}
#ifdef DEBUG
propertyState_ = PropertyState::SpreadOperand;
#endif
return true;
}
bool PropertyEmitter::emitSpread() {
MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand);
if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) {
return false;
}
obj_ = nullptr;
#ifdef DEBUG
propertyState_ = PropertyState::Init;
#endif
return true;
}
MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp(
const Maybe<uint32_t>& keyPos, bool isStatic, bool isIndexOrComputed) {
isStatic_ = isStatic;
isIndexOrComputed_ = isIndexOrComputed;
if (keyPos) {
if (!bce_->updateSourceCoordNotes(*keyPos)) {
return false;
}
}
if (isStatic_) {
if (!bce_->emit1(JSOP_DUP2)) {
return false;
}
if (!bce_->emit1(JSOP_POP)) {
return false;
}
}
return true;
}
bool PropertyEmitter::prepareForPropValue(const Maybe<uint32_t>& keyPos,
Kind kind ) {
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
propertyState_ == PropertyState::Init);
if (!prepareForProp(keyPos,
kind == Kind::Static,
false)) {
return false;
}
#ifdef DEBUG
propertyState_ = PropertyState::PropValue;
#endif
return true;
}
bool PropertyEmitter::prepareForIndexPropKey(
const Maybe<uint32_t>& keyPos, Kind kind ) {
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
propertyState_ == PropertyState::Init);
obj_ = nullptr;
if (!prepareForProp(keyPos,
kind == Kind::Static,
true)) {
return false;
}
#ifdef DEBUG
propertyState_ = PropertyState::IndexKey;
#endif
return true;
}
bool PropertyEmitter::prepareForIndexPropValue() {
MOZ_ASSERT(propertyState_ == PropertyState::IndexKey);
#ifdef DEBUG
propertyState_ = PropertyState::IndexValue;
#endif
return true;
}
bool PropertyEmitter::prepareForComputedPropKey(
const Maybe<uint32_t>& keyPos, Kind kind ) {
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
propertyState_ == PropertyState::Init);
obj_ = nullptr;
if (!prepareForProp(keyPos,
kind == Kind::Static,
true)) {
return false;
}
#ifdef DEBUG
propertyState_ = PropertyState::ComputedKey;
#endif
return true;
}
bool PropertyEmitter::prepareForComputedPropValue() {
MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey);
if (!bce_->emit1(JSOP_TOID)) {
return false;
}
#ifdef DEBUG
propertyState_ = PropertyState::ComputedValue;
#endif
return true;
}
bool PropertyEmitter::emitInitHomeObject() {
MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
propertyState_ == PropertyState::IndexValue ||
propertyState_ == PropertyState::ComputedValue);
if (!bce_->emitDupAt(1 + isIndexOrComputed_)) {
return false;
}
if (!bce_->emit1(JSOP_INITHOMEOBJECT)) {
return false;
}
#ifdef DEBUG
if (propertyState_ == PropertyState::PropValue) {
propertyState_ = PropertyState::InitHomeObj;
} else if (propertyState_ == PropertyState::IndexValue) {
propertyState_ = PropertyState::InitHomeObjForIndex;
} else {
propertyState_ = PropertyState::InitHomeObjForComputed;
}
#endif
return true;
}
bool PropertyEmitter::emitInitProp(JS::Handle<JSAtom*> key) {
return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key);
}
bool PropertyEmitter::emitInitGetter(JS::Handle<JSAtom*> key) {
obj_ = nullptr;
return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER,
key);
}
bool PropertyEmitter::emitInitSetter(JS::Handle<JSAtom*> key) {
obj_ = nullptr;
return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER,
key);
}
bool PropertyEmitter::emitInitIndexProp() {
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM
: JSOP_INITELEM);
}
bool PropertyEmitter::emitInitIndexGetter() {
obj_ = nullptr;
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER
: JSOP_INITELEM_GETTER);
}
bool PropertyEmitter::emitInitIndexSetter() {
obj_ = nullptr;
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER
: JSOP_INITELEM_SETTER);
}
bool PropertyEmitter::emitInitComputedProp() {
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM
: JSOP_INITELEM);
}
bool PropertyEmitter::emitInitComputedGetter() {
obj_ = nullptr;
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER
: JSOP_INITELEM_GETTER);
}
bool PropertyEmitter::emitInitComputedSetter() {
obj_ = nullptr;
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER
: JSOP_INITELEM_SETTER);
}
bool PropertyEmitter::emitInit(JSOp op, JS::Handle<JSAtom*> key) {
MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
propertyState_ == PropertyState::InitHomeObj);
MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP ||
op == JSOP_INITPROP_GETTER || op == JSOP_INITHIDDENPROP_GETTER ||
op == JSOP_INITPROP_SETTER || op == JSOP_INITHIDDENPROP_SETTER);
uint32_t index;
if (!bce_->makeAtomIndex(key, &index)) {
return false;
}
if (obj_) {
MOZ_ASSERT(!IsHiddenInitOp(op));
MOZ_ASSERT(!obj_->inDictionaryMode());
JS::Rooted<JS::PropertyKey> propKey(bce_->cx, AtomToId(key));
if (!NativeDefineDataProperty(bce_->cx, obj_, propKey, UndefinedHandleValue,
JSPROP_ENUMERATE)) {
return false;
}
if (obj_->inDictionaryMode()) {
obj_ = nullptr;
}
}
if (!bce_->emitIndex32(op, index)) {
return false;
}
if (!emitPopClassConstructor()) {
return false;
}
#ifdef DEBUG
propertyState_ = PropertyState::Init;
#endif
return true;
}
bool PropertyEmitter::emitInitIndexOrComputed(JSOp op) {
MOZ_ASSERT(propertyState_ == PropertyState::IndexValue ||
propertyState_ == PropertyState::InitHomeObjForIndex ||
propertyState_ == PropertyState::ComputedValue ||
propertyState_ == PropertyState::InitHomeObjForComputed);
MOZ_ASSERT(op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM ||
op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENELEM_GETTER ||
op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENELEM_SETTER);
if (!bce_->emit1(op)) {
return false;
}
if (!emitPopClassConstructor()) {
return false;
}
#ifdef DEBUG
propertyState_ = PropertyState::Init;
#endif
return true;
}
bool PropertyEmitter::emitPopClassConstructor() {
if (isStatic_) {
if (!bce_->emit1(JSOP_POP)) {
return false;
}
}
return true;
}
ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {}
bool ObjectEmitter::emitObject(size_t propertyCount) {
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(objectState_ == ObjectState::Start);
top_ = bce_->offset();
if (!bce_->emitNewInit()) {
return false;
}
gc::AllocKind kind = gc::GetGCObjectKind(propertyCount);
obj_ = NewBuiltinClassInstance<PlainObject>(bce_->cx, kind, TenuredObject);
if (!obj_) {
return false;
}
#ifdef DEBUG
objectState_ = ObjectState::Object;
#endif
return true;
}
bool ObjectEmitter::emitEnd() {
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
propertyState_ == PropertyState::Init);
MOZ_ASSERT(objectState_ == ObjectState::Object);
if (obj_) {
if (!bce_->replaceNewInitWithNewObject(obj_, top_)) {
return false;
}
}
#ifdef DEBUG
objectState_ = ObjectState::End;
#endif
return true;
}
AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) {
savedStrictness_ = sc_->setLocalStrictMode(true);
}
AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() {
if (sc_) {
restore();
}
}
void AutoSaveLocalStrictMode::restore() {
MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_));
sc_ = nullptr;
}
ClassEmitter::ClassEmitter(BytecodeEmitter* bce)
: PropertyEmitter(bce),
strictMode_(bce->sc),
name_(bce->cx),
nameForAnonymousClass_(bce->cx) {
isClass_ = true;
}
bool ClassEmitter::emitScopeForNamedClass(
JS::Handle<LexicalScope::Data*> scopeBindings) {
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(classState_ == ClassState::Start);
tdzCacheForInnerName_.emplace(bce_);
innerNameScope_.emplace(bce_);
if (!innerNameScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) {
return false;
}
#ifdef DEBUG
classState_ = ClassState::Scope;
#endif
return true;
}
bool ClassEmitter::emitClass(JS::Handle<JSAtom*> name,
JS::Handle<JSAtom*> nameForAnonymousClass,
bool hasNameOnStack) {
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(classState_ == ClassState::Start ||
classState_ == ClassState::Scope);
MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name);
MOZ_ASSERT(!(nameForAnonymousClass && hasNameOnStack));
name_ = name;
nameForAnonymousClass_ = nameForAnonymousClass;
hasNameOnStack_ = hasNameOnStack;
isDerived_ = false;
if (!bce_->emitNewInit()) {
return false;
}
#ifdef DEBUG
classState_ = ClassState::Class;
#endif
return true;
}
bool ClassEmitter::emitDerivedClass(JS::Handle<JSAtom*> name,
JS::Handle<JSAtom*> nameForAnonymousClass,
bool hasNameOnStack) {
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(classState_ == ClassState::Start ||
classState_ == ClassState::Scope);
MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name);
MOZ_ASSERT(!nameForAnonymousClass || !hasNameOnStack);
name_ = name;
nameForAnonymousClass_ = nameForAnonymousClass;
hasNameOnStack_ = hasNameOnStack;
isDerived_ = true;
InternalIfEmitter ifThenElse(bce_);
if (!bce_->emit1(JSOP_CHECKCLASSHERITAGE)) {
return false;
}
if (!bce_->emit1(JSOP_DUP)) {
return false;
}
if (!bce_->emit1(JSOP_NULL)) {
return false;
}
if (!bce_->emit1(JSOP_STRICTNE)) {
return false;
}
if (!ifThenElse.emitThenElse()) {
return false;
}
if (!bce_->emit1(JSOP_DUP)) {
return false;
}
if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_GETPROP)) {
return false;
}
if (!ifThenElse.emitElse()) {
return false;
}
if (!bce_->emit1(JSOP_POP)) {
return false;
}
if (!bce_->emit2(JSOP_BUILTINPROTO, JSProto_Function)) {
return false;
}
if (!bce_->emit1(JSOP_NULL)) {
return false;
}
if (!ifThenElse.emitEnd()) {
return false;
}
if (!bce_->emit1(JSOP_OBJWITHPROTO)) {
return false;
}
if (!bce_->emit1(JSOP_SWAP)) {
return false;
}
#ifdef DEBUG
classState_ = ClassState::Class;
#endif
return true;
}
bool ClassEmitter::emitInitConstructor(bool needsHomeObject) {
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(classState_ == ClassState::Class);
if (needsHomeObject) {
if (!bce_->emitDupAt(1)) {
return false;
}
if (!bce_->emit1(JSOP_INITHOMEOBJECT)) {
return false;
}
}
if (!initProtoAndCtor()) {
return false;
}
#ifdef DEBUG
classState_ = ClassState::InitConstructor;
#endif
return true;
}
bool ClassEmitter::emitInitDefaultConstructor(const Maybe<uint32_t>& classStart,
const Maybe<uint32_t>& classEnd) {
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(classState_ == ClassState::Class);
if (classStart && classEnd) {
if (!bce_->newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(*classStart),
ptrdiff_t(*classEnd))) {
return false;
}
}
RootedAtom className(bce_->cx, name_);
if (!className) {
if (nameForAnonymousClass_) {
className = nameForAnonymousClass_;
} else {
className = bce_->cx->names().empty;
}
}
if (isDerived_) {
if (!bce_->emitAtomOp(className, JSOP_DERIVEDCONSTRUCTOR)) {
return false;
}
} else {
if (!bce_->emitAtomOp(className, JSOP_CLASSCONSTRUCTOR)) {
return false;
}
}
if (nameForAnonymousClass_ == bce_->cx->names().empty) {
if (!emitSetEmptyClassConstructorNameForDefaultCtor()) {
return false;
}
}
if (!initProtoAndCtor()) {
return false;
}
#ifdef DEBUG
classState_ = ClassState::InitConstructor;
#endif
return true;
}
bool ClassEmitter::emitSetEmptyClassConstructorNameForDefaultCtor() {
uint32_t nameIndex;
if (!bce_->makeAtomIndex(bce_->cx->names().empty, &nameIndex)) {
return false;
}
if (!bce_->emitIndexOp(JSOP_STRING, nameIndex)) {
return false;
}
if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(FunctionPrefixKind::None))) {
return false;
}
return true;
}
bool ClassEmitter::initProtoAndCtor() {
if (hasNameOnStack_) {
if (!bce_->emitDupAt(2)) {
return false;
}
if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(FunctionPrefixKind::None))) {
return false;
}
}
if (!bce_->emit1(JSOP_SWAP)) {
return false;
}
if (!bce_->emit1(JSOP_DUP2)) {
return false;
}
if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_INITLOCKEDPROP)) {
return false;
}
if (!bce_->emitAtomOp(bce_->cx->names().constructor, JSOP_INITHIDDENPROP)) {
return false;
}
return true;
}
bool ClassEmitter::emitEnd(Kind kind) {
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
propertyState_ == PropertyState::Init);
MOZ_ASSERT(classState_ == ClassState::InitConstructor);
if (!bce_->emit1(JSOP_POP)) {
return false;
}
if (name_) {
MOZ_ASSERT(tdzCacheForInnerName_.isSome());
MOZ_ASSERT(innerNameScope_.isSome());
if (!bce_->emitLexicalInitialization(name_)) {
return false;
}
if (!innerNameScope_->leave(bce_)) {
return false;
}
innerNameScope_.reset();
if (kind == Kind::Declaration) {
if (!bce_->emitLexicalInitialization(name_)) {
return false;
}
if (!bce_->emit1(JSOP_POP)) {
return false;
}
}
tdzCacheForInnerName_.reset();
} else {
MOZ_ASSERT(tdzCacheForInnerName_.isNothing());
}
strictMode_.restore();
#ifdef DEBUG
classState_ = ClassState::End;
#endif
return true;
}