Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make sure the initializer of hoisted variables is deoptimized #4149

Merged
merged 3 commits into from Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Expand Up @@ -85,6 +85,6 @@ jobs:
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Run tests
run: npm test
run: npm run ci:test:only
env:
CI: true
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -11,7 +11,7 @@
"scripts": {
"build": "shx rm -rf dist && git rev-parse HEAD > .commithash && rollup --config rollup.config.ts --configPlugin typescript && shx cp src/rollup/types.d.ts dist/rollup.d.ts && shx chmod a+x dist/bin/rollup",
"build:cjs": "shx rm -rf dist && rollup --config rollup.config.ts --configPlugin typescript --configTest && shx cp src/rollup/types.d.ts dist/rollup.d.ts && shx chmod a+x dist/bin/rollup",
"build:bootstrap": "dist/bin/rollup --config rollup.config.ts --configPlugin typescript && shx cp src/rollup/types.d.ts dist/rollup.d.ts && shx chmod a+x dist/bin/rollup",
"build:bootstrap": "node dist/bin/rollup --config rollup.config.ts --configPlugin typescript && shx cp src/rollup/types.d.ts dist/rollup.d.ts && shx chmod a+x dist/bin/rollup",
"ci:lint": "npm run lint:nofix",
"ci:test": "npm run build:cjs && npm run build:bootstrap && npm run test:all",
"ci:test:only": "npm run build:cjs && npm run build:bootstrap && npm run test:only",
Expand Down
14 changes: 7 additions & 7 deletions src/ast/nodes/Identifier.ts
Expand Up @@ -9,12 +9,13 @@ 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';
import * as NodeType from './NodeType';
import SpreadElement from './SpreadElement';
import { ExpressionEntity, LiteralValueOrUnknown, UNKNOWN_EXPRESSION } from './shared/Expression';
import { ExpressionEntity, LiteralValueOrUnknown } from './shared/Expression';
import { ExpressionNode, NodeBase } from './shared/Node';
import { PatternNode } from './shared/Pattern';

Expand Down Expand Up @@ -48,12 +49,11 @@ export default class Identifier extends NodeBase implements PatternNode {
const { treeshake } = this.context.options;
switch (kind) {
case 'var':
variable = this.scope.addDeclaration(
this,
this.context,
treeshake && treeshake.correctVarValueBeforeDeclaration ? UNKNOWN_EXPRESSION : init,
true
);
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);
}
break;
case 'function':
// in strict mode, functions are only hoisted within a scope but not across block scopes
Expand Down
7 changes: 5 additions & 2 deletions src/ast/scopes/BlockScope.ts
@@ -1,6 +1,7 @@
import { AstContext } from '../../Module';
import Identifier from '../nodes/Identifier';
import { ExpressionEntity, UNKNOWN_EXPRESSION } from '../nodes/shared/Expression';
import { ExpressionEntity } from '../nodes/shared/Expression';
import { UNDEFINED_EXPRESSION } from '../values';
import LocalVariable from '../variables/LocalVariable';
import ChildScope from './ChildScope';

Expand All @@ -12,7 +13,9 @@ export default class BlockScope extends ChildScope {
isHoisted: boolean
): LocalVariable {
if (isHoisted) {
return this.parent.addDeclaration(identifier, context, UNKNOWN_EXPRESSION, isHoisted);
this.parent.addDeclaration(identifier, context, init, isHoisted);
// Necessary to make sure the init is deoptimized. We cannot call deoptimizePath here.
return this.parent.addDeclaration(identifier, context, UNDEFINED_EXPRESSION, isHoisted);
} else {
return super.addDeclaration(identifier, context, init, false);
}
Expand Down
3 changes: 1 addition & 2 deletions src/ast/scopes/CatchScope.ts
Expand Up @@ -16,8 +16,7 @@ export default class CatchScope extends ParameterScope {
existingParameter.addDeclaration(identifier, init);
return existingParameter;
}
// as parameters are handled differently, all remaining declarations are
// hoisted
// as parameters are handled differently, all remaining declarations are hoisted
return this.parent.addDeclaration(identifier, context, init, isHoisted);
}
}
3 changes: 3 additions & 0 deletions test/form/samples/deoptimize-var-in-hoisted-scopes/_config.js
@@ -0,0 +1,3 @@
module.exports = {
description: 'deoptimizes var variables in hoisted scopes'
};
15 changes: 15 additions & 0 deletions test/form/samples/deoptimize-var-in-hoisted-scopes/_expected.js
@@ -0,0 +1,15 @@
const obj1 = { flag: false };
{
var foo = obj1;
foo.flag = true;
}
if (obj1.flag) console.log('retained');

const obj2 = { flag: false };
try {
throw new Error();
} catch {
var foo = obj2;
foo.flag = true;
}
if (obj2.flag) console.log('retained');
15 changes: 15 additions & 0 deletions test/form/samples/deoptimize-var-in-hoisted-scopes/main.js
@@ -0,0 +1,15 @@
const obj1 = { flag: false };
{
var foo = obj1;
foo.flag = true;
}
if (obj1.flag) console.log('retained');

const obj2 = { flag: false };
try {
throw new Error();
} catch {
var foo = obj2;
foo.flag = true;
}
if (obj2.flag) console.log('retained');
@@ -0,0 +1,6 @@
module.exports = {
description: 'adds necessary deoptimizations when using treeshake.correctVarBeforeDeclaration',
options: {
treeshake: { correctVarValueBeforeDeclaration: true }
}
};
@@ -0,0 +1,15 @@
const obj1 = { flag: false };
{
var foo = obj1;
foo.flag = true;
}
if (obj1.flag) console.log('retained');

const obj2 = { flag: false };
try {
throw new Error();
} catch {
var foo = obj2;
foo.flag = true;
}
if (obj2.flag) console.log('retained');
@@ -0,0 +1,7 @@
const obj = { flag: false };
var foo = obj;
foo.flag = true;
assert.ok(obj.flag ? true : false, 'init deoptimization');

assert.ok(bar ? false : true, 'value deoptimization');
var bar = true;