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