Skip to content

Commit 1b49c8f

Browse files
authored
Merge pull request #18246 from Microsoft/fixControlFlowStackOverflow
Error on excessively large control flow graphs
2 parents 1ee3b65 + c646971 commit 1b49c8f

File tree

6 files changed

+40110
-42
lines changed

6 files changed

+40110
-42
lines changed

src/compiler/checker.ts

Lines changed: 75 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,8 @@ namespace ts {
334334

335335
let flowLoopStart = 0;
336336
let flowLoopCount = 0;
337-
let visitedFlowCount = 0;
337+
let sharedFlowCount = 0;
338+
let flowAnalysisDisabled = false;
338339

339340
const emptyStringType = getLiteralType("");
340341
const zeroType = getLiteralType(0);
@@ -352,8 +353,8 @@ namespace ts {
352353
const flowLoopNodes: FlowNode[] = [];
353354
const flowLoopKeys: string[] = [];
354355
const flowLoopTypes: Type[][] = [];
355-
const visitedFlowNodes: FlowNode[] = [];
356-
const visitedFlowTypes: FlowType[] = [];
356+
const sharedFlowNodes: FlowNode[] = [];
357+
const sharedFlowTypes: FlowType[] = [];
357358
const potentialThisCollisions: Node[] = [];
358359
const potentialNewTargetCollisions: Node[] = [];
359360
const awaitedTypeStack: number[] = [];
@@ -11459,14 +11460,25 @@ namespace ts {
1145911460
return false;
1146011461
}
1146111462

11463+
function reportFlowControlError(node: Node) {
11464+
const block = <Block | ModuleBlock | SourceFile>findAncestor(node, isFunctionOrModuleBlock);
11465+
const sourceFile = getSourceFileOfNode(node);
11466+
const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos);
11467+
diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis));
11468+
}
11469+
1146211470
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) {
1146311471
let key: string;
11472+
let flowDepth = 0;
11473+
if (flowAnalysisDisabled) {
11474+
return unknownType;
11475+
}
1146411476
if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
1146511477
return declaredType;
1146611478
}
11467-
const visitedFlowStart = visitedFlowCount;
11479+
const sharedFlowStart = sharedFlowCount;
1146811480
const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
11469-
visitedFlowCount = visitedFlowStart;
11481+
sharedFlowCount = sharedFlowStart;
1147011482
// When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation,
1147111483
// we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations
1147211484
// on empty arrays are possible without implicit any errors and new element types can be inferred without
@@ -11478,60 +11490,70 @@ namespace ts {
1147811490
return resultType;
1147911491

1148011492
function getTypeAtFlowNode(flow: FlowNode): FlowType {
11493+
if (flowDepth === 2500) {
11494+
// We have made 2500 recursive invocations. To avoid overflowing the call stack we report an error
11495+
// and disable further control flow analysis in the containing function or module body.
11496+
flowAnalysisDisabled = true;
11497+
reportFlowControlError(reference);
11498+
return unknownType;
11499+
}
11500+
flowDepth++;
1148111501
while (true) {
11482-
if (flow.flags & FlowFlags.Shared) {
11502+
const flags = flow.flags;
11503+
if (flags & FlowFlags.Shared) {
1148311504
// We cache results of flow type resolution for shared nodes that were previously visited in
1148411505
// the same getFlowTypeOfReference invocation. A node is considered shared when it is the
1148511506
// antecedent of more than one node.
11486-
for (let i = visitedFlowStart; i < visitedFlowCount; i++) {
11487-
if (visitedFlowNodes[i] === flow) {
11488-
return visitedFlowTypes[i];
11507+
for (let i = sharedFlowStart; i < sharedFlowCount; i++) {
11508+
if (sharedFlowNodes[i] === flow) {
11509+
flowDepth--;
11510+
return sharedFlowTypes[i];
1148911511
}
1149011512
}
1149111513
}
1149211514
let type: FlowType;
11493-
if (flow.flags & FlowFlags.AfterFinally) {
11515+
if (flags & FlowFlags.AfterFinally) {
1149411516
// block flow edge: finally -> pre-try (for larger explanation check comment in binder.ts - bindTryStatement
1149511517
(<AfterFinallyFlow>flow).locked = true;
1149611518
type = getTypeAtFlowNode((<AfterFinallyFlow>flow).antecedent);
1149711519
(<AfterFinallyFlow>flow).locked = false;
1149811520
}
11499-
else if (flow.flags & FlowFlags.PreFinally) {
11521+
else if (flags & FlowFlags.PreFinally) {
1150011522
// locked pre-finally flows are filtered out in getTypeAtFlowBranchLabel
1150111523
// so here just redirect to antecedent
1150211524
flow = (<PreFinallyFlow>flow).antecedent;
1150311525
continue;
1150411526
}
11505-
else if (flow.flags & FlowFlags.Assignment) {
11527+
else if (flags & FlowFlags.Assignment) {
1150611528
type = getTypeAtFlowAssignment(<FlowAssignment>flow);
1150711529
if (!type) {
1150811530
flow = (<FlowAssignment>flow).antecedent;
1150911531
continue;
1151011532
}
1151111533
}
11512-
else if (flow.flags & FlowFlags.Condition) {
11534+
else if (flags & FlowFlags.Condition) {
1151311535
type = getTypeAtFlowCondition(<FlowCondition>flow);
1151411536
}
11515-
else if (flow.flags & FlowFlags.SwitchClause) {
11537+
else if (flags & FlowFlags.SwitchClause) {
1151611538
type = getTypeAtSwitchClause(<FlowSwitchClause>flow);
1151711539
}
11518-
else if (flow.flags & FlowFlags.Label) {
11540+
else if (flags & FlowFlags.Label) {
1151911541
if ((<FlowLabel>flow).antecedents.length === 1) {
1152011542
flow = (<FlowLabel>flow).antecedents[0];
1152111543
continue;
1152211544
}
11523-
type = flow.flags & FlowFlags.BranchLabel ?
11545+
type = flags & FlowFlags.BranchLabel ?
1152411546
getTypeAtFlowBranchLabel(<FlowLabel>flow) :
1152511547
getTypeAtFlowLoopLabel(<FlowLabel>flow);
1152611548
}
11527-
else if (flow.flags & FlowFlags.ArrayMutation) {
11549+
else if (flags & FlowFlags.ArrayMutation) {
1152811550
type = getTypeAtFlowArrayMutation(<FlowArrayMutation>flow);
1152911551
if (!type) {
1153011552
flow = (<FlowArrayMutation>flow).antecedent;
1153111553
continue;
1153211554
}
1153311555
}
11534-
else if (flow.flags & FlowFlags.Start) {
11556+
else if (flags & FlowFlags.Start) {
1153511557
// Check if we should continue with the control flow of the containing function.
1153611558
const container = (<FlowStart>flow).container;
1153711559
if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression && reference.kind !== SyntaxKind.ThisKeyword) {
@@ -11546,12 +11568,13 @@ namespace ts {
1154611568
// simply return the non-auto declared type to reduce follow-on errors.
1154711569
type = convertAutoToAny(declaredType);
1154811570
}
11549-
if (flow.flags & FlowFlags.Shared) {
11571+
if (flags & FlowFlags.Shared) {
1155011572
// Record visited node and the associated type in the cache.
11551-
visitedFlowNodes[visitedFlowCount] = flow;
11552-
visitedFlowTypes[visitedFlowCount] = type;
11553-
visitedFlowCount++;
11573+
sharedFlowNodes[sharedFlowCount] = flow;
11574+
sharedFlowTypes[sharedFlowCount] = type;
11575+
sharedFlowCount++;
1155411576
}
11577+
flowDepth--;
1155511578
return type;
1155611579
}
1155711580
}
@@ -11589,29 +11612,31 @@ namespace ts {
1158911612
}
1159011613

1159111614
function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType {
11592-
const node = flow.node;
11593-
const expr = node.kind === SyntaxKind.CallExpression ?
11594-
(<PropertyAccessExpression>(<CallExpression>node).expression).expression :
11595-
(<ElementAccessExpression>(<BinaryExpression>node).left).expression;
11596-
if (isMatchingReference(reference, getReferenceCandidate(expr))) {
11597-
const flowType = getTypeAtFlowNode(flow.antecedent);
11598-
const type = getTypeFromFlowType(flowType);
11599-
if (getObjectFlags(type) & ObjectFlags.EvolvingArray) {
11600-
let evolvedType = <EvolvingArrayType>type;
11601-
if (node.kind === SyntaxKind.CallExpression) {
11602-
for (const arg of (<CallExpression>node).arguments) {
11603-
evolvedType = addEvolvingArrayElementType(evolvedType, arg);
11615+
if (declaredType === autoType || declaredType === autoArrayType) {
11616+
const node = flow.node;
11617+
const expr = node.kind === SyntaxKind.CallExpression ?
11618+
(<PropertyAccessExpression>(<CallExpression>node).expression).expression :
11619+
(<ElementAccessExpression>(<BinaryExpression>node).left).expression;
11620+
if (isMatchingReference(reference, getReferenceCandidate(expr))) {
11621+
const flowType = getTypeAtFlowNode(flow.antecedent);
11622+
const type = getTypeFromFlowType(flowType);
11623+
if (getObjectFlags(type) & ObjectFlags.EvolvingArray) {
11624+
let evolvedType = <EvolvingArrayType>type;
11625+
if (node.kind === SyntaxKind.CallExpression) {
11626+
for (const arg of (<CallExpression>node).arguments) {
11627+
evolvedType = addEvolvingArrayElementType(evolvedType, arg);
11628+
}
1160411629
}
11605-
}
11606-
else {
11607-
const indexType = getTypeOfExpression((<ElementAccessExpression>(<BinaryExpression>node).left).argumentExpression);
11608-
if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
11609-
evolvedType = addEvolvingArrayElementType(evolvedType, (<BinaryExpression>node).right);
11630+
else {
11631+
const indexType = getTypeOfExpression((<ElementAccessExpression>(<BinaryExpression>node).left).argumentExpression);
11632+
if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
11633+
evolvedType = addEvolvingArrayElementType(evolvedType, (<BinaryExpression>node).right);
11634+
}
1161011635
}
11636+
return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType));
1161111637
}
11612-
return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType));
11638+
return flowType;
1161311639
}
11614-
return flowType;
1161511640
}
1161611641
return undefined;
1161711642
}
@@ -19935,7 +19960,14 @@ namespace ts {
1993519960
if (node.kind === SyntaxKind.Block) {
1993619961
checkGrammarStatementInAmbientContext(node);
1993719962
}
19938-
forEach(node.statements, checkSourceElement);
19963+
if (isFunctionOrModuleBlock(node)) {
19964+
const saveFlowAnalysisDisabled = flowAnalysisDisabled;
19965+
forEach(node.statements, checkSourceElement);
19966+
flowAnalysisDisabled = saveFlowAnalysisDisabled;
19967+
}
19968+
else {
19969+
forEach(node.statements, checkSourceElement);
19970+
}
1993919971
if (node.locals) {
1994019972
registerForUnusedIdentifiersCheck(node);
1994119973
}
@@ -22525,6 +22557,7 @@ namespace ts {
2252522557

2252622558
deferredNodes = [];
2252722559
deferredUnusedIdentifierNodes = produceDiagnostics && noUnusedIdentifiers ? [] : undefined;
22560+
flowAnalysisDisabled = false;
2252822561

2252922562
forEach(node.statements, checkSourceElement);
2253022563

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,6 +1920,10 @@
19201920
"category": "Error",
19211921
"code": 2562
19221922
},
1923+
"The containing function or module body is too large for control flow analysis.": {
1924+
"category": "Error",
1925+
"code": 2563
1926+
},
19231927
"JSX element attributes type '{0}' may not be a union type.": {
19241928
"category": "Error",
19251929
"code": 2600

src/compiler/utilities.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4864,6 +4864,11 @@ namespace ts {
48644864
}
48654865
}
48664866

4867+
/* @internal */
4868+
export function isFunctionOrModuleBlock(node: Node): boolean {
4869+
return isSourceFile(node) || isModuleBlock(node) || isBlock(node) && isFunctionLike(node.parent);
4870+
}
4871+
48674872
// Classes
48684873
export function isClassElement(node: Node): node is ClassElement {
48694874
const kind = node.kind;

0 commit comments

Comments
 (0)