2 * Find out in what ways control flow can exit a statement block.
4 * Copyright: Copyright (C) 1999-2024 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
14 import core
.stdc
.stdio
;
16 import dmd
.arraytypes
;
20 import dmd
.declaration
;
22 import dmd
.expression
;
26 import dmd
.identifier
;
33 * BE stands for BlockExit.
35 * It indicates if a statement does transfer control to another block.
36 * A block is a sequence of statements enclosed in { }
49 any
= (fallthru | throw_ | return_ | goto_ | halt
),
53 /*********************************************
54 * Determine mask of ways that a statement can exit.
56 * Only valid after semantic analysis.
58 * s = statement to check for block exit status
59 * func = function that statement s is in
60 * eSink = generate an error if it throws
64 int blockExit(Statement s
, FuncDeclaration func
, ErrorSink eSink
)
68 void visitDefaultCase(Statement s
)
70 printf("Statement::blockExit(%p)\n", s
);
71 printf("%s\n", s
.toChars());
75 void visitError(ErrorStatement s
)
80 void visitExp(ExpStatement s
)
85 if (s
.exp
.op
== EXP
.halt
)
90 if (AssertExp a
= s
.exp
.isAssertExp())
92 if (a
.e1
.toBool().hasValue(false)) // if it's an assert(0)
98 if (s
.exp
.type
&& s
.exp
.type
.toBasetype().isTypeNoreturn())
101 result |
= canThrow(s
.exp
, func
, eSink
);
105 void visitDtorExp(DtorExpStatement s
)
110 void visitMixin(MixinStatement s
)
112 assert(global
.errors
);
113 result
= BE
.fallthru
;
116 void visitCompound(CompoundStatement cs
)
118 //printf("CompoundStatement.blockExit(%p) %d result = x%X\n", cs, cs.statements.length, result);
119 result
= BE
.fallthru
;
120 Statement slast
= null;
121 foreach (s
; *cs
.statements
)
125 //printf("result = x%x\n", result);
126 //printf("s: %s\n", s.toChars());
127 if (result
& BE
.fallthru
&& slast
)
129 slast
= slast
.last();
130 if (slast
&& (slast
.isCaseStatement() || slast
.isDefaultStatement()) && (s
.isCaseStatement() || s
.isDefaultStatement()))
132 // Allow if last case/default was empty
133 CaseStatement sc
= slast
.isCaseStatement();
134 DefaultStatement sd
= slast
.isDefaultStatement();
135 auto sl
= (sc ? sc
.statement
: (sd ? sd
.statement
: null));
137 if (sl
&& (!sl
.hasCode() || sl
.isErrorStatement()))
140 else if (func
.getModule().filetype
!= FileType
.c
)
142 const(char)* gototype
= s
.isCaseStatement() ?
"case" : "default";
143 // @@@DEPRECATED_2.110@@@ https://issues.dlang.org/show_bug.cgi?id=22999
144 // Deprecated in 2.100
145 // Make an error in 2.110
146 if (sl
&& sl
.isCaseStatement())
147 global
.errorSink
.deprecation(s
.loc
, "switch case fallthrough - use 'goto %s;' if intended", gototype
);
149 global
.errorSink
.error(s
.loc
, "switch case fallthrough - use 'goto %s;' if intended", gototype
);
154 if ((result
& BE
.fallthru
) || s
.comeFrom())
156 result
&= ~BE
.fallthru
;
157 result |
= blockExit(s
, func
, eSink
);
164 void visitUnrolledLoop(UnrolledLoopStatement uls
)
166 result
= BE
.fallthru
;
167 foreach (s
; *uls
.statements
)
171 int r
= blockExit(s
, func
, eSink
);
172 result |
= r
& ~(BE
.break_ | BE
.continue_ | BE
.fallthru
);
173 if ((r
& (BE
.fallthru | BE
.continue_ | BE
.break_
)) == 0)
174 result
&= ~BE
.fallthru
;
179 void visitScope(ScopeStatement s
)
181 //printf("ScopeStatement::blockExit(%p)\n", s.statement);
182 result
= blockExit(s
.statement
, func
, eSink
);
185 void visitWhile(WhileStatement s
)
187 assert(global
.errors
);
188 result
= BE
.fallthru
;
191 void visitDo(DoStatement s
)
195 result
= blockExit(s
._body
, func
, eSink
);
196 if (result
== BE
.break_
)
198 result
= BE
.fallthru
;
201 if (result
& BE
.continue_
)
202 result |
= BE
.fallthru
;
205 result
= BE
.fallthru
;
206 if (result
& BE
.fallthru
)
208 result |
= canThrow(s
.condition
, func
, eSink
);
210 if (!(result
& BE
.break_
) && s
.condition
.toBool().hasValue(true))
211 result
&= ~BE
.fallthru
;
213 result
&= ~(BE
.break_ | BE
.continue_
);
216 void visitFor(ForStatement s
)
218 result
= BE
.fallthru
;
221 result
= blockExit(s
._init
, func
, eSink
);
222 if (!(result
& BE
.fallthru
))
227 result |
= canThrow(s
.condition
, func
, eSink
);
229 const opt
= s
.condition
.toBool();
230 if (opt
.hasValue(true))
231 result
&= ~BE
.fallthru
;
232 else if (opt
.hasValue(false))
236 result
&= ~BE
.fallthru
; // the body must do the exiting
239 int r
= blockExit(s
._body
, func
, eSink
);
240 if (r
& (BE
.break_ | BE
.goto_
))
241 result |
= BE
.fallthru
;
242 result |
= r
& ~(BE
.fallthru | BE
.break_ | BE
.continue_
);
245 result |
= canThrow(s
.increment
, func
, eSink
);
248 void visitForeach(ForeachStatement s
)
250 result
= BE
.fallthru
;
251 result |
= canThrow(s
.aggr
, func
, eSink
);
254 result |
= blockExit(s
._body
, func
, eSink
) & ~(BE
.break_ | BE
.continue_
);
257 void visitForeachRange(ForeachRangeStatement s
)
259 assert(global
.errors
);
260 result
= BE
.fallthru
;
263 void visitIf(IfStatement s
)
265 //printf("IfStatement::blockExit(%p)\n", s);
267 result |
= canThrow(s
.condition
, func
, eSink
);
269 const opt
= s
.condition
.toBool();
270 if (opt
.hasValue(true))
272 result |
= blockExit(s
.ifbody
, func
, eSink
);
274 else if (opt
.hasValue(false))
276 result |
= blockExit(s
.elsebody
, func
, eSink
);
280 result |
= blockExit(s
.ifbody
, func
, eSink
);
281 result |
= blockExit(s
.elsebody
, func
, eSink
);
283 //printf("IfStatement::blockExit(%p) = x%x\n", s, result);
286 void visitConditional(ConditionalStatement s
)
288 result
= blockExit(s
.ifbody
, func
, eSink
);
290 result |
= blockExit(s
.elsebody
, func
, eSink
);
293 void visitPragma(PragmaStatement s
)
295 result
= BE
.fallthru
;
298 void visitStaticAssert(StaticAssertStatement s
)
300 result
= BE
.fallthru
;
303 void visitSwitch(SwitchStatement s
)
306 result |
= canThrow(s
.condition
, func
, eSink
);
310 result |
= blockExit(s
._body
, func
, eSink
);
311 if (result
& BE
.break_
)
313 result |
= BE
.fallthru
;
314 result
&= ~BE
.break_
;
318 result |
= BE
.fallthru
;
321 void visitCase(CaseStatement s
)
323 result
= blockExit(s
.statement
, func
, eSink
);
326 void visitDefault(DefaultStatement s
)
328 result
= blockExit(s
.statement
, func
, eSink
);
331 void visitGotoDefault(GotoDefaultStatement s
)
336 void visitGotoCase(GotoCaseStatement s
)
341 void visitSwitchError(SwitchErrorStatement s
)
343 // Switch errors are non-recoverable
347 void visitReturn(ReturnStatement s
)
351 result |
= canThrow(s
.exp
, func
, eSink
);
354 void visitBreak(BreakStatement s
)
356 //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_);
357 result
= s
.ident ? BE
.goto_
: BE
.break_
;
360 void visitContinue(ContinueStatement s
)
362 result
= s
.ident ? BE
.continue_ | BE
.goto_
: BE
.continue_
;
365 void visitSynchronized(SynchronizedStatement s
)
367 result
= blockExit(s
._body
, func
, eSink
);
370 void visitWith(WithStatement s
)
373 result |
= canThrow(s
.exp
, func
, eSink
);
374 result |
= blockExit(s
._body
, func
, eSink
);
377 void visitTryCatch(TryCatchStatement s
)
380 result
= blockExit(s
._body
, func
, null);
383 foreach (c
; *s
.catches
)
385 if (c
.type
== Type
.terror
)
388 int cresult
= blockExit(c
.handler
, func
, eSink
);
390 /* If we're catching Object, then there is no throwing
392 Identifier id
= c
.type
.toBasetype().isClassHandle().ident
;
393 if (c
.internalCatch
&& (cresult
& BE
.fallthru
))
395 // https://issues.dlang.org/show_bug.cgi?id=11542
396 // leave blockExit flags of the body
397 cresult
&= ~BE
.fallthru
;
399 else if (id
== Id
.Object || id
== Id
.Throwable
)
401 result
&= ~(BE
.throw_ | BE
.errthrow
);
403 else if (id
== Id
.Exception
)
405 result
&= ~BE
.throw_
;
407 catchresult |
= cresult
;
409 if (eSink
&& (result
& BE
.throw_
))
411 // now explain why this is nothrow
412 blockExit(s
._body
, func
, eSink
);
414 result |
= catchresult
;
417 void visitTryFinally(TryFinallyStatement s
)
419 result
= BE
.fallthru
;
421 result
= blockExit(s
._body
, func
, null);
423 // check finally body as well, it may throw (bug #4082)
424 int finalresult
= BE
.fallthru
;
426 finalresult
= blockExit(s
.finalbody
, func
, null);
428 // If either body or finalbody halts
429 if (result
== BE
.halt
)
430 finalresult
= BE
.none
;
431 if (finalresult
== BE
.halt
)
436 // now explain why this is nothrow
437 if (s
._body
&& (result
& BE
.throw_
))
438 blockExit(s
._body
, func
, eSink
);
439 if (s
.finalbody
&& (finalresult
& BE
.throw_
))
440 blockExit(s
.finalbody
, func
, eSink
);
443 if (!(finalresult
& BE
.fallthru
))
444 result
&= ~BE
.fallthru
;
445 result |
= finalresult
& ~BE
.fallthru
;
448 void visitScopeGuard(ScopeGuardStatement s
)
450 // At this point, this statement is just an empty placeholder
451 result
= BE
.fallthru
;
454 void visitThrow(ThrowStatement s
)
458 // https://issues.dlang.org/show_bug.cgi?id=8675
459 // Allow throwing 'Throwable' object even if eSink.
460 result
= BE
.fallthru
;
464 result
= checkThrow(s
.loc
, s
.exp
, func
, eSink
);
467 void visitGoto(GotoStatement s
)
469 //printf("GotoStatement::blockExit(%p)\n", s);
473 void visitLabel(LabelStatement s
)
475 //printf("LabelStatement::blockExit(%p)\n", s);
476 result
= blockExit(s
.statement
, func
, eSink
);
478 result |
= BE
.fallthru
;
481 void visitCompoundAsm(CompoundAsmStatement s
)
484 result
= BE
.fallthru | BE
.return_ | BE
.goto_ | BE
.halt
;
485 if (!(s
.stc & STC
.nothrow_
))
488 func
.setThrow(s
.loc
, "`asm` statement is assumed to throw - mark it with `nothrow` if it does not");
490 eSink
.error(s
.loc
, "`asm` statement is assumed to throw - mark it with `nothrow` if it does not"); // TODO
496 void visitImport(ImportStatement s
)
498 result
= BE
.fallthru
;
503 mixin VisitStatement
!void visit
;
504 visit
.VisitStatement(s
);
509 + Checks whether `throw <exp>` throws an `Exception` or an `Error`
510 + and raises an error if this violates `nothrow`.
513 + loc = location of the `throw`
514 + exp = expression yielding the throwable
515 + eSink = if !null then inside of a `nothrow` scope
516 + func = function containing the `throw`
518 + Returns: `BE.[err]throw` depending on the type of `exp`
520 BE
checkThrow(ref const Loc loc
, Expression exp
, FuncDeclaration func
, ErrorSink eSink
)
522 Type t
= exp
.type
.toBasetype();
523 ClassDeclaration cd
= t
.isClassHandle();
526 if (cd
.isErrorException())
531 eSink
.error(loc
, "`%s` is thrown but not caught", exp
.type
.toChars());
533 func
.setThrow(loc
, "`%s` is thrown but not caught", exp
.type
);