Fix control flow analysis for nested try-catch-finally statements (#39399)

* Fix control flow analysis for nested try-catch-finally statements

* Add tests
This commit is contained in:
Anders Hejlsberg 2020-07-03 09:51:01 -07:00 committed by GitHub
parent fd8ca520ab
commit 2fe2f88047
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 607 additions and 0 deletions

View File

@ -1241,6 +1241,11 @@ namespace ts {
if (currentReturnTarget && returnLabel.antecedents) {
addAntecedent(currentReturnTarget, createReduceLabel(finallyLabel, returnLabel.antecedents, currentFlow));
}
// If we have an outer exception target (i.e. a containing try-finally or try-catch-finally), add a
// control flow that goes back through the finally blok and back through each possible exception source.
if (currentExceptionTarget && exceptionLabel.antecedents) {
addAntecedent(currentExceptionTarget, createReduceLabel(finallyLabel, exceptionLabel.antecedents, currentFlow));
}
// If the end of the finally block is reachable, but the end of the try and catch blocks are not,
// convert the current flow to unreachable. For example, 'try { return 1; } finally { ... }' should
// result in an unreachable current control flow.

View File

@ -275,4 +275,74 @@ tests/cases/compiler/tryCatchFinallyControlFlow.ts(255,9): error TS7027: Unreach
})();
x; // Reachable
}
// Repro from #39043
type State = { tag: "one" } | { tag: "two" } | { tag: "three" };
function notallowed(arg: number) {
let state: State = { tag: "one" };
try {
state = { tag: "two" };
try {
state = { tag: "three" };
}
finally { }
}
catch (err) {
state.tag;
if (state.tag !== "one" && state.tag !== "two") {
console.log(state.tag);
}
}
}
function f20() {
let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
try {
x = 1;
try {
x = 2;
try {
x = 3;
}
finally {
if (!!true) x = 4;
}
x; // 3 | 4
}
finally {
if (!!true) x = 5;
}
x; // 3 | 4 | 5
}
finally {
if (!!true) x = 6;
}
x; // 3 | 4 | 5 | 6
}
function f21() {
let x: 0 | 1 | 2 | 3 | 4 | 5 = 0;
try {
x = 1;
try {
x = 2;
try {
x = 3;
}
finally {
if (!!true) x = 4;
}
x; // 3 | 4
}
finally {
if (!!true) x = 5;
}
x; // 3 | 4 | 5
}
catch (e) {
x; // 0 | 1 | 2 | 3 | 4 | 5
}
}

View File

@ -257,6 +257,76 @@ function t1() {
})();
x; // Reachable
}
// Repro from #39043
type State = { tag: "one" } | { tag: "two" } | { tag: "three" };
function notallowed(arg: number) {
let state: State = { tag: "one" };
try {
state = { tag: "two" };
try {
state = { tag: "three" };
}
finally { }
}
catch (err) {
state.tag;
if (state.tag !== "one" && state.tag !== "two") {
console.log(state.tag);
}
}
}
function f20() {
let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
try {
x = 1;
try {
x = 2;
try {
x = 3;
}
finally {
if (!!true) x = 4;
}
x; // 3 | 4
}
finally {
if (!!true) x = 5;
}
x; // 3 | 4 | 5
}
finally {
if (!!true) x = 6;
}
x; // 3 | 4 | 5 | 6
}
function f21() {
let x: 0 | 1 | 2 | 3 | 4 | 5 = 0;
try {
x = 1;
try {
x = 2;
try {
x = 3;
}
finally {
if (!!true) x = 4;
}
x; // 3 | 4
}
finally {
if (!!true) x = 5;
}
x; // 3 | 4 | 5
}
catch (e) {
x; // 0 | 1 | 2 | 3 | 4 | 5
}
}
//// [tryCatchFinallyControlFlow.js]
@ -503,3 +573,71 @@ function t1() {
})();
x; // Reachable
}
function notallowed(arg) {
var state = { tag: "one" };
try {
state = { tag: "two" };
try {
state = { tag: "three" };
}
finally { }
}
catch (err) {
state.tag;
if (state.tag !== "one" && state.tag !== "two") {
console.log(state.tag);
}
}
}
function f20() {
var x = 0;
try {
x = 1;
try {
x = 2;
try {
x = 3;
}
finally {
if (!!true)
x = 4;
}
x; // 3 | 4
}
finally {
if (!!true)
x = 5;
}
x; // 3 | 4 | 5
}
finally {
if (!!true)
x = 6;
}
x; // 3 | 4 | 5 | 6
}
function f21() {
var x = 0;
try {
x = 1;
try {
x = 2;
try {
x = 3;
}
finally {
if (!!true)
x = 4;
}
x; // 3 | 4
}
finally {
if (!!true)
x = 5;
}
x; // 3 | 4 | 5
}
catch (e) {
x; // 0 | 1 | 2 | 3 | 4 | 5
}
}

View File

@ -445,3 +445,139 @@ function t1() {
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 247, 9))
}
// Repro from #39043
type State = { tag: "one" } | { tag: "two" } | { tag: "three" };
>State : Symbol(State, Decl(tryCatchFinallyControlFlow.ts, 257, 1))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 31))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 48))
function notallowed(arg: number) {
>notallowed : Symbol(notallowed, Decl(tryCatchFinallyControlFlow.ts, 261, 64))
>arg : Symbol(arg, Decl(tryCatchFinallyControlFlow.ts, 263, 20))
let state: State = { tag: "one" };
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
>State : Symbol(State, Decl(tryCatchFinallyControlFlow.ts, 257, 1))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 264, 24))
try {
state = { tag: "two" };
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 266, 17))
try {
state = { tag: "three" };
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 268, 21))
}
finally { }
}
catch (err) {
>err : Symbol(err, Decl(tryCatchFinallyControlFlow.ts, 272, 11))
state.tag;
>state.tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14), Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14), Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
if (state.tag !== "one" && state.tag !== "two") {
>state.tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14), Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 14), Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
>state.tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 31), Decl(tryCatchFinallyControlFlow.ts, 261, 48))
console.log(state.tag);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>state.tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 48))
>state : Symbol(state, Decl(tryCatchFinallyControlFlow.ts, 264, 7))
>tag : Symbol(tag, Decl(tryCatchFinallyControlFlow.ts, 261, 48))
}
}
}
function f20() {
>f20 : Symbol(f20, Decl(tryCatchFinallyControlFlow.ts, 278, 1))
let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
try {
x = 1;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
try {
x = 2;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
try {
x = 3;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
}
finally {
if (!!true) x = 4;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
}
x; // 3 | 4
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
}
finally {
if (!!true) x = 5;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
}
x; // 3 | 4 | 5
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
}
finally {
if (!!true) x = 6;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
}
x; // 3 | 4 | 5 | 6
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 281, 7))
}
function f21() {
>f21 : Symbol(f21, Decl(tryCatchFinallyControlFlow.ts, 303, 1))
let x: 0 | 1 | 2 | 3 | 4 | 5 = 0;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
try {
x = 1;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
try {
x = 2;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
try {
x = 3;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
}
finally {
if (!!true) x = 4;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
}
x; // 3 | 4
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
}
finally {
if (!!true) x = 5;
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
}
x; // 3 | 4 | 5
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
}
catch (e) {
>e : Symbol(e, Decl(tryCatchFinallyControlFlow.ts, 324, 11))
x; // 0 | 1 | 2 | 3 | 4 | 5
>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 306, 7))
}
}

View File

@ -589,3 +589,191 @@ function t1() {
>x : "x" | null
}
// Repro from #39043
type State = { tag: "one" } | { tag: "two" } | { tag: "three" };
>State : State
>tag : "one"
>tag : "two"
>tag : "three"
function notallowed(arg: number) {
>notallowed : (arg: number) => void
>arg : number
let state: State = { tag: "one" };
>state : State
>{ tag: "one" } : { tag: "one"; }
>tag : "one"
>"one" : "one"
try {
state = { tag: "two" };
>state = { tag: "two" } : { tag: "two"; }
>state : State
>{ tag: "two" } : { tag: "two"; }
>tag : "two"
>"two" : "two"
try {
state = { tag: "three" };
>state = { tag: "three" } : { tag: "three"; }
>state : State
>{ tag: "three" } : { tag: "three"; }
>tag : "three"
>"three" : "three"
}
finally { }
}
catch (err) {
>err : any
state.tag;
>state.tag : "one" | "two" | "three"
>state : State
>tag : "one" | "two" | "three"
if (state.tag !== "one" && state.tag !== "two") {
>state.tag !== "one" && state.tag !== "two" : boolean
>state.tag !== "one" : boolean
>state.tag : "one" | "two" | "three"
>state : State
>tag : "one" | "two" | "three"
>"one" : "one"
>state.tag !== "two" : boolean
>state.tag : "two" | "three"
>state : { tag: "two"; } | { tag: "three"; }
>tag : "two" | "three"
>"two" : "two"
console.log(state.tag);
>console.log(state.tag) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>state.tag : "three"
>state : { tag: "three"; }
>tag : "three"
}
}
}
function f20() {
>f20 : () => void
let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
>x : 0 | 1 | 2 | 3 | 4 | 5 | 6
>0 : 0
try {
x = 1;
>x = 1 : 1
>x : 0 | 1 | 2 | 3 | 4 | 5 | 6
>1 : 1
try {
x = 2;
>x = 2 : 2
>x : 0 | 1 | 2 | 3 | 4 | 5 | 6
>2 : 2
try {
x = 3;
>x = 3 : 3
>x : 0 | 1 | 2 | 3 | 4 | 5 | 6
>3 : 3
}
finally {
if (!!true) x = 4;
>!!true : true
>!true : false
>true : true
>x = 4 : 4
>x : 0 | 1 | 2 | 3 | 4 | 5 | 6
>4 : 4
}
x; // 3 | 4
>x : 3 | 4
}
finally {
if (!!true) x = 5;
>!!true : true
>!true : false
>true : true
>x = 5 : 5
>x : 0 | 1 | 2 | 3 | 4 | 5 | 6
>5 : 5
}
x; // 3 | 4 | 5
>x : 3 | 4 | 5
}
finally {
if (!!true) x = 6;
>!!true : true
>!true : false
>true : true
>x = 6 : 6
>x : 0 | 1 | 2 | 3 | 4 | 5 | 6
>6 : 6
}
x; // 3 | 4 | 5 | 6
>x : 3 | 4 | 5 | 6
}
function f21() {
>f21 : () => void
let x: 0 | 1 | 2 | 3 | 4 | 5 = 0;
>x : 0 | 1 | 2 | 3 | 4 | 5
>0 : 0
try {
x = 1;
>x = 1 : 1
>x : 0 | 1 | 2 | 3 | 4 | 5
>1 : 1
try {
x = 2;
>x = 2 : 2
>x : 0 | 1 | 2 | 3 | 4 | 5
>2 : 2
try {
x = 3;
>x = 3 : 3
>x : 0 | 1 | 2 | 3 | 4 | 5
>3 : 3
}
finally {
if (!!true) x = 4;
>!!true : true
>!true : false
>true : true
>x = 4 : 4
>x : 0 | 1 | 2 | 3 | 4 | 5
>4 : 4
}
x; // 3 | 4
>x : 3 | 4
}
finally {
if (!!true) x = 5;
>!!true : true
>!true : false
>true : true
>x = 5 : 5
>x : 0 | 1 | 2 | 3 | 4 | 5
>5 : 5
}
x; // 3 | 4 | 5
>x : 3 | 4 | 5
}
catch (e) {
>e : any
x; // 0 | 1 | 2 | 3 | 4 | 5
>x : 0 | 1 | 2 | 3 | 4 | 5
}
}

View File

@ -259,3 +259,73 @@ function t1() {
})();
x; // Reachable
}
// Repro from #39043
type State = { tag: "one" } | { tag: "two" } | { tag: "three" };
function notallowed(arg: number) {
let state: State = { tag: "one" };
try {
state = { tag: "two" };
try {
state = { tag: "three" };
}
finally { }
}
catch (err) {
state.tag;
if (state.tag !== "one" && state.tag !== "two") {
console.log(state.tag);
}
}
}
function f20() {
let x: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
try {
x = 1;
try {
x = 2;
try {
x = 3;
}
finally {
if (!!true) x = 4;
}
x; // 3 | 4
}
finally {
if (!!true) x = 5;
}
x; // 3 | 4 | 5
}
finally {
if (!!true) x = 6;
}
x; // 3 | 4 | 5 | 6
}
function f21() {
let x: 0 | 1 | 2 | 3 | 4 | 5 = 0;
try {
x = 1;
try {
x = 2;
try {
x = 3;
}
finally {
if (!!true) x = 4;
}
x; // 3 | 4
}
finally {
if (!!true) x = 5;
}
x; // 3 | 4 | 5
}
catch (e) {
x; // 0 | 1 | 2 | 3 | 4 | 5
}
}