Skip to content

Error on excessively large control flow graphs #18246

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

Merged
merged 9 commits into from
Sep 11, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 75 additions & 42 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@ namespace ts {

let flowLoopStart = 0;
let flowLoopCount = 0;
let visitedFlowCount = 0;
let sharedFlowCount = 0;
let flowAnalysisDisabled = false;

const emptyStringType = getLiteralType("");
const zeroType = getLiteralType(0);
Expand All @@ -352,8 +353,8 @@ namespace ts {
const flowLoopNodes: FlowNode[] = [];
const flowLoopKeys: string[] = [];
const flowLoopTypes: Type[][] = [];
const visitedFlowNodes: FlowNode[] = [];
const visitedFlowTypes: FlowType[] = [];
const sharedFlowNodes: FlowNode[] = [];
const sharedFlowTypes: FlowType[] = [];
const potentialThisCollisions: Node[] = [];
const potentialNewTargetCollisions: Node[] = [];
const awaitedTypeStack: number[] = [];
Expand Down Expand Up @@ -11459,14 +11460,25 @@ namespace ts {
return false;
}

function reportFlowControlError(node: Node) {
const block = <Block | ModuleBlock | SourceFile>findAncestor(node, isFunctionOrModuleBlock);
const sourceFile = getSourceFileOfNode(node);
const span = getSpanOfTokenAtPosition(sourceFile, block.statements.pos);
diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis));
}

function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) {
let key: string;
let flowDepth = 0;
if (flowAnalysisDisabled) {
return unknownType;
}
if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) {
return declaredType;
}
const visitedFlowStart = visitedFlowCount;
const sharedFlowStart = sharedFlowCount;
const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode));
visitedFlowCount = visitedFlowStart;
sharedFlowCount = sharedFlowStart;
// When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation,
// we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations
// on empty arrays are possible without implicit any errors and new element types can be inferred without
Expand All @@ -11478,60 +11490,70 @@ namespace ts {
return resultType;

function getTypeAtFlowNode(flow: FlowNode): FlowType {
if (flowDepth === 2500) {
// We have made 2500 recursive invocations. To avoid overflowing the call stack we report an error
// and disable further control flow analysis in the containing function or module body.
flowAnalysisDisabled = true;
reportFlowControlError(reference);
return unknownType;
}
flowDepth++;
while (true) {
if (flow.flags & FlowFlags.Shared) {
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) {
return visitedFlowTypes[i];
for (let i = sharedFlowStart; i < sharedFlowCount; i++) {
if (sharedFlowNodes[i] === flow) {
flowDepth--;
return sharedFlowTypes[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
(<AfterFinallyFlow>flow).locked = true;
type = getTypeAtFlowNode((<AfterFinallyFlow>flow).antecedent);
(<AfterFinallyFlow>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 = (<PreFinallyFlow>flow).antecedent;
continue;
}
else if (flow.flags & FlowFlags.Assignment) {
else if (flags & FlowFlags.Assignment) {
type = getTypeAtFlowAssignment(<FlowAssignment>flow);
if (!type) {
flow = (<FlowAssignment>flow).antecedent;
continue;
}
}
else if (flow.flags & FlowFlags.Condition) {
else if (flags & FlowFlags.Condition) {
type = getTypeAtFlowCondition(<FlowCondition>flow);
}
else if (flow.flags & FlowFlags.SwitchClause) {
else if (flags & FlowFlags.SwitchClause) {
type = getTypeAtSwitchClause(<FlowSwitchClause>flow);
}
else if (flow.flags & FlowFlags.Label) {
else if (flags & FlowFlags.Label) {
if ((<FlowLabel>flow).antecedents.length === 1) {
flow = (<FlowLabel>flow).antecedents[0];
continue;
}
type = flow.flags & FlowFlags.BranchLabel ?
type = flags & FlowFlags.BranchLabel ?
getTypeAtFlowBranchLabel(<FlowLabel>flow) :
getTypeAtFlowLoopLabel(<FlowLabel>flow);
}
else if (flow.flags & FlowFlags.ArrayMutation) {
else if (flags & FlowFlags.ArrayMutation) {
type = getTypeAtFlowArrayMutation(<FlowArrayMutation>flow);
if (!type) {
flow = (<FlowArrayMutation>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 = (<FlowStart>flow).container;
if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression && reference.kind !== SyntaxKind.ThisKeyword) {
Expand All @@ -11546,12 +11568,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++;
sharedFlowNodes[sharedFlowCount] = flow;
sharedFlowTypes[sharedFlowCount] = type;
sharedFlowCount++;
}
flowDepth--;
return type;
}
}
Expand Down Expand Up @@ -11589,29 +11612,31 @@ namespace ts {
}

function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType {
const node = flow.node;
const expr = node.kind === SyntaxKind.CallExpression ?
(<PropertyAccessExpression>(<CallExpression>node).expression).expression :
(<ElementAccessExpression>(<BinaryExpression>node).left).expression;
if (isMatchingReference(reference, getReferenceCandidate(expr))) {
const flowType = getTypeAtFlowNode(flow.antecedent);
const type = getTypeFromFlowType(flowType);
if (getObjectFlags(type) & ObjectFlags.EvolvingArray) {
let evolvedType = <EvolvingArrayType>type;
if (node.kind === SyntaxKind.CallExpression) {
for (const arg of (<CallExpression>node).arguments) {
evolvedType = addEvolvingArrayElementType(evolvedType, arg);
if (declaredType === autoType || declaredType === autoArrayType) {
const node = flow.node;
const expr = node.kind === SyntaxKind.CallExpression ?
(<PropertyAccessExpression>(<CallExpression>node).expression).expression :
(<ElementAccessExpression>(<BinaryExpression>node).left).expression;
if (isMatchingReference(reference, getReferenceCandidate(expr))) {
const flowType = getTypeAtFlowNode(flow.antecedent);
const type = getTypeFromFlowType(flowType);
if (getObjectFlags(type) & ObjectFlags.EvolvingArray) {
let evolvedType = <EvolvingArrayType>type;
if (node.kind === SyntaxKind.CallExpression) {
for (const arg of (<CallExpression>node).arguments) {
evolvedType = addEvolvingArrayElementType(evolvedType, arg);
}
}
}
else {
const indexType = getTypeOfExpression((<ElementAccessExpression>(<BinaryExpression>node).left).argumentExpression);
if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
evolvedType = addEvolvingArrayElementType(evolvedType, (<BinaryExpression>node).right);
else {
const indexType = getTypeOfExpression((<ElementAccessExpression>(<BinaryExpression>node).left).argumentExpression);
if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
evolvedType = addEvolvingArrayElementType(evolvedType, (<BinaryExpression>node).right);
}
}
return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType));
}
return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType));
return flowType;
}
return flowType;
}
return undefined;
}
Expand Down Expand Up @@ -19935,7 +19960,14 @@ namespace ts {
if (node.kind === SyntaxKind.Block) {
checkGrammarStatementInAmbientContext(node);
}
forEach(node.statements, checkSourceElement);
if (isFunctionOrModuleBlock(node)) {
const saveFlowAnalysisDisabled = flowAnalysisDisabled;
forEach(node.statements, checkSourceElement);
flowAnalysisDisabled = saveFlowAnalysisDisabled;
}
else {
forEach(node.statements, checkSourceElement);
}
if (node.locals) {
registerForUnusedIdentifiersCheck(node);
}
Expand Down Expand Up @@ -22525,6 +22557,7 @@ namespace ts {

deferredNodes = [];
deferredUnusedIdentifierNodes = produceDiagnostics && noUnusedIdentifiers ? [] : undefined;
flowAnalysisDisabled = false;

forEach(node.statements, checkSourceElement);

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1920,6 +1920,10 @@
"category": "Error",
"code": 2562
},
"The containing function or module body 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
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4851,6 +4851,11 @@ namespace ts {
return false;
}

/* @internal */
export function isFunctionOrModuleBlock(node: Node): boolean {
return isSourceFile(node) || isModuleBlock(node) || isBlock(node) && isFunctionLike(node.parent);
}

// Classes
export function isClassElement(node: Node): node is ClassElement {
const kind = node.kind;
Expand Down
Loading