From 3f5986f747b581c000481812d68d62cd6bfbda3a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 4 Sep 2017 16:37:51 -0700 Subject: [PATCH] Disable control flow analysis in excessively large statement blocks --- src/compiler/checker.ts | 83 ++++++++++++++++++---------- src/compiler/diagnosticMessages.json | 4 ++ 2 files changed, 57 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9174aa3c023..c3e3473e3f8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -334,6 +334,7 @@ namespace ts { let flowLoopStart = 0; let flowLoopCount = 0; let visitedFlowCount = 0; + let flowAnalysisDisabled = false; const emptyStringType = getLiteralType(""); const zeroType = getLiteralType(0); @@ -11487,6 +11488,10 @@ namespace ts { function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { let key: string; + let flowLength = 0; + if (flowAnalysisDisabled) { + return unknownType; + } if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) { return declaredType; } @@ -11504,60 +11509,72 @@ namespace ts { return resultType; function getTypeAtFlowNode(flow: FlowNode): FlowType { + const saveFlowLength = flowLength; while (true) { - if (flow.flags & FlowFlags.Shared) { + flowLength++; + if (flowLength === 5000) { + // The length of this particular control flow path is 5000 nodes or more. Rather than spending an + // excessive amount of time and possibly overflowing the call stack, we report an error and disable + // further control flow analysis in the containing function or module body. + flowAnalysisDisabled = true; + error(reference, Diagnostics.The_body_of_the_containing_function_or_module_is_too_large_for_control_flow_analysis); + return unknownType; + } + const flags = flow.flags; + if (flags & FlowFlags.Shared) { // We cache results of flow type resolution for shared nodes that were previously visited in // the same getFlowTypeOfReference invocation. A node is considered shared when it is the // antecedent of more than one node. for (let i = visitedFlowStart; i < visitedFlowCount; i++) { if (visitedFlowNodes[i] === flow) { + flowLength = saveFlowLength; return visitedFlowTypes[i]; } } } let type: FlowType; - if (flow.flags & FlowFlags.AfterFinally) { + if (flags & FlowFlags.AfterFinally) { // block flow edge: finally -> pre-try (for larger explanation check comment in binder.ts - bindTryStatement (flow).locked = true; type = getTypeAtFlowNode((flow).antecedent); (flow).locked = false; } - else if (flow.flags & FlowFlags.PreFinally) { + else if (flags & FlowFlags.PreFinally) { // locked pre-finally flows are filtered out in getTypeAtFlowBranchLabel // so here just redirect to antecedent flow = (flow).antecedent; continue; } - else if (flow.flags & FlowFlags.Assignment) { + else if (flags & FlowFlags.Assignment) { type = getTypeAtFlowAssignment(flow); if (!type) { flow = (flow).antecedent; continue; } } - else if (flow.flags & FlowFlags.Condition) { + else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); } - else if (flow.flags & FlowFlags.SwitchClause) { + else if (flags & FlowFlags.SwitchClause) { type = getTypeAtSwitchClause(flow); } - else if (flow.flags & FlowFlags.Label) { + else if (flags & FlowFlags.Label) { if ((flow).antecedents.length === 1) { flow = (flow).antecedents[0]; continue; } - type = flow.flags & FlowFlags.BranchLabel ? + type = flags & FlowFlags.BranchLabel ? getTypeAtFlowBranchLabel(flow) : getTypeAtFlowLoopLabel(flow); } - else if (flow.flags & FlowFlags.ArrayMutation) { + else if (flags & FlowFlags.ArrayMutation) { type = getTypeAtFlowArrayMutation(flow); if (!type) { flow = (flow).antecedent; continue; } } - else if (flow.flags & FlowFlags.Start) { + else if (flags & FlowFlags.Start) { // Check if we should continue with the control flow of the containing function. const container = (flow).container; if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression && reference.kind !== SyntaxKind.ThisKeyword) { @@ -11572,12 +11589,13 @@ namespace ts { // simply return the non-auto declared type to reduce follow-on errors. type = convertAutoToAny(declaredType); } - if (flow.flags & FlowFlags.Shared) { + if (flags & FlowFlags.Shared) { // Record visited node and the associated type in the cache. visitedFlowNodes[visitedFlowCount] = flow; visitedFlowTypes[visitedFlowCount] = type; visitedFlowCount++; } + flowLength = saveFlowLength; return type; } } @@ -11615,29 +11633,31 @@ namespace ts { } function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType { - const node = flow.node; - const expr = node.kind === SyntaxKind.CallExpression ? - ((node).expression).expression : - ((node).left).expression; - if (isMatchingReference(reference, getReferenceCandidate(expr))) { - const flowType = getTypeAtFlowNode(flow.antecedent); - const type = getTypeFromFlowType(flowType); - if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { - let evolvedType = type; - if (node.kind === SyntaxKind.CallExpression) { - for (const arg of (node).arguments) { - evolvedType = addEvolvingArrayElementType(evolvedType, arg); + if (declaredType === autoType || declaredType === autoArrayType) { + const node = flow.node; + const expr = node.kind === SyntaxKind.CallExpression ? + ((node).expression).expression : + ((node).left).expression; + if (isMatchingReference(reference, getReferenceCandidate(expr))) { + const flowType = getTypeAtFlowNode(flow.antecedent); + const type = getTypeFromFlowType(flowType); + if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { + let evolvedType = type; + if (node.kind === SyntaxKind.CallExpression) { + for (const arg of (node).arguments) { + evolvedType = addEvolvingArrayElementType(evolvedType, arg); + } } - } - else { - const indexType = getTypeOfExpression(((node).left).argumentExpression); - if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { - evolvedType = addEvolvingArrayElementType(evolvedType, (node).right); + else { + const indexType = getTypeOfExpression(((node).left).argumentExpression); + if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { + evolvedType = addEvolvingArrayElementType(evolvedType, (node).right); + } } + return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); } - return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); + return flowType; } - return flowType; } return undefined; } @@ -19951,7 +19971,9 @@ namespace ts { if (node.kind === SyntaxKind.Block) { checkGrammarStatementInAmbientContext(node); } + const saveFlowAnalysisDisabled = flowAnalysisDisabled; forEach(node.statements, checkSourceElement); + flowAnalysisDisabled = saveFlowAnalysisDisabled; if (node.locals) { registerForUnusedIdentifiersCheck(node); } @@ -22541,6 +22563,7 @@ namespace ts { deferredNodes = []; deferredUnusedIdentifierNodes = produceDiagnostics && noUnusedIdentifiers ? [] : undefined; + flowAnalysisDisabled = false; forEach(node.statements, checkSourceElement); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9a3492e5c37..b3668cc5acf 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1920,6 +1920,10 @@ "category": "Error", "code": 2562 }, + "The body of the containing function or module is too large for control flow analysis.": { + "category": "Error", + "code": 2563 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600