Skip to content

Commit

Permalink
Detect variable reassignments in modules without side effects (#5658)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert authored Sep 19, 2024
1 parent 8fc1223 commit 184bc4e
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/Graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ export default class Graph {
this.needsTreeshakingPass = false;
for (const module of this.modules) {
if (module.isExecuted) {
module.hasTreeShakingPassStarted = true;
if (module.info.moduleSideEffects === 'no-treeshake') {
module.includeAllInBundle();
} else {
Expand Down
9 changes: 5 additions & 4 deletions src/Module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { extractAssignedNames } from '@rollup/pluginutils';
import { locate } from 'locate-character';
import MagicString from 'magic-string';
import { parseAsync } from '../native';
import ExternalModule from './ExternalModule';
import type Graph from './Graph';
import { createInclusionContext } from './ast/ExecutionContext';
import { convertProgram } from './ast/bufferParsers';
import { createInclusionContext } from './ast/ExecutionContext';
import { nodeConstructors } from './ast/nodes';
import ExportAllDeclaration from './ast/nodes/ExportAllDeclaration';
import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration';
Expand All @@ -19,8 +17,8 @@ import Literal from './ast/nodes/Literal';
import type MetaProperty from './ast/nodes/MetaProperty';
import * as NodeType from './ast/nodes/NodeType';
import type Program from './ast/nodes/Program';
import VariableDeclaration from './ast/nodes/VariableDeclaration';
import type { NodeBase } from './ast/nodes/shared/Node';
import VariableDeclaration from './ast/nodes/VariableDeclaration';
import ModuleScope from './ast/scopes/ModuleScope';
import { type PathTracker, UNKNOWN_PATH } from './ast/utils/PathTracker';
import ExportDefaultVariable from './ast/variables/ExportDefaultVariable';
Expand All @@ -29,6 +27,8 @@ import ExternalVariable from './ast/variables/ExternalVariable';
import NamespaceVariable from './ast/variables/NamespaceVariable';
import SyntheticNamedExportVariable from './ast/variables/SyntheticNamedExportVariable';
import type Variable from './ast/variables/Variable';
import ExternalModule from './ExternalModule';
import type Graph from './Graph';
import type {
AstNode,
CustomPluginOptions,
Expand Down Expand Up @@ -220,6 +220,7 @@ export default class Module {
readonly dynamicImports: DynamicImport[] = [];
excludeFromSourcemap: boolean;
execIndex = Infinity;
hasTreeShakingPassStarted = false;
readonly implicitlyLoadedAfter = new Set<Module>();
readonly implicitlyLoadedBefore = new Set<Module>();
readonly importDescriptions = new Map<string, ImportDescription>();
Expand Down
17 changes: 10 additions & 7 deletions src/ast/nodes/Identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import GlobalVariable from '../variables/GlobalVariable';
import LocalVariable from '../variables/LocalVariable';
import type Variable from '../variables/Variable';
import * as NodeType from './NodeType';
import type SpreadElement from './SpreadElement';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import {
type ExpressionEntity,
Expand All @@ -30,6 +29,7 @@ import {
import { NodeBase } from './shared/Node';
import type { PatternNode } from './shared/Pattern';
import type { VariableKind } from './shared/VariableKinds';
import type SpreadElement from './SpreadElement';

export type IdentifierWithVariable = Identifier & { variable: Variable };

Expand Down Expand Up @@ -220,9 +220,10 @@ export default class Identifier extends NodeBase implements PatternNode {
this.variable instanceof LocalVariable &&
this.variable.kind &&
tdzVariableKinds.has(this.variable.kind) &&
// we ignore possible TDZs due to circular module dependencies as
// otherwise we get many false positives
this.variable.module === this.scope.context.module
// We ignore modules that did not receive a treeshaking pass yet as that
// causes many false positives due to circular dependencies or disabled
// moduleSideEffects.
this.variable.module.hasTreeShakingPassStarted
)
) {
return (this.isTDZAccess = false);
Expand All @@ -241,9 +242,7 @@ export default class Identifier extends NodeBase implements PatternNode {
return (this.isTDZAccess = true);
}

// We ignore the case where the module is not yet executed because
// moduleSideEffects are false.
if (!this.variable.initReached && this.scope.context.module.isExecuted) {
if (!this.variable.initReached) {
// Either a const/let TDZ violation or
// var use before declaration was encountered.
return (this.isTDZAccess = true);
Expand Down Expand Up @@ -294,6 +293,10 @@ export default class Identifier extends NodeBase implements PatternNode {
protected applyDeoptimizations(): void {
this.deoptimized = true;
if (this.variable instanceof LocalVariable) {
// When accessing a variable from a module without side effects, this
// means we use an export of that module and therefore need to potentially
// include it in the bundle.
this.variable.module.isExecuted = true;
this.variable.consolidateInitializers();
this.scope.context.requestTreeshakingPass();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = defineTest({
description: 'detects variable updates in modules without side effects (#5408)',
options: {
treeshake: {
moduleSideEffects: false
}
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export let direct = false;
direct = true;

export {indirect} from './dep2.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export let indirect = false;
(() => {
indirect = true;
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { direct, indirect } from './dep.js';
assert.ok(direct ? true : false, 'direct');
assert.ok(indirect ? true : false, 'indirect');

0 comments on commit 184bc4e

Please sign in to comment.