tor-browser

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

breakpoint.js (6991B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /* global assert */
      6 
      7 "use strict";
      8 
      9 const {
     10  evalAndLogEvent,
     11  getThrownMessage,
     12 } = require("resource://devtools/server/actors/utils/logEvent.js");
     13 
     14 /**
     15 * Set breakpoints on all the given entry points with the given
     16 * BreakpointActor as the handler.
     17 *
     18 * @param BreakpointActor actor
     19 *        The actor handling the breakpoint hits.
     20 * @param Array entryPoints
     21 *        An array of objects of the form `{ script, offsets }`.
     22 */
     23 function setBreakpointAtEntryPoints(actor, entryPoints) {
     24  for (const { script, offsets } of entryPoints) {
     25    actor.addScript(script, offsets);
     26  }
     27 }
     28 
     29 exports.setBreakpointAtEntryPoints = setBreakpointAtEntryPoints;
     30 
     31 /**
     32 * BreakpointActors are instantiated for each breakpoint that has been installed
     33 * by the client. They are not true actors and do not communicate with the
     34 * client directly, but encapsulate the DebuggerScript locations where the
     35 * breakpoint is installed.
     36 */
     37 class BreakpointActor {
     38  constructor(threadActor, location) {
     39    // A map from Debugger.Script instances to the offsets which the breakpoint
     40    // has been set for in that script.
     41    this.scripts = new Map();
     42 
     43    this.threadActor = threadActor;
     44    this.location = location;
     45    this.options = null;
     46  }
     47 
     48  setOptions(options) {
     49    const oldOptions = this.options;
     50    this.options = options;
     51 
     52    for (const [script, offsets] of this.scripts) {
     53      this._newOffsetsOrOptions(script, offsets, oldOptions);
     54    }
     55  }
     56 
     57  destroy() {
     58    this.removeScripts();
     59    this.options = null;
     60  }
     61 
     62  hasScript(script) {
     63    return this.scripts.has(script);
     64  }
     65 
     66  /**
     67   * Called when this same breakpoint is added to another Debugger.Script
     68   * instance.
     69   *
     70   * @param script Debugger.Script
     71   *        The new source script on which the breakpoint has been set.
     72   * @param offsets Array
     73   *        Any offsets in the script the breakpoint is associated with.
     74   */
     75  addScript(script, offsets) {
     76    this.scripts.set(script, offsets.concat(this.scripts.get(offsets) || []));
     77    this._newOffsetsOrOptions(script, offsets, null);
     78  }
     79 
     80  /**
     81   * Remove the breakpoints from associated scripts and clear the script cache.
     82   */
     83  removeScripts() {
     84    for (const [script] of this.scripts) {
     85      script.clearBreakpoint(this);
     86    }
     87    this.scripts.clear();
     88  }
     89 
     90  /**
     91   * Called on changes to this breakpoint's script offsets or options.
     92   */
     93  _newOffsetsOrOptions(script, offsets) {
     94    // Clear any existing handler first in case this is called multiple times
     95    // after options change.
     96    for (const offset of offsets) {
     97      script.clearBreakpoint(this, offset);
     98    }
     99 
    100    // In all other cases, this is used as a script breakpoint handler.
    101    for (const offset of offsets) {
    102      script.setBreakpoint(offset, this);
    103    }
    104  }
    105 
    106  /**
    107   * Check if this breakpoint has a condition that doesn't error and
    108   * evaluates to true in frame.
    109   *
    110   * @param frame Debugger.Frame
    111   *        The frame to evaluate the condition in
    112   * @returns Object
    113   *          - result: boolean|undefined
    114   *            True when the conditional breakpoint should trigger a pause,
    115   *            false otherwise. If the condition evaluation failed/killed,
    116   *            `result` will be `undefined`.
    117   *          - message: string
    118   *            If the condition throws, this is the thrown message.
    119   */
    120  checkCondition(frame, condition) {
    121    // Ensure disabling breakpoint while evaluating the condition.
    122    // All but exception breakpoint to report any exception when running the condition.
    123    this.threadActor.insideClientEvaluation = {
    124      disableBreaks: true,
    125      reportExceptionsWhenBreaksAreDisabled: true,
    126    };
    127    let completion;
    128 
    129    // Temporarily enable pause on exception when evaluating the condition.
    130    const hadToEnablePauseOnException =
    131      !this.threadActor.isPauseOnExceptionsEnabled();
    132    try {
    133      if (hadToEnablePauseOnException) {
    134        this.threadActor.setPauseOnExceptions(true);
    135      }
    136      completion = frame.eval(condition, { hideFromDebugger: true });
    137    } finally {
    138      this.threadActor.insideClientEvaluation = null;
    139      if (hadToEnablePauseOnException) {
    140        this.threadActor.setPauseOnExceptions(false);
    141      }
    142    }
    143    if (completion) {
    144      if (completion.throw) {
    145        // The evaluation failed and threw
    146        return {
    147          result: true,
    148          message: getThrownMessage(completion),
    149        };
    150      } else if (completion.yield) {
    151        assert(false, "Shouldn't ever get yield completions from an eval");
    152      } else {
    153        return { result: !!completion.return };
    154      }
    155    }
    156    // The evaluation was killed (possibly by the slow script dialog)
    157    return { result: undefined };
    158  }
    159 
    160  /**
    161   * A function that the engine calls when a breakpoint has been hit.
    162   *
    163   * @param frame Debugger.Frame
    164   *        The stack frame that contained the breakpoint.
    165   */
    166  // eslint-disable-next-line complexity
    167  hit(frame) {
    168    if (this.threadActor.shouldSkipAnyBreakpoint) {
    169      return undefined;
    170    }
    171 
    172    // Don't pause if we are currently stepping (in or over) or the frame is
    173    // black-boxed.
    174    const location = this.threadActor.sourcesManager.getFrameLocation(frame);
    175    if (this.threadActor.sourcesManager.isFrameBlackBoxed(frame)) {
    176      return undefined;
    177    }
    178 
    179    // If we're trying to pop this frame, and we see a breakpoint at
    180    // the spot at which popping started, ignore it.  See bug 970469.
    181    const locationAtFinish = frame.onPop?.location;
    182    if (
    183      locationAtFinish &&
    184      locationAtFinish.line === location.line &&
    185      locationAtFinish.column === location.column
    186    ) {
    187      return undefined;
    188    }
    189 
    190    if (!this.threadActor.hasMoved(frame, "breakpoint")) {
    191      return undefined;
    192    }
    193 
    194    const reason = { type: "breakpoint", actors: [this.actorID] };
    195    const { condition, logValue } = this.options || {};
    196 
    197    if (condition) {
    198      const { result, message } = this.checkCondition(frame, condition);
    199 
    200      // Don't pause if the result is falsey
    201      if (!result) {
    202        return undefined;
    203      }
    204 
    205      if (message) {
    206        reason.type = "breakpointConditionThrown";
    207        reason.message = message;
    208      }
    209    }
    210 
    211    if (logValue) {
    212      return evalAndLogEvent({
    213        threadActor: this.threadActor,
    214        frame,
    215        level: "logPoint",
    216        expression: `[${logValue}]`,
    217        showStacktrace: this.options.showStacktrace,
    218      });
    219    }
    220 
    221    return this.threadActor._pauseAndRespond(frame, reason);
    222  }
    223 
    224  delete() {
    225    // Remove from the breakpoint store.
    226    this.threadActor.breakpointActorMap.deleteActor(this.location);
    227    // Remove the actual breakpoint from the associated scripts.
    228    this.removeScripts();
    229    this.destroy();
    230  }
    231 }
    232 
    233 exports.BreakpointActor = BreakpointActor;