Skip to content

Commit

Permalink
Introduce hasEffectsWhenAccessed. For now we no longer swallow errors
Browse files Browse the repository at this point in the history
due to forbidden property access. In the future, we can use this to
properly handle getters.
  • Loading branch information
lukastaegert committed Nov 8, 2017
1 parent 3ad6908 commit 09643f3
Show file tree
Hide file tree
Showing 60 changed files with 593 additions and 105 deletions.
21 changes: 20 additions & 1 deletion src/ast/ExecutionPathOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import Immutable from 'immutable';
const OPTION_IGNORE_BREAK_STATEMENTS = 'IGNORE_BREAK_STATEMENTS';
const OPTION_IGNORE_RETURN_AWAIT_YIELD = 'IGNORE_RETURN_AWAIT_YIELD';
const OPTION_IGNORE_SAFE_THIS_MUTATIONS = 'IGNORE_SAFE_THIS_MUTATIONS';
const OPTION_ACCESSED_NODES = 'ACCESSED_NODES';
const OPTION_ASSIGNED_NODES = 'ASSIGNED_NODES';
const OPTION_CALLED_NODES = 'CALLED_NODES';
const OPTION_MUTATED_NODES = 'MUTATED_NODES';
const OPTION_ASSIGNED_NODES = 'ASSIGNED_NODES';
const IGNORED_LABELS = 'IGNORED_LABELS';

const RESULT_KEY = {};
Expand Down Expand Up @@ -114,6 +115,24 @@ export default class ExecutionPathOptions {
return this.set( OPTION_IGNORE_SAFE_THIS_MUTATIONS, value );
}

/**
* @param {String[]} path
* @param {Node} node
* @return {ExecutionPathOptions}
*/
addAccessedNodeAtPath ( path, node ) {
return this.setIn( [ OPTION_ACCESSED_NODES, node, ...path, RESULT_KEY ], true );
}

/**
* @param {String[]} path
* @param {Node} node
* @return {boolean}
*/
hasNodeBeenAccessedAtPath ( path, node ) {
return this._optionValues.getIn( [ OPTION_ACCESSED_NODES, node, ...path, RESULT_KEY ] );
}

/**
* @param {String[]} path
* @param {Node} node
Expand Down
13 changes: 12 additions & 1 deletion src/ast/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ export default class Node {
* @return {boolean}
*/
hasEffects ( options ) {
return this.included || this.someChild( child => child.hasEffects( options ) );
return this.included
|| this.hasEffectsWhenAccessedAtPath( [], options )
|| this.someChild( child => child.hasEffects( options ) );
}

/**
Expand All @@ -77,6 +79,15 @@ export default class Node {
return true;
}

/**
* @param {String[]} path
* @param {ExecutionPathOptions} options
* @return {boolean}
*/
hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 0;
}

/**
* @param {String[]} path
* @param {ExecutionPathOptions} options
Expand Down
7 changes: 7 additions & 0 deletions src/ast/nodes/ArrayExpression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Node from '../Node.js';

export default class ArrayExpression extends Node {
hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 1;
}
}
4 changes: 4 additions & 0 deletions src/ast/nodes/ArrowFunctionExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export default class ArrowFunctionExpression extends Node {
return this.included;
}

hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 1;
}

hasEffectsWhenAssignedAtPath ( path ) {
if ( path.length === 0 ) {
return true;
Expand Down
4 changes: 4 additions & 0 deletions src/ast/nodes/AssignmentExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ export default class AssignmentExpression extends Node {
hasEffectsAsExpressionStatement ( options ) {
return this.hasEffects( options );
}

hasEffectsWhenAccessedAtPath ( path, options ) {
return this.right.hasEffectsWhenAccessedAtPath( path, options );
}
}
4 changes: 4 additions & 0 deletions src/ast/nodes/BinaryExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ export default class BinaryExpression extends Node {

return operators[ this.operator ]( leftValue, rightValue );
}

hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 1;
}
}
5 changes: 5 additions & 0 deletions src/ast/nodes/Identifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export default class Identifier extends Node {
return this.hasEffects( options ) || this.variable.isGlobal;
}

hasEffectsWhenAccessedAtPath ( path, options ) {
return this.variable
&& this.variable.hasEffectsWhenAccessedAtPath( path, options );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
return !this.variable
|| this.variable.hasEffectsWhenAssignedAtPath( path, options );
Expand Down
7 changes: 7 additions & 0 deletions src/ast/nodes/Literal.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ export default class Literal extends Node {
return this.value;
}

hasEffectsWhenAccessedAtPath ( path ) {
if (this.value === null) {
return path.length > 0;
}
return path.length > 1;
}

hasEffectsWhenAssignedAtPath ( path ) {
if (this.value === null) {
return path.length > 0;
Expand Down
15 changes: 15 additions & 0 deletions src/ast/nodes/LogicalExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ export default class LogicalExpression extends Node {
return operators[ this.operator ]( leftValue, rightValue );
}

hasEffectsWhenAccessedAtPath ( path, options ) {
if (path.length === 0) {
return false;
}
const leftValue = this.left.getValue();
if ( leftValue === UNKNOWN_VALUE ) {
return this.left.hasEffectsWhenAccessedAtPath( path, options )
|| this.right.hasEffectsWhenAccessedAtPath( path, options );
}
if ( (leftValue && this.operator === '||') || (!leftValue && this.operator === '&&') ) {
return this.left.hasEffectsWhenAccessedAtPath( path, options );
}
return this.right.hasEffectsWhenAccessedAtPath( path, options );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
return path.length === 0
|| this.hasEffectsWhenMutatedAtPath( path.slice( 1 ), options );
Expand Down
12 changes: 12 additions & 0 deletions src/ast/nodes/MemberExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ export default class MemberExpression extends Node {
}
}

hasEffects ( options ) {
return super.hasEffects( options )
|| this.object.hasEffectsWhenAccessedAtPath( [ this.computed ? UNKNOWN_KEY : this.property.name ], options );
}

hasEffectsWhenAccessedAtPath ( path, options ) {
if ( this.variable ) {
return this.variable.hasEffectsWhenAccessedAtPath( path, options );
}
return this.object.hasEffectsWhenAccessedAtPath( [ this.computed ? UNKNOWN_KEY : this.property.name, ...path ], options );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
if ( this.variable ) {
return this.variable.hasEffectsWhenAssignedAtPath( path, options );
Expand Down
4 changes: 4 additions & 0 deletions src/ast/nodes/NewExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ export default class NewExpression extends Node {
|| this.arguments.some( child => child.hasEffects( options ) )
|| this.callee.hasEffectsWhenCalledAtPath( [], options.getHasEffectsWhenCalledOptions( this.callee ) );
}

hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 1;
}
}
10 changes: 10 additions & 0 deletions src/ast/nodes/ObjectExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ export default class ObjectExpression extends Node {
return { properties, hasCertainHit };
}

hasEffectsWhenAccessedAtPath ( path, options ) {
if ( path.length <= 1 ) {
return false;
}
const { properties, hasCertainHit } = this._getPossiblePropertiesWithName( path[ 0 ] );

return !hasCertainHit || properties.some( property =>
property.hasEffectsWhenAccessedAtPath( path.slice( 1 ), options ) );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
if ( path.length <= 1 ) {
return false;
Expand Down
4 changes: 4 additions & 0 deletions src/ast/nodes/Property.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export default class Property extends Node {
this.value.bindCallAtPath( path, callOptions );
}

hasEffectsWhenAccessedAtPath ( path, options ) {
return this.value.hasEffectsWhenAccessedAtPath( path, options );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
return this.value.hasEffectsWhenAssignedAtPath( path, options );
}
Expand Down
6 changes: 5 additions & 1 deletion src/ast/nodes/ThisExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ export default class ThisExpression extends Node {
this.variable = this.scope.findVariable( 'this' );
}

hasEffectsWhenAccessedAtPath ( path, options ) {
return this.variable.hasEffectsWhenAccessedAtPath( path, options );
}

hasEffectsWhenAssignedAtPath ( path, options ) {
if ( path.length === 0 ) {
return true;
}
return this.hasEffectsWhenMutatedAtPath( path.slice(1), options );
return this.hasEffectsWhenMutatedAtPath( path.slice( 1 ), options );
}

hasEffectsWhenMutatedAtPath ( path, options ) {
Expand Down
7 changes: 7 additions & 0 deletions src/ast/nodes/UnaryExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ export default class UnaryExpression extends Node {
return this.hasEffects( options );
}

hasEffectsWhenAccessedAtPath ( path ) {
if ( this.operator === 'void' ) {
return path.length > 0;
}
return path.length > 1;
}

initialiseNode () {
this.value = this.getValue();
}
Expand Down
4 changes: 4 additions & 0 deletions src/ast/nodes/UpdateExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ export default class UpdateExpression extends Node {
hasEffectsAsExpressionStatement ( options ) {
return this.hasEffects( options );
}

hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 1;
}
}
4 changes: 2 additions & 2 deletions src/ast/nodes/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ArrayExpression from './ArrayExpression.js';
import ArrayPattern from './ArrayPattern.js';
import ArrowFunctionExpression from './ArrowFunctionExpression.js';
import AssignmentExpression from './AssignmentExpression.js';
Expand Down Expand Up @@ -52,10 +53,9 @@ import VariableDeclarator from './VariableDeclarator.js';
import VariableDeclaration from './VariableDeclaration.js';
import WhileStatement from './WhileStatement.js';
import YieldExpression from './YieldExpression.js';
import Node from '../Node';

export default {
ArrayExpression: Node,
ArrayExpression,
ArrayPattern,
ArrowFunctionExpression,
AssignmentExpression,
Expand Down
8 changes: 8 additions & 0 deletions src/ast/nodes/shared/ClassNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ export default class ClassNode extends Node {
return this.hasEffects( options );
}

hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 1;
}

hasEffectsWhenAssignedAtPath ( path ) {
return path.length > 1;
}

hasEffectsWhenCalledAtPath ( path, options ) {
return this.body.hasEffectsWhenCalledAtPath( path, options )
|| ( this.superClass && this.superClass.hasEffectsWhenCalledAtPath( path, options ) );
Expand Down
10 changes: 10 additions & 0 deletions src/ast/nodes/shared/FunctionNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ export default class FunctionNode extends Node {
return this.hasEffects( options );
}

hasEffectsWhenAccessedAtPath ( path, options ) {
if ( path.length <= 1 ) {
return false;
}
if ( path[ 0 ] === 'prototype' ) {
return this.prototypeObject.hasEffectsWhenAccessedAtPath( path.slice( 1 ), options );
}
return true;
}

hasEffectsWhenAssignedAtPath ( path, options ) {
if ( path.length <= 1 ) {
return false;
Expand Down
12 changes: 12 additions & 0 deletions src/ast/nodes/shared/UndefinedIdentifier.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import Node from '../../Node';

export default class UndefinedIdentifier extends Node {
hasEffects () {
return false;
}

hasEffectsAsExpressionStatement () {
return false;
}

hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 0;
}

hasEffectsWhenAssignedAtPath ( path ) {
return path.length > 0;
}

hasEffectsWhenMutatedAtPath ( path ) {
return path.length > 0;
}

toString () {
return 'undefined';
}
}
8 changes: 8 additions & 0 deletions src/ast/nodes/shared/VirtualNumberLiteral.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import Node from '../../Node';

export default class VirtualNumberLiteral extends Node {
hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 1;
}

hasEffectsWhenAssignedAtPath ( path ) {
return path.length > 1;
}

hasEffectsWhenMutatedAtPath ( path ) {
return path.length > 0;
}

toString () {
return '[[VIRTUAL NUMBER]]';
}
}
8 changes: 8 additions & 0 deletions src/ast/nodes/shared/VirtualObjectExpression.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import Node from '../../Node';

export default class VirtualObjectExpression extends Node {
hasEffectsWhenAccessedAtPath ( path ) {
return path.length > 1;
}

hasEffectsWhenAssignedAtPath ( path ) {
return path.length > 1;
}

hasEffectsWhenMutatedAtPath ( path ) {
return path.length > 0;
}

toString () {
return '[[VIRTUAL OBJECT]]';
}
}
2 changes: 2 additions & 0 deletions src/ast/values.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export const UNKNOWN_ASSIGNMENT = {
type: 'UNKNOWN',
bindAssignmentAtPath: () => {},
bindCallAtPath: () => {},
hasEffectsWhenAccessedAtPath: path => path.length > 0,
hasEffectsWhenAssignedAtPath: () => true,
hasEffectsWhenCalledAtPath: () => true,
hasEffectsWhenMutatedAtPath: () => true,
toString: () => '[[UNKNOWN]]'
};
6 changes: 6 additions & 0 deletions src/ast/variables/GlobalVariable.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export default class GlobalVariable extends Variable {
if ( reference.isReassignment ) this.isReassigned = true;
}

hasEffectsWhenAccessedAtPath ( path ) {
// path.length == 0 can also have an effect but we postpone this for now
return path.length > 0
&& !pureFunctions[ [ this.name, ...path ].join( '.' ) ];
}

hasEffectsWhenCalledAtPath ( path ) {
return !pureFunctions[ [ this.name, ...path ].join( '.' ) ];
}
Expand Down
Loading

0 comments on commit 09643f3

Please sign in to comment.