diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 990674b7b40a1..d88c135a22c70 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1198,8 +1198,10 @@ namespace ts { // exceptions. We add all mutation flow nodes as antecedents of this label such that we can analyze them // as possible antecedents of the start of catch or finally blocks. Furthermore, we add the current // control flow to represent exceptions that occur before any mutations. + const saveReturnTarget = currentReturnTarget; const saveExceptionTarget = currentExceptionTarget; - currentExceptionTarget = createBranchLabel(); + currentReturnTarget = createBranchLabel(); + currentExceptionTarget = node.catchClause ? createBranchLabel() : currentReturnTarget; addAntecedent(currentExceptionTarget, currentFlow); bind(node.tryBlock); addAntecedent(preFinallyLabel, currentFlow); @@ -1211,22 +1213,24 @@ namespace ts { // The currentExceptionTarget now represents control flows from exceptions in the catch clause. // Effectively, in a try-catch-finally, if an exception occurs in the try block, the catch block // acts like a second try block. - currentExceptionTarget = createBranchLabel(); + currentExceptionTarget = currentReturnTarget; addAntecedent(currentExceptionTarget, currentFlow); bind(node.catchClause); addAntecedent(preFinallyLabel, currentFlow); flowAfterCatch = currentFlow; } const exceptionTarget = finishFlowLabel(currentExceptionTarget); + currentReturnTarget = saveReturnTarget; currentExceptionTarget = saveExceptionTarget; if (node.finallyBlock) { // Possible ways control can reach the finally block: // 1) Normal completion of try block of a try-finally or try-catch-finally // 2) Normal completion of catch block (following exception in try block) of a try-catch-finally - // 3) Exception in try block of a try-finally - // 4) Exception in catch block of a try-catch-finally + // 3) Return in try or catch block of a try-finally or try-catch-finally + // 4) Exception in try block of a try-finally + // 5) Exception in catch block of a try-catch-finally // When analyzing a control flow graph that starts inside a finally block we want to consider all - // four possibilities above. However, when analyzing a control flow graph that starts outside (past) + // five possibilities above. However, when analyzing a control flow graph that starts outside (past) // the finally block, we only want to consider the first two (if we're past a finally block then it // must have completed normally). To make this possible, we inject two extra nodes into the control // flow graph: An after-finally with an antecedent of the control flow at the end of the finally diff --git a/tests/baselines/reference/tryCatchFinallyControlFlow.errors.txt b/tests/baselines/reference/tryCatchFinallyControlFlow.errors.txt new file mode 100644 index 0000000000000..11ec1515acadb --- /dev/null +++ b/tests/baselines/reference/tryCatchFinallyControlFlow.errors.txt @@ -0,0 +1,132 @@ +tests/cases/compiler/tryCatchFinallyControlFlow.ts(105,5): error TS7027: Unreachable code detected. + + +==== tests/cases/compiler/tryCatchFinallyControlFlow.ts (1 errors) ==== + // Repro from #34797 + + function f1() { + let a: number | null = null; + try { + a = 123; + return a; + } + catch (e) { + throw e; + } + finally { + if (a != null && a.toFixed(0) == "123") { + } + } + } + + function f2() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + } + catch (e) { + x = 2; + throw e; + } + finally { + x; // 0 | 1 | 2 + } + x; // 1 + } + + function f3() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + } + catch (e) { + x = 2; + return; + } + finally { + x; // 0 | 1 | 2 + } + x; // 1 + } + + function f4() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + } + catch (e) { + x = 2; + } + finally { + x; // 0 | 1 | 2 + } + x; // 1 | 2 + } + + function f5() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + return; + } + catch (e) { + x = 2; + } + finally { + x; // 0 | 1 | 2 + } + x; // 2 + } + + function f6() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + } + catch (e) { + x = 2; + return; + } + finally { + x; // 0 | 1 | 2 + } + x; // 1 + } + + function f7() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + return; + } + catch (e) { + x = 2; + return; + } + finally { + x; // 0 | 1 | 2 + } + x; // Unreachable + ~~ +!!! error TS7027: Unreachable code detected. + } + + // Repro from #35644 + + const main = () => { + let hoge: string | undefined = undefined; + try { + hoge = 'hoge!'; + return; + } + catch { + return; + } + finally { + if (hoge) { + hoge.length; + } + return; + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/tryCatchFinallyControlFlow.js b/tests/baselines/reference/tryCatchFinallyControlFlow.js index d0f8781cef19a..79d9a3d597275 100644 --- a/tests/baselines/reference/tryCatchFinallyControlFlow.js +++ b/tests/baselines/reference/tryCatchFinallyControlFlow.js @@ -59,6 +59,71 @@ function f4() { } x; // 1 | 2 } + +function f5() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + return; + } + catch (e) { + x = 2; + } + finally { + x; // 0 | 1 | 2 + } + x; // 2 +} + +function f6() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + } + catch (e) { + x = 2; + return; + } + finally { + x; // 0 | 1 | 2 + } + x; // 1 +} + +function f7() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + return; + } + catch (e) { + x = 2; + return; + } + finally { + x; // 0 | 1 | 2 + } + x; // Unreachable +} + +// Repro from #35644 + +const main = () => { + let hoge: string | undefined = undefined; + try { + hoge = 'hoge!'; + return; + } + catch { + return; + } + finally { + if (hoge) { + hoge.length; + } + return; + } +} //// [tryCatchFinallyControlFlow.js] @@ -119,3 +184,63 @@ function f4() { } x; // 1 | 2 } +function f5() { + var x = 0; + try { + x = 1; + return; + } + catch (e) { + x = 2; + } + finally { + x; // 0 | 1 | 2 + } + x; // 2 +} +function f6() { + var x = 0; + try { + x = 1; + } + catch (e) { + x = 2; + return; + } + finally { + x; // 0 | 1 | 2 + } + x; // 1 +} +function f7() { + var x = 0; + try { + x = 1; + return; + } + catch (e) { + x = 2; + return; + } + finally { + x; // 0 | 1 | 2 + } + x; // Unreachable +} +// Repro from #35644 +var main = function () { + var hoge = undefined; + try { + hoge = 'hoge!'; + return; + } + catch (_a) { + return; + } + finally { + if (hoge) { + hoge.length; + } + return; + } +}; diff --git a/tests/baselines/reference/tryCatchFinallyControlFlow.symbols b/tests/baselines/reference/tryCatchFinallyControlFlow.symbols index fa4171ec7de92..054151d6328d5 100644 --- a/tests/baselines/reference/tryCatchFinallyControlFlow.symbols +++ b/tests/baselines/reference/tryCatchFinallyControlFlow.symbols @@ -107,3 +107,114 @@ function f4() { >x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 48, 7)) } +function f5() { +>f5 : Symbol(f5, Decl(tryCatchFinallyControlFlow.ts, 59, 1)) + + let x: 0 | 1 | 2 | 3 = 0; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 62, 7)) + + try { + x = 1; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 62, 7)) + + return; + } + catch (e) { +>e : Symbol(e, Decl(tryCatchFinallyControlFlow.ts, 67, 11)) + + x = 2; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 62, 7)) + } + finally { + x; // 0 | 1 | 2 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 62, 7)) + } + x; // 2 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 62, 7)) +} + +function f6() { +>f6 : Symbol(f6, Decl(tryCatchFinallyControlFlow.ts, 74, 1)) + + let x: 0 | 1 | 2 | 3 = 0; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 77, 7)) + + try { + x = 1; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 77, 7)) + } + catch (e) { +>e : Symbol(e, Decl(tryCatchFinallyControlFlow.ts, 81, 11)) + + x = 2; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 77, 7)) + + return; + } + finally { + x; // 0 | 1 | 2 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 77, 7)) + } + x; // 1 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 77, 7)) +} + +function f7() { +>f7 : Symbol(f7, Decl(tryCatchFinallyControlFlow.ts, 89, 1)) + + let x: 0 | 1 | 2 | 3 = 0; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 92, 7)) + + try { + x = 1; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 92, 7)) + + return; + } + catch (e) { +>e : Symbol(e, Decl(tryCatchFinallyControlFlow.ts, 97, 11)) + + x = 2; +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 92, 7)) + + return; + } + finally { + x; // 0 | 1 | 2 +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 92, 7)) + } + x; // Unreachable +>x : Symbol(x, Decl(tryCatchFinallyControlFlow.ts, 92, 7)) +} + +// Repro from #35644 + +const main = () => { +>main : Symbol(main, Decl(tryCatchFinallyControlFlow.ts, 109, 5)) + + let hoge: string | undefined = undefined; +>hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 110, 7)) +>undefined : Symbol(undefined) + + try { + hoge = 'hoge!'; +>hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 110, 7)) + + return; + } + catch { + return; + } + finally { + if (hoge) { +>hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 110, 7)) + + hoge.length; +>hoge.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>hoge : Symbol(hoge, Decl(tryCatchFinallyControlFlow.ts, 110, 7)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + } + return; + } +} + diff --git a/tests/baselines/reference/tryCatchFinallyControlFlow.types b/tests/baselines/reference/tryCatchFinallyControlFlow.types index 29ce77f27241a..d29a00750d787 100644 --- a/tests/baselines/reference/tryCatchFinallyControlFlow.types +++ b/tests/baselines/reference/tryCatchFinallyControlFlow.types @@ -133,3 +133,132 @@ function f4() { >x : 1 | 2 } +function f5() { +>f5 : () => void + + let x: 0 | 1 | 2 | 3 = 0; +>x : 0 | 1 | 2 | 3 +>0 : 0 + + try { + x = 1; +>x = 1 : 1 +>x : 0 | 1 | 2 | 3 +>1 : 1 + + return; + } + catch (e) { +>e : any + + x = 2; +>x = 2 : 2 +>x : 0 | 1 | 2 | 3 +>2 : 2 + } + finally { + x; // 0 | 1 | 2 +>x : 0 | 1 | 2 + } + x; // 2 +>x : 2 +} + +function f6() { +>f6 : () => void + + let x: 0 | 1 | 2 | 3 = 0; +>x : 0 | 1 | 2 | 3 +>0 : 0 + + try { + x = 1; +>x = 1 : 1 +>x : 0 | 1 | 2 | 3 +>1 : 1 + } + catch (e) { +>e : any + + x = 2; +>x = 2 : 2 +>x : 0 | 1 | 2 | 3 +>2 : 2 + + return; + } + finally { + x; // 0 | 1 | 2 +>x : 0 | 1 | 2 + } + x; // 1 +>x : 1 +} + +function f7() { +>f7 : () => void + + let x: 0 | 1 | 2 | 3 = 0; +>x : 0 | 1 | 2 | 3 +>0 : 0 + + try { + x = 1; +>x = 1 : 1 +>x : 0 | 1 | 2 | 3 +>1 : 1 + + return; + } + catch (e) { +>e : any + + x = 2; +>x = 2 : 2 +>x : 0 | 1 | 2 | 3 +>2 : 2 + + return; + } + finally { + x; // 0 | 1 | 2 +>x : 0 | 1 | 2 + } + x; // Unreachable +>x : 0 | 1 | 2 | 3 +} + +// Repro from #35644 + +const main = () => { +>main : () => void +>() => { let hoge: string | undefined = undefined; try { hoge = 'hoge!'; return; } catch { return; } finally { if (hoge) { hoge.length; } return; }} : () => void + + let hoge: string | undefined = undefined; +>hoge : string | undefined +>undefined : undefined + + try { + hoge = 'hoge!'; +>hoge = 'hoge!' : "hoge!" +>hoge : string | undefined +>'hoge!' : "hoge!" + + return; + } + catch { + return; + } + finally { + if (hoge) { +>hoge : string | undefined + + hoge.length; +>hoge.length : number +>hoge : string +>length : number + } + return; + } +} + diff --git a/tests/cases/compiler/tryCatchFinallyControlFlow.ts b/tests/cases/compiler/tryCatchFinallyControlFlow.ts index daef2d7947a93..694d9f0c4d576 100644 --- a/tests/cases/compiler/tryCatchFinallyControlFlow.ts +++ b/tests/cases/compiler/tryCatchFinallyControlFlow.ts @@ -1,4 +1,5 @@ // @strict: true +// @allowUnreachableCode: false // Repro from #34797 @@ -60,3 +61,68 @@ function f4() { } x; // 1 | 2 } + +function f5() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + return; + } + catch (e) { + x = 2; + } + finally { + x; // 0 | 1 | 2 + } + x; // 2 +} + +function f6() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + } + catch (e) { + x = 2; + return; + } + finally { + x; // 0 | 1 | 2 + } + x; // 1 +} + +function f7() { + let x: 0 | 1 | 2 | 3 = 0; + try { + x = 1; + return; + } + catch (e) { + x = 2; + return; + } + finally { + x; // 0 | 1 | 2 + } + x; // Unreachable +} + +// Repro from #35644 + +const main = () => { + let hoge: string | undefined = undefined; + try { + hoge = 'hoge!'; + return; + } + catch { + return; + } + finally { + if (hoge) { + hoge.length; + } + return; + } +}