From 9a81b83c43ad5c47fc1ba7883b19edbe42d94c17 Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Wed, 21 Jul 2021 07:58:42 +0200 Subject: [PATCH] Avoid memory issues with hoisted variables --- src/ast/nodes/Identifier.ts | 3 +- src/ast/scopes/BlockScope.ts | 6 ++-- src/ast/variables/LocalVariable.ts | 19 +++++++---- .../deep-switch-declarations/_config.js | 3 ++ .../deep-switch-declarations/_expected.js | 32 +++++++++++++++++++ .../samples/deep-switch-declarations/main.js | 32 +++++++++++++++++++ 6 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 test/form/samples/deep-switch-declarations/_config.js create mode 100644 test/form/samples/deep-switch-declarations/_expected.js create mode 100644 test/form/samples/deep-switch-declarations/main.js diff --git a/src/ast/nodes/Identifier.ts b/src/ast/nodes/Identifier.ts index 63e6a5149d9..d6b2a1e36e3 100644 --- a/src/ast/nodes/Identifier.ts +++ b/src/ast/nodes/Identifier.ts @@ -9,7 +9,6 @@ import { HasEffectsContext, InclusionContext } from '../ExecutionContext'; import { NodeEvent } from '../NodeEvents'; import FunctionScope from '../scopes/FunctionScope'; import { EMPTY_PATH, ObjectPath, PathTracker } from '../utils/PathTracker'; -import { UNDEFINED_EXPRESSION } from '../values'; import GlobalVariable from '../variables/GlobalVariable'; import LocalVariable from '../variables/LocalVariable'; import Variable from '../variables/Variable'; @@ -60,7 +59,7 @@ export default class Identifier extends NodeBase implements PatternNode { variable = this.scope.addDeclaration(this, this.context, init, true); if (treeshake && treeshake.correctVarValueBeforeDeclaration) { // Necessary to make sure the init is deoptimized. We cannot call deoptimizePath here. - this.scope.addDeclaration(this, this.context, UNDEFINED_EXPRESSION, true); + variable.markInitializersForDeoptimization(); } break; case 'function': diff --git a/src/ast/scopes/BlockScope.ts b/src/ast/scopes/BlockScope.ts index ee694c790b5..7029bf00764 100644 --- a/src/ast/scopes/BlockScope.ts +++ b/src/ast/scopes/BlockScope.ts @@ -1,7 +1,6 @@ import { AstContext } from '../../Module'; import Identifier from '../nodes/Identifier'; import { ExpressionEntity } from '../nodes/shared/Expression'; -import { UNDEFINED_EXPRESSION } from '../values'; import LocalVariable from '../variables/LocalVariable'; import ChildScope from './ChildScope'; @@ -13,10 +12,11 @@ export default class BlockScope extends ChildScope { isHoisted: boolean ): LocalVariable { if (isHoisted) { - this.parent.addDeclaration(identifier, context, init, isHoisted); + const variable = this.parent.addDeclaration(identifier, context, init, isHoisted); // Necessary to make sure the init is deoptimized for conditional declarations. // We cannot call deoptimizePath here. - return this.parent.addDeclaration(identifier, context, UNDEFINED_EXPRESSION, isHoisted); + variable.markInitializersForDeoptimization(); + return variable; } else { return super.addDeclaration(identifier, context, init, false); } diff --git a/src/ast/variables/LocalVariable.ts b/src/ast/variables/LocalVariable.ts index 7401001757a..dc5ef57f7da 100644 --- a/src/ast/variables/LocalVariable.ts +++ b/src/ast/variables/LocalVariable.ts @@ -21,7 +21,6 @@ import Variable from './Variable'; const MAX_PATH_DEPTH = 7; export default class LocalVariable extends Variable { - additionalInitializers: ExpressionEntity[] | null = null; calledFromTryStatement = false; declarations: (Identifier | ExportDefaultDeclaration)[]; init: ExpressionEntity | null; @@ -30,6 +29,7 @@ export default class LocalVariable extends Variable { // Caching and deoptimization: // We track deoptimization when we do not return something unknown protected deoptimizationTracker: PathTracker; + private additionalInitializers: ExpressionEntity[] | null = null; private expressionsToBeDeoptimized: DeoptimizableEntity[] = []; constructor( @@ -47,13 +47,9 @@ export default class LocalVariable extends Variable { addDeclaration(identifier: Identifier, init: ExpressionEntity | null): void { this.declarations.push(identifier); - if (this.additionalInitializers === null) { - this.additionalInitializers = this.init === null ? [] : [this.init]; - this.init = UNKNOWN_EXPRESSION; - this.isReassigned = true; - } + const additionalInitializers = this.markInitializersForDeoptimization(); if (init !== null) { - this.additionalInitializers.push(init); + additionalInitializers.push(init); } } @@ -212,4 +208,13 @@ export default class LocalVariable extends Variable { markCalledFromTryStatement(): void { this.calledFromTryStatement = true; } + + markInitializersForDeoptimization(): ExpressionEntity[] { + if (this.additionalInitializers === null) { + this.additionalInitializers = this.init === null ? [] : [this.init]; + this.init = UNKNOWN_EXPRESSION; + this.isReassigned = true; + } + return this.additionalInitializers; + } } diff --git a/test/form/samples/deep-switch-declarations/_config.js b/test/form/samples/deep-switch-declarations/_config.js new file mode 100644 index 00000000000..1487e3c6852 --- /dev/null +++ b/test/form/samples/deep-switch-declarations/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'handles hoisted declarations in deeply nested switch statements' +}; diff --git a/test/form/samples/deep-switch-declarations/_expected.js b/test/form/samples/deep-switch-declarations/_expected.js new file mode 100644 index 00000000000..4b15e14b28c --- /dev/null +++ b/test/form/samples/deep-switch-declarations/_expected.js @@ -0,0 +1,32 @@ +switch(x){ default: var x=1; + switch(x){ default: var x=2; + switch(x){ default: var x=3; + switch(x){ default: var x=4; + switch(x){ default: var x=5; + switch(x){ default: var x=6; + switch(x){ default: var x=7; + switch(x){ default: var x=8; + switch(x){ default: var x=9; + switch(x){ default: var x=10; + switch(x){ default: var x=11; + switch(x){ default: var x=12; + switch(x){ default: var x=13; + switch(x){ default: var x=14; + switch(x){ default: var x=15; + switch(x){ default: var x=16; + switch(x){ default: var x=17; + switch(x){ default: var x=18; + switch(x){ default: var x=19; + switch(x){ default: var x=20; + switch(x){ default: var x=21; + switch(x){ default: var x=22; + switch(x){ default: var x=23; + switch(x){ default: var x=24; + switch(x){ default: var x=25; + switch(x){ default: var x=26; + switch(x){ default: var x=27; + switch(x){ default: var x=28; + switch(x){ default: var x=29; + switch(x){ default: var x=30; + }}}}}}}}}}}}}}}}}}}}}}}}}}}}}} +console.log(x); diff --git a/test/form/samples/deep-switch-declarations/main.js b/test/form/samples/deep-switch-declarations/main.js new file mode 100644 index 00000000000..03d8f89b61d --- /dev/null +++ b/test/form/samples/deep-switch-declarations/main.js @@ -0,0 +1,32 @@ +switch(x){ default: var x=1; + switch(x){ default: var x=2; + switch(x){ default: var x=3; + switch(x){ default: var x=4; + switch(x){ default: var x=5; + switch(x){ default: var x=6; + switch(x){ default: var x=7; + switch(x){ default: var x=8; + switch(x){ default: var x=9; + switch(x){ default: var x=10; + switch(x){ default: var x=11; + switch(x){ default: var x=12; + switch(x){ default: var x=13; + switch(x){ default: var x=14; + switch(x){ default: var x=15; + switch(x){ default: var x=16; + switch(x){ default: var x=17; + switch(x){ default: var x=18; + switch(x){ default: var x=19; + switch(x){ default: var x=20; + switch(x){ default: var x=21; + switch(x){ default: var x=22; + switch(x){ default: var x=23; + switch(x){ default: var x=24; + switch(x){ default: var x=25; + switch(x){ default: var x=26; + switch(x){ default: var x=27; + switch(x){ default: var x=28; + switch(x){ default: var x=29; + switch(x){ default: var x=30; + }}}}}}}}}}}}}}}}}}}}}}}}}}}}}} +console.log(x); \ No newline at end of file