Skip to content

Commit 20285e6

Browse files
authored
Include all flow nodes made within try blocks as antecedents for catch or finally blocks (#29466)
* Include all flow nodes made within `try` blocks as antecedents for `catch` or `finally` blocks * Fix typo
1 parent b317334 commit 20285e6

File tree

5 files changed

+500
-6
lines changed

5 files changed

+500
-6
lines changed

src/compiler/binder.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ namespace ts {
100100
IsObjectLiteralOrClassExpressionMethod = 1 << 7,
101101
}
102102

103+
let flowNodeCreated: <T extends FlowNode>(node: T) => T = identity;
104+
103105
const binder = createBinder();
104106

105107
export function bindSourceFile(file: SourceFile, options: CompilerOptions) {
@@ -530,6 +532,7 @@ namespace ts {
530532
blockScopeContainer.locals = undefined;
531533
}
532534
if (containerFlags & ContainerFlags.IsControlFlowContainer) {
535+
const saveFlowNodeCreated = flowNodeCreated;
533536
const saveCurrentFlow = currentFlow;
534537
const saveBreakTarget = currentBreakTarget;
535538
const saveContinueTarget = currentContinueTarget;
@@ -553,6 +556,7 @@ namespace ts {
553556
currentContinueTarget = undefined;
554557
activeLabels = undefined;
555558
hasExplicitReturn = false;
559+
flowNodeCreated = identity;
556560
bindChildren(node);
557561
// Reset all reachability check related flags on node (for incremental scenarios)
558562
node.flags &= ~NodeFlags.ReachabilityAndEmitFlags;
@@ -579,6 +583,7 @@ namespace ts {
579583
currentReturnTarget = saveReturnTarget;
580584
activeLabels = saveActiveLabels;
581585
hasExplicitReturn = saveHasExplicitReturn;
586+
flowNodeCreated = saveFlowNodeCreated;
582587
}
583588
else if (containerFlags & ContainerFlags.IsInterface) {
584589
seenThisKeyword = false;
@@ -858,25 +863,25 @@ namespace ts {
858863
return antecedent;
859864
}
860865
setFlowNodeReferenced(antecedent);
861-
return { flags, expression, antecedent };
866+
return flowNodeCreated({ flags, expression, antecedent });
862867
}
863868

864869
function createFlowSwitchClause(antecedent: FlowNode, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): FlowNode {
865870
if (!isNarrowingExpression(switchStatement.expression)) {
866871
return antecedent;
867872
}
868873
setFlowNodeReferenced(antecedent);
869-
return { flags: FlowFlags.SwitchClause, switchStatement, clauseStart, clauseEnd, antecedent };
874+
return flowNodeCreated({ flags: FlowFlags.SwitchClause, switchStatement, clauseStart, clauseEnd, antecedent });
870875
}
871876

872877
function createFlowAssignment(antecedent: FlowNode, node: Expression | VariableDeclaration | BindingElement): FlowNode {
873878
setFlowNodeReferenced(antecedent);
874-
return { flags: FlowFlags.Assignment, antecedent, node };
879+
return flowNodeCreated({ flags: FlowFlags.Assignment, antecedent, node });
875880
}
876881

877882
function createFlowArrayMutation(antecedent: FlowNode, node: CallExpression | BinaryExpression): FlowNode {
878883
setFlowNodeReferenced(antecedent);
879-
const res: FlowArrayMutation = { flags: FlowFlags.ArrayMutation, antecedent, node };
884+
const res: FlowArrayMutation = flowNodeCreated({ flags: FlowFlags.ArrayMutation, antecedent, node });
880885
return res;
881886
}
882887

@@ -1080,21 +1085,49 @@ namespace ts {
10801085
function bindTryStatement(node: TryStatement): void {
10811086
const preFinallyLabel = createBranchLabel();
10821087
const preTryFlow = currentFlow;
1083-
// TODO: Every statement in try block is potentially an exit point!
1088+
const tryPriors: FlowNode[] = [];
1089+
const oldFlowNodeCreated = flowNodeCreated;
1090+
// We hook the creation of all flow nodes within the `try` scope and store them so we can add _all_ of them
1091+
// as possible antecedents of the start of the `catch` or `finally` blocks.
1092+
// Don't bother intercepting the call if there's no finally or catch block that needs the information
1093+
if (node.catchClause || node.finallyBlock) {
1094+
flowNodeCreated = node => (tryPriors.push(node), node);
1095+
}
10841096
bind(node.tryBlock);
1097+
flowNodeCreated = oldFlowNodeCreated;
10851098
addAntecedent(preFinallyLabel, currentFlow);
10861099

10871100
const flowAfterTry = currentFlow;
10881101
let flowAfterCatch = unreachableFlow;
10891102

10901103
if (node.catchClause) {
10911104
currentFlow = preTryFlow;
1105+
if (tryPriors.length) {
1106+
const preCatchFlow = createBranchLabel();
1107+
addAntecedent(preCatchFlow, currentFlow);
1108+
for (const p of tryPriors) {
1109+
addAntecedent(preCatchFlow, p);
1110+
}
1111+
currentFlow = finishFlowLabel(preCatchFlow);
1112+
}
1113+
10921114
bind(node.catchClause);
10931115
addAntecedent(preFinallyLabel, currentFlow);
10941116

10951117
flowAfterCatch = currentFlow;
10961118
}
10971119
if (node.finallyBlock) {
1120+
// We add the nodes within the `try` block to the `finally`'s antecedents if there's no catch block
1121+
// (If there is a `catch` block, it will have all these antecedents instead, and the `finally` will
1122+
// have the end of the `try` block and the end of the `catch` block)
1123+
if (!node.catchClause) {
1124+
if (tryPriors.length) {
1125+
for (const p of tryPriors) {
1126+
addAntecedent(preFinallyLabel, p);
1127+
}
1128+
}
1129+
}
1130+
10981131
// in finally flow is combined from pre-try/flow from try/flow from catch
10991132
// pre-flow is necessary to make sure that finally is reachable even if finally flows in both try and finally blocks are unreachable
11001133

@@ -1142,7 +1175,7 @@ namespace ts {
11421175
}
11431176
}
11441177
if (!(currentFlow.flags & FlowFlags.Unreachable)) {
1145-
const afterFinallyFlow: AfterFinallyFlow = { flags: FlowFlags.AfterFinally, antecedent: currentFlow };
1178+
const afterFinallyFlow: AfterFinallyFlow = flowNodeCreated({ flags: FlowFlags.AfterFinally, antecedent: currentFlow });
11461179
preFinallyFlow.lock = afterFinallyFlow;
11471180
currentFlow = afterFinallyFlow;
11481181
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//// [controlFlowForCatchAndFinally.ts]
2+
type Page = {close(): Promise<void>; content(): Promise<string>};
3+
type Browser = {close(): Promise<void>};
4+
declare function test1(): Promise<Browser>;
5+
declare function test2(obj: Browser): Promise<Page>;
6+
async function test(): Promise<string> {
7+
let browser: Browser | undefined = undefined;
8+
let page: Page | undefined = undefined;
9+
try {
10+
browser = await test1();
11+
page = await test2(browser);
12+
return await page.content();;
13+
} finally {
14+
if (page) {
15+
await page.close(); // ok
16+
}
17+
18+
if (browser) {
19+
await browser.close(); // ok
20+
}
21+
}
22+
}
23+
24+
declare class Aborter { abort(): void };
25+
class Foo {
26+
abortController: Aborter | undefined = undefined;
27+
28+
async operation() {
29+
if (this.abortController !== undefined) {
30+
this.abortController.abort();
31+
this.abortController = undefined;
32+
}
33+
try {
34+
this.abortController = new Aborter();
35+
} catch (error) {
36+
if (this.abortController !== undefined) {
37+
this.abortController.abort(); // ok
38+
}
39+
}
40+
}
41+
}
42+
43+
//// [controlFlowForCatchAndFinally.js]
44+
"use strict";
45+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
46+
return new (P || (P = Promise))(function (resolve, reject) {
47+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
48+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
49+
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
50+
step((generator = generator.apply(thisArg, _arguments || [])).next());
51+
});
52+
};
53+
var __generator = (this && this.__generator) || function (thisArg, body) {
54+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
55+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
56+
function verb(n) { return function (v) { return step([n, v]); }; }
57+
function step(op) {
58+
if (f) throw new TypeError("Generator is already executing.");
59+
while (_) try {
60+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
61+
if (y = 0, t) op = [op[0] & 2, t.value];
62+
switch (op[0]) {
63+
case 0: case 1: t = op; break;
64+
case 4: _.label++; return { value: op[1], done: false };
65+
case 5: _.label++; y = op[1]; op = [0]; continue;
66+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
67+
default:
68+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
69+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
70+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
71+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
72+
if (t[2]) _.ops.pop();
73+
_.trys.pop(); continue;
74+
}
75+
op = body.call(thisArg, _);
76+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
77+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
78+
}
79+
};
80+
function test() {
81+
return __awaiter(this, void 0, void 0, function () {
82+
var browser, page;
83+
return __generator(this, function (_a) {
84+
switch (_a.label) {
85+
case 0:
86+
browser = undefined;
87+
page = undefined;
88+
_a.label = 1;
89+
case 1:
90+
_a.trys.push([1, , 5, 10]);
91+
return [4 /*yield*/, test1()];
92+
case 2:
93+
browser = _a.sent();
94+
return [4 /*yield*/, test2(browser)];
95+
case 3:
96+
page = _a.sent();
97+
return [4 /*yield*/, page.content()];
98+
case 4: return [2 /*return*/, _a.sent()];
99+
case 5:
100+
if (!page) return [3 /*break*/, 7];
101+
return [4 /*yield*/, page.close()];
102+
case 6:
103+
_a.sent(); // ok
104+
_a.label = 7;
105+
case 7:
106+
if (!browser) return [3 /*break*/, 9];
107+
return [4 /*yield*/, browser.close()];
108+
case 8:
109+
_a.sent(); // ok
110+
_a.label = 9;
111+
case 9: return [7 /*endfinally*/];
112+
case 10: return [2 /*return*/];
113+
}
114+
});
115+
});
116+
}
117+
;
118+
var Foo = /** @class */ (function () {
119+
function Foo() {
120+
this.abortController = undefined;
121+
}
122+
Foo.prototype.operation = function () {
123+
return __awaiter(this, void 0, void 0, function () {
124+
return __generator(this, function (_a) {
125+
if (this.abortController !== undefined) {
126+
this.abortController.abort();
127+
this.abortController = undefined;
128+
}
129+
try {
130+
this.abortController = new Aborter();
131+
}
132+
catch (error) {
133+
if (this.abortController !== undefined) {
134+
this.abortController.abort(); // ok
135+
}
136+
}
137+
return [2 /*return*/];
138+
});
139+
});
140+
};
141+
return Foo;
142+
}());

0 commit comments

Comments
 (0)