tor-browser

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

BytecodeUtil-inl.h (7987B)


      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 #ifndef vm_BytecodeUtil_inl_h
      8 #define vm_BytecodeUtil_inl_h
      9 
     10 #include "vm/BytecodeUtil.h"
     11 
     12 #include "frontend/SourceNotes.h"  // SrcNote, SrcNoteType, SrcNoteIterator
     13 #include "js/ColumnNumber.h"  // JS::LimitedColumnNumberOneOrigin, JS::ColumnNumberOffset
     14 #include "vm/JSScript.h"
     15 
     16 namespace js {
     17 
     18 static inline unsigned GetDefCount(jsbytecode* pc) {
     19  /*
     20   * Add an extra pushed value for Or/And opcodes, so that they are included
     21   * in the pushed array of stack values for type inference.
     22   */
     23  JSOp op = JSOp(*pc);
     24  switch (op) {
     25    case JSOp::Or:
     26    case JSOp::And:
     27    case JSOp::Coalesce:
     28      return 1;
     29    case JSOp::Pick:
     30    case JSOp::Unpick:
     31      /*
     32       * Pick pops and pushes how deep it looks in the stack + 1
     33       * items. i.e. if the stack were |a b[2] c[1] d[0]|, pick 2
     34       * would pop b, c, and d to rearrange the stack to |a c[0]
     35       * d[1] b[2]|.
     36       */
     37      return pc[1] + 1;
     38    default:
     39      return StackDefs(op);
     40  }
     41 }
     42 
     43 static inline unsigned GetUseCount(jsbytecode* pc) {
     44  JSOp op = JSOp(*pc);
     45  if (op == JSOp::Pick || op == JSOp::Unpick) {
     46    return pc[1] + 1;
     47  }
     48 
     49  return StackUses(op, pc);
     50 }
     51 
     52 static inline JSOp ReverseCompareOp(JSOp op) {
     53  switch (op) {
     54    case JSOp::Gt:
     55      return JSOp::Lt;
     56    case JSOp::Ge:
     57      return JSOp::Le;
     58    case JSOp::Lt:
     59      return JSOp::Gt;
     60    case JSOp::Le:
     61      return JSOp::Ge;
     62    case JSOp::Eq:
     63    case JSOp::Ne:
     64    case JSOp::StrictEq:
     65    case JSOp::StrictNe:
     66      return op;
     67    default:
     68      MOZ_CRASH("unrecognized op");
     69  }
     70 }
     71 
     72 static inline JSOp NegateCompareOp(JSOp op) {
     73  switch (op) {
     74    case JSOp::Gt:
     75      return JSOp::Le;
     76    case JSOp::Ge:
     77      return JSOp::Lt;
     78    case JSOp::Lt:
     79      return JSOp::Ge;
     80    case JSOp::Le:
     81      return JSOp::Gt;
     82    case JSOp::Eq:
     83      return JSOp::Ne;
     84    case JSOp::Ne:
     85      return JSOp::Eq;
     86    case JSOp::StrictNe:
     87      return JSOp::StrictEq;
     88    case JSOp::StrictEq:
     89      return JSOp::StrictNe;
     90    default:
     91      MOZ_CRASH("unrecognized op");
     92  }
     93 }
     94 
     95 class BytecodeRange {
     96 public:
     97  BytecodeRange(JSContext* cx, JSScript* script)
     98      : script(cx, script), pc(script->code()), end(pc + script->length()) {}
     99  bool empty() const { return pc == end; }
    100  jsbytecode* frontPC() const { return pc; }
    101  JSOp frontOpcode() const { return JSOp(*pc); }
    102  size_t frontOffset() const { return script->pcToOffset(pc); }
    103  void popFront() { pc += GetBytecodeLength(pc); }
    104 
    105 private:
    106  RootedScript script;
    107  jsbytecode* pc;
    108  jsbytecode* end;
    109 };
    110 
    111 enum class SkipPrologueOps { No, Yes };
    112 
    113 class BytecodeRangeWithPosition : private BytecodeRange {
    114 public:
    115  using BytecodeRange::empty;
    116  using BytecodeRange::frontOffset;
    117  using BytecodeRange::frontOpcode;
    118  using BytecodeRange::frontPC;
    119 
    120  BytecodeRangeWithPosition(JSContext* cx, JSScript* script,
    121                            SkipPrologueOps skipPrologueOps)
    122      : BytecodeRange(cx, script),
    123        initialLine(script->lineno()),
    124        lineno(script->lineno()),
    125        column(script->column()),
    126        sn(script->notes()),
    127        snEnd(script->notesEnd()),
    128        snpc(script->code()),
    129        mainPC(script->main()),
    130        isBreakpoint(false),
    131        seenStepSeparator(false) {
    132    if (sn < snEnd) {
    133      snpc += sn->delta();
    134    }
    135    updatePosition();
    136    if (skipPrologueOps == SkipPrologueOps::Yes) {
    137      while (frontPC() != mainPC) {
    138        popFront();
    139      }
    140      MOZ_ASSERT(entryPointState != EntryPointState::NotEntryPoint);
    141    }
    142  }
    143 
    144  void popFront() {
    145    BytecodeRange::popFront();
    146    if (empty()) {
    147      entryPointState = EntryPointState::NotEntryPoint;
    148    } else {
    149      updatePosition();
    150    }
    151  }
    152 
    153  uint32_t frontLineNumber() const { return lineno; }
    154  JS::LimitedColumnNumberOneOrigin frontColumnNumber() const { return column; }
    155 
    156  // Entry points are restricted to bytecode offsets that have an
    157  // explicit mention in the line table.  This restriction avoids a
    158  // number of failing cases caused by some instructions not having
    159  // sensible (to the user) line numbers, and it is one way to
    160  // implement the idea that the bytecode emitter should tell the
    161  // debugger exactly which offsets represent "interesting" (to the
    162  // user) places to stop.
    163  bool frontIsEntryPoint() const {
    164    return entryPointState == EntryPointState::EntryPoint;
    165  }
    166 
    167  // Breakable points are explicitly marked by the emitter as locations where
    168  // the debugger may want to allow users to pause.
    169  bool frontIsBreakablePoint() const { return isBreakpoint; }
    170 
    171  // Breakable step points are the first breakable point after a
    172  // SrcNote::StepSep note has been encountered.
    173  bool frontIsBreakableStepPoint() const {
    174    return isBreakpoint && seenStepSeparator;
    175  }
    176 
    177 private:
    178  void updatePosition() {
    179    if (isBreakpoint) {
    180      isBreakpoint = false;
    181      seenStepSeparator = false;
    182    }
    183 
    184    // Determine the current line number by reading all source notes up to
    185    // and including the current offset.
    186    jsbytecode* lastLinePC = nullptr;
    187    SrcNoteIterator iter(sn, snEnd);
    188    while (!iter.atEnd() && snpc <= frontPC()) {
    189      auto sn = *iter;
    190 
    191      SrcNoteType type = sn->type();
    192      if (type == SrcNoteType::ColSpan) {
    193        column += SrcNote::ColSpan::getSpan(sn);
    194      } else if (type == SrcNoteType::SetLine) {
    195        lineno = SrcNote::SetLine::getLine(sn, initialLine);
    196        column = JS::LimitedColumnNumberOneOrigin();
    197      } else if (type == SrcNoteType::SetLineColumn) {
    198        lineno = SrcNote::SetLineColumn::getLine(sn, initialLine);
    199        column = SrcNote::SetLineColumn::getColumn(sn);
    200      } else if (type == SrcNoteType::NewLine) {
    201        lineno++;
    202        column = JS::LimitedColumnNumberOneOrigin();
    203      } else if (type == SrcNoteType::NewLineColumn) {
    204        lineno++;
    205        column = SrcNote::NewLineColumn::getColumn(sn);
    206      } else if (type == SrcNoteType::Breakpoint) {
    207        isBreakpoint = true;
    208      } else if (type == SrcNoteType::BreakpointStepSep) {
    209        isBreakpoint = true;
    210        seenStepSeparator = true;
    211      }
    212      lastLinePC = snpc;
    213      ++iter;
    214      if (!iter.atEnd()) {
    215        snpc += (*iter)->delta();
    216      }
    217    }
    218 
    219    sn = *iter;
    220 
    221    // The current bytecode op is an entry point if it's the first op of the
    222    // 'main' bytecode section or if it matches lastLinePC.
    223    //
    224    // If we're at a JSOp::JumpTarget op, we use ArtifactEntryPoint to mark the
    225    // next op as entry point instead. This prevents adding breakpoints for
    226    // empty statements in the JS code.
    227    if (frontPC() == mainPC || frontPC() == lastLinePC ||
    228        entryPointState == EntryPointState::ArtifactEntryPoint) {
    229      if (frontOpcode() == JSOp::JumpTarget) {
    230        entryPointState = EntryPointState::ArtifactEntryPoint;
    231      } else {
    232        entryPointState = EntryPointState::EntryPoint;
    233      }
    234    } else {
    235      entryPointState = EntryPointState::NotEntryPoint;
    236    }
    237 
    238    // Ops in the prologue are never entry points or breakable locations.
    239    if (frontPC() < mainPC) {
    240      MOZ_ASSERT(!frontIsEntryPoint());
    241      MOZ_ASSERT(!frontIsBreakablePoint());
    242      MOZ_ASSERT(!frontIsBreakableStepPoint());
    243    }
    244  }
    245 
    246  uint32_t initialLine;
    247 
    248  // Line number (1-origin).
    249  uint32_t lineno;
    250 
    251  // Column number in UTF-16 code units.
    252  JS::LimitedColumnNumberOneOrigin column;
    253 
    254  const SrcNote* sn;
    255  const SrcNote* snEnd;
    256  jsbytecode* snpc;
    257  jsbytecode* mainPC;
    258  bool isBreakpoint;
    259  bool seenStepSeparator;
    260 
    261  enum class EntryPointState : uint8_t {
    262    NotEntryPoint,
    263    EntryPoint,
    264    ArtifactEntryPoint
    265  };
    266  EntryPointState entryPointState = EntryPointState::NotEntryPoint;
    267 };
    268 
    269 }  // namespace js
    270 
    271 #endif /* vm_BytecodeUtil_inl_h */