simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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 : */