This repository has been archived by the owner on Feb 26, 2024. It is now read-only.
/
index.js
355 lines (318 loc) · 12.4 KB
/
index.js
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
import debugModule from "debug";
const debug = debugModule("debugger:controller:sagas");
import { put, call, race, take, select } from "redux-saga/effects";
import { prefixName, isDeliberatelySkippedNodeType } from "lib/helpers";
import * as trace from "lib/trace/sagas";
import * as data from "lib/data/sagas";
import * as txlog from "lib/txlog/sagas";
import * as evm from "lib/evm/sagas";
import * as sourcemapping from "lib/sourcemapping/sagas";
import * as stacktrace from "lib/stacktrace/sagas";
import * as actions from "../actions";
import controller from "../selectors";
const STEP_SAGAS = {
[actions.ADVANCE]: advance,
[actions.STEP_NEXT]: stepNext,
[actions.STEP_OVER]: stepOver,
[actions.STEP_INTO]: stepInto,
[actions.STEP_OUT]: stepOut,
[actions.CONTINUE]: continueUntilBreakpoint,
[actions.RUN_TO_END]: runToEnd
};
export function* saga() {
while (true) {
debug("waiting for control action");
let action = yield take(Object.keys(STEP_SAGAS));
if (!(yield select(controller.current.trace.loaded))) {
continue; //while no trace is loaded, step actions are ignored
}
debug("got control action");
let saga = STEP_SAGAS[action.type];
yield put(actions.startStepping());
yield race({
exec: call(saga, action), //not all will use this
interrupt: take(actions.INTERRUPT)
});
yield put(actions.doneStepping());
}
}
export default prefixName("controller", saga);
/**
* Advance the state by the given number of instructions (but not past the end)
* (if no count given, advance 1)
*/
function* advance(action) {
let count =
action !== undefined && action.count !== undefined ? action.count : 1;
//default is, as mentioned, to advance 1
for (
let i = 0;
i < count && !(yield select(controller.current.trace.finished));
i++
) {
yield* trace.advance();
}
}
/**
* stepNext - step to the next logical code segment
*
* Note: It might take multiple instructions to express the same section of code.
* "Stepping", then, is stepping to the next logical item, not stepping to the next
* instruction. See advance() if you'd like to advance by one instruction.
*
* Note that if you are not in an internal source, this function will not stop in one
* (unless it hits the end of the trace); you will need to use advance() to get into
* one. However, if you are already in an internal source, this function will not
* automatically step all the way out of it.
*/
function* stepNext() {
const starting = yield select(controller.current.location);
const isStartingGenerated = yield select(
controller.current.isAnyFrameGenerated
);
const allowInternal = yield select(controller.stepIntoInternalSources);
let upcoming, finished, isUpcomingGenerated;
do {
// advance at least once step
yield* advance();
// and check the next source range
upcoming = yield select(controller.current.location);
isUpcomingGenerated = yield select(controller.current.isAnyFrameGenerated);
finished = yield select(controller.current.trace.finished);
// if the next step's source range is still the same, keep going
} while (
!finished &&
(!upcoming ||
//don't stop on an internal source unless allowInternal is on or
//we started in an internal source
(!allowInternal &&
(upcoming.source.internal || isUpcomingGenerated) &&
!(starting.source.internal || isStartingGenerated)) ||
upcoming.sourceRange.length === 0 ||
upcoming.source.id === undefined ||
(upcoming.node && isDeliberatelySkippedNodeType(upcoming.node)) ||
(upcoming.sourceRange.start === starting.sourceRange.start &&
upcoming.sourceRange.length === starting.sourceRange.length &&
upcoming.source.id === starting.source.id))
);
}
/**
* stepInto - step into the current function
*
* Conceptually this is easy, but from a programming standpoint it's hard.
* Code like `getBalance(msg.sender)` might be highlighted, but there could
* be a number of different intermediate steps (like evaluating `msg.sender`)
* before `getBalance` is stepped into. This function will step into the first
* function available (where instruction.jump == "i"), ignoring any intermediate
* steps that fall within the same code range. If there's a step encountered
* that exists outside of the range, then stepInto will only execute until that
* step.
*/
function* stepInto() {
const startingDepth = yield select(controller.current.functionDepth);
const startingLocation = yield select(controller.current.location);
debug("startingDepth: %d", startingDepth);
debug("starting source range: %O", (startingLocation || {}).sourceRange);
let currentDepth;
let currentLocation;
let finished;
do {
yield* stepNext();
currentDepth = yield select(controller.current.functionDepth);
currentLocation = yield select(controller.current.location);
finished = yield select(controller.current.trace.finished);
debug("currentDepth: %d", currentDepth);
debug("current source range: %O", (currentLocation || {}).sourceRange);
debug("finished: %o", finished);
} while (
//we aren't finished,
!finished &&
// the function stack has not increased,
currentDepth <= startingDepth &&
// we haven't changed files,
currentLocation.source.id === startingLocation.source.id &&
//and we haven't changed lines
currentLocation.sourceRange.lines.start.line ===
startingLocation.sourceRange.lines.start.line
);
}
/**
* Step out of the current function
*
* This will run until the debugger encounters a decrease in function depth
* (or finishes).
*/
function* stepOut() {
const startingDepth = yield select(controller.current.functionDepth);
let currentDepth;
let finished;
do {
yield* stepNext();
currentDepth = yield select(controller.current.functionDepth);
finished = yield select(controller.current.trace.finished);
} while (!finished && currentDepth >= startingDepth);
}
/**
* stepOver - step over the current line
*
* Step over the current line. This will step to the next instruction that
* exists on a different line of code within the same function depth (or lower).
* (However, if you are on the definition of a function, it will step over that
* function entirely, returning you to the next function depth.)
*/
function* stepOver() {
const startingDepth = yield select(controller.current.functionDepth);
const startingLocation = yield select(controller.current.location);
const startingNode = yield select(controller.current.location.node);
let currentDepth;
let currentLocation;
let finished;
//special case: what if you're on a function definition?
if (
startingNode &&
((startingNode.nodeType === "FunctionDefinition" &&
//note that we exclude base constructors here, because we don't have a
//good way to go to the end of a base constructor; we'll just perform
//an ordinary stepOver in that case
!(yield select(controller.current.onBaseConstructorDefinition))) ||
//for Yul functions, we use a special selector to make sure we're seeing
//the function definition as we enter it rather than as it's defined
(yield select(controller.current.onYulFunctionDefinitionWhileEntering)))
) {
yield* stepOut();
return;
}
do {
yield* stepNext();
currentDepth = yield select(controller.current.functionDepth);
currentLocation = yield select(controller.current.location);
finished = yield select(controller.current.trace.finished);
} while (
// keep stepping provided:
//
// we haven't finished
!finished &&
// we haven't jumped out
currentDepth >= startingDepth &&
// either: function depth is greater than starting (ignore function calls)
// or, if we're at the same depth, keep stepping until we're on a new
// line (which may be in a new file)
(currentDepth > startingDepth ||
(currentLocation.source.id === startingLocation.source.id &&
currentLocation.sourceRange.lines.start.line ===
startingLocation.sourceRange.lines.start.line))
);
}
/**
* runToEnd - run the debugger till the end
*/
function* runToEnd() {
let finished;
do {
yield* advance();
finished = yield select(controller.current.trace.finished);
} while (!finished);
}
/**
* continueUntilBreakpoint - step through execution until a breakpoint
*/
function* continueUntilBreakpoint(action) {
//if breakpoints was not specified, use the stored list from the state.
//if it was, override that with the specified list.
//note that explicitly specifying an empty list will advance to the end.
let breakpoints =
action !== undefined && action.breakpoints !== undefined
? action.breakpoints
: yield select(controller.breakpoints);
let breakpointHit = false;
let currentLocation = yield select(controller.current.location);
let currentSourceId = currentLocation.source.id;
let currentLine = currentLocation.sourceRange.lines.start.line;
let currentStart = currentLocation.sourceRange.start;
let currentLength = currentLocation.sourceRange.start;
//note that if allow internal is on, we don't turn on the special treatment
//of user sources even if we started in one
const startedInUserSource =
!(yield select(controller.stepIntoInternalSources)) &&
currentLocation.source.id !== undefined &&
!currentLocation.source.internal;
//the following are set regardless, but only used if startedInUserSource
let lastUserSourceId = currentSourceId;
let lastUserLine = currentLine;
let lastUserStart = currentStart;
let lastUserLength = currentLength;
do {
yield* advance(); //note: this avoids using stepNext in order to
//allow breakpoints in internal sources to work properly
//note these three have not been updated yet; they'll be updated a
//few lines down. but at this point these are still the previous
//values.
let previousLine = currentLine;
let previousStart = currentStart;
let previousLength = currentLength;
let previousSourceId = currentSourceId;
if (!currentLocation.source.internal) {
lastUserSourceId = currentSourceId;
lastUserLine = currentLine;
lastUserStart = currentStart;
lastUserLength = currentLength;
}
currentLocation = yield select(controller.current.location);
let finished = yield select(controller.current.trace.finished);
if (finished) {
break; //can break immediately if finished
}
currentSourceId = currentLocation.source.id;
if (currentSourceId === undefined) {
continue; //never stop on an unmapped instruction
}
currentLine = currentLocation.sourceRange.lines.start.line;
currentStart = currentLocation.sourceRange.start;
currentLength = currentLocation.sourceRange.length;
breakpointHit =
breakpoints.filter(({ sourceId, line, start, length }) => {
if (start !== undefined && length !== undefined) {
//node-based (well, source-range-based) breakpoint
return (
sourceId === currentSourceId &&
start === currentStart &&
length === currentLength &&
(currentSourceId !== previousSourceId ||
currentStart !== previousStart ||
currentLength !== previousLength) &&
//if we started in a user source (& allow internal is off),
//we need to make sure we've moved from a user-source POV
(!startedInUserSource ||
currentSourceId !== lastUserSourceId ||
currentStart !== lastUserStart ||
currentLength !== lastUserLength)
);
}
//otherwise, we have a line-style breakpoint; we want to stop at the
//*first* point on the line
return (
sourceId === currentSourceId &&
line === currentLine &&
(currentSourceId !== previousSourceId ||
currentLine !== previousLine) &&
//again, if started in a user source w/ allow internal off,
//need to make sure we've moved from a *user*-source POV
(!startedInUserSource ||
currentSourceId !== lastUserSourceId ||
currentLine !== lastUserLine)
);
}).length > 0;
} while (!breakpointHit);
}
/**
* reset -- reset the state of the debugger
* (we'll just reset all submodules regardless of which are in use)
*/
export function* reset() {
yield* data.reset();
yield* evm.reset();
yield* sourcemapping.reset();
yield* trace.reset();
yield* stacktrace.reset();
yield* txlog.reset();
}