diff --git a/src/ast/nodes/MemberExpression.ts b/src/ast/nodes/MemberExpression.ts index c585d5c9aa0..f7a3ab0b61a 100644 --- a/src/ast/nodes/MemberExpression.ts +++ b/src/ast/nodes/MemberExpression.ts @@ -34,6 +34,9 @@ import { } from './shared/Expression'; import { ExpressionNode, IncludeChildren, NodeBase } from './shared/Node'; +// To avoid infinite recursions +const MAX_PATH_DEPTH = 7; + function getResolvablePropertyKey(memberExpression: MemberExpression): string | null { return memberExpression.computed ? getResolvableComputedPropertyKey(memberExpression.property) @@ -124,7 +127,9 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE if (this.variable) { this.variable.deoptimizePath(path); } else if (!this.replacement) { - this.object.deoptimizePath([this.getPropertyKey(), ...path]); + if (path.length < MAX_PATH_DEPTH) { + this.object.deoptimizePath([this.getPropertyKey(), ...path]); + } } } @@ -137,12 +142,16 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE if (this.variable) { this.variable.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); } else if (!this.replacement) { - this.object.deoptimizeThisOnEventAtPath( - event, - [this.getPropertyKey(), ...path], - thisParameter, - recursionTracker - ); + if (path.length < MAX_PATH_DEPTH) { + this.object.deoptimizeThisOnEventAtPath( + event, + [this.getPropertyKey(), ...path], + thisParameter, + recursionTracker + ); + } else { + thisParameter.deoptimizePath(UNKNOWN_PATH); + } } } @@ -158,11 +167,14 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE return UnknownValue; } this.expressionsToBeDeoptimized.push(origin); - return this.object.getLiteralValueAtPath( - [this.getPropertyKey(), ...path], - recursionTracker, - origin - ); + if (path.length < MAX_PATH_DEPTH) { + return this.object.getLiteralValueAtPath( + [this.getPropertyKey(), ...path], + recursionTracker, + origin + ); + } + return UnknownValue; } getReturnExpressionWhenCalledAtPath( @@ -183,12 +195,15 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE return UNKNOWN_EXPRESSION; } this.expressionsToBeDeoptimized.push(origin); - return this.object.getReturnExpressionWhenCalledAtPath( - [this.getPropertyKey(), ...path], - callOptions, - recursionTracker, - origin - ); + if (path.length < MAX_PATH_DEPTH) { + return this.object.getReturnExpressionWhenCalledAtPath( + [this.getPropertyKey(), ...path], + callOptions, + recursionTracker, + origin + ); + } + return UNKNOWN_EXPRESSION; } hasEffects(context: HasEffectsContext): boolean { @@ -217,7 +232,10 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE if (this.replacement) { return true; } - return this.object.hasEffectsWhenAccessedAtPath([this.getPropertyKey(), ...path], context); + if (path.length < MAX_PATH_DEPTH) { + return this.object.hasEffectsWhenAccessedAtPath([this.getPropertyKey(), ...path], context); + } + return true; } hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { @@ -227,7 +245,10 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE if (this.replacement) { return true; } - return this.object.hasEffectsWhenAssignedAtPath([this.getPropertyKey(), ...path], context); + if (path.length < MAX_PATH_DEPTH) { + return this.object.hasEffectsWhenAssignedAtPath([this.getPropertyKey(), ...path], context); + } + return true; } hasEffectsWhenCalledAtPath( @@ -241,11 +262,14 @@ export default class MemberExpression extends NodeBase implements DeoptimizableE if (this.replacement) { return true; } - return this.object.hasEffectsWhenCalledAtPath( - [this.getPropertyKey(), ...path], - callOptions, - context - ); + if (path.length < MAX_PATH_DEPTH) { + return this.object.hasEffectsWhenCalledAtPath( + [this.getPropertyKey(), ...path], + callOptions, + context + ); + } + return true; } include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void { diff --git a/src/ast/nodes/shared/ObjectMember.ts b/src/ast/nodes/shared/ObjectMember.ts index 744fe054e82..fd20d31721e 100644 --- a/src/ast/nodes/shared/ObjectMember.ts +++ b/src/ast/nodes/shared/ObjectMember.ts @@ -20,7 +20,12 @@ export class ObjectMember extends ExpressionEntity { thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - this.object.deoptimizeThisOnEventAtPath(event, path, thisParameter, recursionTracker); + this.object.deoptimizeThisOnEventAtPath( + event, + [this.key, ...path], + thisParameter, + recursionTracker + ); } getLiteralValueAtPath( diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index dc5ef57f7da..3e9acf7df30 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -17,9 +17,6 @@ import { ExpressionNode, Node } from '../nodes/shared/Node'; import { ObjectPath, PathTracker, UNKNOWN_PATH } from '../utils/PathTracker'; import Variable from './Variable'; -// To avoid infinite recursions -const MAX_PATH_DEPTH = 7; - export default class LocalVariable extends Variable { calledFromTryStatement = false; declarations: (Identifier | ExportDefaultDeclaration)[]; @@ -64,7 +61,6 @@ export default class LocalVariable extends Variable { deoptimizePath(path: ObjectPath): void { if ( - path.length > MAX_PATH_DEPTH || this.isReassigned || this.deoptimizationTracker.trackEntityAtPathAndGetIfTracked(path, this) ) { @@ -91,7 +87,7 @@ export default class LocalVariable extends Variable { thisParameter: ExpressionEntity, recursionTracker: PathTracker ): void { - if (this.isReassigned || !this.init || path.length > MAX_PATH_DEPTH) { + if (this.isReassigned || !this.init) { return thisParameter.deoptimizePath(UNKNOWN_PATH); } recursionTracker.withTrackedEntityAtPath( @@ -107,7 +103,7 @@ export default class LocalVariable extends Variable { recursionTracker: PathTracker, origin: DeoptimizableEntity ): LiteralValueOrUnknown { - if (this.isReassigned || !this.init || path.length > MAX_PATH_DEPTH) { + if (this.isReassigned || !this.init) { return UnknownValue; } return recursionTracker.withTrackedEntityAtPath( @@ -127,7 +123,7 @@ export default class LocalVariable extends Variable { recursionTracker: PathTracker, origin: DeoptimizableEntity ): ExpressionEntity { - if (this.isReassigned || !this.init || path.length > MAX_PATH_DEPTH) { + if (this.isReassigned || !this.init) { return UNKNOWN_EXPRESSION; } return recursionTracker.withTrackedEntityAtPath( @@ -147,14 +143,14 @@ export default class LocalVariable extends Variable { } hasEffectsWhenAccessedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.isReassigned || path.length > MAX_PATH_DEPTH) return true; + if (this.isReassigned) return true; return (this.init && !context.accessed.trackEntityAtPathAndGetIfTracked(path, this) && this.init.hasEffectsWhenAccessedAtPath(path, context))!; } hasEffectsWhenAssignedAtPath(path: ObjectPath, context: HasEffectsContext): boolean { - if (this.included || path.length > MAX_PATH_DEPTH) return true; + if (this.included) return true; if (path.length === 0) return false; if (this.isReassigned) return true; return (this.init && @@ -167,7 +163,7 @@ export default class LocalVariable extends Variable { callOptions: CallOptions, context: HasEffectsContext ): boolean { - if (path.length > MAX_PATH_DEPTH || this.isReassigned) return true; + if (this.isReassigned) return true; return (this.init && !( callOptions.withNew ? context.instantiated : context.called diff --git a/test/form/samples/deep-properties-access/_config.js b/test/form/samples/deep-properties-access/_config.js new file mode 100644 index 00000000000..e71a93b8870 --- /dev/null +++ b/test/form/samples/deep-properties-access/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles deeply nested property accesses' +}; diff --git a/test/form/samples/deep-properties-access/_expected.js b/test/form/samples/deep-properties-access/_expected.js new file mode 100644 index 00000000000..c1d319eb87d --- /dev/null +++ b/test/form/samples/deep-properties-access/_expected.js @@ -0,0 +1,2 @@ +var obj = { obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {}}}}}}}}}}}; +console.log(obj.obj.obj.obj.obj.obj.obj.obj.obj.foo); diff --git a/test/form/samples/deep-properties-access/main.js b/test/form/samples/deep-properties-access/main.js new file mode 100644 index 00000000000..c1d319eb87d --- /dev/null +++ b/test/form/samples/deep-properties-access/main.js @@ -0,0 +1,2 @@ +var obj = { obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {}}}}}}}}}}}; +console.log(obj.obj.obj.obj.obj.obj.obj.obj.obj.foo); diff --git a/test/form/samples/deep-properties/_config.js b/test/form/samples/deep-properties/_config.js new file mode 100644 index 00000000000..e721c6711ce --- /dev/null +++ b/test/form/samples/deep-properties/_config.js @@ -0,0 +1,6 @@ +module.exports = { + description: 'handles deeply nested properties', + options: { + treeshake: { propertyReadSideEffects: false } + } +}; diff --git a/test/form/samples/deep-properties/_expected.js b/test/form/samples/deep-properties/_expected.js new file mode 100644 index 00000000000..ea78417e0f4 --- /dev/null +++ b/test/form/samples/deep-properties/_expected.js @@ -0,0 +1,11 @@ +var obj1 = obj1; +console.log(obj1.foo()); + +var obj2 = { obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {}}}}}}}}}}}; +obj2.obj.obj.obj.obj.obj.obj.obj.foo(); + +var obj3 = { obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {}}}}}}}}}}}; +if (obj3.obj.obj.obj.obj.obj.obj.obj.obj.foo) console.log('nested'); + +var obj4 = { obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {}}}}}}}}}}}; +obj4.obj.obj.obj.obj.obj.obj.obj.foo = 'nested'; diff --git a/test/form/samples/deep-properties/main.js b/test/form/samples/deep-properties/main.js new file mode 100644 index 00000000000..ea78417e0f4 --- /dev/null +++ b/test/form/samples/deep-properties/main.js @@ -0,0 +1,11 @@ +var obj1 = obj1; +console.log(obj1.foo()); + +var obj2 = { obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {}}}}}}}}}}}; +obj2.obj.obj.obj.obj.obj.obj.obj.foo(); + +var obj3 = { obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {}}}}}}}}}}}; +if (obj3.obj.obj.obj.obj.obj.obj.obj.obj.foo) console.log('nested'); + +var obj4 = { obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {obj: {}}}}}}}}}}}; +obj4.obj.obj.obj.obj.obj.obj.obj.foo = 'nested'; diff --git a/test/form/samples/recursive-property-call/_config.js b/test/form/samples/recursive-property-call/_config.js deleted file mode 100644 index 99bde22fce9..00000000000 --- a/test/form/samples/recursive-property-call/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - description: 'handles calls to properties of recursively defined variables' -}; diff --git a/test/form/samples/recursive-property-call/_expected.js b/test/form/samples/recursive-property-call/_expected.js deleted file mode 100644 index 37fff7100ad..00000000000 --- a/test/form/samples/recursive-property-call/_expected.js +++ /dev/null @@ -1,9 +0,0 @@ -var obj1 = obj1; -console.log(obj1.foo()); - -var obj2 = {foo: () => {}}; -var obj2 = {foo: log}; -obj2.foo(); - -var obj3 = {obj: obj3}; -obj3.obj.obj.obj.obj.obj.obj.obj.obj.obj.foo(); diff --git a/test/form/samples/recursive-property-call/main.js b/test/form/samples/recursive-property-call/main.js deleted file mode 100644 index 9337bc08b1d..00000000000 --- a/test/form/samples/recursive-property-call/main.js +++ /dev/null @@ -1,9 +0,0 @@ -var obj1 = obj1; -console.log(obj1.foo()); - -var obj2 = {foo: () => {}}; -var obj2 = {foo: log}; -obj2.foo(); - -var obj3 = {obj: obj3} -obj3.obj.obj.obj.obj.obj.obj.obj.obj.obj.foo() diff --git a/test/form/samples/static-method-deoptimization/_config.js b/test/form/samples/static-method-deoptimization/_config.js new file mode 100644 index 00000000000..064ca3bc55e --- /dev/null +++ b/test/form/samples/static-method-deoptimization/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'avoids infinite recursions when deoptimizing "this" context' +}; diff --git a/test/form/samples/static-method-deoptimization/_expected.js b/test/form/samples/static-method-deoptimization/_expected.js new file mode 100644 index 00000000000..e3db19a59fe --- /dev/null +++ b/test/form/samples/static-method-deoptimization/_expected.js @@ -0,0 +1,13 @@ +class Foo { + static echo(message) { + this.prototype.echo(message); + } + echo(message) { + console.log(message); + } +} + +class Bar extends Foo {} + +global.baz = 'PASS'; +Bar.echo(baz); diff --git a/test/form/samples/static-method-deoptimization/main.js b/test/form/samples/static-method-deoptimization/main.js new file mode 100644 index 00000000000..e3db19a59fe --- /dev/null +++ b/test/form/samples/static-method-deoptimization/main.js @@ -0,0 +1,13 @@ +class Foo { + static echo(message) { + this.prototype.echo(message); + } + echo(message) { + console.log(message); + } +} + +class Bar extends Foo {} + +global.baz = 'PASS'; +Bar.echo(baz);