@@ -167,8 +167,6 @@ namespace ts {
167
167
return node ;
168
168
}
169
169
170
- let flowNodeCreated : < T extends FlowNode > ( node : T ) => T = initFlowNode ;
171
-
172
170
const binder = createBinder ( ) ;
173
171
174
172
export function bindSourceFile ( file : SourceFile , options : CompilerOptions ) {
@@ -199,6 +197,7 @@ namespace ts {
199
197
let currentReturnTarget : FlowLabel | undefined ;
200
198
let currentTrueTarget : FlowLabel | undefined ;
201
199
let currentFalseTarget : FlowLabel | undefined ;
200
+ let currentExceptionTarget : FlowLabel | undefined ;
202
201
let preSwitchCaseFlow : FlowNode | undefined ;
203
202
let activeLabels : ActiveLabel [ ] | undefined ;
204
203
let hasExplicitReturn : boolean ;
@@ -271,6 +270,7 @@ namespace ts {
271
270
currentReturnTarget = undefined ;
272
271
currentTrueTarget = undefined ;
273
272
currentFalseTarget = undefined ;
273
+ currentExceptionTarget = undefined ;
274
274
activeLabels = undefined ! ;
275
275
hasExplicitReturn = false ;
276
276
emitFlags = NodeFlags . None ;
@@ -624,11 +624,11 @@ namespace ts {
624
624
blockScopeContainer . locals = undefined ;
625
625
}
626
626
if ( containerFlags & ContainerFlags . IsControlFlowContainer ) {
627
- const saveFlowNodeCreated = flowNodeCreated ;
628
627
const saveCurrentFlow = currentFlow ;
629
628
const saveBreakTarget = currentBreakTarget ;
630
629
const saveContinueTarget = currentContinueTarget ;
631
630
const saveReturnTarget = currentReturnTarget ;
631
+ const saveExceptionTarget = currentExceptionTarget ;
632
632
const saveActiveLabels = activeLabels ;
633
633
const saveHasExplicitReturn = hasExplicitReturn ;
634
634
const isIIFE = containerFlags & ContainerFlags . IsFunctionExpression && ! hasModifier ( node , ModifierFlags . Async ) &&
@@ -644,11 +644,11 @@ namespace ts {
644
644
// We create a return control flow graph for IIFEs and constructors. For constructors
645
645
// we use the return control flow graph in strict property initialization checks.
646
646
currentReturnTarget = isIIFE || node . kind === SyntaxKind . Constructor ? createBranchLabel ( ) : undefined ;
647
+ currentExceptionTarget = undefined ;
647
648
currentBreakTarget = undefined ;
648
649
currentContinueTarget = undefined ;
649
650
activeLabels = undefined ;
650
651
hasExplicitReturn = false ;
651
- flowNodeCreated = initFlowNode ;
652
652
bindChildren ( node ) ;
653
653
// Reset all reachability check related flags on node (for incremental scenarios)
654
654
node . flags &= ~ NodeFlags . ReachabilityAndEmitFlags ;
@@ -674,9 +674,9 @@ namespace ts {
674
674
currentBreakTarget = saveBreakTarget ;
675
675
currentContinueTarget = saveContinueTarget ;
676
676
currentReturnTarget = saveReturnTarget ;
677
+ currentExceptionTarget = saveExceptionTarget ;
677
678
activeLabels = saveActiveLabels ;
678
679
hasExplicitReturn = saveHasExplicitReturn ;
679
- flowNodeCreated = saveFlowNodeCreated ;
680
680
}
681
681
else if ( containerFlags & ContainerFlags . IsInterface ) {
682
682
seenThisKeyword = false ;
@@ -961,27 +961,26 @@ namespace ts {
961
961
return antecedent ;
962
962
}
963
963
setFlowNodeReferenced ( antecedent ) ;
964
- return flowNodeCreated ( { flags, antecedent, node : expression } ) ;
964
+ return initFlowNode ( { flags, antecedent, node : expression } ) ;
965
965
}
966
966
967
967
function createFlowSwitchClause ( antecedent : FlowNode , switchStatement : SwitchStatement , clauseStart : number , clauseEnd : number ) : FlowNode {
968
968
setFlowNodeReferenced ( antecedent ) ;
969
- return flowNodeCreated ( { flags : FlowFlags . SwitchClause , antecedent, switchStatement, clauseStart, clauseEnd } ) ;
969
+ return initFlowNode ( { flags : FlowFlags . SwitchClause , antecedent, switchStatement, clauseStart, clauseEnd } ) ;
970
970
}
971
971
972
- function createFlowAssignment ( antecedent : FlowNode , node : Expression | VariableDeclaration | BindingElement ) : FlowNode {
972
+ function createFlowMutation ( flags : FlowFlags , antecedent : FlowNode , node : Node ) : FlowNode {
973
973
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 ;
975
979
}
976
980
977
981
function createFlowCall ( antecedent : FlowNode , node : CallExpression ) : FlowNode {
978
982
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 } ) ;
985
984
}
986
985
987
986
function finishFlowLabel ( flow : FlowLabel ) : FlowNode {
@@ -1189,93 +1188,56 @@ namespace ts {
1189
1188
1190
1189
function bindTryStatement ( node : TryStatement ) : void {
1191
1190
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 ) ;
1201
1200
bind ( node . tryBlock ) ;
1202
- flowNodeCreated = oldFlowNodeCreated ;
1203
1201
addAntecedent ( preFinallyLabel , currentFlow ) ;
1204
-
1205
1202
const flowAfterTry = currentFlow ;
1206
1203
let flowAfterCatch = unreachableFlow ;
1207
-
1208
1204
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 ) ;
1219
1212
bind ( node . catchClause ) ;
1220
1213
addAntecedent ( preFinallyLabel , currentFlow ) ;
1221
-
1222
1214
flowAfterCatch = currentFlow ;
1223
1215
}
1216
+ const exceptionTarget = finishFlowLabel ( currentExceptionTarget ) ;
1217
+ currentExceptionTarget = saveExceptionTarget ;
1224
1218
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 : { } } ) ;
1269
1235
addAntecedent ( preFinallyLabel , preFinallyFlow ) ;
1270
-
1271
1236
currentFlow = finishFlowLabel ( preFinallyLabel ) ;
1272
1237
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.
1279
1241
if ( ! ( currentFlow . flags & FlowFlags . Unreachable ) ) {
1280
1242
if ( ( flowAfterTry . flags & FlowFlags . Unreachable ) && ( flowAfterCatch . flags & FlowFlags . Unreachable ) ) {
1281
1243
currentFlow = flowAfterTry === reportedUnreachableFlow || flowAfterCatch === reportedUnreachableFlow
@@ -1284,7 +1246,7 @@ namespace ts {
1284
1246
}
1285
1247
}
1286
1248
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 } ) ;
1288
1250
preFinallyFlow . lock = afterFinallyFlow ;
1289
1251
currentFlow = afterFinallyFlow ;
1290
1252
}
@@ -1407,7 +1369,7 @@ namespace ts {
1407
1369
1408
1370
function bindAssignmentTargetFlow ( node : Expression ) {
1409
1371
if ( isNarrowableReference ( node ) ) {
1410
- currentFlow = createFlowAssignment ( currentFlow , node ) ;
1372
+ currentFlow = createFlowMutation ( FlowFlags . Assignment , currentFlow , node ) ;
1411
1373
}
1412
1374
else if ( node . kind === SyntaxKind . ArrayLiteralExpression ) {
1413
1375
for ( const e of ( < ArrayLiteralExpression > node ) . elements ) {
@@ -1490,7 +1452,7 @@ namespace ts {
1490
1452
if ( operator === SyntaxKind . EqualsToken && node . left . kind === SyntaxKind . ElementAccessExpression ) {
1491
1453
const elementAccess = < ElementAccessExpression > node . left ;
1492
1454
if ( isNarrowableOperand ( elementAccess . expression ) ) {
1493
- currentFlow = createFlowArrayMutation ( currentFlow , node ) ;
1455
+ currentFlow = createFlowMutation ( FlowFlags . ArrayMutation , currentFlow , node ) ;
1494
1456
}
1495
1457
}
1496
1458
}
@@ -1528,7 +1490,7 @@ namespace ts {
1528
1490
}
1529
1491
}
1530
1492
else {
1531
- currentFlow = createFlowAssignment ( currentFlow , node ) ;
1493
+ currentFlow = createFlowMutation ( FlowFlags . Assignment , currentFlow , node ) ;
1532
1494
}
1533
1495
}
1534
1496
@@ -1643,7 +1605,7 @@ namespace ts {
1643
1605
if ( node . expression . kind === SyntaxKind . PropertyAccessExpression ) {
1644
1606
const propertyAccess = < PropertyAccessExpression > node . expression ;
1645
1607
if ( isNarrowableOperand ( propertyAccess . expression ) && isPushOrUnshiftIdentifier ( propertyAccess . name ) ) {
1646
- currentFlow = createFlowArrayMutation ( currentFlow , node ) ;
1608
+ currentFlow = createFlowMutation ( FlowFlags . ArrayMutation , currentFlow , node ) ;
1647
1609
}
1648
1610
}
1649
1611
}
0 commit comments