0
# Control Flow Analysis
1
2
Control flow analysis utilities for determining statement reachability, analyzing function signatures, and identifying control flow termination points. These functions help understand code execution paths and unreachable code detection.
3
4
## Capabilities
5
6
### Control Flow Termination Analysis
7
8
Utilities for determining if statements end control flow execution.
9
10
```typescript { .api }
11
/**
12
* Check if statement or block ends control flow
13
* @param statement - Statement or block to analyze
14
* @param checker - Optional type checker for advanced analysis
15
* @returns true if statement terminates control flow
16
*/
17
function endsControlFlow(statement: ts.Statement | ts.BlockLike, checker?: ts.TypeChecker): boolean;
18
19
/**
20
* Get detailed control flow end information
21
* @param statement - Statement or block to analyze
22
* @param checker - Optional type checker for advanced analysis
23
* @returns Control flow end details
24
*/
25
function getControlFlowEnd(statement: ts.Statement | ts.BlockLike, checker?: ts.TypeChecker): ControlFlowEnd;
26
27
/**
28
* Control flow end result interface
29
*/
30
interface ControlFlowEnd {
31
/** Array of statements that end control flow */
32
readonly statements: ReadonlyArray<ControlFlowStatement>;
33
/** Whether control flow definitely ends */
34
readonly end: boolean;
35
}
36
37
/**
38
* Types of statements that can end control flow
39
*/
40
type ControlFlowStatement = ts.BreakStatement
41
| ts.ContinueStatement
42
| ts.ReturnStatement
43
| ts.ThrowStatement
44
| ts.ExpressionStatement & {expression: ts.CallExpression};
45
```
46
47
### Function Signature Analysis
48
49
Utilities for analyzing function call effects on control flow.
50
51
```typescript { .api }
52
/**
53
* Check if call expression affects control flow
54
* @param node - Call expression to analyze
55
* @param checker - Type checker instance
56
* @returns Signature effect if call affects control flow
57
*/
58
function callExpressionAffectsControlFlow(
59
node: ts.CallExpression,
60
checker: ts.TypeChecker
61
): SignatureEffect | undefined;
62
63
/**
64
* Function signature effects on control flow
65
*/
66
enum SignatureEffect {
67
/** Function never returns (throws or infinite loop) */
68
Never = 1,
69
/** Function performs assertions that may throw */
70
Asserts = 2
71
}
72
```
73
74
**Usage Examples:**
75
76
```typescript
77
import * as ts from "typescript";
78
import {
79
endsControlFlow,
80
getControlFlowEnd,
81
callExpressionAffectsControlFlow,
82
SignatureEffect
83
} from "tsutils/util";
84
85
// Basic control flow analysis
86
function analyzeControlFlow(statement: ts.Statement) {
87
if (endsControlFlow(statement)) {
88
console.log("Statement ends control flow");
89
90
const details = getControlFlowEnd(statement);
91
console.log(`Control flow ends: ${details.end}`);
92
console.log(`Terminating statements: ${details.statements.length}`);
93
94
details.statements.forEach((stmt, index) => {
95
console.log(` ${index}: ${ts.SyntaxKind[stmt.kind]}`);
96
});
97
}
98
}
99
100
// Function call analysis
101
function analyzeFunctionCalls(checker: ts.TypeChecker, callExpr: ts.CallExpression) {
102
const effect = callExpressionAffectsControlFlow(callExpr, checker);
103
104
if (effect !== undefined) {
105
switch (effect) {
106
case SignatureEffect.Never:
107
console.log("Function never returns");
108
break;
109
case SignatureEffect.Asserts:
110
console.log("Function performs assertions");
111
break;
112
}
113
}
114
}
115
116
// Unreachable code detection
117
function detectUnreachableCode(block: ts.Block) {
118
for (let i = 0; i < block.statements.length; i++) {
119
const statement = block.statements[i];
120
121
if (endsControlFlow(statement)) {
122
// Check if there are statements after this one
123
if (i < block.statements.length - 1) {
124
console.log(`Unreachable code detected after statement at index ${i}`);
125
126
// Analyze what statements are unreachable
127
for (let j = i + 1; j < block.statements.length; j++) {
128
const unreachableStmt = block.statements[j];
129
console.log(` Unreachable: ${ts.SyntaxKind[unreachableStmt.kind]}`);
130
}
131
}
132
break;
133
}
134
}
135
}
136
137
// Advanced control flow analysis with type checker
138
function analyzeAdvancedControlFlow(checker: ts.TypeChecker, node: ts.Node) {
139
node.forEachChild(child => {
140
if (ts.isStatement(child)) {
141
const details = getControlFlowEnd(child, checker);
142
143
if (details.end) {
144
console.log(`Statement ends control flow: ${ts.SyntaxKind[child.kind]}`);
145
146
// Analyze the specific terminating statements
147
details.statements.forEach(stmt => {
148
if (ts.isReturnStatement(stmt)) {
149
console.log(" Ends with return");
150
} else if (ts.isThrowStatement(stmt)) {
151
console.log(" Ends with throw");
152
} else if (ts.isBreakStatement(stmt)) {
153
console.log(" Ends with break");
154
} else if (ts.isContinueStatement(stmt)) {
155
console.log(" Ends with continue");
156
} else if (ts.isExpressionStatement(stmt) && ts.isCallExpression(stmt.expression)) {
157
// Check if call never returns
158
const effect = callExpressionAffectsControlFlow(stmt.expression, checker);
159
if (effect === SignatureEffect.Never) {
160
console.log(" Ends with never-returning function call");
161
}
162
}
163
});
164
}
165
}
166
167
analyzeAdvancedControlFlow(checker, child);
168
});
169
}
170
171
// Switch statement analysis
172
function analyzeSwitchControlFlow(switchStmt: ts.SwitchStatement, checker?: ts.TypeChecker) {
173
let hasDefaultCase = false;
174
let allCasesEndControlFlow = true;
175
176
switchStmt.caseBlock.clauses.forEach(clause => {
177
if (ts.isDefaultClause(clause)) {
178
hasDefaultCase = true;
179
}
180
181
// Check if this case ends control flow
182
const statements = clause.statements;
183
if (statements.length === 0) {
184
// Empty case falls through
185
allCasesEndControlFlow = false;
186
} else {
187
const lastStatement = statements[statements.length - 1];
188
if (!endsControlFlow(lastStatement, checker)) {
189
allCasesEndControlFlow = false;
190
}
191
}
192
});
193
194
if (hasDefaultCase && allCasesEndControlFlow) {
195
console.log("Switch statement is exhaustive and all cases end control flow");
196
}
197
}
198
199
// If-else chain analysis
200
function analyzeIfElseControlFlow(ifStmt: ts.IfStatement, checker?: ts.TypeChecker) {
201
let allBranchesEndControlFlow = true;
202
203
// Check then branch
204
if (!endsControlFlow(ifStmt.thenStatement, checker)) {
205
allBranchesEndControlFlow = false;
206
}
207
208
// Check else branch
209
if (ifStmt.elseStatement) {
210
if (!endsControlFlow(ifStmt.elseStatement, checker)) {
211
allBranchesEndControlFlow = false;
212
}
213
} else {
214
// No else branch means control flow can continue
215
allBranchesEndControlFlow = false;
216
}
217
218
if (allBranchesEndControlFlow) {
219
console.log("All branches of if-else end control flow");
220
}
221
}
222
223
// Try-catch-finally analysis
224
function analyzeTryCatchControlFlow(tryStmt: ts.TryStatement, checker?: ts.TypeChecker) {
225
const tryEnds = endsControlFlow(tryStmt.tryBlock, checker);
226
let catchEnds = true;
227
228
if (tryStmt.catchClause) {
229
catchEnds = endsControlFlow(tryStmt.catchClause.block, checker);
230
}
231
232
if (tryStmt.finallyBlock) {
233
const finallyEnds = endsControlFlow(tryStmt.finallyBlock, checker);
234
if (finallyEnds) {
235
console.log("Finally block ends control flow (overrides try/catch)");
236
}
237
}
238
239
if (tryEnds && catchEnds) {
240
console.log("Both try and catch blocks end control flow");
241
}
242
}
243
244
// Dead code elimination helper
245
function identifyDeadCode(sourceFile: ts.SourceFile, checker: ts.TypeChecker) {
246
const deadCodeRanges: ts.TextRange[] = [];
247
248
function visitNode(node: ts.Node) {
249
if (ts.isBlock(node)) {
250
for (let i = 0; i < node.statements.length; i++) {
251
const statement = node.statements[i];
252
253
if (endsControlFlow(statement, checker)) {
254
// Mark remaining statements as dead code
255
for (let j = i + 1; j < node.statements.length; j++) {
256
const deadStatement = node.statements[j];
257
deadCodeRanges.push({
258
pos: deadStatement.pos,
259
end: deadStatement.end
260
});
261
}
262
break;
263
}
264
}
265
}
266
267
node.forEachChild(visitNode);
268
}
269
270
visitNode(sourceFile);
271
return deadCodeRanges;
272
}
273
```
274
275
**Common Use Cases:**
276
277
1. **Unreachable Code Detection**: Identify code that can never be executed
278
2. **Dead Code Elimination**: Remove statements after control flow termination
279
3. **Control Flow Graph Construction**: Build flow graphs for analysis
280
4. **Switch Statement Exhaustiveness**: Verify all code paths are handled
281
5. **Return Path Analysis**: Ensure all function paths return values
282
6. **Exception Flow Analysis**: Track throw statements and exception propagation
283
7. **Loop Termination Analysis**: Detect infinite loops and guaranteed exits
284
8. **Conditional Branch Analysis**: Analyze if-else chains and nested conditions
285
286
The control flow analysis utilities are essential for:
287
- Static analysis tools and linters
288
- Code optimization and dead code elimination
289
- Compiler warnings about unreachable code
290
- Code coverage analysis
291
- Refactoring tools that need to understand execution paths
292
- Security analysis tools that track data flow