@@ -167,8 +167,6 @@ namespace ts {
167167 return node ;
168168 }
169169
170- let flowNodeCreated : < T extends FlowNode > ( node : T ) => T = initFlowNode ;
171-
172170 const binder = createBinder ( ) ;
173171
174172 export function bindSourceFile ( file : SourceFile , options : CompilerOptions ) {
@@ -199,6 +197,7 @@ namespace ts {
199197 let currentReturnTarget : FlowLabel | undefined ;
200198 let currentTrueTarget : FlowLabel | undefined ;
201199 let currentFalseTarget : FlowLabel | undefined ;
200+ let currentExceptionTarget : FlowLabel | undefined ;
202201 let preSwitchCaseFlow : FlowNode | undefined ;
203202 let activeLabels : ActiveLabel [ ] | undefined ;
204203 let hasExplicitReturn : boolean ;
@@ -271,6 +270,7 @@ namespace ts {
271270 currentReturnTarget = undefined ;
272271 currentTrueTarget = undefined ;
273272 currentFalseTarget = undefined ;
273+ currentExceptionTarget = undefined ;
274274 activeLabels = undefined ! ;
275275 hasExplicitReturn = false ;
276276 emitFlags = NodeFlags . None ;
@@ -624,11 +624,11 @@ namespace ts {
624624 blockScopeContainer . locals = undefined ;
625625 }
626626 if ( containerFlags & ContainerFlags . IsControlFlowContainer ) {
627- const saveFlowNodeCreated = flowNodeCreated ;
628627 const saveCurrentFlow = currentFlow ;
629628 const saveBreakTarget = currentBreakTarget ;
630629 const saveContinueTarget = currentContinueTarget ;
631630 const saveReturnTarget = currentReturnTarget ;
631+ const saveExceptionTarget = currentExceptionTarget ;
632632 const saveActiveLabels = activeLabels ;
633633 const saveHasExplicitReturn = hasExplicitReturn ;
634634 const isIIFE = containerFlags & ContainerFlags . IsFunctionExpression && ! hasModifier ( node , ModifierFlags . Async ) &&
@@ -644,11 +644,11 @@ namespace ts {
644644 // We create a return control flow graph for IIFEs and constructors. For constructors
645645 // we use the return control flow graph in strict property initialization checks.
646646 currentReturnTarget = isIIFE || node . kind === SyntaxKind . Constructor ? createBranchLabel ( ) : undefined ;
647+ currentExceptionTarget = undefined ;
647648 currentBreakTarget = undefined ;
648649 currentContinueTarget = undefined ;
649650 activeLabels = undefined ;
650651 hasExplicitReturn = false ;
651- flowNodeCreated = initFlowNode ;
652652 bindChildren ( node ) ;
653653 // Reset all reachability check related flags on node (for incremental scenarios)
654654 node . flags &= ~ NodeFlags . ReachabilityAndEmitFlags ;
@@ -674,9 +674,9 @@ namespace ts {
674674 currentBreakTarget = saveBreakTarget ;
675675 currentContinueTarget = saveContinueTarget ;
676676 currentReturnTarget = saveReturnTarget ;
677+ currentExceptionTarget = saveExceptionTarget ;
677678 activeLabels = saveActiveLabels ;
678679 hasExplicitReturn = saveHasExplicitReturn ;
679- flowNodeCreated = saveFlowNodeCreated ;
680680 }
681681 else if ( containerFlags & ContainerFlags . IsInterface ) {
682682 seenThisKeyword = false ;
@@ -961,27 +961,26 @@ namespace ts {
961961 return antecedent ;
962962 }
963963 setFlowNodeReferenced ( antecedent ) ;
964- return flowNodeCreated ( { flags, antecedent, node : expression } ) ;
964+ return initFlowNode ( { flags, antecedent, node : expression } ) ;
965965 }
966966
967967 function createFlowSwitchClause ( antecedent : FlowNode , switchStatement : SwitchStatement , clauseStart : number , clauseEnd : number ) : FlowNode {
968968 setFlowNodeReferenced ( antecedent ) ;
969- return flowNodeCreated ( { flags : FlowFlags . SwitchClause , antecedent, switchStatement, clauseStart, clauseEnd } ) ;
969+ return initFlowNode ( { flags : FlowFlags . SwitchClause , antecedent, switchStatement, clauseStart, clauseEnd } ) ;
970970 }
971971
972- function createFlowAssignment ( antecedent : FlowNode , node : Expression | VariableDeclaration | BindingElement ) : FlowNode {
972+ function createFlowMutation ( flags : FlowFlags , antecedent : FlowNode , node : Node ) : FlowNode {
973973 setFlowNodeReferenced ( antecedent ) ;
974- return flowNodeCreated ( { flags : FlowFlags . Assignment , antecedent, node } ) ;
974+ const result = initFlowNode ( { flags, antecedent, node } ) ;
975+ if ( currentExceptionTarget ) {
976+ addAntecedent ( currentExceptionTarget , result ) ;
977+ }
978+ return result ;
975979 }
976980
977981 function createFlowCall ( antecedent : FlowNode , node : CallExpression ) : FlowNode {
978982 setFlowNodeReferenced ( antecedent ) ;
979- return flowNodeCreated ( { flags : FlowFlags . Call , antecedent, node } ) ;
980- }
981-
982- function createFlowArrayMutation ( antecedent : FlowNode , node : CallExpression | BinaryExpression ) : FlowNode {
983- setFlowNodeReferenced ( antecedent ) ;
984- return flowNodeCreated ( { flags : FlowFlags . ArrayMutation , antecedent, node } ) ;
983+ return initFlowNode ( { flags : FlowFlags . Call , antecedent, node } ) ;
985984 }
986985
987986 function finishFlowLabel ( flow : FlowLabel ) : FlowNode {
@@ -1189,93 +1188,56 @@ namespace ts {
11891188
11901189 function bindTryStatement ( node : TryStatement ) : void {
11911190 const preFinallyLabel = createBranchLabel ( ) ;
1192- const preTryFlow = currentFlow ;
1193- const tryPriors : FlowNode [ ] = [ ] ;
1194- const oldFlowNodeCreated = flowNodeCreated ;
1195- // We hook the creation of all flow nodes within the `try` scope and store them so we can add _all_ of them
1196- // as possible antecedents of the start of the ` catch` or ` finally` blocks.
1197- // Don't bother intercepting the call if there's no finally or catch block that needs the information
1198- if ( node . catchClause || node . finallyBlock ) {
1199- flowNodeCreated = node => ( tryPriors . push ( node ) , initFlowNode ( node ) ) ;
1200- }
1191+ // We conservatively assume that *any* code in the try block can cause an exception, but we only need
1192+ // to track code that causes mutations (because only mutations widen the possible control flow type of
1193+ // a variable). The currentExceptionTarget is the target label for control flows that result from
1194+ // exceptions. We add all mutation flow nodes as antecedents of this label such that we can analyze them
1195+ // as possible antecedents of the start of catch or finally blocks. Furthermore, we add the current
1196+ // control flow to represent exceptions that occur before any mutations.
1197+ const saveExceptionTarget = currentExceptionTarget ;
1198+ currentExceptionTarget = createBranchLabel ( ) ;
1199+ addAntecedent ( currentExceptionTarget , currentFlow ) ;
12011200 bind ( node . tryBlock ) ;
1202- flowNodeCreated = oldFlowNodeCreated ;
12031201 addAntecedent ( preFinallyLabel , currentFlow ) ;
1204-
12051202 const flowAfterTry = currentFlow ;
12061203 let flowAfterCatch = unreachableFlow ;
1207-
12081204 if ( node . catchClause ) {
1209- currentFlow = preTryFlow ;
1210- if ( tryPriors . length ) {
1211- const preCatchFlow = createBranchLabel ( ) ;
1212- addAntecedent ( preCatchFlow , currentFlow ) ;
1213- for ( const p of tryPriors ) {
1214- addAntecedent ( preCatchFlow , p ) ;
1215- }
1216- currentFlow = finishFlowLabel ( preCatchFlow ) ;
1217- }
1218-
1205+ // Start of catch clause is the target of exceptions from try block.
1206+ currentFlow = finishFlowLabel ( currentExceptionTarget ) ;
1207+ // The currentExceptionTarget now represents control flows from exceptions in the catch clause.
1208+ // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block
1209+ // acts like a second try block.
1210+ currentExceptionTarget = createBranchLabel ( ) ;
1211+ addAntecedent ( currentExceptionTarget , currentFlow ) ;
12191212 bind ( node . catchClause ) ;
12201213 addAntecedent ( preFinallyLabel , currentFlow ) ;
1221-
12221214 flowAfterCatch = currentFlow ;
12231215 }
1216+ const exceptionTarget = finishFlowLabel ( currentExceptionTarget ) ;
1217+ currentExceptionTarget = saveExceptionTarget ;
12241218 if ( node . finallyBlock ) {
1225- // We add the nodes within the `try` block to the `finally`'s antecedents if there's no catch block
1226- // (If there is a `catch` block, it will have all these antecedents instead, and the `finally` will
1227- // have the end of the `try` block and the end of the `catch` block)
1228- let preFinallyPrior = preTryFlow ;
1229- if ( ! node . catchClause ) {
1230- if ( tryPriors . length ) {
1231- const preFinallyFlow = createBranchLabel ( ) ;
1232- addAntecedent ( preFinallyFlow , preTryFlow ) ;
1233- for ( const p of tryPriors ) {
1234- addAntecedent ( preFinallyFlow , p ) ;
1235- }
1236- preFinallyPrior = finishFlowLabel ( preFinallyFlow ) ;
1237- }
1238- }
1239-
1240- // in finally flow is combined from pre-try/flow from try/flow from catch
1241- // pre-flow is necessary to make sure that finally is reachable even if finally flows in both try and finally blocks are unreachable
1242-
1243- // also for finally blocks we inject two extra edges into the flow graph.
1244- // first -> edge that connects pre-try flow with the label at the beginning of the finally block, it has lock associated with it
1245- // second -> edge that represents post-finally flow.
1246- // these edges are used in following scenario:
1247- // let a; (1)
1248- // try { a = someOperation(); (2)}
1249- // finally { (3) console.log(a) } (4)
1250- // (5) a
1251-
1252- // flow graph for this case looks roughly like this (arrows show ):
1253- // (1-pre-try-flow) <--.. <-- (2-post-try-flow)
1254- // ^ ^
1255- // |*****(3-pre-finally-label) -----|
1256- // ^
1257- // |-- ... <-- (4-post-finally-label) <--- (5)
1258- // In case when we walk the flow starting from inside the finally block we want to take edge '*****' into account
1259- // since it ensures that finally is always reachable. However when we start outside the finally block and go through label (5)
1260- // then edge '*****' should be discarded because label 4 is only reachable if post-finally label-4 is reachable
1261- // Simply speaking code inside finally block is treated as reachable as pre-try-flow
1262- // since we conservatively assume that any line in try block can throw or return in which case we'll enter finally.
1263- // However code after finally is reachable only if control flow was not abrupted in try/catch or finally blocks - it should be composed from
1264- // final flows of these blocks without taking pre-try flow into account.
1265- //
1266- // extra edges that we inject allows to control this behavior
1267- // if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped.
1268- const preFinallyFlow : PreFinallyFlow = initFlowNode ( { flags : FlowFlags . PreFinally , antecedent : preFinallyPrior , lock : { } } ) ;
1219+ // Possible ways control can reach the finally block:
1220+ // 1) Normal completion of try block of a try-finally or try-catch-finally
1221+ // 2) Normal completion of catch block (following exception in try block) of a try-catch-finally
1222+ // 3) Exception in try block of a try-finally
1223+ // 4) Exception in catch block of a try-catch-finally
1224+ // When analyzing a control flow graph that starts inside a finally block we want to consider all
1225+ // four possibilities above. However, when analyzing a control flow graph that starts outside (past)
1226+ // the finally block, we only want to consider the first two (if we're past a finally block then it
1227+ // must have completed normally). To make this possible, we inject two extra nodes into the control
1228+ // flow graph: An after-finally with an antecedent of the control flow at the end of the finally
1229+ // block, and a pre-finally with an antecedent that represents all exceptional control flows. The
1230+ // 'lock' property of the pre-finally references the after-finally, and the after-finally has a
1231+ // boolean 'locked' property that we set to true when analyzing a control flow that contained the
1232+ // the after-finally node. When the lock associated with a pre-finally is locked, the antecedent of
1233+ // the pre-finally (i.e. the exceptional control flows) are skipped.
1234+ const preFinallyFlow : PreFinallyFlow = initFlowNode ( { flags : FlowFlags . PreFinally , antecedent : exceptionTarget , lock : { } } ) ;
12691235 addAntecedent ( preFinallyLabel , preFinallyFlow ) ;
1270-
12711236 currentFlow = finishFlowLabel ( preFinallyLabel ) ;
12721237 bind ( node . finallyBlock ) ;
1273- // if flow after finally is unreachable - keep it
1274- // otherwise check if flows after try and after catch are unreachable
1275- // if yes - convert current flow to unreachable
1276- // i.e.
1277- // try { return "1" } finally { console.log(1); }
1278- // console.log(2); // this line should be unreachable even if flow falls out of finally block
1238+ // If the end of the finally block is reachable, but the end of the try and catch blocks are not,
1239+ // convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should
1240+ // result in an unreachable current control flow.
12791241 if ( ! ( currentFlow . flags & FlowFlags . Unreachable ) ) {
12801242 if ( ( flowAfterTry . flags & FlowFlags . Unreachable ) && ( flowAfterCatch . flags & FlowFlags . Unreachable ) ) {
12811243 currentFlow = flowAfterTry === reportedUnreachableFlow || flowAfterCatch === reportedUnreachableFlow
@@ -1284,7 +1246,7 @@ namespace ts {
12841246 }
12851247 }
12861248 if ( ! ( currentFlow . flags & FlowFlags . Unreachable ) ) {
1287- const afterFinallyFlow : AfterFinallyFlow = flowNodeCreated ( { flags : FlowFlags . AfterFinally , antecedent : currentFlow } ) ;
1249+ const afterFinallyFlow : AfterFinallyFlow = initFlowNode ( { flags : FlowFlags . AfterFinally , antecedent : currentFlow } ) ;
12881250 preFinallyFlow . lock = afterFinallyFlow ;
12891251 currentFlow = afterFinallyFlow ;
12901252 }
@@ -1407,7 +1369,7 @@ namespace ts {
14071369
14081370 function bindAssignmentTargetFlow ( node : Expression ) {
14091371 if ( isNarrowableReference ( node ) ) {
1410- currentFlow = createFlowAssignment ( currentFlow , node ) ;
1372+ currentFlow = createFlowMutation ( FlowFlags . Assignment , currentFlow , node ) ;
14111373 }
14121374 else if ( node . kind === SyntaxKind . ArrayLiteralExpression ) {
14131375 for ( const e of ( < ArrayLiteralExpression > node ) . elements ) {
@@ -1490,7 +1452,7 @@ namespace ts {
14901452 if ( operator === SyntaxKind . EqualsToken && node . left . kind === SyntaxKind . ElementAccessExpression ) {
14911453 const elementAccess = < ElementAccessExpression > node . left ;
14921454 if ( isNarrowableOperand ( elementAccess . expression ) ) {
1493- currentFlow = createFlowArrayMutation ( currentFlow , node ) ;
1455+ currentFlow = createFlowMutation ( FlowFlags . ArrayMutation , currentFlow , node ) ;
14941456 }
14951457 }
14961458 }
@@ -1528,7 +1490,7 @@ namespace ts {
15281490 }
15291491 }
15301492 else {
1531- currentFlow = createFlowAssignment ( currentFlow , node ) ;
1493+ currentFlow = createFlowMutation ( FlowFlags . Assignment , currentFlow , node ) ;
15321494 }
15331495 }
15341496
@@ -1643,7 +1605,7 @@ namespace ts {
16431605 if ( node . expression . kind === SyntaxKind . PropertyAccessExpression ) {
16441606 const propertyAccess = < PropertyAccessExpression > node . expression ;
16451607 if ( isNarrowableOperand ( propertyAccess . expression ) && isPushOrUnshiftIdentifier ( propertyAccess . name ) ) {
1646- currentFlow = createFlowArrayMutation ( currentFlow , node ) ;
1608+ currentFlow = createFlowMutation ( FlowFlags . ArrayMutation , currentFlow , node ) ;
16471609 }
16481610 }
16491611 }
0 commit comments