This repository was archived by the owner on Jul 9, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Expand file tree
/
Copy pathexecution-point-utils.js
More file actions
209 lines (186 loc) · 6.85 KB
/
Copy pathexecution-point-utils.js
File metadata and controls
209 lines (186 loc) · 6.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
/* 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: The ID of the position's script. This is optional for "EnterFrame".
//
// offset: For "Break" and "OnStep", the offset within the script.
//
// frameIndex: For "OnStep", "OnPop" and optionally "EnterFrame", 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.
// Ideally, we would only bump the progress counter when a script starts
// executing, but this is not always guaranteed to happen and so we tolerate
// such comparisons. See bug 1580337.
if (!!posA != !!posB) {
return !!posB;
}
if (!posA || positionEquals(posA, posB)) {
return false;
}
assert("frameIndex" in posA && "frameIndex" in posB);
assert("script" in posA && "script" in posB);
// If either point is an EnterFrame, it bumped the progress counter and
// happens first.
if (posA.kind == "EnterFrame" || posB.kind == "EnterFrame") {
return posA.kind == "EnterFrame";
}
// 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.
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);
}
// eslint-disable-next-line no-unused-vars
function pointToString(point) {
if (point.position) {
return `${point.checkpoint}:${point.progress}:${positionToString(
point.position
)}`;
}
return `${point.checkpoint}:${point.progress}`;
}
// eslint-disable-next-line no-unused-vars
function pointArrayIncludes(points, point) {
return points.some(p => pointEquals(point, p));
}
// Find the closest point in an array to point, either before or after it.
// If inclusive is set then the point itself can be returned, if it is in the
// array.
// eslint-disable-next-line no-unused-vars
function findClosestPoint(points, point, before, inclusive) {
let result = null;
for (const p of points) {
if (inclusive && pointEquals(p, point)) {
return p;
}
if (before ? pointPrecedes(p, point) : pointPrecedes(point, p)) {
if (
!result ||
(before ? pointPrecedes(result, p) : pointPrecedes(p, result))
) {
result = p;
}
}
}
return result;
}
// 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;
}
// EnterFrame positions may or may not specify a script.
if (
posA.kind == "EnterFrame" &&
posB.kind == "EnterFrame" &&
!posA.script &&
posB.script
) {
return true;
}
return false;
}
function positionToString(pos) {
return `${pos.kind}:${pos.script}:${pos.offset}:${pos.frameIndex}`;
}
function assert(v) {
if (!v) {
throw new Error(`Assertion failed! ${Error().stack}`);
}
}
this.EXPORTED_SYMBOLS = [
"pointPrecedes",
"pointEquals",
"pointToString",
"pointArrayIncludes",
"findClosestPoint",
"positionEquals",
"positionSubsumes",
"positionToString",
];