UNPKG

22.4 kBJavaScriptView Raw
1/*
2 Copyright (C) 2015 Yusuke Suzuki <utatane.tea@gmail.com>
3
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions are met:
6
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12
13 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
17 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23*/
24
25/* eslint-disable no-underscore-dangle */
26/* eslint-disable no-undefined */
27
28import estraverse from "estraverse";
29
30import Reference from "./reference.js";
31import Variable from "./variable.js";
32import { Definition } from "./definition.js";
33import assert from "assert";
34
35const { Syntax } = estraverse;
36
37/**
38 * Test if scope is struct
39 * @param {Scope} scope scope
40 * @param {Block} block block
41 * @param {boolean} isMethodDefinition is method definition
42 * @param {boolean} useDirective use directive
43 * @returns {boolean} is strict scope
44 */
45function isStrictScope(scope, block, isMethodDefinition, useDirective) {
46 let body;
47
48 // When upper scope is exists and strict, inner scope is also strict.
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 // Search 'use strict' directive.
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 * Register scope
125 * @param {ScopeManager} scopeManager scope manager
126 * @param {Scope} scope scope
127 * @returns {void}
128 */
129function 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 * Should be statically
143 * @param {Object} def def
144 * @returns {boolean} should be statically
145 */
146function shouldBeStatically(def) {
147 return (
148 (def.type === Variable.ClassName) ||
149 (def.type === Variable.Variable && def.parent.kind !== "var")
150 );
151}
152
153/**
154 * @constructor Scope
155 */
156class Scope {
157 constructor(scopeManager, type, upperScope, block, isMethodDefinition) {
158
159 /**
160 * One of "global", "module", "function", "function-expression-name", "block", "switch", "catch", "with", "for",
161 * "class", "class-field-initializer", "class-static-block".
162 * @member {string} Scope#type
163 */
164 this.type = type;
165
166 /**
167 * The scoped {@link Variable}s of this scope, as <code>{ Variable.name
168 * : Variable }</code>.
169 * @member {Map} Scope#set
170 */
171 this.set = new Map();
172
173 /**
174 * The tainted variables of this scope, as <code>{ Variable.name :
175 * boolean }</code>.
176 * @member {Map} Scope#taints */
177 this.taints = new Map();
178
179 /**
180 * Generally, through the lexical scoping of JS you can always know
181 * which variable an identifier in the source code refers to. There are
182 * a few exceptions to this rule. With 'global' and 'with' scopes you
183 * can only decide at runtime which variable a reference refers to.
184 * Moreover, if 'eval()' is used in a scope, it might introduce new
185 * bindings in this or its parent scopes.
186 * All those scopes are considered 'dynamic'.
187 * @member {boolean} Scope#dynamic
188 */
189 this.dynamic = this.type === "global" || this.type === "with";
190
191 /**
192 * A reference to the scope-defining syntax node.
193 * @member {espree.Node} Scope#block
194 */
195 this.block = block;
196
197 /**
198 * The {@link Reference|references} that are not resolved with this scope.
199 * @member {Reference[]} Scope#through
200 */
201 this.through = [];
202
203 /**
204 * The scoped {@link Variable}s of this scope. In the case of a
205 * 'function' scope this includes the automatic argument <em>arguments</em> as
206 * its first element, as well as all further formal arguments.
207 * @member {Variable[]} Scope#variables
208 */
209 this.variables = [];
210
211 /**
212 * Any variable {@link Reference|reference} found in this scope. This
213 * includes occurrences of local variables as well as variables from
214 * parent scopes (including the global scope). For local variables
215 * this also includes defining occurrences (like in a 'var' statement).
216 * In a 'function' scope this does not include the occurrences of the
217 * formal parameter in the parameter list.
218 * @member {Reference[]} Scope#references
219 */
220 this.references = [];
221
222 /**
223 * For 'global' and 'function' scopes, this is a self-reference. For
224 * other scope types this is the <em>variableScope</em> value of the
225 * parent scope.
226 * @member {Scope} Scope#variableScope
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 * Whether this scope is created by a FunctionExpression.
239 * @member {boolean} Scope#functionExpressionScope
240 */
241 this.functionExpressionScope = false;
242
243 /**
244 * Whether this is a scope that contains an 'eval()' invocation.
245 * @member {boolean} Scope#directCallToEvalScope
246 */
247 this.directCallToEvalScope = false;
248
249 /**
250 * @member {boolean} Scope#thisFound
251 */
252 this.thisFound = false;
253
254 this.__left = [];
255
256 /**
257 * Reference to the parent {@link Scope|scope}.
258 * @member {Scope} Scope#upper
259 */
260 this.upper = upperScope;
261
262 /**
263 * Whether 'use strict' is in effect in this scope.
264 * @member {boolean} Scope#isStrict
265 */
266 this.isStrict = scopeManager.isStrictModeSupported()
267 ? isStrictScope(this, block, isMethodDefinition, scopeManager.__useDirective())
268 : false;
269
270 /**
271 * List of nested {@link Scope}s.
272 * @member {Scope[]} Scope#childScopes
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 // On global scope, let/const/class declarations should be resolved statically.
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 // notify all names are through to global
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 // let/const/class declarations should be resolved statically.
323 // others should be resolved dynamically.
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 // Try Resolving all references in this scope.
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 // To override by function scopes.
354 // References in default parameters isn't resolved to variables which are in their function body.
355 __isValidResolution(ref, variable) { // eslint-disable-line class-methods-use-this, no-unused-vars
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 // because Array element may be null
439 if (!node || node.type !== Syntax.Identifier) {
440 return;
441 }
442
443 // Specially handle like `this`.
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 * returns resolved {Reference}
474 * @function Scope#resolve
475 * @param {Espree.Identifier} ident identifier to be resolved.
476 * @returns {Reference} reference
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 * returns this scope is static
494 * @function Scope#isStatic
495 * @returns {boolean} static
496 */
497 isStatic() {
498 return !this.dynamic;
499 }
500
501 /**
502 * returns this scope has materialized arguments
503 * @function Scope#isArgumentsMaterialized
504 * @returns {boolean} arguemnts materialized
505 */
506 isArgumentsMaterialized() { // eslint-disable-line class-methods-use-this
507 return true;
508 }
509
510 /**
511 * returns this scope has materialized `this` reference
512 * @function Scope#isThisMaterialized
513 * @returns {boolean} this materialized
514 */
515 isThisMaterialized() { // eslint-disable-line class-methods-use-this
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
532class 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 // create an implicit global variable from assignment expression
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
593class ModuleScope extends Scope {
594 constructor(scopeManager, upperScope, block) {
595 super(scopeManager, "module", upperScope, block, false);
596 }
597}
598
599class 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
615class CatchScope extends Scope {
616 constructor(scopeManager, upperScope, block) {
617 super(scopeManager, "catch", upperScope, block, false);
618 }
619}
620
621class 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
643class BlockScope extends Scope {
644 constructor(scopeManager, upperScope, block) {
645 super(scopeManager, "block", upperScope, block, false);
646 }
647}
648
649class SwitchScope extends Scope {
650 constructor(scopeManager, upperScope, block) {
651 super(scopeManager, "switch", upperScope, block, false);
652 }
653}
654
655class FunctionScope extends Scope {
656 constructor(scopeManager, upperScope, block, isMethodDefinition) {
657 super(scopeManager, "function", upperScope, block, isMethodDefinition);
658
659 // section 9.2.13, FunctionDeclarationInstantiation.
660 // NOTE Arrow functions never have an arguments objects.
661 if (this.block.type !== Syntax.ArrowFunctionExpression) {
662 this.__defineArguments();
663 }
664 }
665
666 isArgumentsMaterialized() {
667
668 // TODO(Constellation)
669 // We can more aggressive on this condition like this.
670 //
671 // function t() {
672 // // arguments of t is always hidden.
673 // function arguments() {
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 // References in default parameters isn't resolved to variables which are in their function body.
709 // const x = 1
710 // function f(a = x) { // This `x` is resolved to the `x` in the outer scope.
711 // const x = 2
712 // console.log(a)
713 // }
714 __isValidResolution(ref, variable) {
715
716 // If `options.nodejsScope` is true, `this.block` becomes a Program node.
717 if (this.block.type === "Program") {
718 return true;
719 }
720
721 const bodyStart = this.block.body.range[0];
722
723 // It's invalid resolution in the following case:
724 return !(
725 variable.scope === this &&
726 ref.identifier.range[0] < bodyStart && // the reference is in the parameter part.
727 variable.defs.every(d => d.name.range[0] >= bodyStart) // the variable is in the body.
728 );
729 }
730}
731
732class ForScope extends Scope {
733 constructor(scopeManager, upperScope, block) {
734 super(scopeManager, "for", upperScope, block, false);
735 }
736}
737
738class ClassScope extends Scope {
739 constructor(scopeManager, upperScope, block) {
740 super(scopeManager, "class", upperScope, block, false);
741 }
742}
743
744class ClassFieldInitializerScope extends Scope {
745 constructor(scopeManager, upperScope, block) {
746 super(scopeManager, "class-field-initializer", upperScope, block, true);
747 }
748}
749
750class ClassStaticBlockScope extends Scope {
751 constructor(scopeManager, upperScope, block) {
752 super(scopeManager, "class-static-block", upperScope, block, true);
753 }
754}
755
756export {
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/* vim: set sw=4 ts=4 et tw=80 : */