1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | const assert = require("assert"),
|
13 | CodePath = require("./code-path"),
|
14 | CodePathSegment = require("./code-path-segment"),
|
15 | IdGenerator = require("./id-generator"),
|
16 | debug = require("./debug-helpers"),
|
17 | astUtils = require("../ast-utils");
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | function isCaseNode(node) {
|
30 | return Boolean(node.test);
|
31 | }
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | function isForkingByTrueOrFalse(node) {
|
41 | const parent = node.parent;
|
42 |
|
43 | switch (parent.type) {
|
44 | case "ConditionalExpression":
|
45 | case "IfStatement":
|
46 | case "WhileStatement":
|
47 | case "DoWhileStatement":
|
48 | case "ForStatement":
|
49 | return parent.test === node;
|
50 |
|
51 | case "LogicalExpression":
|
52 | return true;
|
53 |
|
54 | default:
|
55 | return false;
|
56 | }
|
57 | }
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 | function getBooleanValueIfSimpleConstant(node) {
|
71 | if (node.type === "Literal") {
|
72 | return Boolean(node.value);
|
73 | }
|
74 | return void 0;
|
75 | }
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 | function isIdentifierReference(node) {
|
86 | const parent = node.parent;
|
87 |
|
88 | switch (parent.type) {
|
89 | case "LabeledStatement":
|
90 | case "BreakStatement":
|
91 | case "ContinueStatement":
|
92 | case "ArrayPattern":
|
93 | case "RestElement":
|
94 | case "ImportSpecifier":
|
95 | case "ImportDefaultSpecifier":
|
96 | case "ImportNamespaceSpecifier":
|
97 | case "CatchClause":
|
98 | return false;
|
99 |
|
100 | case "FunctionDeclaration":
|
101 | case "FunctionExpression":
|
102 | case "ArrowFunctionExpression":
|
103 | case "ClassDeclaration":
|
104 | case "ClassExpression":
|
105 | case "VariableDeclarator":
|
106 | return parent.id !== node;
|
107 |
|
108 | case "Property":
|
109 | case "MethodDefinition":
|
110 | return (
|
111 | parent.key !== node ||
|
112 | parent.computed ||
|
113 | parent.shorthand
|
114 | );
|
115 |
|
116 | case "AssignmentPattern":
|
117 | return parent.key !== node;
|
118 |
|
119 | default:
|
120 | return true;
|
121 | }
|
122 | }
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | function forwardCurrentToHead(analyzer, node) {
|
138 | const codePath = analyzer.codePath;
|
139 | const state = CodePath.getState(codePath);
|
140 | const currentSegments = state.currentSegments;
|
141 | const headSegments = state.headSegments;
|
142 | const end = Math.max(currentSegments.length, headSegments.length);
|
143 | let i, currentSegment, headSegment;
|
144 |
|
145 |
|
146 | for (i = 0; i < end; ++i) {
|
147 | currentSegment = currentSegments[i];
|
148 | headSegment = headSegments[i];
|
149 |
|
150 | if (currentSegment !== headSegment && currentSegment) {
|
151 | debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
|
152 |
|
153 | if (currentSegment.reachable) {
|
154 | analyzer.emitter.emit(
|
155 | "onCodePathSegmentEnd",
|
156 | currentSegment,
|
157 | node
|
158 | );
|
159 | }
|
160 | }
|
161 | }
|
162 |
|
163 |
|
164 | state.currentSegments = headSegments;
|
165 |
|
166 |
|
167 | for (i = 0; i < end; ++i) {
|
168 | currentSegment = currentSegments[i];
|
169 | headSegment = headSegments[i];
|
170 |
|
171 | if (currentSegment !== headSegment && headSegment) {
|
172 | debug.dump(`onCodePathSegmentStart ${headSegment.id}`);
|
173 |
|
174 | CodePathSegment.markUsed(headSegment);
|
175 | if (headSegment.reachable) {
|
176 | analyzer.emitter.emit(
|
177 | "onCodePathSegmentStart",
|
178 | headSegment,
|
179 | node
|
180 | );
|
181 | }
|
182 | }
|
183 | }
|
184 |
|
185 | }
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 | function leaveFromCurrentSegment(analyzer, node) {
|
196 | const state = CodePath.getState(analyzer.codePath);
|
197 | const currentSegments = state.currentSegments;
|
198 |
|
199 | for (let i = 0; i < currentSegments.length; ++i) {
|
200 | const currentSegment = currentSegments[i];
|
201 |
|
202 | debug.dump(`onCodePathSegmentEnd ${currentSegment.id}`);
|
203 | if (currentSegment.reachable) {
|
204 | analyzer.emitter.emit(
|
205 | "onCodePathSegmentEnd",
|
206 | currentSegment,
|
207 | node
|
208 | );
|
209 | }
|
210 | }
|
211 |
|
212 | state.currentSegments = [];
|
213 | }
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 | function preprocess(analyzer, node) {
|
227 | const codePath = analyzer.codePath;
|
228 | const state = CodePath.getState(codePath);
|
229 | const parent = node.parent;
|
230 |
|
231 | switch (parent.type) {
|
232 | case "LogicalExpression":
|
233 | if (parent.right === node) {
|
234 | state.makeLogicalRight();
|
235 | }
|
236 | break;
|
237 |
|
238 | case "ConditionalExpression":
|
239 | case "IfStatement":
|
240 |
|
241 | |
242 |
|
243 |
|
244 |
|
245 |
|
246 | if (parent.consequent === node) {
|
247 | state.makeIfConsequent();
|
248 | } else if (parent.alternate === node) {
|
249 | state.makeIfAlternate();
|
250 | }
|
251 | break;
|
252 |
|
253 | case "SwitchCase":
|
254 | if (parent.consequent[0] === node) {
|
255 | state.makeSwitchCaseBody(false, !parent.test);
|
256 | }
|
257 | break;
|
258 |
|
259 | case "TryStatement":
|
260 | if (parent.handler === node) {
|
261 | state.makeCatchBlock();
|
262 | } else if (parent.finalizer === node) {
|
263 | state.makeFinallyBlock();
|
264 | }
|
265 | break;
|
266 |
|
267 | case "WhileStatement":
|
268 | if (parent.test === node) {
|
269 | state.makeWhileTest(getBooleanValueIfSimpleConstant(node));
|
270 | } else {
|
271 | assert(parent.body === node);
|
272 | state.makeWhileBody();
|
273 | }
|
274 | break;
|
275 |
|
276 | case "DoWhileStatement":
|
277 | if (parent.body === node) {
|
278 | state.makeDoWhileBody();
|
279 | } else {
|
280 | assert(parent.test === node);
|
281 | state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node));
|
282 | }
|
283 | break;
|
284 |
|
285 | case "ForStatement":
|
286 | if (parent.test === node) {
|
287 | state.makeForTest(getBooleanValueIfSimpleConstant(node));
|
288 | } else if (parent.update === node) {
|
289 | state.makeForUpdate();
|
290 | } else if (parent.body === node) {
|
291 | state.makeForBody();
|
292 | }
|
293 | break;
|
294 |
|
295 | case "ForInStatement":
|
296 | case "ForOfStatement":
|
297 | if (parent.left === node) {
|
298 | state.makeForInOfLeft();
|
299 | } else if (parent.right === node) {
|
300 | state.makeForInOfRight();
|
301 | } else {
|
302 | assert(parent.body === node);
|
303 | state.makeForInOfBody();
|
304 | }
|
305 | break;
|
306 |
|
307 | case "AssignmentPattern":
|
308 |
|
309 | |
310 |
|
311 |
|
312 |
|
313 |
|
314 | if (parent.right === node) {
|
315 | state.pushForkContext();
|
316 | state.forkBypassPath();
|
317 | state.forkPath();
|
318 | }
|
319 | break;
|
320 |
|
321 | default:
|
322 | break;
|
323 | }
|
324 | }
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 | function processCodePathToEnter(analyzer, node) {
|
334 | let codePath = analyzer.codePath;
|
335 | let state = codePath && CodePath.getState(codePath);
|
336 | const parent = node.parent;
|
337 |
|
338 | switch (node.type) {
|
339 | case "Program":
|
340 | case "FunctionDeclaration":
|
341 | case "FunctionExpression":
|
342 | case "ArrowFunctionExpression":
|
343 | if (codePath) {
|
344 |
|
345 |
|
346 | forwardCurrentToHead(analyzer, node);
|
347 | debug.dumpState(node, state, false);
|
348 | }
|
349 |
|
350 |
|
351 | codePath = analyzer.codePath = new CodePath(
|
352 | analyzer.idGenerator.next(),
|
353 | codePath,
|
354 | analyzer.onLooped
|
355 | );
|
356 | state = CodePath.getState(codePath);
|
357 |
|
358 |
|
359 | debug.dump(`onCodePathStart ${codePath.id}`);
|
360 | analyzer.emitter.emit("onCodePathStart", codePath, node);
|
361 | break;
|
362 |
|
363 | case "LogicalExpression":
|
364 | state.pushChoiceContext(node.operator, isForkingByTrueOrFalse(node));
|
365 | break;
|
366 |
|
367 | case "ConditionalExpression":
|
368 | case "IfStatement":
|
369 | state.pushChoiceContext("test", false);
|
370 | break;
|
371 |
|
372 | case "SwitchStatement":
|
373 | state.pushSwitchContext(
|
374 | node.cases.some(isCaseNode),
|
375 | astUtils.getLabel(node)
|
376 | );
|
377 | break;
|
378 |
|
379 | case "TryStatement":
|
380 | state.pushTryContext(Boolean(node.finalizer));
|
381 | break;
|
382 |
|
383 | case "SwitchCase":
|
384 |
|
385 | |
386 |
|
387 |
|
388 |
|
389 |
|
390 | if (parent.discriminant !== node && parent.cases[0] !== node) {
|
391 | state.forkPath();
|
392 | }
|
393 | break;
|
394 |
|
395 | case "WhileStatement":
|
396 | case "DoWhileStatement":
|
397 | case "ForStatement":
|
398 | case "ForInStatement":
|
399 | case "ForOfStatement":
|
400 | state.pushLoopContext(node.type, astUtils.getLabel(node));
|
401 | break;
|
402 |
|
403 | case "LabeledStatement":
|
404 | if (!astUtils.isBreakableStatement(node.body)) {
|
405 | state.pushBreakContext(false, node.label.name);
|
406 | }
|
407 | break;
|
408 |
|
409 | default:
|
410 | break;
|
411 | }
|
412 |
|
413 |
|
414 | forwardCurrentToHead(analyzer, node);
|
415 | debug.dumpState(node, state, false);
|
416 | }
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 | function processCodePathToExit(analyzer, node) {
|
426 | const codePath = analyzer.codePath;
|
427 | const state = CodePath.getState(codePath);
|
428 | let dontForward = false;
|
429 |
|
430 | switch (node.type) {
|
431 | case "IfStatement":
|
432 | case "ConditionalExpression":
|
433 | case "LogicalExpression":
|
434 | state.popChoiceContext();
|
435 | break;
|
436 |
|
437 | case "SwitchStatement":
|
438 | state.popSwitchContext();
|
439 | break;
|
440 |
|
441 | case "SwitchCase":
|
442 |
|
443 | |
444 |
|
445 |
|
446 |
|
447 |
|
448 | if (node.consequent.length === 0) {
|
449 | state.makeSwitchCaseBody(true, !node.test);
|
450 | }
|
451 | if (state.forkContext.reachable) {
|
452 | dontForward = true;
|
453 | }
|
454 | break;
|
455 |
|
456 | case "TryStatement":
|
457 | state.popTryContext();
|
458 | break;
|
459 |
|
460 | case "BreakStatement":
|
461 | forwardCurrentToHead(analyzer, node);
|
462 | state.makeBreak(node.label && node.label.name);
|
463 | dontForward = true;
|
464 | break;
|
465 |
|
466 | case "ContinueStatement":
|
467 | forwardCurrentToHead(analyzer, node);
|
468 | state.makeContinue(node.label && node.label.name);
|
469 | dontForward = true;
|
470 | break;
|
471 |
|
472 | case "ReturnStatement":
|
473 | forwardCurrentToHead(analyzer, node);
|
474 | state.makeReturn();
|
475 | dontForward = true;
|
476 | break;
|
477 |
|
478 | case "ThrowStatement":
|
479 | forwardCurrentToHead(analyzer, node);
|
480 | state.makeThrow();
|
481 | dontForward = true;
|
482 | break;
|
483 |
|
484 | case "Identifier":
|
485 | if (isIdentifierReference(node)) {
|
486 | state.makeFirstThrowablePathInTryBlock();
|
487 | dontForward = true;
|
488 | }
|
489 | break;
|
490 |
|
491 | case "CallExpression":
|
492 | case "MemberExpression":
|
493 | case "NewExpression":
|
494 | state.makeFirstThrowablePathInTryBlock();
|
495 | break;
|
496 |
|
497 | case "WhileStatement":
|
498 | case "DoWhileStatement":
|
499 | case "ForStatement":
|
500 | case "ForInStatement":
|
501 | case "ForOfStatement":
|
502 | state.popLoopContext();
|
503 | break;
|
504 |
|
505 | case "AssignmentPattern":
|
506 | state.popForkContext();
|
507 | break;
|
508 |
|
509 | case "LabeledStatement":
|
510 | if (!astUtils.isBreakableStatement(node.body)) {
|
511 | state.popBreakContext();
|
512 | }
|
513 | break;
|
514 |
|
515 | default:
|
516 | break;
|
517 | }
|
518 |
|
519 |
|
520 | if (!dontForward) {
|
521 | forwardCurrentToHead(analyzer, node);
|
522 | }
|
523 | debug.dumpState(node, state, true);
|
524 | }
|
525 |
|
526 |
|
527 |
|
528 |
|
529 |
|
530 |
|
531 |
|
532 |
|
533 | function postprocess(analyzer, node) {
|
534 | switch (node.type) {
|
535 | case "Program":
|
536 | case "FunctionDeclaration":
|
537 | case "FunctionExpression":
|
538 | case "ArrowFunctionExpression": {
|
539 | let codePath = analyzer.codePath;
|
540 |
|
541 |
|
542 | CodePath.getState(codePath).makeFinal();
|
543 |
|
544 |
|
545 | leaveFromCurrentSegment(analyzer, node);
|
546 |
|
547 |
|
548 | debug.dump(`onCodePathEnd ${codePath.id}`);
|
549 | analyzer.emitter.emit("onCodePathEnd", codePath, node);
|
550 | debug.dumpDot(codePath);
|
551 |
|
552 | codePath = analyzer.codePath = analyzer.codePath.upper;
|
553 | if (codePath) {
|
554 | debug.dumpState(node, CodePath.getState(codePath), true);
|
555 | }
|
556 | break;
|
557 | }
|
558 |
|
559 | default:
|
560 | break;
|
561 | }
|
562 | }
|
563 |
|
564 |
|
565 |
|
566 |
|
567 |
|
568 |
|
569 |
|
570 |
|
571 |
|
572 | class CodePathAnalyzer {
|
573 |
|
574 | |
575 |
|
576 |
|
577 | constructor(eventGenerator) {
|
578 | this.original = eventGenerator;
|
579 | this.emitter = eventGenerator.emitter;
|
580 | this.codePath = null;
|
581 | this.idGenerator = new IdGenerator("s");
|
582 | this.currentNode = null;
|
583 | this.onLooped = this.onLooped.bind(this);
|
584 | }
|
585 |
|
586 | |
587 |
|
588 |
|
589 |
|
590 |
|
591 |
|
592 |
|
593 | enterNode(node) {
|
594 | this.currentNode = node;
|
595 |
|
596 |
|
597 | if (node.parent) {
|
598 | preprocess(this, node);
|
599 | }
|
600 |
|
601 | |
602 |
|
603 |
|
604 |
|
605 | processCodePathToEnter(this, node);
|
606 |
|
607 |
|
608 | this.original.enterNode(node);
|
609 |
|
610 | this.currentNode = null;
|
611 | }
|
612 |
|
613 | |
614 |
|
615 |
|
616 |
|
617 |
|
618 |
|
619 |
|
620 | leaveNode(node) {
|
621 | this.currentNode = node;
|
622 |
|
623 | |
624 |
|
625 |
|
626 |
|
627 | processCodePathToExit(this, node);
|
628 |
|
629 |
|
630 | this.original.leaveNode(node);
|
631 |
|
632 |
|
633 | postprocess(this, node);
|
634 |
|
635 | this.currentNode = null;
|
636 | }
|
637 |
|
638 | |
639 |
|
640 |
|
641 |
|
642 |
|
643 |
|
644 |
|
645 |
|
646 | onLooped(fromSegment, toSegment) {
|
647 | if (fromSegment.reachable && toSegment.reachable) {
|
648 | debug.dump(`onCodePathSegmentLoop ${fromSegment.id} -> ${toSegment.id}`);
|
649 | this.emitter.emit(
|
650 | "onCodePathSegmentLoop",
|
651 | fromSegment,
|
652 | toSegment,
|
653 | this.currentNode
|
654 | );
|
655 | }
|
656 | }
|
657 | }
|
658 |
|
659 | module.exports = CodePathAnalyzer;
|