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