Properly reflect CFA effects of return in try or catch blocks (#35730)

* Properly reflect CFA effects of return in try or catch blocks

* Add tests

* Accept new baselines
This commit is contained in:
Anders Hejlsberg 2019-12-17 16:46:34 -08:00 committed by GitHub
parent f90cde4706
commit cafa175501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 572 additions and 5 deletions

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
};

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}