1 /**
2 * Find out in what ways control flow can exit a statement block.
3 *
4 * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
5 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
6 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/blockexit.d, _blockexit.d)
8 * Documentation: https://dlang.org/phobos/dmd_blockexit.html
9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/blockexit.d
10 */
11
12 module dmd.blockexit;
13
14 import core.stdc.stdio;
15
16 import dmd.arraytypes;
17 import dmd.astenums;
18 import dmd.canthrow;
19 import dmd.dclass;
20 import dmd.declaration;
21 import dmd.expression;
22 import dmd.func;
23 import dmd.globals;
24 import dmd.id;
25 import dmd.identifier;
26 import dmd.mtype;
27 import dmd.statement;
28 import dmd.tokens;
29 import dmd.visitor;
30
31 /**
32 * BE stands for BlockExit.
33 *
34 * It indicates if a statement does transfer control to another block.
35 * A block is a sequence of statements enclosed in { }
36 */
37 enum BE : int
38 {
39 none = 0,
40 fallthru = 1,
41 throw_ = 2,
42 return_ = 4,
43 goto_ = 8,
44 halt = 0x10,
45 break_ = 0x20,
46 continue_ = 0x40,
47 errthrow = 0x80,
48 any = (fallthru | throw_ | return_ | goto_ | halt),
49 }
50
51
52 /*********************************************
53 * Determine mask of ways that a statement can exit.
54 *
55 * Only valid after semantic analysis.
56 * Params:
57 * s = statement to check for block exit status
58 * func = function that statement s is in
59 * mustNotThrow = generate an error if it throws
60 * Returns:
61 * BE.xxxx
62 */
blockExit(Statement s,FuncDeclaration func,bool mustNotThrow)63 int blockExit(Statement s, FuncDeclaration func, bool mustNotThrow)
64 {
65 extern (C++) final class BlockExit : Visitor
66 {
67 alias visit = Visitor.visit;
68 public:
69 FuncDeclaration func;
70 bool mustNotThrow;
71 int result;
72
73 extern (D) this(FuncDeclaration func, bool mustNotThrow)
74 {
75 this.func = func;
76 this.mustNotThrow = mustNotThrow;
77 result = BE.none;
78 }
79
80 override void visit(Statement s)
81 {
82 printf("Statement::blockExit(%p)\n", s);
83 printf("%s\n", s.toChars());
84 assert(0);
85 }
86
87 override void visit(ErrorStatement s)
88 {
89 result = BE.none;
90 }
91
92 override void visit(ExpStatement s)
93 {
94 result = BE.fallthru;
95 if (s.exp)
96 {
97 if (s.exp.op == EXP.halt)
98 {
99 result = BE.halt;
100 return;
101 }
102 if (AssertExp a = s.exp.isAssertExp())
103 {
104 if (a.e1.toBool().hasValue(false)) // if it's an assert(0)
105 {
106 result = BE.halt;
107 return;
108 }
109 }
110 if (s.exp.type && s.exp.type.toBasetype().isTypeNoreturn())
111 result = BE.halt;
112
113 result |= canThrow(s.exp, func, mustNotThrow);
114 }
115 }
116
117 override void visit(CompileStatement s)
118 {
119 assert(global.errors);
120 result = BE.fallthru;
121 }
122
123 override void visit(CompoundStatement cs)
124 {
125 //printf("CompoundStatement.blockExit(%p) %d result = x%X\n", cs, cs.statements.dim, result);
126 result = BE.fallthru;
127 Statement slast = null;
128 foreach (s; *cs.statements)
129 {
130 if (s)
131 {
132 //printf("result = x%x\n", result);
133 //printf("s: %s\n", s.toChars());
134 if (result & BE.fallthru && slast)
135 {
136 slast = slast.last();
137 if (slast && (slast.isCaseStatement() || slast.isDefaultStatement()) && (s.isCaseStatement() || s.isDefaultStatement()))
138 {
139 // Allow if last case/default was empty
140 CaseStatement sc = slast.isCaseStatement();
141 DefaultStatement sd = slast.isDefaultStatement();
142 auto sl = (sc ? sc.statement : (sd ? sd.statement : null));
143
144 if (sl && (!sl.hasCode() || sl.isErrorStatement()))
145 {
146 }
147 else if (func.getModule().filetype != FileType.c)
148 {
149 const(char)* gototype = s.isCaseStatement() ? "case" : "default";
150 // @@@DEPRECATED_2.110@@@ https://issues.dlang.org/show_bug.cgi?id=22999
151 // Deprecated in 2.100
152 // Make an error in 2.110
153 if (sl && sl.isCaseStatement())
154 s.deprecation("switch case fallthrough - use 'goto %s;' if intended", gototype);
155 else
156 s.error("switch case fallthrough - use 'goto %s;' if intended", gototype);
157 }
158 }
159 }
160
161 if (!(result & BE.fallthru) && !s.comeFrom())
162 {
163 if (blockExit(s, func, mustNotThrow) != BE.halt && s.hasCode() &&
164 s.loc != Loc.initial) // don't emit warning for generated code
165 s.warning("statement is not reachable");
166 }
167 else
168 {
169 result &= ~BE.fallthru;
170 result |= blockExit(s, func, mustNotThrow);
171 }
172 slast = s;
173 }
174 }
175 }
176
177 override void visit(UnrolledLoopStatement uls)
178 {
179 result = BE.fallthru;
180 foreach (s; *uls.statements)
181 {
182 if (s)
183 {
184 int r = blockExit(s, func, mustNotThrow);
185 result |= r & ~(BE.break_ | BE.continue_ | BE.fallthru);
186 if ((r & (BE.fallthru | BE.continue_ | BE.break_)) == 0)
187 result &= ~BE.fallthru;
188 }
189 }
190 }
191
192 override void visit(ScopeStatement s)
193 {
194 //printf("ScopeStatement::blockExit(%p)\n", s.statement);
195 result = blockExit(s.statement, func, mustNotThrow);
196 }
197
198 override void visit(WhileStatement s)
199 {
200 assert(global.errors);
201 result = BE.fallthru;
202 }
203
204 override void visit(DoStatement s)
205 {
206 if (s._body)
207 {
208 result = blockExit(s._body, func, mustNotThrow);
209 if (result == BE.break_)
210 {
211 result = BE.fallthru;
212 return;
213 }
214 if (result & BE.continue_)
215 result |= BE.fallthru;
216 }
217 else
218 result = BE.fallthru;
219 if (result & BE.fallthru)
220 {
221 result |= canThrow(s.condition, func, mustNotThrow);
222
223 if (!(result & BE.break_) && s.condition.toBool().hasValue(true))
224 result &= ~BE.fallthru;
225 }
226 result &= ~(BE.break_ | BE.continue_);
227 }
228
229 override void visit(ForStatement s)
230 {
231 result = BE.fallthru;
232 if (s._init)
233 {
234 result = blockExit(s._init, func, mustNotThrow);
235 if (!(result & BE.fallthru))
236 return;
237 }
238 if (s.condition)
239 {
240 result |= canThrow(s.condition, func, mustNotThrow);
241
242 const opt = s.condition.toBool();
243 if (opt.hasValue(true))
244 result &= ~BE.fallthru;
245 else if (opt.hasValue(false))
246 return;
247 }
248 else
249 result &= ~BE.fallthru; // the body must do the exiting
250 if (s._body)
251 {
252 int r = blockExit(s._body, func, mustNotThrow);
253 if (r & (BE.break_ | BE.goto_))
254 result |= BE.fallthru;
255 result |= r & ~(BE.fallthru | BE.break_ | BE.continue_);
256 }
257 if (s.increment)
258 result |= canThrow(s.increment, func, mustNotThrow);
259 }
260
261 override void visit(ForeachStatement s)
262 {
263 result = BE.fallthru;
264 result |= canThrow(s.aggr, func, mustNotThrow);
265
266 if (s._body)
267 result |= blockExit(s._body, func, mustNotThrow) & ~(BE.break_ | BE.continue_);
268 }
269
270 override void visit(ForeachRangeStatement s)
271 {
272 assert(global.errors);
273 result = BE.fallthru;
274 }
275
276 override void visit(IfStatement s)
277 {
278 //printf("IfStatement::blockExit(%p)\n", s);
279 result = BE.none;
280 result |= canThrow(s.condition, func, mustNotThrow);
281
282 const opt = s.condition.toBool();
283 if (opt.hasValue(true))
284 {
285 result |= blockExit(s.ifbody, func, mustNotThrow);
286 }
287 else if (opt.hasValue(false))
288 {
289 result |= blockExit(s.elsebody, func, mustNotThrow);
290 }
291 else
292 {
293 result |= blockExit(s.ifbody, func, mustNotThrow);
294 result |= blockExit(s.elsebody, func, mustNotThrow);
295 }
296 //printf("IfStatement::blockExit(%p) = x%x\n", s, result);
297 }
298
299 override void visit(ConditionalStatement s)
300 {
301 result = blockExit(s.ifbody, func, mustNotThrow);
302 if (s.elsebody)
303 result |= blockExit(s.elsebody, func, mustNotThrow);
304 }
305
306 override void visit(PragmaStatement s)
307 {
308 result = BE.fallthru;
309 }
310
311 override void visit(StaticAssertStatement s)
312 {
313 result = BE.fallthru;
314 }
315
316 override void visit(SwitchStatement s)
317 {
318 result = BE.none;
319 result |= canThrow(s.condition, func, mustNotThrow);
320
321 if (s._body)
322 {
323 result |= blockExit(s._body, func, mustNotThrow);
324 if (result & BE.break_)
325 {
326 result |= BE.fallthru;
327 result &= ~BE.break_;
328 }
329 }
330 else
331 result |= BE.fallthru;
332 }
333
334 override void visit(CaseStatement s)
335 {
336 result = blockExit(s.statement, func, mustNotThrow);
337 }
338
339 override void visit(DefaultStatement s)
340 {
341 result = blockExit(s.statement, func, mustNotThrow);
342 }
343
344 override void visit(GotoDefaultStatement s)
345 {
346 result = BE.goto_;
347 }
348
349 override void visit(GotoCaseStatement s)
350 {
351 result = BE.goto_;
352 }
353
354 override void visit(SwitchErrorStatement s)
355 {
356 // Switch errors are non-recoverable
357 result = BE.halt;
358 }
359
360 override void visit(ReturnStatement s)
361 {
362 result = BE.return_;
363 if (s.exp)
364 result |= canThrow(s.exp, func, mustNotThrow);
365 }
366
367 override void visit(BreakStatement s)
368 {
369 //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_);
370 result = s.ident ? BE.goto_ : BE.break_;
371 }
372
373 override void visit(ContinueStatement s)
374 {
375 result = s.ident ? BE.continue_ | BE.goto_ : BE.continue_;
376 }
377
378 override void visit(SynchronizedStatement s)
379 {
380 result = blockExit(s._body, func, mustNotThrow);
381 }
382
383 override void visit(WithStatement s)
384 {
385 result = BE.none;
386 result |= canThrow(s.exp, func, mustNotThrow);
387 result |= blockExit(s._body, func, mustNotThrow);
388 }
389
390 override void visit(TryCatchStatement s)
391 {
392 assert(s._body);
393 result = blockExit(s._body, func, false);
394
395 int catchresult = 0;
396 foreach (c; *s.catches)
397 {
398 if (c.type == Type.terror)
399 continue;
400
401 int cresult = blockExit(c.handler, func, mustNotThrow);
402
403 /* If we're catching Object, then there is no throwing
404 */
405 Identifier id = c.type.toBasetype().isClassHandle().ident;
406 if (c.internalCatch && (cresult & BE.fallthru))
407 {
408 // https://issues.dlang.org/show_bug.cgi?id=11542
409 // leave blockExit flags of the body
410 cresult &= ~BE.fallthru;
411 }
412 else if (id == Id.Object || id == Id.Throwable)
413 {
414 result &= ~(BE.throw_ | BE.errthrow);
415 }
416 else if (id == Id.Exception)
417 {
418 result &= ~BE.throw_;
419 }
420 catchresult |= cresult;
421 }
422 if (mustNotThrow && (result & BE.throw_))
423 {
424 // now explain why this is nothrow
425 blockExit(s._body, func, mustNotThrow);
426 }
427 result |= catchresult;
428 }
429
430 override void visit(TryFinallyStatement s)
431 {
432 result = BE.fallthru;
433 if (s._body)
434 result = blockExit(s._body, func, false);
435
436 // check finally body as well, it may throw (bug #4082)
437 int finalresult = BE.fallthru;
438 if (s.finalbody)
439 finalresult = blockExit(s.finalbody, func, false);
440
441 // If either body or finalbody halts
442 if (result == BE.halt)
443 finalresult = BE.none;
444 if (finalresult == BE.halt)
445 result = BE.none;
446
447 if (mustNotThrow)
448 {
449 // now explain why this is nothrow
450 if (s._body && (result & BE.throw_))
451 blockExit(s._body, func, mustNotThrow);
452 if (s.finalbody && (finalresult & BE.throw_))
453 blockExit(s.finalbody, func, mustNotThrow);
454 }
455
456 version (none)
457 {
458 // https://issues.dlang.org/show_bug.cgi?id=13201
459 // Mask to prevent spurious warnings for
460 // destructor call, exit of synchronized statement, etc.
461 if (result == BE.halt && finalresult != BE.halt && s.finalbody && s.finalbody.hasCode())
462 {
463 s.finalbody.warning("statement is not reachable");
464 }
465 }
466
467 if (!(finalresult & BE.fallthru))
468 result &= ~BE.fallthru;
469 result |= finalresult & ~BE.fallthru;
470 }
471
472 override void visit(ScopeGuardStatement s)
473 {
474 // At this point, this statement is just an empty placeholder
475 result = BE.fallthru;
476 }
477
478 override void visit(ThrowStatement s)
479 {
480 if (s.internalThrow)
481 {
482 // https://issues.dlang.org/show_bug.cgi?id=8675
483 // Allow throwing 'Throwable' object even if mustNotThrow.
484 result = BE.fallthru;
485 return;
486 }
487
488 result = checkThrow(s.loc, s.exp, mustNotThrow);
489 }
490
491 override void visit(GotoStatement s)
492 {
493 //printf("GotoStatement::blockExit(%p)\n", s);
494 result = BE.goto_;
495 }
496
497 override void visit(LabelStatement s)
498 {
499 //printf("LabelStatement::blockExit(%p)\n", s);
500 result = blockExit(s.statement, func, mustNotThrow);
501 if (s.breaks)
502 result |= BE.fallthru;
503 }
504
505 override void visit(CompoundAsmStatement s)
506 {
507 // Assume the worst
508 result = BE.fallthru | BE.return_ | BE.goto_ | BE.halt;
509 if (!(s.stc & STC.nothrow_))
510 {
511 if (mustNotThrow && !(s.stc & STC.nothrow_))
512 s.error("`asm` statement is assumed to throw - mark it with `nothrow` if it does not");
513 else
514 result |= BE.throw_;
515 }
516 }
517
518 override void visit(ImportStatement s)
519 {
520 result = BE.fallthru;
521 }
522 }
523
524 if (!s)
525 return BE.fallthru;
526 scope BlockExit be = new BlockExit(func, mustNotThrow);
527 s.accept(be);
528 return be.result;
529 }
530
531 /++
532 + Checks whether `throw <exp>` throws an `Exception` or an `Error`
533 + and raises an error if this violates `nothrow`.
534 +
535 + Params:
536 + loc = location of the `throw`
537 + exp = expression yielding the throwable
538 + mustNotThrow = inside of a `nothrow` scope?
539 +
540 + Returns: `BE.[err]throw` depending on the type of `exp`
541 +/
checkThrow(ref const Loc loc,Expression exp,const bool mustNotThrow)542 BE checkThrow(ref const Loc loc, Expression exp, const bool mustNotThrow)
543 {
544 import dmd.errors : error;
545
546 Type t = exp.type.toBasetype();
547 ClassDeclaration cd = t.isClassHandle();
548 assert(cd);
549
550 if (cd == ClassDeclaration.errorException || ClassDeclaration.errorException.isBaseOf(cd, null))
551 {
552 return BE.errthrow;
553 }
554 if (mustNotThrow)
555 loc.error("`%s` is thrown but not caught", exp.type.toChars());
556
557 return BE.throw_;
558 }
559