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 | import estraverse from "estraverse";
|
29 |
|
30 | import Reference from "./reference.js";
|
31 | import Variable from "./variable.js";
|
32 | import { Definition } from "./definition.js";
|
33 | import assert from "assert";
|
34 |
|
35 | const { Syntax } = estraverse;
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | function isStrictScope(scope, block, isMethodDefinition, useDirective) {
|
46 | let body;
|
47 |
|
48 |
|
49 | if (scope.upper && scope.upper.isStrict) {
|
50 | return true;
|
51 | }
|
52 |
|
53 | if (isMethodDefinition) {
|
54 | return true;
|
55 | }
|
56 |
|
57 | if (scope.type === "class" || scope.type === "module") {
|
58 | return true;
|
59 | }
|
60 |
|
61 | if (scope.type === "block" || scope.type === "switch") {
|
62 | return false;
|
63 | }
|
64 |
|
65 | if (scope.type === "function") {
|
66 | if (block.type === Syntax.ArrowFunctionExpression && block.body.type !== Syntax.BlockStatement) {
|
67 | return false;
|
68 | }
|
69 |
|
70 | if (block.type === Syntax.Program) {
|
71 | body = block;
|
72 | } else {
|
73 | body = block.body;
|
74 | }
|
75 |
|
76 | if (!body) {
|
77 | return false;
|
78 | }
|
79 | } else if (scope.type === "global") {
|
80 | body = block;
|
81 | } else {
|
82 | return false;
|
83 | }
|
84 |
|
85 |
|
86 | if (useDirective) {
|
87 | for (let i = 0, iz = body.body.length; i < iz; ++i) {
|
88 | const stmt = body.body[i];
|
89 |
|
90 | if (stmt.type !== Syntax.DirectiveStatement) {
|
91 | break;
|
92 | }
|
93 | if (stmt.raw === "\"use strict\"" || stmt.raw === "'use strict'") {
|
94 | return true;
|
95 | }
|
96 | }
|
97 | } else {
|
98 | for (let i = 0, iz = body.body.length; i < iz; ++i) {
|
99 | const stmt = body.body[i];
|
100 |
|
101 | if (stmt.type !== Syntax.ExpressionStatement) {
|
102 | break;
|
103 | }
|
104 | const expr = stmt.expression;
|
105 |
|
106 | if (expr.type !== Syntax.Literal || typeof expr.value !== "string") {
|
107 | break;
|
108 | }
|
109 | if (expr.raw !== null && expr.raw !== undefined) {
|
110 | if (expr.raw === "\"use strict\"" || expr.raw === "'use strict'") {
|
111 | return true;
|
112 | }
|
113 | } else {
|
114 | if (expr.value === "use strict") {
|
115 | return true;
|
116 | }
|
117 | }
|
118 | }
|
119 | }
|
120 | return false;
|
121 | }
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 | function registerScope(scopeManager, scope) {
|
130 | scopeManager.scopes.push(scope);
|
131 |
|
132 | const scopes = scopeManager.__nodeToScope.get(scope.block);
|
133 |
|
134 | if (scopes) {
|
135 | scopes.push(scope);
|
136 | } else {
|
137 | scopeManager.__nodeToScope.set(scope.block, [scope]);
|
138 | }
|
139 | }
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | function shouldBeStatically(def) {
|
147 | return (
|
148 | (def.type === Variable.ClassName) ||
|
149 | (def.type === Variable.Variable && def.parent.kind !== "var")
|
150 | );
|
151 | }
|
152 |
|
153 |
|
154 |
|
155 |
|
156 | class Scope {
|
157 | constructor(scopeManager, type, upperScope, block, isMethodDefinition) {
|
158 |
|
159 | |
160 |
|
161 |
|
162 |
|
163 |
|
164 | this.type = type;
|
165 |
|
166 | |
167 |
|
168 |
|
169 |
|
170 |
|
171 | this.set = new Map();
|
172 |
|
173 | |
174 |
|
175 |
|
176 |
|
177 | this.taints = new Map();
|
178 |
|
179 | |
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 | this.dynamic = this.type === "global" || this.type === "with";
|
190 |
|
191 | |
192 |
|
193 |
|
194 |
|
195 | this.block = block;
|
196 |
|
197 | |
198 |
|
199 |
|
200 |
|
201 | this.through = [];
|
202 |
|
203 | |
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 | this.variables = [];
|
210 |
|
211 | |
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | this.references = [];
|
221 |
|
222 | |
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 | this.variableScope =
|
229 | this.type === "global" ||
|
230 | this.type === "module" ||
|
231 | this.type === "function" ||
|
232 | this.type === "class-field-initializer" ||
|
233 | this.type === "class-static-block"
|
234 | ? this
|
235 | : upperScope.variableScope;
|
236 |
|
237 | |
238 |
|
239 |
|
240 |
|
241 | this.functionExpressionScope = false;
|
242 |
|
243 | |
244 |
|
245 |
|
246 |
|
247 | this.directCallToEvalScope = false;
|
248 |
|
249 | |
250 |
|
251 |
|
252 | this.thisFound = false;
|
253 |
|
254 | this.__left = [];
|
255 |
|
256 | |
257 |
|
258 |
|
259 |
|
260 | this.upper = upperScope;
|
261 |
|
262 | |
263 |
|
264 |
|
265 |
|
266 | this.isStrict = scopeManager.isStrictModeSupported()
|
267 | ? isStrictScope(this, block, isMethodDefinition, scopeManager.__useDirective())
|
268 | : false;
|
269 |
|
270 | |
271 |
|
272 |
|
273 |
|
274 | this.childScopes = [];
|
275 | if (this.upper) {
|
276 | this.upper.childScopes.push(this);
|
277 | }
|
278 |
|
279 | this.__declaredVariables = scopeManager.__declaredVariables;
|
280 |
|
281 | registerScope(scopeManager, this);
|
282 | }
|
283 |
|
284 | __shouldStaticallyClose(scopeManager) {
|
285 | return (!this.dynamic || scopeManager.__isOptimistic());
|
286 | }
|
287 |
|
288 | __shouldStaticallyCloseForGlobal(ref) {
|
289 |
|
290 |
|
291 | const name = ref.identifier.name;
|
292 |
|
293 | if (!this.set.has(name)) {
|
294 | return false;
|
295 | }
|
296 |
|
297 | const variable = this.set.get(name);
|
298 | const defs = variable.defs;
|
299 |
|
300 | return defs.length > 0 && defs.every(shouldBeStatically);
|
301 | }
|
302 |
|
303 | __staticCloseRef(ref) {
|
304 | if (!this.__resolve(ref)) {
|
305 | this.__delegateToUpperScope(ref);
|
306 | }
|
307 | }
|
308 |
|
309 | __dynamicCloseRef(ref) {
|
310 |
|
311 |
|
312 | let current = this;
|
313 |
|
314 | do {
|
315 | current.through.push(ref);
|
316 | current = current.upper;
|
317 | } while (current);
|
318 | }
|
319 |
|
320 | __globalCloseRef(ref) {
|
321 |
|
322 |
|
323 |
|
324 | if (this.__shouldStaticallyCloseForGlobal(ref)) {
|
325 | this.__staticCloseRef(ref);
|
326 | } else {
|
327 | this.__dynamicCloseRef(ref);
|
328 | }
|
329 | }
|
330 |
|
331 | __close(scopeManager) {
|
332 | let closeRef;
|
333 |
|
334 | if (this.__shouldStaticallyClose(scopeManager)) {
|
335 | closeRef = this.__staticCloseRef;
|
336 | } else if (this.type !== "global") {
|
337 | closeRef = this.__dynamicCloseRef;
|
338 | } else {
|
339 | closeRef = this.__globalCloseRef;
|
340 | }
|
341 |
|
342 |
|
343 | for (let i = 0, iz = this.__left.length; i < iz; ++i) {
|
344 | const ref = this.__left[i];
|
345 |
|
346 | closeRef.call(this, ref);
|
347 | }
|
348 | this.__left = null;
|
349 |
|
350 | return this.upper;
|
351 | }
|
352 |
|
353 |
|
354 |
|
355 | __isValidResolution(ref, variable) {
|
356 | return true;
|
357 | }
|
358 |
|
359 | __resolve(ref) {
|
360 | const name = ref.identifier.name;
|
361 |
|
362 | if (!this.set.has(name)) {
|
363 | return false;
|
364 | }
|
365 | const variable = this.set.get(name);
|
366 |
|
367 | if (!this.__isValidResolution(ref, variable)) {
|
368 | return false;
|
369 | }
|
370 | variable.references.push(ref);
|
371 | variable.stack = variable.stack && ref.from.variableScope === this.variableScope;
|
372 | if (ref.tainted) {
|
373 | variable.tainted = true;
|
374 | this.taints.set(variable.name, true);
|
375 | }
|
376 | ref.resolved = variable;
|
377 |
|
378 | return true;
|
379 | }
|
380 |
|
381 | __delegateToUpperScope(ref) {
|
382 | if (this.upper) {
|
383 | this.upper.__left.push(ref);
|
384 | }
|
385 | this.through.push(ref);
|
386 | }
|
387 |
|
388 | __addDeclaredVariablesOfNode(variable, node) {
|
389 | if (node === null || node === undefined) {
|
390 | return;
|
391 | }
|
392 |
|
393 | let variables = this.__declaredVariables.get(node);
|
394 |
|
395 | if (variables === null || variables === undefined) {
|
396 | variables = [];
|
397 | this.__declaredVariables.set(node, variables);
|
398 | }
|
399 | if (variables.indexOf(variable) === -1) {
|
400 | variables.push(variable);
|
401 | }
|
402 | }
|
403 |
|
404 | __defineGeneric(name, set, variables, node, def) {
|
405 | let variable;
|
406 |
|
407 | variable = set.get(name);
|
408 | if (!variable) {
|
409 | variable = new Variable(name, this);
|
410 | set.set(name, variable);
|
411 | variables.push(variable);
|
412 | }
|
413 |
|
414 | if (def) {
|
415 | variable.defs.push(def);
|
416 | this.__addDeclaredVariablesOfNode(variable, def.node);
|
417 | this.__addDeclaredVariablesOfNode(variable, def.parent);
|
418 | }
|
419 | if (node) {
|
420 | variable.identifiers.push(node);
|
421 | }
|
422 | }
|
423 |
|
424 | __define(node, def) {
|
425 | if (node && node.type === Syntax.Identifier) {
|
426 | this.__defineGeneric(
|
427 | node.name,
|
428 | this.set,
|
429 | this.variables,
|
430 | node,
|
431 | def
|
432 | );
|
433 | }
|
434 | }
|
435 |
|
436 | __referencing(node, assign, writeExpr, maybeImplicitGlobal, partial, init) {
|
437 |
|
438 |
|
439 | if (!node || node.type !== Syntax.Identifier) {
|
440 | return;
|
441 | }
|
442 |
|
443 |
|
444 | if (node.name === "super") {
|
445 | return;
|
446 | }
|
447 |
|
448 | const ref = new Reference(node, this, assign || Reference.READ, writeExpr, maybeImplicitGlobal, !!partial, !!init);
|
449 |
|
450 | this.references.push(ref);
|
451 | this.__left.push(ref);
|
452 | }
|
453 |
|
454 | __detectEval() {
|
455 | let current = this;
|
456 |
|
457 | this.directCallToEvalScope = true;
|
458 | do {
|
459 | current.dynamic = true;
|
460 | current = current.upper;
|
461 | } while (current);
|
462 | }
|
463 |
|
464 | __detectThis() {
|
465 | this.thisFound = true;
|
466 | }
|
467 |
|
468 | __isClosed() {
|
469 | return this.__left === null;
|
470 | }
|
471 |
|
472 | |
473 |
|
474 |
|
475 |
|
476 |
|
477 |
|
478 | resolve(ident) {
|
479 | let ref, i, iz;
|
480 |
|
481 | assert(this.__isClosed(), "Scope should be closed.");
|
482 | assert(ident.type === Syntax.Identifier, "Target should be identifier.");
|
483 | for (i = 0, iz = this.references.length; i < iz; ++i) {
|
484 | ref = this.references[i];
|
485 | if (ref.identifier === ident) {
|
486 | return ref;
|
487 | }
|
488 | }
|
489 | return null;
|
490 | }
|
491 |
|
492 | |
493 |
|
494 |
|
495 |
|
496 |
|
497 | isStatic() {
|
498 | return !this.dynamic;
|
499 | }
|
500 |
|
501 | |
502 |
|
503 |
|
504 |
|
505 |
|
506 | isArgumentsMaterialized() {
|
507 | return true;
|
508 | }
|
509 |
|
510 | |
511 |
|
512 |
|
513 |
|
514 |
|
515 | isThisMaterialized() {
|
516 | return true;
|
517 | }
|
518 |
|
519 | isUsedName(name) {
|
520 | if (this.set.has(name)) {
|
521 | return true;
|
522 | }
|
523 | for (let i = 0, iz = this.through.length; i < iz; ++i) {
|
524 | if (this.through[i].identifier.name === name) {
|
525 | return true;
|
526 | }
|
527 | }
|
528 | return false;
|
529 | }
|
530 | }
|
531 |
|
532 | class GlobalScope extends Scope {
|
533 | constructor(scopeManager, block) {
|
534 | super(scopeManager, "global", null, block, false);
|
535 | this.implicit = {
|
536 | set: new Map(),
|
537 | variables: [],
|
538 |
|
539 | /**
|
540 | * List of {@link Reference}s that are left to be resolved (i.e. which
|
541 | * need to be linked to the variable they refer to).
|
542 | * @member {Reference[]} Scope#implicit#left
|
543 | */
|
544 | left: []
|
545 | };
|
546 | }
|
547 |
|
548 | __close(scopeManager) {
|
549 | const implicit = [];
|
550 |
|
551 | for (let i = 0, iz = this.__left.length; i < iz; ++i) {
|
552 | const ref = this.__left[i];
|
553 |
|
554 | if (ref.__maybeImplicitGlobal && !this.set.has(ref.identifier.name)) {
|
555 | implicit.push(ref.__maybeImplicitGlobal);
|
556 | }
|
557 | }
|
558 |
|
559 |
|
560 | for (let i = 0, iz = implicit.length; i < iz; ++i) {
|
561 | const info = implicit[i];
|
562 |
|
563 | this.__defineImplicit(info.pattern,
|
564 | new Definition(
|
565 | Variable.ImplicitGlobalVariable,
|
566 | info.pattern,
|
567 | info.node,
|
568 | null,
|
569 | null,
|
570 | null
|
571 | ));
|
572 |
|
573 | }
|
574 |
|
575 | this.implicit.left = this.__left;
|
576 |
|
577 | return super.__close(scopeManager);
|
578 | }
|
579 |
|
580 | __defineImplicit(node, def) {
|
581 | if (node && node.type === Syntax.Identifier) {
|
582 | this.__defineGeneric(
|
583 | node.name,
|
584 | this.implicit.set,
|
585 | this.implicit.variables,
|
586 | node,
|
587 | def
|
588 | );
|
589 | }
|
590 | }
|
591 | }
|
592 |
|
593 | class ModuleScope extends Scope {
|
594 | constructor(scopeManager, upperScope, block) {
|
595 | super(scopeManager, "module", upperScope, block, false);
|
596 | }
|
597 | }
|
598 |
|
599 | class FunctionExpressionNameScope extends Scope {
|
600 | constructor(scopeManager, upperScope, block) {
|
601 | super(scopeManager, "function-expression-name", upperScope, block, false);
|
602 | this.__define(block.id,
|
603 | new Definition(
|
604 | Variable.FunctionName,
|
605 | block.id,
|
606 | block,
|
607 | null,
|
608 | null,
|
609 | null
|
610 | ));
|
611 | this.functionExpressionScope = true;
|
612 | }
|
613 | }
|
614 |
|
615 | class CatchScope extends Scope {
|
616 | constructor(scopeManager, upperScope, block) {
|
617 | super(scopeManager, "catch", upperScope, block, false);
|
618 | }
|
619 | }
|
620 |
|
621 | class WithScope extends Scope {
|
622 | constructor(scopeManager, upperScope, block) {
|
623 | super(scopeManager, "with", upperScope, block, false);
|
624 | }
|
625 |
|
626 | __close(scopeManager) {
|
627 | if (this.__shouldStaticallyClose(scopeManager)) {
|
628 | return super.__close(scopeManager);
|
629 | }
|
630 |
|
631 | for (let i = 0, iz = this.__left.length; i < iz; ++i) {
|
632 | const ref = this.__left[i];
|
633 |
|
634 | ref.tainted = true;
|
635 | this.__delegateToUpperScope(ref);
|
636 | }
|
637 | this.__left = null;
|
638 |
|
639 | return this.upper;
|
640 | }
|
641 | }
|
642 |
|
643 | class BlockScope extends Scope {
|
644 | constructor(scopeManager, upperScope, block) {
|
645 | super(scopeManager, "block", upperScope, block, false);
|
646 | }
|
647 | }
|
648 |
|
649 | class SwitchScope extends Scope {
|
650 | constructor(scopeManager, upperScope, block) {
|
651 | super(scopeManager, "switch", upperScope, block, false);
|
652 | }
|
653 | }
|
654 |
|
655 | class FunctionScope extends Scope {
|
656 | constructor(scopeManager, upperScope, block, isMethodDefinition) {
|
657 | super(scopeManager, "function", upperScope, block, isMethodDefinition);
|
658 |
|
659 |
|
660 |
|
661 | if (this.block.type !== Syntax.ArrowFunctionExpression) {
|
662 | this.__defineArguments();
|
663 | }
|
664 | }
|
665 |
|
666 | isArgumentsMaterialized() {
|
667 |
|
668 |
|
669 |
|
670 |
|
671 |
|
672 |
|
673 |
|
674 |
|
675 |
|
676 | if (this.block.type === Syntax.ArrowFunctionExpression) {
|
677 | return false;
|
678 | }
|
679 |
|
680 | if (!this.isStatic()) {
|
681 | return true;
|
682 | }
|
683 |
|
684 | const variable = this.set.get("arguments");
|
685 |
|
686 | assert(variable, "Always have arguments variable.");
|
687 | return variable.tainted || variable.references.length !== 0;
|
688 | }
|
689 |
|
690 | isThisMaterialized() {
|
691 | if (!this.isStatic()) {
|
692 | return true;
|
693 | }
|
694 | return this.thisFound;
|
695 | }
|
696 |
|
697 | __defineArguments() {
|
698 | this.__defineGeneric(
|
699 | "arguments",
|
700 | this.set,
|
701 | this.variables,
|
702 | null,
|
703 | null
|
704 | );
|
705 | this.taints.set("arguments", true);
|
706 | }
|
707 |
|
708 |
|
709 |
|
710 |
|
711 |
|
712 |
|
713 |
|
714 | __isValidResolution(ref, variable) {
|
715 |
|
716 |
|
717 | if (this.block.type === "Program") {
|
718 | return true;
|
719 | }
|
720 |
|
721 | const bodyStart = this.block.body.range[0];
|
722 |
|
723 |
|
724 | return !(
|
725 | variable.scope === this &&
|
726 | ref.identifier.range[0] < bodyStart &&
|
727 | variable.defs.every(d => d.name.range[0] >= bodyStart)
|
728 | );
|
729 | }
|
730 | }
|
731 |
|
732 | class ForScope extends Scope {
|
733 | constructor(scopeManager, upperScope, block) {
|
734 | super(scopeManager, "for", upperScope, block, false);
|
735 | }
|
736 | }
|
737 |
|
738 | class ClassScope extends Scope {
|
739 | constructor(scopeManager, upperScope, block) {
|
740 | super(scopeManager, "class", upperScope, block, false);
|
741 | }
|
742 | }
|
743 |
|
744 | class ClassFieldInitializerScope extends Scope {
|
745 | constructor(scopeManager, upperScope, block) {
|
746 | super(scopeManager, "class-field-initializer", upperScope, block, true);
|
747 | }
|
748 | }
|
749 |
|
750 | class ClassStaticBlockScope extends Scope {
|
751 | constructor(scopeManager, upperScope, block) {
|
752 | super(scopeManager, "class-static-block", upperScope, block, true);
|
753 | }
|
754 | }
|
755 |
|
756 | export {
|
757 | Scope,
|
758 | GlobalScope,
|
759 | ModuleScope,
|
760 | FunctionExpressionNameScope,
|
761 | CatchScope,
|
762 | WithScope,
|
763 | BlockScope,
|
764 | SwitchScope,
|
765 | FunctionScope,
|
766 | ForScope,
|
767 | ClassScope,
|
768 | ClassFieldInitializerScope,
|
769 | ClassStaticBlockScope
|
770 | };
|
771 |
|
772 |
|