Skip to content

Commit 15a6f73

Browse files
authored
Fix prop inclusion with try-catch-deoptimization (#5842)
* Test that argument inclusion works correctly when using try-catch-deopt * Refine call argument inclusion - When call arguments are included, do not call "include" on the "this" argument again, as this will already be included at the call site. This allows to make the inclusion logic more straightforward - create shared helper to do that - if a call/new/tagged template expression is called with recursive inclusion, do not specifically include the arguments. Instead, if an identifier is included recursively, then include an unknown path of the variable. * Refine inclusion and deoptimization logic for reassigned member expressions
1 parent a724b60 commit 15a6f73

20 files changed

Lines changed: 116 additions & 89 deletions

src/ast/nodes/BreakStatement.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext';
22
import type Identifier from './Identifier';
33
import type * as NodeType from './NodeType';
4-
import { doNotDeoptimize, onlyIncludeSelfNoDeoptimize, StatementBase } from './shared/Node';
4+
import {
5+
doNotDeoptimize,
6+
type IncludeChildren,
7+
onlyIncludeSelfNoDeoptimize,
8+
StatementBase
9+
} from './shared/Node';
510

611
export default class BreakStatement extends StatementBase {
712
declare label: Identifier | null;
@@ -19,10 +24,10 @@ export default class BreakStatement extends StatementBase {
1924
return false;
2025
}
2126

22-
include(context: InclusionContext): void {
27+
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
2328
this.included = true;
2429
if (this.label) {
25-
this.label.include(context);
30+
this.label.include(context, includeChildrenRecursively);
2631
context.includedLabels.add(this.label.name);
2732
} else {
2833
context.hasBreak = true;

src/ast/nodes/CallExpression.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,7 @@ export default class CallExpression
114114
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
115115
if (!this.included) this.includeNode(context);
116116
if (includeChildrenRecursively) {
117-
this.callee.include(context, true);
118-
for (const argument of this.arguments) {
119-
argument.includePath(UNKNOWN_PATH, context);
120-
argument.include(context, true);
121-
}
117+
super.include(context, true);
122118
if (
123119
includeChildrenRecursively === INCLUDE_PARAMETERS &&
124120
this.callee instanceof Identifier &&
@@ -127,16 +123,8 @@ export default class CallExpression
127123
this.callee.variable.markCalledFromTryStatement();
128124
}
129125
} else {
130-
// If the callee is a member expression and does not have a variable, its
131-
// object will already be included via the first argument of the
132-
// interaction in includeCallArguments. Including it again can lead to
133-
// severe performance problems.
134-
if (this.callee instanceof MemberExpression && !this.callee.variable) {
135-
this.callee.property.include(context, false);
136-
} else {
137-
this.callee.include(context, false);
138-
}
139-
this.callee.includeCallArguments(context, this.interaction);
126+
this.callee.include(context, false);
127+
this.callee.includeCallArguments(this.interaction, context);
140128
}
141129
}
142130

src/ast/nodes/ConditionalExpression.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,13 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz
164164
}
165165
}
166166

167-
includeCallArguments(context: InclusionContext, interaction: NodeInteractionCalled): void {
167+
includeCallArguments(interaction: NodeInteractionCalled, context: InclusionContext): void {
168168
const usedBranch = this.getUsedBranch();
169169
if (usedBranch) {
170-
usedBranch.includeCallArguments(context, interaction);
170+
usedBranch.includeCallArguments(interaction, context);
171171
} else {
172-
this.consequent.includeCallArguments(context, interaction);
173-
this.alternate.includeCallArguments(context, interaction);
172+
this.consequent.includeCallArguments(interaction, context);
173+
this.alternate.includeCallArguments(interaction, context);
174174
}
175175
}
176176

src/ast/nodes/ContinueStatement.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext';
22
import type Identifier from './Identifier';
33
import type * as NodeType from './NodeType';
4-
import { doNotDeoptimize, onlyIncludeSelfNoDeoptimize, StatementBase } from './shared/Node';
4+
import {
5+
doNotDeoptimize,
6+
type IncludeChildren,
7+
onlyIncludeSelfNoDeoptimize,
8+
StatementBase
9+
} from './shared/Node';
510

611
export default class ContinueStatement extends StatementBase {
712
declare label: Identifier | null;
@@ -19,10 +24,10 @@ export default class ContinueStatement extends StatementBase {
1924
return false;
2025
}
2126

22-
include(context: InclusionContext): void {
27+
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
2328
this.included = true;
2429
if (this.label) {
25-
this.label.include(context);
30+
this.label.include(context, includeChildrenRecursively);
2631
context.includedLabels.add(this.label.name);
2732
} else {
2833
context.hasContinue = true;

src/ast/nodes/LabeledStatement.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default class LabeledStatement extends StatementBase {
4444
context.includedLabels = new Set<string>();
4545
this.body.include(context, includeChildrenRecursively);
4646
if (includeChildrenRecursively || context.includedLabels.has(this.label.name)) {
47-
this.label.include(context);
47+
this.label.include(context, includeChildrenRecursively);
4848
context.includedLabels.delete(this.label.name);
4949
context.brokenFlow = brokenFlow;
5050
}

src/ast/nodes/MemberExpression.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { getChainElementLiteralValueAtPath } from './shared/chainElements';
4444
import {
4545
deoptimizeInteraction,
4646
type ExpressionEntity,
47+
includeInteraction,
4748
type LiteralValueOrUnknown,
4849
UNKNOWN_RETURN_EXPRESSION,
4950
UnknownValue
@@ -375,6 +376,16 @@ export default class MemberExpression
375376
}
376377
}
377378

379+
includeNodeAsAssignmentTarget(context: InclusionContext) {
380+
this.included = true;
381+
if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization();
382+
if (this.variable) {
383+
this.scope.context.includeVariableInModule(this.variable, EMPTY_PATH, context);
384+
} else if (!this.isUndefined) {
385+
this.object.includePath([this.propertyKey], context);
386+
}
387+
}
388+
378389
includePath(path: ObjectPath, context: InclusionContext): void {
379390
if (!this.included) this.includeNode(context);
380391
if (this.variable) {
@@ -397,21 +408,17 @@ export default class MemberExpression
397408
includeChildrenRecursively: IncludeChildren,
398409
deoptimizeAccess: boolean
399410
): void {
400-
if (!this.assignmentDeoptimized) this.applyAssignmentDeoptimization();
401-
if (deoptimizeAccess) {
402-
this.include(context, includeChildrenRecursively);
403-
} else {
404-
if (!this.included) this.includeNode(context);
405-
this.object.include(context, includeChildrenRecursively);
406-
this.property.include(context, includeChildrenRecursively);
407-
}
411+
if (!this.included) this.includeNodeAsAssignmentTarget(context);
412+
if (deoptimizeAccess && !this.deoptimized) this.applyDeoptimizations();
413+
this.object.include(context, includeChildrenRecursively);
414+
this.property.include(context, includeChildrenRecursively);
408415
}
409416

410-
includeCallArguments(context: InclusionContext, interaction: NodeInteractionCalled): void {
417+
includeCallArguments(interaction: NodeInteractionCalled, context: InclusionContext): void {
411418
if (this.variable) {
412-
this.variable.includeCallArguments(context, interaction);
419+
this.variable.includeCallArguments(interaction, context);
413420
} else {
414-
super.includeCallArguments(context, interaction);
421+
includeInteraction(interaction, context);
415422
}
416423
}
417424

src/ast/nodes/NewExpression.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ export default class NewExpression extends NodeBase {
4242
}
4343

4444
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
45+
if (!this.included) this.includeNode(context);
4546
if (includeChildrenRecursively) {
46-
super.include(context, includeChildrenRecursively);
47+
super.include(context, true);
4748
} else {
48-
if (!this.included) this.includeNode(context);
4949
this.callee.include(context, false);
50+
this.callee.includeCallArguments(this.interaction, context);
5051
}
51-
this.callee.includeCallArguments(context, this.interaction);
5252
}
5353

5454
includeNode(context: InclusionContext) {

src/ast/nodes/TaggedTemplateExpression.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,11 @@ export default class TaggedTemplateExpression extends CallExpressionBase {
4848
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
4949
if (!this.included) this.includeNode(context);
5050
if (includeChildrenRecursively) {
51-
super.include(context, includeChildrenRecursively);
51+
super.include(context, true);
5252
} else {
53-
this.included = true;
54-
this.tag.include(context, includeChildrenRecursively);
55-
this.quasi.include(context, includeChildrenRecursively);
56-
}
57-
this.tag.includeCallArguments(context, this.interaction);
58-
const [returnExpression] = this.getReturnExpression();
59-
if (!returnExpression.included) {
60-
returnExpression.include(context, false);
53+
this.quasi.include(context, false);
54+
this.tag.include(context, false);
55+
this.tag.includeCallArguments(this.interaction, context);
6156
}
6257
}
6358

src/ast/nodes/shared/ClassNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export default class ClassNode extends NodeBase implements DeoptimizableEntity {
106106
for (const decorator of this.decorators) decorator.include(context, includeChildrenRecursively);
107107
if (this.id) {
108108
this.id.markDeclarationReached();
109-
this.id.include(context);
109+
this.id.include(context, includeChildrenRecursively);
110110
}
111111
}
112112

src/ast/nodes/shared/Expression.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,8 @@ export class ExpressionEntity implements WritableEntity {
9797
* paths of the expression are included.
9898
* */
9999

100-
includeCallArguments(context: InclusionContext, interaction: NodeInteractionCalled): void {
101-
for (const argument of interaction.args) {
102-
if (argument) {
103-
argument.includePath(UNKNOWN_PATH, context);
104-
argument.include(context, false);
105-
}
106-
}
100+
includeCallArguments(interaction: NodeInteractionCalled, context: InclusionContext): void {
101+
includeInteraction(interaction, context);
107102
}
108103

109104
shouldBeIncluded(_context: InclusionContext): boolean {
@@ -124,3 +119,16 @@ export const deoptimizeInteraction = (interaction: NodeInteraction) => {
124119
argument?.deoptimizePath(UNKNOWN_PATH);
125120
}
126121
};
122+
123+
export const includeInteraction = ({ args }: NodeInteraction, context: InclusionContext) => {
124+
// We do not re-include the "this" argument as we expect this is already
125+
// re-included at the call site
126+
args[0]?.includePath(UNKNOWN_PATH, context);
127+
for (let argumentIndex = 1; argumentIndex < args.length; argumentIndex++) {
128+
const argument = args[argumentIndex];
129+
if (argument) {
130+
argument.includePath(UNKNOWN_PATH, context);
131+
argument.include(context, false);
132+
}
133+
}
134+
};

0 commit comments

Comments
 (0)