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 int result = BE.none; 67 68 void visitDefaultCase(Statement s) 69 { 70 printf("Statement::blockExit(%p)\n", s); 71 printf("%s\n", s.toChars()); 72 assert(0); 73 } 74 75 void visitError(ErrorStatement s) 76 { 77 result = BE.none; 78 } 79 80 void visitExp(ExpStatement s) 81 { 82 result = BE.fallthru; 83 if (s.exp) 84 { 85 if (s.exp.op == EXP.halt) 86 { 87 result = BE.halt; 88 return; 89 } 90 if (AssertExp a = s.exp.isAssertExp()) 91 { 92 if (a.e1.toBool().hasValue(false)) // if it's an assert(0) 93 { 94 result = BE.halt; 95 return; 96 } 97 } 98 if (s.exp.type && s.exp.type.toBasetype().isTypeNoreturn()) 99 result = BE.halt; 100 101 result |= canThrow(s.exp, func, mustNotThrow); 102 } 103 } 104 105 void visitDtorExp(DtorExpStatement s) 106 { 107 visitExp(s); 108 } 109 110 void visitMixin(MixinStatement s) 111 { 112 assert(global.errors); 113 result = BE.fallthru; 114 } 115 116 void visitCompound(CompoundStatement cs) 117 { 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) 122 { 123 if (s) 124 { 125 //printf("result = x%x\n", result); 126 //printf("s: %s\n", s.toChars()); 127 if (result & BE.fallthru && slast) 128 { 129 slast = slast.last(); 130 if (slast && (slast.isCaseStatement() || slast.isDefaultStatement()) && (s.isCaseStatement() || s.isDefaultStatement())) 131 { 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)); 136 137 if (sl && (!sl.hasCode() || sl.isErrorStatement())) 138 { 139 } 140 else if (func.getModule().filetype != FileType.c) 141 { 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 s.deprecation("switch case fallthrough - use 'goto %s;' if intended", gototype); 148 else 149 s.error("switch case fallthrough - use 'goto %s;' if intended", gototype); 150 } 151 } 152 } 153 154 if (!(result & BE.fallthru) && !s.comeFrom()) 155 { 156 if (blockExit(s, func, mustNotThrow) != BE.halt && s.hasCode() && 157 s.loc != Loc.initial) // don't emit warning for generated code 158 s.warning("statement is not reachable"); 159 } 160 else 161 { 162 result &= ~BE.fallthru; 163 result |= blockExit(s, func, mustNotThrow); 164 } 165 slast = s; 166 } 167 } 168 } 169 170 void visitUnrolledLoop(UnrolledLoopStatement uls) 171 { 172 result = BE.fallthru; 173 foreach (s; *uls.statements) 174 { 175 if (s) 176 { 177 int r = blockExit(s, func, mustNotThrow); 178 result |= r & ~(BE.break_ | BE.continue_ | BE.fallthru); 179 if ((r & (BE.fallthru | BE.continue_ | BE.break_)) == 0) 180 result &= ~BE.fallthru; 181 } 182 } 183 } 184 185 void visitScope(ScopeStatement s) 186 { 187 //printf("ScopeStatement::blockExit(%p)\n", s.statement); 188 result = blockExit(s.statement, func, mustNotThrow); 189 } 190 191 void visitWhile(WhileStatement s) 192 { 193 assert(global.errors); 194 result = BE.fallthru; 195 } 196 197 void visitDo(DoStatement s) 198 { 199 if (s._body) 200 { 201 result = blockExit(s._body, func, mustNotThrow); 202 if (result == BE.break_) 203 { 204 result = BE.fallthru; 205 return; 206 } 207 if (result & BE.continue_) 208 result |= BE.fallthru; 209 } 210 else 211 result = BE.fallthru; 212 if (result & BE.fallthru) 213 { 214 result |= canThrow(s.condition, func, mustNotThrow); 215 216 if (!(result & BE.break_) && s.condition.toBool().hasValue(true)) 217 result &= ~BE.fallthru; 218 } 219 result &= ~(BE.break_ | BE.continue_); 220 } 221 222 void visitFor(ForStatement s) 223 { 224 result = BE.fallthru; 225 if (s._init) 226 { 227 result = blockExit(s._init, func, mustNotThrow); 228 if (!(result & BE.fallthru)) 229 return; 230 } 231 if (s.condition) 232 { 233 result |= canThrow(s.condition, func, mustNotThrow); 234 235 const opt = s.condition.toBool(); 236 if (opt.hasValue(true)) 237 result &= ~BE.fallthru; 238 else if (opt.hasValue(false)) 239 return; 240 } 241 else 242 result &= ~BE.fallthru; // the body must do the exiting 243 if (s._body) 244 { 245 int r = blockExit(s._body, func, mustNotThrow); 246 if (r & (BE.break_ | BE.goto_)) 247 result |= BE.fallthru; 248 result |= r & ~(BE.fallthru | BE.break_ | BE.continue_); 249 } 250 if (s.increment) 251 result |= canThrow(s.increment, func, mustNotThrow); 252 } 253 254 void visitForeach(ForeachStatement s) 255 { 256 result = BE.fallthru; 257 result |= canThrow(s.aggr, func, mustNotThrow); 258 259 if (s._body) 260 result |= blockExit(s._body, func, mustNotThrow) & ~(BE.break_ | BE.continue_); 261 } 262 263 void visitForeachRange(ForeachRangeStatement s) 264 { 265 assert(global.errors); 266 result = BE.fallthru; 267 } 268 269 void visitIf(IfStatement s) 270 { 271 //printf("IfStatement::blockExit(%p)\n", s); 272 result = BE.none; 273 result |= canThrow(s.condition, func, mustNotThrow); 274 275 const opt = s.condition.toBool(); 276 if (opt.hasValue(true)) 277 { 278 result |= blockExit(s.ifbody, func, mustNotThrow); 279 } 280 else if (opt.hasValue(false)) 281 { 282 result |= blockExit(s.elsebody, func, mustNotThrow); 283 } 284 else 285 { 286 result |= blockExit(s.ifbody, func, mustNotThrow); 287 result |= blockExit(s.elsebody, func, mustNotThrow); 288 } 289 //printf("IfStatement::blockExit(%p) = x%x\n", s, result); 290 } 291 292 void visitConditional(ConditionalStatement s) 293 { 294 result = blockExit(s.ifbody, func, mustNotThrow); 295 if (s.elsebody) 296 result |= blockExit(s.elsebody, func, mustNotThrow); 297 } 298 299 void visitPragma(PragmaStatement s) 300 { 301 result = BE.fallthru; 302 } 303 304 void visitStaticAssert(StaticAssertStatement s) 305 { 306 result = BE.fallthru; 307 } 308 309 void visitSwitch(SwitchStatement s) 310 { 311 result = BE.none; 312 result |= canThrow(s.condition, func, mustNotThrow); 313 314 if (s._body) 315 { 316 result |= blockExit(s._body, func, mustNotThrow); 317 if (result & BE.break_) 318 { 319 result |= BE.fallthru; 320 result &= ~BE.break_; 321 } 322 } 323 else 324 result |= BE.fallthru; 325 } 326 327 void visitCase(CaseStatement s) 328 { 329 result = blockExit(s.statement, func, mustNotThrow); 330 } 331 332 void visitDefault(DefaultStatement s) 333 { 334 result = blockExit(s.statement, func, mustNotThrow); 335 } 336 337 void visitGotoDefault(GotoDefaultStatement s) 338 { 339 result = BE.goto_; 340 } 341 342 void visitGotoCase(GotoCaseStatement s) 343 { 344 result = BE.goto_; 345 } 346 347 void visitSwitchError(SwitchErrorStatement s) 348 { 349 // Switch errors are non-recoverable 350 result = BE.halt; 351 } 352 353 void visitReturn(ReturnStatement s) 354 { 355 result = BE.return_; 356 if (s.exp) 357 result |= canThrow(s.exp, func, mustNotThrow); 358 } 359 360 void visitBreak(BreakStatement s) 361 { 362 //printf("BreakStatement::blockExit(%p) = x%x\n", s, s.ident ? BE.goto_ : BE.break_); 363 result = s.ident ? BE.goto_ : BE.break_; 364 } 365 366 void visitContinue(ContinueStatement s) 367 { 368 result = s.ident ? BE.continue_ | BE.goto_ : BE.continue_; 369 } 370 371 void visitSynchronized(SynchronizedStatement s) 372 { 373 result = blockExit(s._body, func, mustNotThrow); 374 } 375 376 void visitWith(WithStatement s) 377 { 378 result = BE.none; 379 result |= canThrow(s.exp, func, mustNotThrow); 380 result |= blockExit(s._body, func, mustNotThrow); 381 } 382 383 void visitTryCatch(TryCatchStatement s) 384 { 385 assert(s._body); 386 result = blockExit(s._body, func, false); 387 388 int catchresult = 0; 389 foreach (c; *s.catches) 390 { 391 if (c.type == Type.terror) 392 continue; 393 394 int cresult = blockExit(c.handler, func, mustNotThrow); 395 396 /* If we're catching Object, then there is no throwing 397 */ 398 Identifier id = c.type.toBasetype().isClassHandle().ident; 399 if (c.internalCatch && (cresult & BE.fallthru)) 400 { 401 // https://issues.dlang.org/show_bug.cgi?id=11542 402 // leave blockExit flags of the body 403 cresult &= ~BE.fallthru; 404 } 405 else if (id == Id.Object || id == Id.Throwable) 406 { 407 result &= ~(BE.throw_ | BE.errthrow); 408 } 409 else if (id == Id.Exception) 410 { 411 result &= ~BE.throw_; 412 } 413 catchresult |= cresult; 414 } 415 if (mustNotThrow && (result & BE.throw_)) 416 { 417 // now explain why this is nothrow 418 blockExit(s._body, func, mustNotThrow); 419 } 420 result |= catchresult; 421 } 422 423 void visitTryFinally(TryFinallyStatement s) 424 { 425 result = BE.fallthru; 426 if (s._body) 427 result = blockExit(s._body, func, false); 428 429 // check finally body as well, it may throw (bug #4082) 430 int finalresult = BE.fallthru; 431 if (s.finalbody) 432 finalresult = blockExit(s.finalbody, func, false); 433 434 // If either body or finalbody halts 435 if (result == BE.halt) 436 finalresult = BE.none; 437 if (finalresult == BE.halt) 438 result = BE.none; 439 440 if (mustNotThrow) 441 { 442 // now explain why this is nothrow 443 if (s._body && (result & BE.throw_)) 444 blockExit(s._body, func, mustNotThrow); 445 if (s.finalbody && (finalresult & BE.throw_)) 446 blockExit(s.finalbody, func, mustNotThrow); 447 } 448 449 version (none) 450 { 451 // https://issues.dlang.org/show_bug.cgi?id=13201 452 // Mask to prevent spurious warnings for 453 // destructor call, exit of synchronized statement, etc. 454 if (result == BE.halt && finalresult != BE.halt && s.finalbody && s.finalbody.hasCode()) 455 { 456 s.finalbody.warning("statement is not reachable"); 457 } 458 } 459 460 if (!(finalresult & BE.fallthru)) 461 result &= ~BE.fallthru; 462 result |= finalresult & ~BE.fallthru; 463 } 464 465 void visitScopeGuard(ScopeGuardStatement s) 466 { 467 // At this point, this statement is just an empty placeholder 468 result = BE.fallthru; 469 } 470 471 void visitThrow(ThrowStatement s) 472 { 473 if (s.internalThrow) 474 { 475 // https://issues.dlang.org/show_bug.cgi?id=8675 476 // Allow throwing 'Throwable' object even if mustNotThrow. 477 result = BE.fallthru; 478 return; 479 } 480 481 result = checkThrow(s.loc, s.exp, mustNotThrow, func); 482 } 483 484 void visitGoto(GotoStatement s) 485 { 486 //printf("GotoStatement::blockExit(%p)\n", s); 487 result = BE.goto_; 488 } 489 490 void visitLabel(LabelStatement s) 491 { 492 //printf("LabelStatement::blockExit(%p)\n", s); 493 result = blockExit(s.statement, func, mustNotThrow); 494 if (s.breaks) 495 result |= BE.fallthru; 496 } 497 498 void visitCompoundAsm(CompoundAsmStatement s) 499 { 500 // Assume the worst 501 result = BE.fallthru | BE.return_ | BE.goto_ | BE.halt; 502 if (!(s.stc & STC.nothrow_)) 503 { 504 if(func) 505 func.setThrow(s.loc, "`asm` statement is assumed to throw - mark it with `nothrow` if it does not"); 506 if (mustNotThrow) 507 s.error("`asm` statement is assumed to throw - mark it with `nothrow` if it does not"); // TODO 508 else 509 result |= BE.throw_; 510 } 511 } 512 513 void visitImport(ImportStatement s) 514 { 515 result = BE.fallthru; 516 } 517 518 if (!s) 519 return BE.fallthru; 520 mixin VisitStatement!void visit; 521 visit.VisitStatement(s); 522 return result; 523 } 524 525 /++ 526 + Checks whether `throw <exp>` throws an `Exception` or an `Error` 527 + and raises an error if this violates `nothrow`. 528 + 529 + Params: 530 + loc = location of the `throw` 531 + exp = expression yielding the throwable 532 + mustNotThrow = inside of a `nothrow` scope? 533 + func = function containing the `throw` 534 + 535 + Returns: `BE.[err]throw` depending on the type of `exp` 536 +/ 537 BE checkThrow(ref const Loc loc, Expression exp, const bool mustNotThrow, FuncDeclaration func) 538 { 539 import dmd.errors : error; 540 541 Type t = exp.type.toBasetype(); 542 ClassDeclaration cd = t.isClassHandle(); 543 assert(cd); 544 545 if (cd.isErrorException()) 546 { 547 return BE.errthrow; 548 } 549 if (mustNotThrow) 550 loc.error("`%s` is thrown but not caught", exp.type.toChars()); 551 else if (func) 552 func.setThrow(loc, "`%s` is thrown but not caught", exp.type); 553 554 return BE.throw_; 555 }