| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import estraverse from "estraverse"; |
|
|
| import Reference from "./reference.js"; |
| import Variable from "./variable.js"; |
| import { Definition } from "./definition.js"; |
| import { assert } from "./assert.js"; |
|
|
| |
| |
| |
| |
| |
|
|
| const { Syntax } = estraverse; |
|
|
| |
| |
| |
| |
| |
| |
| |
| function isStrictScope(scope, block, isMethodDefinition) { |
| let body; |
|
|
| |
| if (scope.upper && scope.upper.isStrict) { |
| return true; |
| } |
|
|
| if (isMethodDefinition) { |
| return true; |
| } |
|
|
| if (scope.type === "class" || scope.type === "module") { |
| return true; |
| } |
|
|
| if (scope.type === "block" || scope.type === "switch") { |
| return false; |
| } |
|
|
| if (scope.type === "function") { |
| if ( |
| block.type === Syntax.ArrowFunctionExpression && |
| |
| block.body.type !== Syntax.BlockStatement |
| ) { |
| return false; |
| } |
|
|
| if (block.type === Syntax.Program) { |
| body = block; |
| } else { |
| body = block.body; |
| } |
|
|
| if (!body) { |
| return false; |
| } |
| } else if (scope.type === "global") { |
| body = block; |
| } else { |
| return false; |
| } |
|
|
| |
| |
| for (let i = 0, iz = body.body.length; i < iz; ++i) { |
| |
| const stmt = body.body[i]; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if (typeof stmt.directive !== "string") { |
| break; |
| } |
|
|
| if (stmt.directive === "use strict") { |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function registerScope(scopeManager, scope) { |
| scopeManager.scopes.push(scope); |
|
|
| const scopes = scopeManager.__nodeToScope.get(scope.block); |
|
|
| if (scopes) { |
| scopes.push(scope); |
| } else { |
| scopeManager.__nodeToScope.set(scope.block, [scope]); |
| } |
| } |
|
|
| |
| |
| |
| |
| class Scope { |
| constructor(scopeManager, type, upperScope, block, isMethodDefinition) { |
| |
| |
| |
| |
| |
| this.type = type; |
|
|
| |
| |
| |
| |
| |
| this.set = new Map(); |
|
|
| |
| |
| |
| |
| |
| this.taints = new Map(); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| this.dynamic = this.type === "global" || this.type === "with"; |
|
|
| |
| |
| |
| |
| this.block = block; |
|
|
| |
| |
| |
| |
| this.through = []; |
|
|
| |
| |
| |
| |
| |
| |
| this.variables = []; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| this.references = []; |
|
|
| |
| |
| |
| |
| |
| |
| this.variableScope = |
| this.type === "global" || |
| this.type === "module" || |
| this.type === "function" || |
| this.type === "class-field-initializer" || |
| this.type === "class-static-block" |
| ? this |
| : upperScope.variableScope; |
|
|
| |
| |
| |
| |
| this.functionExpressionScope = (false); |
|
|
| |
| |
| |
| |
| this.directCallToEvalScope = false; |
|
|
| |
| |
| |
| this.thisFound = false; |
|
|
| |
| this.__left = []; |
|
|
| |
| |
| |
| |
| this.upper = upperScope; |
|
|
| |
| |
| |
| |
| this.isStrict = scopeManager.isStrictModeSupported() |
| ? isStrictScope(this, block, isMethodDefinition) |
| : false; |
|
|
| |
| |
| |
| |
| this.childScopes = []; |
| if (this.upper) { |
| this.upper.childScopes.push(this); |
| } |
|
|
| this.__declaredVariables = scopeManager.__declaredVariables; |
|
|
| registerScope(scopeManager, this); |
| } |
|
|
| __shouldStaticallyClose(scopeManager) { |
| return ( |
| !this.dynamic || |
| scopeManager.__isOptimistic() || |
| this.type === "global" |
| ); |
| } |
|
|
| __staticCloseRef(ref) { |
| if (!this.__resolve(ref)) { |
| this.__delegateToUpperScope(ref); |
| } |
| } |
|
|
| __dynamicCloseRef(ref) { |
| |
| let current = this; |
|
|
| do { |
| current.through.push(ref); |
| current = current.upper; |
| } while (current); |
| } |
|
|
| __close(scopeManager) { |
| let closeRef; |
|
|
| if (this.__shouldStaticallyClose(scopeManager)) { |
| closeRef = this.__staticCloseRef; |
| } else { |
| closeRef = this.__dynamicCloseRef; |
| } |
|
|
| |
| |
| for (let i = 0, iz = this.__left.length; i < iz; ++i) { |
| |
| const ref = this.__left[i]; |
|
|
| closeRef.call(this, ref); |
| } |
| this.__left = null; |
|
|
| return this.upper; |
| } |
|
|
| |
| |
| |
| __isValidResolution(ref, variable) { |
| return true; |
| } |
|
|
| __resolve(ref) { |
| const name = ref.identifier.name; |
|
|
| if (!this.set.has(name)) { |
| return false; |
| } |
| const variable = this.set.get(name); |
|
|
| if (!this.__isValidResolution(ref, variable)) { |
| return false; |
| } |
| variable.references.push(ref); |
| variable.stack = |
| variable.stack && ref.from.variableScope === this.variableScope; |
| if (ref.tainted) { |
| variable.tainted = true; |
| this.taints.set(variable.name, true); |
| } |
| ref.resolved = variable; |
|
|
| return true; |
| } |
|
|
| __delegateToUpperScope(ref) { |
| if (this.upper) { |
| this.upper.__left.push(ref); |
| } |
| this.through.push(ref); |
| } |
|
|
| __addDeclaredVariablesOfNode(variable, node) { |
| if (node === null || node === void 0) { |
| return; |
| } |
|
|
| let variables = this.__declaredVariables.get(node); |
|
|
| if (variables === null || variables === void 0) { |
| variables = []; |
| this.__declaredVariables.set(node, variables); |
| } |
| if (!variables.includes(variable)) { |
| variables.push(variable); |
| } |
| } |
|
|
| __defineGeneric(name, set, variables, node, def) { |
| let variable; |
|
|
| variable = set.get(name); |
| if (!variable) { |
| variable = new Variable(name, this); |
| set.set(name, variable); |
| variables.push(variable); |
| } |
|
|
| if (def) { |
| variable.defs.push(def); |
| this.__addDeclaredVariablesOfNode(variable, def.node); |
| this.__addDeclaredVariablesOfNode(variable, def.parent); |
| } |
| if (node) { |
| variable.identifiers.push(node); |
| } |
| } |
|
|
| __define(node, def) { |
| if (node && node.type === Syntax.Identifier) { |
| this.__defineGeneric( |
| node.name, |
| this.set, |
| this.variables, |
| node, |
| def, |
| ); |
| } |
| } |
|
|
| __referencing(node, assign, writeExpr, maybeImplicitGlobal, partial, init) { |
| |
| if ( |
| !node || |
| (node.type !== Syntax.Identifier && node.type !== "JSXIdentifier") |
| ) { |
| return; |
| } |
|
|
| |
| if (node.name === "super") { |
| return; |
| } |
|
|
| const ref = new Reference( |
| node, |
| this, |
| assign || Reference.READ, |
| writeExpr, |
| maybeImplicitGlobal, |
| !!partial, |
| !!init, |
| ); |
|
|
| this.references.push(ref); |
|
|
| |
| this.__left.push(ref); |
| } |
|
|
| __detectEval() { |
| let current = this; |
|
|
| this.directCallToEvalScope = true; |
| do { |
| current.dynamic = true; |
| current = current.upper; |
| } while (current); |
| } |
|
|
| __detectThis() { |
| this.thisFound = true; |
| } |
|
|
| __isClosed() { |
| return this.__left === null; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| resolve(ident) { |
| let ref, i, iz; |
|
|
| assert(this.__isClosed(), "Scope should be closed."); |
| assert( |
| ident.type === Syntax.Identifier, |
| "Target should be identifier.", |
| ); |
| for (i = 0, iz = this.references.length; i < iz; ++i) { |
| ref = this.references[i]; |
| if (ref.identifier === ident) { |
| return ref; |
| } |
| } |
| return null; |
| } |
|
|
| |
| |
| |
| |
| |
| isStatic() { |
| return !this.dynamic; |
| } |
|
|
| |
| |
| |
| |
| |
| isArgumentsMaterialized() { |
| return true; |
| } |
|
|
| |
| |
| |
| |
| |
| isThisMaterialized() { |
| return true; |
| } |
|
|
| isUsedName(name) { |
| if (this.set.has(name)) { |
| return true; |
| } |
| for (let i = 0, iz = this.through.length; i < iz; ++i) { |
| if (this.through[i].identifier.name === name) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
|
|
| |
| |
| |
| |
| class GlobalScope extends Scope { |
| constructor(scopeManager, block) { |
| super(scopeManager, "global", null, block, false); |
| this.implicit = { |
| set: new Map(), |
|
|
| |
| variables: [], |
|
|
| |
| |
| |
| |
| |
| |
| left: [], |
| }; |
| } |
|
|
| __close(scopeManager) { |
| const implicit = []; |
|
|
| |
| for (let i = 0, iz = this.__left.length; i < iz; ++i) { |
| |
| const ref = this.__left[i]; |
|
|
| if ( |
| ref.__maybeImplicitGlobal && |
| !this.set.has(ref.identifier.name) |
| ) { |
| implicit.push(ref.__maybeImplicitGlobal); |
| } |
| } |
|
|
| |
| for (let i = 0, iz = implicit.length; i < iz; ++i) { |
| const info = implicit[i]; |
|
|
| this.__defineImplicit( |
| info.pattern, |
| new Definition( |
| Variable.ImplicitGlobalVariable, |
| info.pattern, |
| info.node, |
| null, |
| null, |
| null, |
| ), |
| ); |
| } |
|
|
| super.__close(scopeManager); |
|
|
| this.implicit.left = [...this.through]; |
|
|
| return null; |
| } |
|
|
| __defineImplicit(node, def) { |
| if (node && node.type === Syntax.Identifier) { |
| this.__defineGeneric( |
| node.name, |
| this.implicit.set, |
| this.implicit.variables, |
| node, |
| def, |
| ); |
| } |
| } |
|
|
| __addVariables(names) { |
| for (const name of names) { |
| this.__defineGeneric(name, this.set, this.variables, null, null); |
| } |
|
|
| const namesSet = new Set(names); |
|
|
| this.through = this.through.filter(reference => { |
| const name = reference.identifier.name; |
|
|
| if (namesSet.has(name)) { |
| const variable = this.set.get(name); |
|
|
| reference.resolved = variable; |
| variable.references.push(reference); |
|
|
| return false; |
| } |
|
|
| return true; |
| }); |
|
|
| this.implicit.variables = this.implicit.variables.filter(variable => { |
| const name = variable.name; |
|
|
| if (namesSet.has(name)) { |
| this.implicit.set.delete(name); |
|
|
| return false; |
| } |
|
|
| return true; |
| }); |
|
|
| this.implicit.left = this.implicit.left.filter( |
| reference => !namesSet.has(reference.identifier.name), |
| ); |
| } |
| } |
|
|
| |
| |
| |
| |
| class ModuleScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super(scopeManager, "module", upperScope, block, false); |
| } |
| } |
|
|
| |
| |
| |
| |
| class FunctionExpressionNameScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super( |
| scopeManager, |
| "function-expression-name", |
| upperScope, |
| block, |
| false, |
| ); |
| this.__define( |
| block.id, |
| new Definition( |
| Variable.FunctionName, |
| block.id, |
| block, |
| null, |
| null, |
| null, |
| ), |
| ); |
| this.functionExpressionScope = (true); |
| } |
| } |
|
|
| |
| |
| |
| |
| class CatchScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super(scopeManager, "catch", upperScope, block, false); |
| } |
| } |
|
|
| |
| |
| |
| |
| class WithScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super(scopeManager, "with", upperScope, block, false); |
| } |
|
|
| __close(scopeManager) { |
| if (this.__shouldStaticallyClose(scopeManager)) { |
| return super.__close(scopeManager); |
| } |
|
|
| |
| for (let i = 0, iz = this.__left.length; i < iz; ++i) { |
| |
| const ref = this.__left[i]; |
|
|
| ref.tainted = true; |
| this.__delegateToUpperScope(ref); |
| } |
| this.__left = null; |
|
|
| return this.upper; |
| } |
| } |
|
|
| |
| |
| |
| |
| class BlockScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super(scopeManager, "block", upperScope, block, false); |
| } |
| } |
|
|
| |
| |
| |
| |
| class SwitchScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super(scopeManager, "switch", upperScope, block, false); |
| } |
| } |
|
|
| |
| |
| |
| |
| class FunctionScope extends Scope { |
| constructor(scopeManager, upperScope, block, isMethodDefinition) { |
| super(scopeManager, "function", upperScope, block, isMethodDefinition); |
|
|
| |
| |
| if (this.block.type !== Syntax.ArrowFunctionExpression) { |
| this.__defineArguments(); |
| } |
| } |
|
|
| isArgumentsMaterialized() { |
| |
| |
| |
| |
| |
| |
| |
| |
| if (this.block.type === Syntax.ArrowFunctionExpression) { |
| return false; |
| } |
|
|
| if (!this.isStatic()) { |
| return true; |
| } |
|
|
| const variable = this.set.get("arguments"); |
|
|
| assert(variable, "Always have arguments variable."); |
| return variable.tainted || variable.references.length !== 0; |
| } |
|
|
| isThisMaterialized() { |
| if (!this.isStatic()) { |
| return true; |
| } |
| return this.thisFound; |
| } |
|
|
| __defineArguments() { |
| this.__defineGeneric("arguments", this.set, this.variables, null, null); |
| this.taints.set("arguments", true); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| __isValidResolution(ref, variable) { |
| |
| if (this.block.type === "Program") { |
| return true; |
| } |
|
|
| const bodyStart = this.block.body.range[0]; |
|
|
| |
| return !( |
| variable.scope === this && |
| ref.identifier.range[0] < bodyStart && |
| variable.defs.every(d => d.name.range[0] >= bodyStart) |
| ); |
| } |
| } |
|
|
| |
| |
| |
| |
| class ForScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super(scopeManager, "for", upperScope, block, false); |
| } |
| } |
|
|
| |
| |
| |
| |
| class ClassScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super(scopeManager, "class", upperScope, block, false); |
| } |
| } |
|
|
| |
| |
| |
| |
| class ClassFieldInitializerScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super(scopeManager, "class-field-initializer", upperScope, block, true); |
| } |
| } |
|
|
| |
| |
| |
| |
| class ClassStaticBlockScope extends Scope { |
| constructor(scopeManager, upperScope, block) { |
| super(scopeManager, "class-static-block", upperScope, block, true); |
| } |
| } |
|
|
| export { |
| Scope, |
| GlobalScope, |
| ModuleScope, |
| FunctionExpressionNameScope, |
| CatchScope, |
| WithScope, |
| BlockScope, |
| SwitchScope, |
| FunctionScope, |
| ForScope, |
| ClassScope, |
| ClassFieldInitializerScope, |
| ClassStaticBlockScope, |
| }; |
|
|
| |
|
|