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