tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TryEmitter.cpp (9101B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "frontend/TryEmitter.h"
      8 
      9 #include "mozilla/Assertions.h"  // MOZ_ASSERT
     10 
     11 #include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
     12 #include "frontend/IfEmitter.h"        // BytecodeEmitter
     13 #include "frontend/SharedContext.h"    // StatementKind
     14 #include "vm/Opcodes.h"                // JSOp
     15 
     16 using namespace js;
     17 using namespace js::frontend;
     18 
     19 using mozilla::Maybe;
     20 
     21 TryEmitter::TryEmitter(BytecodeEmitter* bce, Kind kind, ControlKind controlKind)
     22    : bce_(bce),
     23      kind_(kind),
     24      controlKind_(controlKind),
     25      depth_(0),
     26      tryOpOffset_(0)
     27 #ifdef DEBUG
     28      ,
     29      state_(State::Start)
     30 #endif
     31 {
     32  MOZ_ASSERT_IF(controlKind_ == ControlKind::Disposal,
     33                kind_ == Kind::TryFinally);
     34 }
     35 
     36 #ifdef DEBUG
     37 bool TryEmitter::hasControlInfo() { return controlInfo_.get() != nullptr; }
     38 #endif
     39 
     40 bool TryEmitter::emitTry() {
     41  MOZ_ASSERT(state_ == State::Start);
     42 
     43  if (requiresControlInfo()) {
     44    controlInfo_ = bce_->fc->getAllocator()->make_unique<TryFinallyControl>(
     45        bce_, hasFinally() ? StatementKind::Finally : StatementKind::Try);
     46    if (!controlInfo_) {
     47      return false;
     48    }
     49  }
     50 
     51  // Since an exception can be thrown at any place inside the try block,
     52  // we need to restore the stack and the scope chain before we transfer
     53  // the control to the exception handler.
     54  //
     55  // For that we store in a try note associated with the catch or
     56  // finally block the stack depth upon the try entry. The interpreter
     57  // uses this depth to properly unwind the stack and the scope chain.
     58  depth_ = bce_->bytecodeSection().stackDepth();
     59 
     60  tryOpOffset_ = bce_->bytecodeSection().offset();
     61  if (!bce_->emit1(JSOp::Try)) {
     62    return false;
     63  }
     64 
     65 #ifdef DEBUG
     66  state_ = State::Try;
     67 #endif
     68  return true;
     69 }
     70 
     71 bool TryEmitter::emitJumpToFinallyWithFallthrough() {
     72  uint32_t stackDepthForNextBlock = bce_->bytecodeSection().stackDepth();
     73 
     74  // The fallthrough continuation is special-cased with index 0.
     75  uint32_t idx = TryFinallyControl::SpecialContinuations::Fallthrough;
     76  if (!bce_->emitJumpToFinally(&controlInfo_->finallyJumps_, idx)) {
     77    return false;
     78  }
     79 
     80  // Reset the stack depth for the following catch or finally block.
     81  bce_->bytecodeSection().setStackDepth(stackDepthForNextBlock);
     82  return true;
     83 }
     84 
     85 bool TryEmitter::emitTryEnd() {
     86  MOZ_ASSERT(state_ == State::Try);
     87  MOZ_ASSERT(depth_ == bce_->bytecodeSection().stackDepth());
     88 
     89  if (hasFinally() && controlInfo_) {
     90    if (!emitJumpToFinallyWithFallthrough()) {
     91      return false;
     92    }
     93  } else {
     94    // Emit jump over catch
     95    if (!bce_->emitJump(JSOp::Goto, &catchAndFinallyJump_)) {
     96      return false;
     97    }
     98  }
     99 
    100  if (!bce_->emitJumpTarget(&tryEnd_)) {
    101    return false;
    102  }
    103 
    104  return true;
    105 }
    106 
    107 bool TryEmitter::emitCatch(ExceptionStack stack) {
    108  MOZ_ASSERT(state_ == State::Try);
    109  if (!emitTryEnd()) {
    110    return false;
    111  }
    112 
    113  MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
    114 
    115  if (shouldUpdateRval()) {
    116    // Clear the frame's return value that might have been set by the
    117    // try block:
    118    //
    119    //   eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1
    120    if (!bce_->emit1(JSOp::Undefined)) {
    121      return false;
    122    }
    123    if (!bce_->emit1(JSOp::SetRval)) {
    124      return false;
    125    }
    126  }
    127 
    128  if (stack == ExceptionStack::No) {
    129    if (!bce_->emit1(JSOp::Exception)) {
    130      return false;
    131    }
    132  } else {
    133    if (!bce_->emit1(JSOp::ExceptionAndStack)) {
    134      return false;
    135    }
    136  }
    137 
    138 #ifdef DEBUG
    139  state_ = State::Catch;
    140 #endif
    141  return true;
    142 }
    143 
    144 bool TryEmitter::emitCatchEnd() {
    145  MOZ_ASSERT(state_ == State::Catch);
    146 
    147  if (!controlInfo_) {
    148    return true;
    149  }
    150 
    151  // Jump to <finally>, if required.
    152  if (hasFinally()) {
    153    if (!emitJumpToFinallyWithFallthrough()) {
    154      return false;
    155    }
    156  }
    157 
    158  return true;
    159 }
    160 
    161 bool TryEmitter::emitFinally(
    162    const Maybe<uint32_t>& finallyPos /* = Nothing() */) {
    163  // If we are using controlInfo_ (i.e., emitting a syntactic try
    164  // blocks), we must have specified up front if there will be a finally
    165  // close. For internal non-syntactic try blocks, like those emitted for
    166  // yield* and IteratorClose inside for-of loops, we can emitFinally even
    167  // without specifying up front, since the internal non-syntactic try
    168  // blocks emit no JSOp::Goto.
    169  if (!controlInfo_) {
    170    if (kind_ == Kind::TryCatch) {
    171      kind_ = Kind::TryCatchFinally;
    172    }
    173  } else {
    174    MOZ_ASSERT(hasFinally());
    175  }
    176 
    177  if (!hasCatch()) {
    178    MOZ_ASSERT(state_ == State::Try);
    179    if (!emitTryEnd()) {
    180      return false;
    181    }
    182  } else {
    183    MOZ_ASSERT(state_ == State::Catch);
    184    if (!emitCatchEnd()) {
    185      return false;
    186    }
    187  }
    188 
    189  MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
    190 
    191  // Upon entry to the finally, there are three additional values on the stack:
    192  // a boolean value to indicate whether we're throwing an exception, the
    193  // exception stack (if we're throwing) or null, and either that exception (if
    194  // we're throwing) or a resume index to which we will return (if we're not
    195  // throwing).
    196  bce_->bytecodeSection().setStackDepth(depth_ + 3);
    197 
    198  if (!bce_->emitJumpTarget(&finallyStart_)) {
    199    return false;
    200  }
    201 
    202  if (controlInfo_) {
    203    // Fix up the jumps to the finally code.
    204    bce_->patchJumpsToTarget(controlInfo_->finallyJumps_, finallyStart_);
    205 
    206    // Indicate that we're emitting a subroutine body.
    207    controlInfo_->setEmittingSubroutine();
    208  }
    209  if (finallyPos) {
    210    if (!bce_->updateSourceCoordNotes(finallyPos.value())) {
    211      return false;
    212    }
    213  }
    214  if (!bce_->emit1(JSOp::Finally)) {
    215    return false;
    216  }
    217 
    218  if (shouldUpdateRval()) {
    219    if (!bce_->emit1(JSOp::GetRval)) {
    220      return false;
    221    }
    222 
    223    // Clear the frame's return value to make break/continue return
    224    // correct value even if there's no other statement before them:
    225    //
    226    //   eval("x: try { 1 } finally { break x; }"); // undefined, not 1
    227    if (!bce_->emit1(JSOp::Undefined)) {
    228      return false;
    229    }
    230    if (!bce_->emit1(JSOp::SetRval)) {
    231      return false;
    232    }
    233  }
    234 
    235 #ifdef DEBUG
    236  state_ = State::Finally;
    237 #endif
    238  return true;
    239 }
    240 
    241 bool TryEmitter::emitFinallyEnd() {
    242  MOZ_ASSERT(state_ == State::Finally);
    243 
    244  if (shouldUpdateRval()) {
    245    if (!bce_->emit1(JSOp::SetRval)) {
    246      return false;
    247    }
    248  }
    249 
    250  //                [stack] RESUME_INDEX_OR_EXCEPTION, EXCEPTION_STACK, THROWING
    251 
    252  InternalIfEmitter ifThrowing(bce_);
    253  if (!ifThrowing.emitThenElse()) {
    254    //              [stack] RESUME_INDEX_OR_EXCEPTION, EXCEPTION_STACK
    255    return false;
    256  }
    257 
    258  if (!bce_->emit1(JSOp::ThrowWithStack)) {
    259    //              [stack]
    260    return false;
    261  }
    262 
    263  if (!ifThrowing.emitElse()) {
    264    //              [stack] RESUME_INDEX_OR_EXCEPTION, EXCEPTION_STACK
    265    return false;
    266  }
    267 
    268  if (!bce_->emit1(JSOp::Pop)) {
    269    //              [stack] RESUME_INDEX_OR_EXCEPTION
    270    return false;
    271  }
    272 
    273  if (controlInfo_ && !controlInfo_->continuations_.empty()) {
    274    if (!controlInfo_->emitContinuations(bce_)) {
    275      //              [stack]
    276      return false;
    277    }
    278  } else {
    279    // If there are no non-local jumps, then the only possible jump target
    280    // is the code immediately following this finally block. Instead of
    281    // emitting a tableswitch, we can simply pop the continuation index
    282    // and fall through.
    283    if (!bce_->emit1(JSOp::Pop)) {
    284      //              [stack]
    285      return false;
    286    }
    287  }
    288 
    289  if (!ifThrowing.emitEnd()) {
    290    return false;
    291  }
    292 
    293  bce_->hasTryFinally = true;
    294  return true;
    295 }
    296 
    297 bool TryEmitter::emitEnd() {
    298  if (!hasFinally()) {
    299    MOZ_ASSERT(state_ == State::Catch);
    300    if (!emitCatchEnd()) {
    301      return false;
    302    }
    303  } else {
    304    MOZ_ASSERT(state_ == State::Finally);
    305    if (!emitFinallyEnd()) {
    306      return false;
    307    }
    308  }
    309 
    310  MOZ_ASSERT(bce_->bytecodeSection().stackDepth() == depth_);
    311 
    312  if (catchAndFinallyJump_.offset.valid()) {
    313    // Fix up the end-of-try/catch jumps to come here.
    314    if (!bce_->emitJumpTargetAndPatch(catchAndFinallyJump_)) {
    315      return false;
    316    }
    317  }
    318 
    319  // Add the try note last, to let post-order give us the right ordering
    320  // (first to last for a given nesting level, inner to outer by level).
    321  if (hasCatch()) {
    322    if (!bce_->addTryNote(TryNoteKind::Catch, depth_, offsetAfterTryOp(),
    323                          tryEnd_.offset)) {
    324      return false;
    325    }
    326  }
    327 
    328  // If we've got a finally, mark try+catch region with additional
    329  // trynote to catch exceptions (re)thrown from a catch block or
    330  // for the try{}finally{} case.
    331  if (hasFinally()) {
    332    if (!bce_->addTryNote(TryNoteKind::Finally, depth_, offsetAfterTryOp(),
    333                          finallyStart_.offset)) {
    334      return false;
    335    }
    336  }
    337 
    338 #ifdef DEBUG
    339  state_ = State::End;
    340 #endif
    341  return true;
    342 }
    343 
    344 bool TryEmitter::shouldUpdateRval() const {
    345  return requiresControlInfo() && !bce_->sc->noScriptRval();
    346 }