forked from mirrors/gecko-dev
# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D35894 --HG-- extra : source : 4722b924e08478f5337ab509718bd66906bf472f extra : amend_source : a5baa1aab21639fdba44537e3a10b179b0073cb4
152 lines
5.5 KiB
JavaScript
152 lines
5.5 KiB
JavaScript
/* 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/. */
|
|
|
|
"use strict";
|
|
|
|
// Utilities for working with execution points and breakpoint positions, which
|
|
// are used when replaying to identify points in the execution where the
|
|
// debugger can stop or where events of interest have occurred.
|
|
//
|
|
// A breakpoint position describes where breakpoints can be installed when
|
|
// replaying, and has the following properties:
|
|
//
|
|
// kind: The kind of breakpoint, which has one of the following values:
|
|
// "Break": Break at an offset in a script.
|
|
// "OnStep": Break at an offset in a script with a given frame depth.
|
|
// "OnPop": Break when a script's frame with a given frame depth is popped.
|
|
// "EnterFrame": Break when any script is entered.
|
|
//
|
|
// script: For all kinds but "EnterFrame", the ID of the position's script.
|
|
//
|
|
// offset: For "Break" and "OnStep", the offset within the script.
|
|
//
|
|
// frameIndex: For "OnStep" and "OnPop", the index of the topmost script frame.
|
|
// Indexes start at zero for the first frame pushed, and increase with the
|
|
// depth of the frame.
|
|
//
|
|
// An execution point is a unique identifier for a point in the recording where
|
|
// the debugger can pause, and has the following properties:
|
|
//
|
|
// checkpoint: ID of the most recent checkpoint.
|
|
//
|
|
// progress: Value of the progress counter when the point is reached.
|
|
//
|
|
// position: Optional breakpoint position where the pause occurs at. This cannot
|
|
// have the "Break" kind (see below) and is missing if the execution point is
|
|
// at the checkpoint itself.
|
|
//
|
|
// The above properties must uniquely identify a single point in the recording.
|
|
// This property is ensured mainly through how the progress counter is managed.
|
|
// The position in the point must not be able to execute multiple times without
|
|
// the progress counter being incremented.
|
|
//
|
|
// The progress counter is incremented whenever a frame is pushed onto the stack
|
|
// or a loop entry point is reached. Because it increments when looping, a
|
|
// specific JS frame cannot reach the same position multiple times with the same
|
|
// progress counter. Because it increments when pushing frames, different JS
|
|
// frames with the same frame depth cannot reach the same position multiple
|
|
// times with the same progress counter. The position must specify the frame
|
|
// depth for this argument to hold, so "Break" positions are not used in
|
|
// execution points. They are used when the user-specified breakpoints are being
|
|
// installed, though, and when pausing the execution point will use the
|
|
// appropriate "OnStep" position for the frame depth.
|
|
|
|
// Return whether pointA happens before pointB in the recording.
|
|
function pointPrecedes(pointA, pointB) {
|
|
if (pointA.checkpoint != pointB.checkpoint) {
|
|
return pointA.checkpoint < pointB.checkpoint;
|
|
}
|
|
if (pointA.progress != pointB.progress) {
|
|
return pointA.progress < pointB.progress;
|
|
}
|
|
|
|
const posA = pointA.position;
|
|
const posB = pointB.position;
|
|
|
|
// Except when we're at a checkpoint, all execution points have positions.
|
|
// Because the progress counter is bumped when executing script, points with
|
|
// the same checkpoint and progress counter will either both be at that
|
|
// checkpoint, or both be at an intra-checkpoint point.
|
|
assert(!!posA == !!posB);
|
|
if (!posA || positionEquals(posA, posB)) {
|
|
return false;
|
|
}
|
|
|
|
// If an execution point doesn't have a frame index (i.e. EnterFrame) then it
|
|
// has bumped the progress counter and predates everything else that is
|
|
// associated with the same progress counter.
|
|
if ("frameIndex" in posA != "frameIndex" in posB) {
|
|
return "frameIndex" in posB;
|
|
}
|
|
|
|
// Only certain execution point kinds do not bump the progress counter.
|
|
assert(posA.kind == "OnStep" || posA.kind == "OnPop");
|
|
assert(posB.kind == "OnStep" || posB.kind == "OnPop");
|
|
|
|
// Deeper frames predate shallower frames, if the progress counter is the
|
|
// same. We bump the progress counter when pushing frames, but not when
|
|
// popping them.
|
|
assert("frameIndex" in posA && "frameIndex" in posB);
|
|
if (posA.frameIndex != posB.frameIndex) {
|
|
return posA.frameIndex > posB.frameIndex;
|
|
}
|
|
|
|
// Within a frame, OnStep points come before OnPop points.
|
|
if (posA.kind != posB.kind) {
|
|
return posA.kind == "OnStep";
|
|
}
|
|
|
|
// Earlier script locations predate later script locations.
|
|
assert("offset" in posA && "offset" in posB);
|
|
return posA.offset < posB.offset;
|
|
}
|
|
|
|
// Return whether two execution points are the same.
|
|
// eslint-disable-next-line no-unused-vars
|
|
function pointEquals(pointA, pointB) {
|
|
return !pointPrecedes(pointA, pointB) && !pointPrecedes(pointB, pointA);
|
|
}
|
|
|
|
// Return whether two breakpoint positions are the same.
|
|
function positionEquals(posA, posB) {
|
|
return (
|
|
posA.kind == posB.kind &&
|
|
posA.script == posB.script &&
|
|
posA.offset == posB.offset &&
|
|
posA.frameIndex == posB.frameIndex
|
|
);
|
|
}
|
|
|
|
// Return whether an execution point matching posB also matches posA.
|
|
// eslint-disable-next-line no-unused-vars
|
|
function positionSubsumes(posA, posB) {
|
|
if (positionEquals(posA, posB)) {
|
|
return true;
|
|
}
|
|
|
|
if (
|
|
posA.kind == "Break" &&
|
|
posB.kind == "OnStep" &&
|
|
posA.script == posB.script &&
|
|
posA.offset == posB.offset
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function assert(v) {
|
|
if (!v) {
|
|
dump(`Assertion failed: ${Error().stack}\n`);
|
|
throw new Error("Assertion failed!");
|
|
}
|
|
}
|
|
|
|
this.EXPORTED_SYMBOLS = [
|
|
"pointPrecedes",
|
|
"pointEquals",
|
|
"positionEquals",
|
|
"positionSubsumes",
|
|
];
|