summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/TryEmitter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/frontend/TryEmitter.cpp')
-rw-r--r--js/src/frontend/TryEmitter.cpp291
1 files changed, 291 insertions, 0 deletions
diff --git a/js/src/frontend/TryEmitter.cpp b/js/src/frontend/TryEmitter.cpp
new file mode 100644
index 0000000000..1004f4ce25
--- /dev/null
+++ b/js/src/frontend/TryEmitter.cpp
@@ -0,0 +1,291 @@
+/* -*- 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/TryEmitter.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "frontend/BytecodeEmitter.h" // BytecodeEmitter
+#include "frontend/SharedContext.h" // StatementKind
+#include "vm/JSScript.h" // JSTRY_CATCH, JSTRY_FINALLY
+#include "vm/Opcodes.h" // JSOp
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+TryEmitter::TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind)
+ : bce_(bce),
+ kind_(kind),
+ controlKind_(controlKind),
+ depth_(0),
+ tryOpOffset_(0)
+#ifdef DEBUG
+ ,
+ state_(State::Start)
+#endif
+{
+ if (controlKind_ == ControlKind::Syntactic) {
+ controlInfo_.emplace(
+ bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
+ }
+}
+
+bool TryEmitter::emitTry() {
+ MOZ_ASSERT(state_ == State::Start);
+
+ // Since an exception can be thrown at any place inside the try block,
+ // we need to restore the stack and the scope chain before we transfer
+ // the control to the exception handler.
+ //
+ // For that we store in a try note associated with the catch or
+ // finally block the stack depth upon the try entry. The interpreter
+ // uses this depth to properly unwind the stack and the scope chain.
+ depth_ = bce_->bytecodeSection().stackDepth();
+
+ tryOpOffset_ = bce_->bytecodeSection().offset();
+ if (!bce_->emit1(JSOp::Try)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Try;
+#endif
+ return true;
+}
+
+bool TryEmitter::emitTryEnd() {
+ MOZ_ASSERT(state_ == State::Try);
+ MOZ_ASSERT(depth_ == bce_->bytecodeSection().stackDepth());
+
+ // Gosub to finally, if present.
+ if (hasFinally() && controlInfo_) {
+ if (!bce_->emitGoSub(&controlInfo_->gosubs)) {
+ return false;
+ }
+ }
+
+ // Emit jump over catch and/or finally.
+ if (!bce_->emitJump(JSOp::Goto, &catchAndFinallyJump_)) {
+ return false;
+ }
+
+ if (!bce_->emitJumpTarget(&tryEnd_)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool TryEmitter::emitCatch() {
+ MOZ_ASSERT(state_ == State::Try);
+ if (!emitTryEnd()) {
+ return false;
+ }
+
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
+
+ if (controlKind_ == ControlKind::Syntactic) {
+ // Clear the frame's return value that might have been set by the
+ // try block:
+ //
+ // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
+ if (!bce_->emit1(JSOp::Undefined)) {
+ return false;
+ }
+ if (!bce_->emit1(JSOp::SetRval)) {
+ return false;
+ }
+ }
+
+ if (!bce_->emit1(JSOp::Exception)) {
+ return false;
+ }
+
+ if (!instrumentEntryPoint()) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Catch;
+#endif
+ return true;
+}
+
+bool TryEmitter::emitCatchEnd() {
+ MOZ_ASSERT(state_ == State::Catch);
+
+ if (!controlInfo_) {
+ return true;
+ }
+
+ // gosub <finally>, if required.
+ if (hasFinally()) {
+ if (!bce_->emitGoSub(&controlInfo_->gosubs)) {
+ return false;
+ }
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
+
+ // Jump over the finally block.
+ if (!bce_->emitJump(JSOp::Goto, &catchAndFinallyJump_)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool TryEmitter::emitFinally(
+ const Maybe<uint32_t>& finallyPos /* = Nothing() */) {
+ // If we are using controlInfo_ (i.e., emitting a syntactic try
+ // blocks), we must have specified up front if there will be a finally
+ // close. For internal non-syntactic try blocks, like those emitted for
+ // yield* and IteratorClose inside for-of loops, we can emitFinally even
+ // without specifying up front, since the internal non-syntactic try
+ // blocks emit no GOSUBs.
+ if (!controlInfo_) {
+ if (kind_ == Kind::TryCatch) {
+ kind_ = Kind::TryCatchFinally;
+ }
+ } else {
+ MOZ_ASSERT(hasFinally());
+ }
+
+ if (!hasCatch()) {
+ MOZ_ASSERT(state_ == State::Try);
+ if (!emitTryEnd()) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(state_ == State::Catch);
+ if (!emitCatchEnd()) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
+
+ if (!bce_->emitJumpTarget(&finallyStart_)) {
+ return false;
+ }
+
+ if (controlInfo_) {
+ // Fix up the gosubs that might have been emitted before non-local
+ // jumps to the finally code.
+ bce_->patchJumpsToTarget(controlInfo_->gosubs, finallyStart_);
+
+ // Indicate that we're emitting a subroutine body.
+ controlInfo_->setEmittingSubroutine();
+ }
+ if (finallyPos) {
+ if (!bce_->updateSourceCoordNotes(finallyPos.value())) {
+ return false;
+ }
+ }
+ if (!bce_->emit1(JSOp::Finally)) {
+ return false;
+ }
+
+ if (controlKind_ == ControlKind::Syntactic) {
+ if (!bce_->emit1(JSOp::GetRval)) {
+ return false;
+ }
+
+ // Clear the frame's return value to make break/continue return
+ // correct value even if there's no other statement before them:
+ //
+ // eval("x: try { 1 } finally { break x; }"); // undefined, not 1
+ if (!bce_->emit1(JSOp::Undefined)) {
+ return false;
+ }
+ if (!bce_->emit1(JSOp::SetRval)) {
+ return false;
+ }
+ }
+
+ if (!instrumentEntryPoint()) {
+ return false;
+ }
+
+#ifdef DEBUG
+ state_ = State::Finally;
+#endif
+ return true;
+}
+
+bool TryEmitter::emitFinallyEnd() {
+ MOZ_ASSERT(state_ == State::Finally);
+
+ if (controlKind_ == ControlKind::Syntactic) {
+ if (!bce_->emit1(JSOp::SetRval)) {
+ return false;
+ }
+ }
+
+ if (!bce_->emit1(JSOp::Retsub)) {
+ return false;
+ }
+
+ bce_->hasTryFinally = true;
+ return true;
+}
+
+bool TryEmitter::emitEnd() {
+ if (!hasFinally()) {
+ MOZ_ASSERT(state_ == State::Catch);
+ if (!emitCatchEnd()) {
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(state_ == State::Finally);
+ if (!emitFinallyEnd()) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
+
+ // Fix up the end-of-try/catch jumps to come here.
+ if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) {
+ return false;
+ }
+
+ // Add the try note last, to let post-order give us the right ordering
+ // (first to last for a given nesting level, inner to outer by level).
+ if (hasCatch()) {
+ if (!bce_->addTryNote(TryNoteKind::Catch, depth_, offsetAfterTryOp(),
+ tryEnd_.offset)) {
+ return false;
+ }
+ }
+
+ // If we've got a finally, mark try+catch region with additional
+ // trynote to catch exceptions (re)thrown from a catch block or
+ // for the try{}finally{} case.
+ if (hasFinally()) {
+ if (!bce_->addTryNote(TryNoteKind::Finally, depth_, offsetAfterTryOp(),
+ finallyStart_.offset)) {
+ return false;
+ }
+ }
+
+#ifdef DEBUG
+ state_ = State::End;
+#endif
+ return true;
+}
+
+bool TryEmitter::instrumentEntryPoint() {
+ // Frames for async functions can resume execution at catch or finally blocks
+ // if an await operation threw an exception. While the frame might already be
+ // on the stack, the Entry instrumentation kind only indicates that a new
+ // frame *might* have been pushed.
+ if (bce_->sc->isFunctionBox() && bce_->sc->asFunctionBox()->isAsync()) {
+ return bce_->emitInstrumentation(InstrumentationKind::Entry);
+ }
+ return true;
+}