1
+ /// <reference path="types.ts"/>
2
+
3
+ module ts {
4
+ export function checkControlFlowOfFunction ( decl : FunctionLikeDeclaration , noImplicitReturns : boolean , error : ( n : Node , message : DiagnosticMessage , arg0 ?: any ) => void ) {
5
+ if ( ! decl . body || decl . body . kind !== SyntaxKind . FunctionBlock ) {
6
+ return ;
7
+ }
8
+
9
+ var finalState = checkControlFlow ( decl . body , error ) ;
10
+ if ( noImplicitReturns && finalState === ControlFlowState . Reachable ) {
11
+ var errorNode : Node = decl . name || decl ;
12
+ error ( errorNode , Diagnostics . Not_all_code_paths_return_a_value ) ;
13
+ }
14
+ }
15
+
16
+ export function checkControlFlowOfBlock ( block : Block , error : ( n : Node , message : DiagnosticMessage , arg0 ?: any ) => void ) {
17
+ checkControlFlow ( block , error ) ;
18
+ }
19
+
20
+ function checkControlFlow ( decl : Node , error : ( n : Node , message : DiagnosticMessage , arg0 ?: any ) => void ) : ControlFlowState {
21
+ var currentState = ControlFlowState . Reachable ;
22
+
23
+ function setState ( newState : ControlFlowState ) {
24
+ currentState = newState ;
25
+ }
26
+
27
+ function or ( s1 : ControlFlowState , s2 : ControlFlowState ) : ControlFlowState {
28
+ if ( s1 === ControlFlowState . Reachable || s2 === ControlFlowState . Reachable ) {
29
+ return ControlFlowState . Reachable ;
30
+ }
31
+ if ( s1 === ControlFlowState . ReportedUnreachable && s2 === ControlFlowState . ReportedUnreachable ) {
32
+ return ControlFlowState . ReportedUnreachable ;
33
+ }
34
+ return ControlFlowState . Unreachable ;
35
+ }
36
+
37
+ function verifyReachable ( n : Node ) : void {
38
+ if ( currentState === ControlFlowState . Unreachable ) {
39
+ error ( n , Diagnostics . Unreachable_code_detected ) ;
40
+ currentState = ControlFlowState . ReportedUnreachable ;
41
+ }
42
+ }
43
+
44
+ // label name -> index in 'labelStack'
45
+ var labels : Map < number > = { } ;
46
+ // CF state at all seen labels
47
+ var labelStack : ControlFlowState [ ] = [ ] ;
48
+ // indices of implicit labels in 'labelStack'
49
+ var implicitLabels : number [ ] = [ ] ;
50
+
51
+ function pushNamedLabel ( name : Identifier ) : boolean {
52
+ if ( hasProperty ( labels , name . text ) ) {
53
+ return false ;
54
+ }
55
+ var newLen = labelStack . push ( ControlFlowState . Uninitialized ) ;
56
+ labels [ name . text ] = newLen - 1 ;
57
+ return true ;
58
+ }
59
+
60
+ function pushImplicitLabel ( ) : number {
61
+ var newLen = labelStack . push ( ControlFlowState . Uninitialized ) ;
62
+ implicitLabels . push ( newLen - 1 ) ;
63
+ return newLen - 1 ;
64
+ }
65
+
66
+ function setFinalStateAtLabel ( mergedStates : ControlFlowState , outerState : ControlFlowState , name : Identifier ) : void {
67
+ if ( mergedStates === ControlFlowState . Uninitialized ) {
68
+ if ( name ) {
69
+ error ( name , Diagnostics . Unused_label ) ;
70
+ }
71
+ setState ( outerState ) ;
72
+ }
73
+ else {
74
+ setState ( or ( mergedStates , outerState ) ) ;
75
+ }
76
+ }
77
+
78
+ function popNamedLabel ( name : Identifier , outerState : ControlFlowState ) : void {
79
+ Debug . assert ( hasProperty ( labels , name . text ) ) ;
80
+ var index = labels [ name . text ] ;
81
+ Debug . assert ( labelStack . length === index + 1 ) ;
82
+ labels [ name . text ] = undefined ;
83
+ var mergedStates = labelStack . pop ( ) ;
84
+ setFinalStateAtLabel ( mergedStates , outerState , name ) ;
85
+ }
86
+
87
+ function popImplicitLabel ( index : number , outerState : ControlFlowState ) : void {
88
+ Debug . assert ( labelStack . length === index + 1 ) ;
89
+ var i = implicitLabels . pop ( ) ;
90
+ Debug . assert ( index === i ) ;
91
+ var mergedStates = labelStack . pop ( ) ;
92
+ setFinalStateAtLabel ( mergedStates , outerState , /*name*/ undefined ) ;
93
+ }
94
+
95
+ function gotoLabel ( label : Identifier , outerState : ControlFlowState ) : void {
96
+ var stateIndex : number ;
97
+ if ( label ) {
98
+ if ( ! hasProperty ( labels , label . text ) ) {
99
+ // reference to non-existing label
100
+ return ;
101
+ }
102
+ stateIndex = labels [ label . text ] ;
103
+ }
104
+ else {
105
+ if ( implicitLabels . length === 0 ) {
106
+ // non-labeled break\continue being used outside loops
107
+ return ;
108
+ }
109
+
110
+ stateIndex = implicitLabels [ implicitLabels . length - 1 ] ;
111
+ }
112
+ var stateAtLabel = labelStack [ stateIndex ] ;
113
+ labelStack [ stateIndex ] = stateAtLabel === ControlFlowState . Uninitialized ? outerState : or ( outerState , stateAtLabel ) ;
114
+ }
115
+
116
+ function checkWhileStatement ( n : WhileStatement ) : void {
117
+ verifyReachable ( n ) ;
118
+
119
+ var preWhileState : ControlFlowState = n . expression . kind === SyntaxKind . FalseKeyword ? ControlFlowState . Unreachable : currentState ;
120
+ var postWhileState : ControlFlowState = n . expression . kind === SyntaxKind . TrueKeyword ? ControlFlowState . Unreachable : currentState ;
121
+
122
+ setState ( preWhileState ) ;
123
+
124
+ var index = pushImplicitLabel ( ) ;
125
+ check ( n . statement ) ;
126
+ popImplicitLabel ( index , postWhileState ) ;
127
+ }
128
+
129
+ function checkDoStatement ( n : DoStatement ) : void {
130
+ verifyReachable ( n ) ;
131
+ var preDoState = currentState ;
132
+
133
+ var index = pushImplicitLabel ( ) ;
134
+ check ( n . statement ) ;
135
+
136
+ var postDoState = n . expression . kind === SyntaxKind . TrueKeyword ? ControlFlowState . Unreachable : preDoState ;
137
+ popImplicitLabel ( index , postDoState ) ;
138
+ }
139
+
140
+ function checkForStatement ( n : ForStatement ) : void {
141
+ verifyReachable ( n ) ;
142
+
143
+ var preForState = currentState ;
144
+ var index = pushImplicitLabel ( ) ;
145
+ check ( n . statement ) ;
146
+ var postForState = n . declarations || n . initializer || n . condition || n . iterator ? preForState : ControlFlowState . Unreachable ;
147
+ popImplicitLabel ( index , postForState ) ;
148
+ }
149
+
150
+ function checkForInStatement ( n : ForInStatement ) : void {
151
+ verifyReachable ( n ) ;
152
+ var preForInState = currentState ;
153
+ var index = pushImplicitLabel ( ) ;
154
+ check ( n . statement ) ;
155
+ popImplicitLabel ( index , preForInState ) ;
156
+ }
157
+
158
+ function checkBlock ( n : Block ) : void {
159
+ forEach ( n . statements , check ) ;
160
+ }
161
+
162
+ function checkIfStatement ( n : IfStatement ) : void {
163
+ var ifTrueState : ControlFlowState = n . expression . kind === SyntaxKind . FalseKeyword ? ControlFlowState . Unreachable : currentState ;
164
+ var ifFalseState : ControlFlowState = n . expression . kind === SyntaxKind . TrueKeyword ? ControlFlowState . Unreachable : currentState ;
165
+
166
+ setState ( ifTrueState ) ;
167
+ check ( n . thenStatement ) ;
168
+ ifTrueState = currentState ;
169
+
170
+ setState ( ifFalseState ) ;
171
+ check ( n . elseStatement ) ;
172
+
173
+ currentState = or ( currentState , ifTrueState ) ;
174
+ }
175
+
176
+ function checkReturnOrThrow ( n : Node ) : void {
177
+ verifyReachable ( n ) ;
178
+ setState ( ControlFlowState . Unreachable ) ;
179
+ }
180
+
181
+ function checkBreakOrContinueStatement ( n : BreakOrContinueStatement ) : void {
182
+ verifyReachable ( n ) ;
183
+ if ( n . kind === SyntaxKind . BreakStatement ) {
184
+ gotoLabel ( n . label , currentState ) ;
185
+ }
186
+ else {
187
+ gotoLabel ( n . label , ControlFlowState . Unreachable ) ; // touch label so it will be marked a used
188
+ }
189
+ setState ( ControlFlowState . Unreachable ) ;
190
+ }
191
+
192
+ function checkTryStatement ( n : TryStatement ) : void {
193
+ verifyReachable ( n ) ;
194
+
195
+ // catch\finally blocks has the same reachability as try block
196
+ var startState = currentState ;
197
+ check ( n . tryBlock ) ;
198
+ var postTryState = currentState ;
199
+
200
+ setState ( startState ) ;
201
+ check ( n . catchBlock ) ;
202
+ var postCatchState = currentState ;
203
+
204
+ setState ( startState ) ;
205
+ check ( n . finallyBlock ) ;
206
+ setState ( or ( postTryState , postCatchState ) ) ;
207
+ }
208
+
209
+ function checkSwitchStatement ( n : SwitchStatement ) : void {
210
+ verifyReachable ( n ) ;
211
+ var startState = currentState ;
212
+ var hasDefault = false ;
213
+
214
+ var index = pushImplicitLabel ( ) ;
215
+
216
+ forEach ( n . clauses , ( c : CaseOrDefaultClause ) => {
217
+ hasDefault = hasDefault || c . kind === SyntaxKind . DefaultClause ;
218
+ setState ( startState ) ;
219
+ forEach ( c . statements , check ) ;
220
+ if ( c . statements . length && currentState === ControlFlowState . Reachable ) {
221
+ error ( c . expression , Diagnostics . Fallthrough_case_in_switch ) ;
222
+ }
223
+ } ) ;
224
+
225
+ // post switch state is unreachable if switch is exaustive (has a default case ) and does not have fallthrough from the last case
226
+ var postSwitchState = hasDefault && currentState !== ControlFlowState . Reachable ? ControlFlowState . Unreachable : startState ;
227
+
228
+ popImplicitLabel ( index , postSwitchState ) ;
229
+ }
230
+
231
+ function checkLabelledStatement ( n : LabeledStatement ) : void {
232
+ verifyReachable ( n ) ;
233
+ var ok = pushNamedLabel ( n . label ) ;
234
+ check ( n . statement ) ;
235
+ if ( ok ) {
236
+ popNamedLabel ( n . label , currentState ) ;
237
+ }
238
+ }
239
+
240
+ function checkWithStatement ( n : WithStatement ) : void {
241
+ verifyReachable ( n ) ;
242
+ check ( n . statement ) ;
243
+ }
244
+
245
+ // current assumption: only statements affect CF
246
+ function check ( n : Node ) : void {
247
+ if ( ! n || currentState === ControlFlowState . ReportedUnreachable ) {
248
+ return ;
249
+ }
250
+ switch ( n . kind ) {
251
+ case SyntaxKind . WhileStatement :
252
+ checkWhileStatement ( < WhileStatement > n ) ;
253
+ break ;
254
+ case SyntaxKind . SourceFile :
255
+ checkBlock ( < SourceFile > n ) ;
256
+ break ;
257
+ case SyntaxKind . Block :
258
+ case SyntaxKind . TryBlock :
259
+ case SyntaxKind . CatchBlock :
260
+ case SyntaxKind . FinallyBlock :
261
+ case SyntaxKind . ModuleBlock :
262
+ case SyntaxKind . FunctionBlock :
263
+ checkBlock ( < Block > n ) ;
264
+ break ;
265
+ case SyntaxKind . IfStatement :
266
+ checkIfStatement ( < IfStatement > n ) ;
267
+ break ;
268
+ case SyntaxKind . ReturnStatement :
269
+ case SyntaxKind . ThrowStatement :
270
+ checkReturnOrThrow ( n ) ;
271
+ break ;
272
+ case SyntaxKind . BreakStatement :
273
+ case SyntaxKind . ContinueStatement :
274
+ checkBreakOrContinueStatement ( < BreakOrContinueStatement > n ) ;
275
+ break ;
276
+ case SyntaxKind . VariableStatement :
277
+ case SyntaxKind . EmptyStatement :
278
+ case SyntaxKind . ExpressionStatement :
279
+ case SyntaxKind . DebuggerStatement :
280
+ verifyReachable ( n ) ;
281
+ break ;
282
+ case SyntaxKind . DoStatement :
283
+ checkDoStatement ( < DoStatement > n ) ;
284
+ break ;
285
+ case SyntaxKind . ForInStatement :
286
+ checkForInStatement ( < ForInStatement > n ) ;
287
+ break ;
288
+ case SyntaxKind . ForStatement :
289
+ checkForStatement ( < ForStatement > n ) ;
290
+ break ;
291
+ case SyntaxKind . LabeledStatement :
292
+ checkLabelledStatement ( < LabeledStatement > n ) ;
293
+ break ;
294
+ case SyntaxKind . SwitchStatement :
295
+ checkSwitchStatement ( < SwitchStatement > n ) ;
296
+ break ;
297
+ case SyntaxKind . TryStatement :
298
+ checkTryStatement ( < TryStatement > n ) ;
299
+ break ;
300
+ case SyntaxKind . WithStatement :
301
+ checkWithStatement ( < WithStatement > n ) ;
302
+ break ;
303
+ }
304
+ }
305
+
306
+ check ( decl ) ;
307
+ return currentState ;
308
+ }
309
+
310
+ const enum ControlFlowState {
311
+ Uninitialized = 0 ,
312
+ Reachable = 1 ,
313
+ Unreachable = 2 ,
314
+ ReportedUnreachable = 3 ,
315
+ }
316
+ }
0 commit comments