| |
| /* Compiler implementation of the D programming language |
| * Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved |
| * written by Walter Bright |
| * http://www.digitalmars.com |
| * Distributed under the Boost Software License, Version 1.0. |
| * http://www.boost.org/LICENSE_1_0.txt |
| */ |
| |
| #include "statement.h" |
| #include "declaration.h" |
| #include "aggregate.h" |
| #include "id.h" |
| |
| /* Only valid after semantic analysis |
| * If 'mustNotThrow' is true, generate an error if it throws |
| */ |
| int blockExit(Statement *s, FuncDeclaration *func, bool mustNotThrow) |
| { |
| class BlockExit : public Visitor |
| { |
| public: |
| FuncDeclaration *func; |
| bool mustNotThrow; |
| int result; |
| |
| BlockExit(FuncDeclaration *func, bool mustNotThrow) |
| : func(func), mustNotThrow(mustNotThrow) |
| { |
| result = BEnone; |
| } |
| |
| void visit(Statement *s) |
| { |
| printf("Statement::blockExit(%p)\n", s); |
| printf("%s\n", s->toChars()); |
| assert(0); |
| result = BEany; |
| } |
| |
| void visit(ErrorStatement *) |
| { |
| result = BEany; |
| } |
| |
| void visit(ExpStatement *s) |
| { |
| result = BEfallthru; |
| if (s->exp) |
| { |
| if (s->exp->op == TOKhalt) |
| { |
| result = BEhalt; |
| return; |
| } |
| if (s->exp->op == TOKassert) |
| { |
| AssertExp *a = (AssertExp *)s->exp; |
| if (a->e1->isBool(false)) // if it's an assert(0) |
| { |
| result = BEhalt; |
| return; |
| } |
| } |
| if (canThrow(s->exp, func, mustNotThrow)) |
| result |= BEthrow; |
| } |
| } |
| |
| void visit(CompileStatement *) |
| { |
| assert(global.errors); |
| result = BEfallthru; |
| } |
| |
| void visit(CompoundStatement *cs) |
| { |
| //printf("CompoundStatement::blockExit(%p) %d result = x%X\n", cs, cs->statements->dim, result); |
| result = BEfallthru; |
| Statement *slast = NULL; |
| for (size_t i = 0; i < cs->statements->dim; i++) |
| { |
| Statement *s = (*cs->statements)[i]; |
| if (s) |
| { |
| //printf("result = x%x\n", result); |
| //printf("s: %s\n", s->toChars()); |
| if (result & BEfallthru && slast) |
| { |
| slast = slast->last(); |
| if (slast && (slast->isCaseStatement() || slast->isDefaultStatement()) && |
| (s->isCaseStatement() || s->isDefaultStatement())) |
| { |
| // Allow if last case/default was empty |
| CaseStatement *sc = slast->isCaseStatement(); |
| DefaultStatement *sd = slast->isDefaultStatement(); |
| if (sc && (!sc->statement->hasCode() || sc->statement->isCaseStatement() || sc->statement->isErrorStatement())) |
| ; |
| else if (sd && (!sd->statement->hasCode() || sd->statement->isCaseStatement() || sd->statement->isErrorStatement())) |
| ; |
| else |
| { |
| const char *gototype = s->isCaseStatement() ? "case" : "default"; |
| s->deprecation("switch case fallthrough - use 'goto %s;' if intended", gototype); |
| } |
| } |
| } |
| |
| if (!(result & BEfallthru) && !s->comeFrom()) |
| { |
| if (blockExit(s, func, mustNotThrow) != BEhalt && s->hasCode()) |
| s->warning("statement is not reachable"); |
| } |
| else |
| { |
| result &= ~BEfallthru; |
| result |= blockExit(s, func, mustNotThrow); |
| } |
| slast = s; |
| } |
| } |
| } |
| |
| void visit(UnrolledLoopStatement *uls) |
| { |
| result = BEfallthru; |
| for (size_t i = 0; i < uls->statements->dim; i++) |
| { |
| Statement *s = (*uls->statements)[i]; |
| if (s) |
| { |
| int r = blockExit(s, func, mustNotThrow); |
| result |= r & ~(BEbreak | BEcontinue | BEfallthru); |
| if ((r & (BEfallthru | BEcontinue | BEbreak)) == 0) |
| result &= ~BEfallthru; |
| } |
| } |
| } |
| |
| void visit(ScopeStatement *s) |
| { |
| //printf("ScopeStatement::blockExit(%p)\n", s->statement); |
| result = s->statement ? blockExit(s->statement, func, mustNotThrow) : BEfallthru; |
| } |
| |
| void visit(WhileStatement *) |
| { |
| assert(global.errors); |
| result = BEfallthru; |
| } |
| |
| void visit(DoStatement *s) |
| { |
| if (s->_body) |
| { |
| result = blockExit(s->_body, func, mustNotThrow); |
| if (result == BEbreak) |
| { |
| result = BEfallthru; |
| return; |
| } |
| if (result & BEcontinue) |
| result |= BEfallthru; |
| } |
| else |
| result = BEfallthru; |
| if (result & BEfallthru) |
| { |
| if (canThrow(s->condition, func, mustNotThrow)) |
| result |= BEthrow; |
| if (!(result & BEbreak) && s->condition->isBool(true)) |
| result &= ~BEfallthru; |
| } |
| result &= ~(BEbreak | BEcontinue); |
| } |
| |
| void visit(ForStatement *s) |
| { |
| result = BEfallthru; |
| if (s->_init) |
| { |
| result = blockExit(s->_init, func, mustNotThrow); |
| if (!(result & BEfallthru)) |
| return; |
| } |
| if (s->condition) |
| { |
| if (canThrow(s->condition, func, mustNotThrow)) |
| result |= BEthrow; |
| if (s->condition->isBool(true)) |
| result &= ~BEfallthru; |
| else if (s->condition->isBool(false)) |
| return; |
| } |
| else |
| result &= ~BEfallthru; // the body must do the exiting |
| if (s->_body) |
| { |
| int r = blockExit(s->_body, func, mustNotThrow); |
| if (r & (BEbreak | BEgoto)) |
| result |= BEfallthru; |
| result |= r & ~(BEfallthru | BEbreak | BEcontinue); |
| } |
| if (s->increment && canThrow(s->increment, func, mustNotThrow)) |
| result |= BEthrow; |
| } |
| |
| void visit(ForeachStatement *s) |
| { |
| result = BEfallthru; |
| if (canThrow(s->aggr, func, mustNotThrow)) |
| result |= BEthrow; |
| if (s->_body) |
| result |= blockExit(s->_body, func, mustNotThrow) & ~(BEbreak | BEcontinue); |
| } |
| |
| void visit(ForeachRangeStatement *) |
| { |
| assert(global.errors); |
| result = BEfallthru; |
| } |
| |
| void visit(IfStatement *s) |
| { |
| //printf("IfStatement::blockExit(%p)\n", s); |
| |
| result = BEnone; |
| if (canThrow(s->condition, func, mustNotThrow)) |
| result |= BEthrow; |
| if (s->condition->isBool(true)) |
| { |
| if (s->ifbody) |
| result |= blockExit(s->ifbody, func, mustNotThrow); |
| else |
| result |= BEfallthru; |
| } |
| else if (s->condition->isBool(false)) |
| { |
| if (s->elsebody) |
| result |= blockExit(s->elsebody, func, mustNotThrow); |
| else |
| result |= BEfallthru; |
| } |
| else |
| { |
| if (s->ifbody) |
| result |= blockExit(s->ifbody, func, mustNotThrow); |
| else |
| result |= BEfallthru; |
| if (s->elsebody) |
| result |= blockExit(s->elsebody, func, mustNotThrow); |
| else |
| result |= BEfallthru; |
| } |
| //printf("IfStatement::blockExit(%p) = x%x\n", s, result); |
| } |
| |
| void visit(ConditionalStatement *s) |
| { |
| result = blockExit(s->ifbody, func, mustNotThrow); |
| if (s->elsebody) |
| result |= blockExit(s->elsebody, func, mustNotThrow); |
| } |
| |
| void visit(PragmaStatement *) |
| { |
| result = BEfallthru; |
| } |
| |
| void visit(StaticAssertStatement *) |
| { |
| result = BEfallthru; |
| } |
| |
| void visit(SwitchStatement *s) |
| { |
| result = BEnone; |
| if (canThrow(s->condition, func, mustNotThrow)) |
| result |= BEthrow; |
| if (s->_body) |
| { |
| result |= blockExit(s->_body, func, mustNotThrow); |
| if (result & BEbreak) |
| { |
| result |= BEfallthru; |
| result &= ~BEbreak; |
| } |
| } |
| else |
| result |= BEfallthru; |
| } |
| |
| void visit(CaseStatement *s) |
| { |
| result = blockExit(s->statement, func, mustNotThrow); |
| } |
| |
| void visit(DefaultStatement *s) |
| { |
| result = blockExit(s->statement, func, mustNotThrow); |
| } |
| |
| void visit(GotoDefaultStatement *) |
| { |
| result = BEgoto; |
| } |
| |
| void visit(GotoCaseStatement *) |
| { |
| result = BEgoto; |
| } |
| |
| void visit(SwitchErrorStatement *) |
| { |
| // Switch errors are non-recoverable |
| result = BEhalt; |
| } |
| |
| void visit(ReturnStatement *s) |
| { |
| result = BEreturn; |
| if (s->exp && canThrow(s->exp, func, mustNotThrow)) |
| result |= BEthrow; |
| } |
| |
| void visit(BreakStatement *s) |
| { |
| //printf("BreakStatement::blockExit(%p) = x%x\n", s, s->ident ? BEgoto : BEbreak); |
| result = s->ident ? BEgoto : BEbreak; |
| } |
| |
| void visit(ContinueStatement *s) |
| { |
| result = s->ident ? BEgoto : BEcontinue; |
| } |
| |
| void visit(SynchronizedStatement *s) |
| { |
| result = s->_body ? blockExit(s->_body, func, mustNotThrow) : BEfallthru; |
| } |
| |
| void visit(WithStatement *s) |
| { |
| result = BEnone; |
| if (canThrow(s->exp, func, mustNotThrow)) |
| result = BEthrow; |
| if (s->_body) |
| result |= blockExit(s->_body, func, mustNotThrow); |
| else |
| result |= BEfallthru; |
| } |
| |
| void visit(TryCatchStatement *s) |
| { |
| assert(s->_body); |
| result = blockExit(s->_body, func, false); |
| |
| int catchresult = 0; |
| for (size_t i = 0; i < s->catches->dim; i++) |
| { |
| Catch *c = (*s->catches)[i]; |
| if (c->type == Type::terror) |
| continue; |
| |
| int cresult; |
| if (c->handler) |
| cresult = blockExit(c->handler, func, mustNotThrow); |
| else |
| cresult = BEfallthru; |
| |
| /* If we're catching Object, then there is no throwing |
| */ |
| Identifier *id = c->type->toBasetype()->isClassHandle()->ident; |
| if (c->internalCatch && (cresult & BEfallthru)) |
| { |
| // Bugzilla 11542: leave blockExit flags of the body |
| cresult &= ~BEfallthru; |
| } |
| else if (id == Id::Object || id == Id::Throwable) |
| { |
| result &= ~(BEthrow | BEerrthrow); |
| } |
| else if (id == Id::Exception) |
| { |
| result &= ~BEthrow; |
| } |
| catchresult |= cresult; |
| } |
| if (mustNotThrow && (result & BEthrow)) |
| { |
| // now explain why this is nothrow |
| blockExit(s->_body, func, mustNotThrow); |
| } |
| result |= catchresult; |
| } |
| |
| void visit(TryFinallyStatement *s) |
| { |
| result = BEfallthru; |
| if (s->_body) |
| result = blockExit(s->_body, func, false); |
| |
| // check finally body as well, it may throw (bug #4082) |
| int finalresult = BEfallthru; |
| if (s->finalbody) |
| finalresult = blockExit(s->finalbody, func, false); |
| |
| // If either body or finalbody halts |
| if (result == BEhalt) |
| finalresult = BEnone; |
| if (finalresult == BEhalt) |
| result = BEnone; |
| |
| if (mustNotThrow) |
| { |
| // now explain why this is nothrow |
| if (s->_body && (result & BEthrow)) |
| blockExit(s->_body, func, mustNotThrow); |
| if (s->finalbody && (finalresult & BEthrow)) |
| blockExit(s->finalbody, func, mustNotThrow); |
| } |
| |
| #if 0 |
| // Bugzilla 13201: Mask to prevent spurious warnings for |
| // destructor call, exit of synchronized statement, etc. |
| if (result == BEhalt && finalresult != BEhalt && s->finalbody && |
| s->finalbody->hasCode()) |
| { |
| s->finalbody->warning("statement is not reachable"); |
| } |
| #endif |
| |
| if (!(finalresult & BEfallthru)) |
| result &= ~BEfallthru; |
| result |= finalresult & ~BEfallthru; |
| } |
| |
| void visit(OnScopeStatement *) |
| { |
| // At this point, this statement is just an empty placeholder |
| result = BEfallthru; |
| } |
| |
| void visit(ThrowStatement *s) |
| { |
| if (s->internalThrow) |
| { |
| // Bugzilla 8675: Allow throwing 'Throwable' object even if mustNotThrow. |
| result = BEfallthru; |
| return; |
| } |
| |
| Type *t = s->exp->type->toBasetype(); |
| ClassDeclaration *cd = t->isClassHandle(); |
| assert(cd); |
| |
| if (cd == ClassDeclaration::errorException || |
| ClassDeclaration::errorException->isBaseOf(cd, NULL)) |
| { |
| result = BEerrthrow; |
| return; |
| } |
| if (mustNotThrow) |
| s->error("%s is thrown but not caught", s->exp->type->toChars()); |
| |
| result = BEthrow; |
| } |
| |
| void visit(GotoStatement *) |
| { |
| //printf("GotoStatement::blockExit(%p)\n", s); |
| result = BEgoto; |
| } |
| |
| void visit(LabelStatement *s) |
| { |
| //printf("LabelStatement::blockExit(%p)\n", s); |
| result = s->statement ? blockExit(s->statement, func, mustNotThrow) : BEfallthru; |
| if (s->breaks) |
| result |= BEfallthru; |
| } |
| |
| void visit(CompoundAsmStatement *s) |
| { |
| if (mustNotThrow && !(s->stc & STCnothrow)) |
| s->deprecation("asm statement is assumed to throw - mark it with 'nothrow' if it does not"); |
| |
| // Assume the worst |
| result = BEfallthru | BEreturn | BEgoto | BEhalt; |
| if (!(s->stc & STCnothrow)) result |= BEthrow; |
| } |
| |
| void visit(ImportStatement *) |
| { |
| result = BEfallthru; |
| } |
| }; |
| |
| BlockExit be(func, mustNotThrow); |
| s->accept(&be); |
| return be.result; |
| } |