summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/PrivateOpEmitter.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/frontend/PrivateOpEmitter.cpp331
1 files changed, 331 insertions, 0 deletions
diff --git a/js/src/frontend/PrivateOpEmitter.cpp b/js/src/frontend/PrivateOpEmitter.cpp
new file mode 100644
index 0000000000..9e617b209e
--- /dev/null
+++ b/js/src/frontend/PrivateOpEmitter.cpp
@@ -0,0 +1,331 @@
+/* -*- 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 "frontend/PrivateOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/NameOpEmitter.h"
+#include "vm/Opcodes.h"
+#include "vm/ThrowMsgKind.h" // ThrowMsgKind
+
+using namespace js;
+using namespace js::frontend;
+
+PrivateOpEmitter::PrivateOpEmitter(BytecodeEmitter* bce, Kind kind,
+ TaggedParserAtomIndex name)
+ : bce_(bce), kind_(kind), name_(name) {
+ MOZ_ASSERT(kind_ != Kind::Delete);
+}
+
+bool PrivateOpEmitter::init() {
+ // Static analysis needs us to initialise this to something, so use Dynamic()
+ NameLocation loc = NameLocation::Dynamic();
+ bce_->lookupPrivate(name_, loc, brandLoc_);
+ loc_ = mozilla::Some(loc);
+ return true;
+}
+
+bool PrivateOpEmitter::emitLoad(TaggedParserAtomIndex name,
+ const NameLocation& loc) {
+ NameOpEmitter noe(bce_, name, loc, NameOpEmitter::Kind::Get);
+ return noe.emitGet();
+}
+
+bool PrivateOpEmitter::emitLoadPrivateBrand() {
+ return emitLoad(TaggedParserAtomIndex::WellKnown::dotPrivateBrand(),
+ *brandLoc_);
+}
+
+bool PrivateOpEmitter::emitBrandCheck() {
+ MOZ_ASSERT(state_ == State::Reference);
+
+ if (isBrandCheck()) {
+ // Emit a CheckPrivateField CheckRhs; note: The message is irrelvant here,
+ // it will never be thrown, so DoubleInit was chosen arbitrarily.
+ if (!bce_->emitCheckPrivateField(ThrowCondition::OnlyCheckRhs,
+ ThrowMsgKind::PrivateDoubleInit)) {
+ // [stack] OBJ KEY BBOOL
+ return false;
+ }
+
+ return true;
+ }
+
+ // [stack] OBJ KEY
+ if (isFieldInit()) {
+ if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHas,
+ ThrowMsgKind::PrivateDoubleInit)) {
+ // [stack] OBJ KEY false
+ return false;
+ }
+ } else {
+ bool assigning =
+ isSimpleAssignment() || isCompoundAssignment() || isIncDec();
+ if (!bce_->emitCheckPrivateField(ThrowCondition::ThrowHasNot,
+ assigning
+ ? ThrowMsgKind::MissingPrivateOnSet
+ : ThrowMsgKind::MissingPrivateOnGet)) {
+ // [stack] OBJ KEY true
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool PrivateOpEmitter::emitReference() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ if (!init()) {
+ return false;
+ }
+
+ if (brandLoc_) {
+ if (!emitLoadPrivateBrand()) {
+ // [stack] OBJ BRAND
+ return false;
+ }
+ } else {
+ if (!emitLoad(name_, loc_.ref())) {
+ // [stack] OBJ NAME
+ return false;
+ }
+ }
+#ifdef DEBUG
+ state_ = State::Reference;
+#endif
+ return true;
+}
+
+bool PrivateOpEmitter::skipReference() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ if (!init()) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Reference;
+#endif
+ return true;
+}
+
+bool PrivateOpEmitter::emitGet() {
+ MOZ_ASSERT(state_ == State::Reference);
+
+ // [stack] OBJ NAME
+
+ if (brandLoc_) {
+ // Note that the decision of what we leave on the stack depends on kind_,
+ // not loc_->bindingKind(). We can't emit code for a call just because this
+ // private member is a method. `obj.#method` is allowed without a call,
+ // just fetching the function object (it's useful in code like
+ // `obj.#method.bind(...)`). Even if the user says `obj.#method += 7`, we
+ // emit honest bytecode for the brand check, method load, and addition, and
+ // throw the error later. This preserves stack nuses/ndefs balance.
+ if (!emitBrandCheck()) {
+ // [stack] OBJ BRAND true
+ return false;
+ }
+
+ if (isCompoundAssignment()) {
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] OBJ BRAND
+ return false;
+ }
+ } else if (isCall()) {
+ if (!bce_->emitPopN(2)) {
+ // [stack] OBJ
+ return false;
+ }
+ } else {
+ if (!bce_->emitPopN(3)) {
+ // [stack]
+ return false;
+ }
+ }
+
+ if (!emitLoad(name_, loc_.ref())) {
+ // [stack] OBJ BRAND METHOD # if isCompoundAssignment
+ // [stack] OBJ METHOD # if call
+ // [stack] METHOD # otherwise
+ return false;
+ }
+ } else {
+ if (isCall()) {
+ if (!bce_->emitDupAt(1)) {
+ // [stack] OBJ NAME OBJ
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] OBJ OBJ NAME
+ return false;
+ }
+ }
+ // [stack] OBJ? OBJ NAME
+ if (!emitBrandCheck()) {
+ // [stack] OBJ? OBJ NAME true
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] OBJ? OBJ NAME
+ return false;
+ }
+
+ if (isCompoundAssignment()) {
+ if (!bce_->emit1(JSOp::Dup2)) {
+ // [stack] OBJ NAME OBJ NAME
+ return false;
+ }
+ }
+
+ if (!bce_->emitElemOpBase(JSOp::GetElem)) {
+ // [stack] OBJ NAME VALUE # if isCompoundAssignment
+ // [stack] OBJ METHOD # if Call
+ // [stack] VALUE # otherwise
+ return false;
+ }
+ }
+
+ if (isCall()) {
+ if (!bce_->emit1(JSOp::Swap)) {
+ // [stack] METHOD OBJ
+ return false;
+ }
+ }
+
+ // [stack] OBJ NAME VALUE # if isCompoundAssignment
+ // [stack] METHOD OBJ # if call
+ // [stack] VALUE # otherwise
+
+#ifdef DEBUG
+ state_ = State::Get;
+#endif
+ return true;
+}
+
+bool PrivateOpEmitter::emitGetForCallOrNew() { return emitGet(); }
+
+bool PrivateOpEmitter::emitAssignment() {
+ MOZ_ASSERT(isSimpleAssignment() || isFieldInit() || isCompoundAssignment());
+ MOZ_ASSERT_IF(!isCompoundAssignment(), state_ == State::Reference);
+ MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
+
+ // [stack] OBJ KEY RHS
+
+ if (brandLoc_) {
+ if (!bce_->emit2(JSOp::ThrowMsg,
+ uint8_t(ThrowMsgKind::AssignToPrivateMethod))) {
+ return false;
+ }
+
+ // Balance the expression stack.
+ if (!bce_->emitPopN(2)) {
+ // [stack] OBJ
+ return false;
+ }
+ } else {
+ // Emit a brand check. If this is compound assignment, emitGet() already
+ // emitted a check for this object and key. There's no point checking
+ // again--a private field can't be removed from an object.
+ if (!isCompoundAssignment()) {
+ if (!bce_->emitUnpickN(2)) {
+ // [stack] RHS OBJ KEY
+ return false;
+ }
+ if (!emitBrandCheck()) {
+ // [stack] RHS OBJ KEY BOOL
+ return false;
+ }
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] RHS OBJ KEY
+ return false;
+ }
+ if (!bce_->emitPickN(2)) {
+ // [stack] OBJ KEY RHS
+ return false;
+ }
+ }
+
+ JSOp setOp = isFieldInit() ? JSOp::InitElem : JSOp::StrictSetElem;
+ if (!bce_->emitElemOpBase(setOp)) {
+ // [stack] RHS
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::Assignment;
+#endif
+ return true;
+}
+
+bool PrivateOpEmitter::emitIncDec(ValueUsage valueUsage) {
+ MOZ_ASSERT(state_ == State::Reference);
+ MOZ_ASSERT(isIncDec());
+ // [stack] OBJ NAME
+
+ if (!bce_->emitDupAt(1, 2)) {
+ // [stack] OBJ NAME OBJ NAME
+ return false;
+ }
+
+ if (!emitGet()) {
+ // [stack] OBJ NAME VALUE
+ return false;
+ }
+
+ MOZ_ASSERT(state_ == State::Get);
+
+ JSOp incOp = isInc() ? JSOp::Inc : JSOp::Dec;
+
+ if (!bce_->emit1(JSOp::ToNumeric)) {
+ // [stack] OBJ NAME N
+ return false;
+ }
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ // [stack] OBJ NAME N
+ if (!bce_->emit1(JSOp::Dup)) {
+ // [stack] OBJ NAME N N
+ return false;
+ }
+ if (!bce_->emit2(JSOp::Unpick, 3)) {
+ // [stack] N OBJ NAME N
+ return false;
+ }
+ }
+ if (!bce_->emit1(incOp)) {
+ // [stack] N? OBJ NAME N+1
+ return false;
+ }
+
+ if (brandLoc_) {
+ if (!bce_->emit2(JSOp::ThrowMsg,
+ uint8_t(ThrowMsgKind::AssignToPrivateMethod))) {
+ return false;
+ }
+
+ // Balance the expression stack.
+ if (!bce_->emitPopN(2)) {
+ // [stack] N? N+1
+ return false;
+ }
+ } else {
+ if (!bce_->emitElemOpBase(JSOp::StrictSetElem)) {
+ // [stack] N? N+1
+ return false;
+ }
+ }
+
+ if (isPostIncDec() && valueUsage == ValueUsage::WantValue) {
+ if (!bce_->emit1(JSOp::Pop)) {
+ // [stack] N
+ return false;
+ }
+ }
+
+ return true;
+}