Skip to content

Commit 2fe2f88

Browse files
authored
Fix control flow analysis for nested try-catch-finally statements (microsoft#39399)
* Fix control flow analysis for nested try-catch-finally statements * Add tests
1 parent fd8ca52 commit 2fe2f88

File tree

6 files changed

+607
-0
lines changed

6 files changed

+607
-0
lines changed

src/compiler/binder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,11 @@ namespace ts {
12411241
if (currentReturnTarget && returnLabel.antecedents) {
12421242
addAntecedent(currentReturnTarget, createReduceLabel(finallyLabel, returnLabel.antecedents, currentFlow));
12431243
}
1244+
// If we have an outer exception target (i.e. a containing try-finally or try-catch-finally), add a
1245+
// control flow that goes back through the finally blok and back through each possible exception source.
1246+
if (currentExceptionTarget && exceptionLabel.antecedents) {
1247+
addAntecedent(currentExceptionTarget, createReduceLabel(finallyLabel, exceptionLabel.antecedents, currentFlow));
1248+
}
12441249
// If the end of the finally block is reachable, but the end of the try and catch blocks are not,
12451250
// convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should
12461251
// result in an unreachable current control flow.

tests/baselines/reference/tryCatchFinallyControlFlow.errors.txt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,74 @@ tests/cases/compiler/tryCatchFinallyControlFlow.ts(255,9): error TS7027: Unreach
275275
})();
276276
x; // Reachable
277277
}
278+
279+
// Repro from #39043
280+
281+
type State = { tag: "one" } | { tag: "two" } | { tag: "three" };
282+
283+
function notallowed(arg: number) {
284+
let state: State = { tag: "one" };
285+
try {
286+
state = { tag: "two" };
287+
try {
288+
state = { tag: "three" };
289+
}
290+
finally { }
291+
}
292+
catch (err) {
293+
state.tag;
294+
if (state.tag !== "one" && state.tag !== "two") {
295+
console.log(state.tag);
296+
}
297+
}
298+
}
299+
300+
function f20() {
301+
let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
302+
try {
303+
x = 1;
304+
try {
305+
x = 2;
306+
try {
307+
x = 3;
308+
}
309+
finally {
310+
if (!!true) x = 4;
311+
}
312+
x; // 3 | 4
313+
}
314+
finally {
315+
if (!!true) x = 5;
316+
}
317+
x; // 3 | 4 | 5
318+
}
319+
finally {
320+
if (!!true) x = 6;
321+
}
322+
x; // 3 | 4 | 5 | 6
323+
}
324+
325+
function f21() {
326+
let x: 0 | 1 | 2 | 3 | 4 | 5 = 0;
327+
try {
328+
x = 1;
329+
try {
330+
x = 2;
331+
try {
332+
x = 3;
333+
}
334+
finally {
335+
if (!!true) x = 4;
336+
}
337+
x; // 3 | 4
338+
}
339+
finally {
340+
if (!!true) x = 5;
341+
}
342+
x; // 3 | 4 | 5
343+
}
344+
catch (e) {
345+
x; // 0 | 1 | 2 | 3 | 4 | 5
346+
}
347+
}
278348

tests/baselines/reference/tryCatchFinallyControlFlow.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,76 @@ function t1() {
257257
})();
258258
x; // Reachable
259259
}
260+
261+
// Repro from #39043
262+
263+
type State = { tag: "one" } | { tag: "two" } | { tag: "three" };
264+
265+
function notallowed(arg: number) {
266+
let state: State = { tag: "one" };
267+
try {
268+
state = { tag: "two" };
269+
try {
270+
state = { tag: "three" };
271+
}
272+
finally { }
273+
}
274+
catch (err) {
275+
state.tag;
276+
if (state.tag !== "one" && state.tag !== "two") {
277+
console.log(state.tag);
278+
}
279+
}
280+
}
281+
282+
function f20() {
283+
let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
284+
try {
285+
x = 1;
286+
try {
287+
x = 2;
288+
try {
289+
x = 3;
290+
}
291+
finally {
292+
if (!!true) x = 4;
293+
}
294+
x; // 3 | 4
295+
}
296+
finally {
297+
if (!!true) x = 5;
298+
}
299+
x; // 3 | 4 | 5
300+
}
301+
finally {
302+
if (!!true) x = 6;
303+
}
304+
x; // 3 | 4 | 5 | 6
305+
}
306+
307+
function f21() {
308+
let x: 0 | 1 | 2 | 3 | 4 | 5 = 0;
309+
try {
310+
x = 1;
311+
try {
312+
x = 2;
313+
try {
314+
x = 3;
315+
}
316+
finally {
317+
if (!!true) x = 4;
318+
}
319+
x; // 3 | 4
320+
}
321+
finally {
322+
if (!!true) x = 5;
323+
}
324+
x; // 3 | 4 | 5
325+
}
326+
catch (e) {
327+
x; // 0 | 1 | 2 | 3 | 4 | 5
328+
}
329+
}
260330

261331

262332
//// [tryCatchFinallyControlFlow.js]
@@ -503,3 +573,71 @@ function t1() {
503573
})();
504574
x; // Reachable
505575
}
576+
function notallowed(arg) {
577+
var state = { tag: "one" };
578+
try {
579+
state = { tag: "two" };
580+
try {
581+
state = { tag: "three" };
582+
}
583+
finally { }
584+
}
585+
catch (err) {
586+
state.tag;
587+
if (state.tag !== "one" && state.tag !== "two") {
588+
console.log(state.tag);
589+
}
590+
}
591+
}
592+
function f20() {
593+
var x = 0;
594+
try {
595+
x = 1;
596+
try {
597+
x = 2;
598+
try {
599+
x = 3;
600+
}
601+
finally {
602+
if (!!true)
603+
x = 4;
604+
}
605+
x; // 3 | 4
606+
}
607+
finally {
608+
if (!!true)
609+
x = 5;
610+
}
611+
x; // 3 | 4 | 5
612+
}
613+
finally {
614+
if (!!true)
615+
x = 6;
616+
}
617+
x; // 3 | 4 | 5 | 6
618+
}
619+
function f21() {
620+
var x = 0;
621+
try {
622+
x = 1;
623+
try {
624+
x = 2;
625+
try {
626+
x = 3;
627+
}
628+
finally {
629+
if (!!true)
630+
x = 4;
631+
}
632+
x; // 3 | 4
633+
}
634+
finally {
635+
if (!!true)
636+
x = 5;
637+
}
638+
x; // 3 | 4 | 5
639+
}
640+
catch (e) {
641+
x; // 0 | 1 | 2 | 3 | 4 | 5
642+
}
643+
}

tests/baselines/reference/tryCatchFinallyControlFlow.symbols

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,3 +445,139 @@ function t1() {
445445
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 247, 9))
446446
}
447447

448+
// Repro from #39043
449+
450+
type State = { tag: "one" } | { tag: "two" } | { tag: "three" };
451+
>State : Symbol(State, Decl(tryCatchFinallyControlFlow.ts, 257, 1))
452+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14))
453+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 31))
454+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 48))
455+
456+
function notallowed(arg: number) {
457+
>notallowed : Symbol(notallowed, Decl(tryCatchFinallyControlFlow.ts, 261, 64))
458+
>arg : Symbol(arg, Decl(tryCatchFinallyControlFlow.ts, 263, 20))
459+
460+
let state: State = { tag: "one" };
461+
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
462+
>State : Symbol(State, Decl(tryCatchFinallyControlFlow.ts, 257, 1))
463+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 264, 24))
464+
465+
try {
466+
state = { tag: "two" };
467+
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
468+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 266, 17))
469+
470+
try {
471+
state = { tag: "three" };
472+
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
473+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 268, 21))
474+
}
475+
finally { }
476+
}
477+
catch (err) {
478+
>err : Symbol(err, Decl(tryCatchFinallyControlFlow.ts, 272, 11))
479+
480+
state.tag;
481+
>state.tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14), Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
482+
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
483+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14), Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
484+
485+
if (state.tag !== "one" && state.tag !== "two") {
486+
>state.tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14), Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
487+
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
488+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14), Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
489+
>state.tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
490+
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
491+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
492+
493+
console.log(state.tag);
494+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
495+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
496+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
497+
>state.tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 48))
498+
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
499+
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 48))
500+
}
501+
}
502+
}
503+
504+
function f20() {
505+
>f20 : Symbol(f20, Decl(tryCatchFinallyControlFlow.ts, 278, 1))
506+
507+
let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
508+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
509+
510+
try {
511+
x = 1;
512+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
513+
514+
try {
515+
x = 2;
516+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
517+
518+
try {
519+
x = 3;
520+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
521+
}
522+
finally {
523+
if (!!true) x = 4;
524+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
525+
}
526+
x; // 3 | 4
527+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
528+
}
529+
finally {
530+
if (!!true) x = 5;
531+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
532+
}
533+
x; // 3 | 4 | 5
534+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
535+
}
536+
finally {
537+
if (!!true) x = 6;
538+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
539+
}
540+
x; // 3 | 4 | 5 | 6
541+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
542+
}
543+
544+
function f21() {
545+
>f21 : Symbol(f21, Decl(tryCatchFinallyControlFlow.ts, 303, 1))
546+
547+
let x: 0 | 1 | 2 | 3 | 4 | 5 = 0;
548+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
549+
550+
try {
551+
x = 1;
552+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
553+
554+
try {
555+
x = 2;
556+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
557+
558+
try {
559+
x = 3;
560+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
561+
}
562+
finally {
563+
if (!!true) x = 4;
564+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
565+
}
566+
x; // 3 | 4
567+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
568+
}
569+
finally {
570+
if (!!true) x = 5;
571+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
572+
}
573+
x; // 3 | 4 | 5
574+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
575+
}
576+
catch (e) {
577+
>e : Symbol(e, Decl(tryCatchFinallyControlFlow.ts, 324, 11))
578+
579+
x; // 0 | 1 | 2 | 3 | 4 | 5
580+
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
581+
}
582+
}
583+

0 commit comments

Comments
 (0)