blob: 0c237e6da567015bc5ac14b68a8337daafbac2fb [file] [log] [blame]
/**
* Perform checks for `nothrow`.
*
* Specification: $(LINK2 https://dlang.org/spec/function.html#nothrow-functions, Nothrow Functions)
*
* Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/canthrow.d, _canthrow.d)
* Documentation: https://dlang.org/phobos/dmd_canthrow.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/canthrow.d
*/
module dmd.canthrow;
import dmd.aggregate;
import dmd.apply;
import dmd.arraytypes;
import dmd.attrib;
import dmd.astenums;
import dmd.blockexit : BE, checkThrow;
import dmd.declaration;
import dmd.dsymbol;
import dmd.expression;
import dmd.func;
import dmd.globals;
import dmd.init;
import dmd.mtype;
import dmd.root.rootobject;
import dmd.tokens;
import dmd.visitor;
/**
* Status indicating what kind of throwable might be caused by an expression.
*
* This is a subset of `BE` restricted to the values actually used by `canThrow`.
*/
enum CT : BE
{
/// Never throws an `Exception` or `Throwable`
none = BE.none,
/// Might throw an `Exception`
exception = BE.throw_,
// Might throw an `Error`
error = BE.errthrow,
}
/********************************************
* Returns true if the expression may throw exceptions.
* If 'mustNotThrow' is true, generate an error if it throws
*/
extern (C++) /* CT */ BE canThrow(Expression e, FuncDeclaration func, bool mustNotThrow)
{
//printf("Expression::canThrow(%d) %s\n", mustNotThrow, toChars());
// stop walking if we determine this expression can throw
extern (C++) final class CanThrow : StoppableVisitor
{
alias visit = typeof(super).visit;
FuncDeclaration func;
bool mustNotThrow;
CT result;
public:
extern (D) this(FuncDeclaration func, bool mustNotThrow) scope
{
this.func = func;
this.mustNotThrow = mustNotThrow;
}
void checkFuncThrows(Expression e, FuncDeclaration f)
{
auto tf = f.type.toBasetype().isTypeFunction();
if (tf && !tf.isnothrow)
{
if (mustNotThrow)
{
e.error("%s `%s` is not `nothrow`",
f.kind(), f.toPrettyChars());
e.checkOverridenDtor(null, f, dd => dd.type.toTypeFunction().isnothrow, "not nothrow");
}
result |= CT.exception;
}
}
override void visit(Expression)
{
}
override void visit(DeclarationExp de)
{
result |= Dsymbol_canThrow(de.declaration, func, mustNotThrow);
}
override void visit(CallExp ce)
{
if (ce.inDebugStatement)
return;
if (global.errors && !ce.e1.type)
return; // error recovery
if (ce.f && ce.arguments.length > 0)
{
Type tb = (*ce.arguments)[0].type.toBasetype();
auto tbNext = tb.nextOf();
if (tbNext)
{
auto ts = tbNext.baseElemOf().isTypeStruct();
if (ts)
{
auto sd = ts.sym;
const id = ce.f.ident;
if (sd.postblit && isArrayConstructionOrAssign(id))
{
checkFuncThrows(ce, sd.postblit);
return;
}
}
}
}
/* If calling a function or delegate that is typed as nothrow,
* then this expression cannot throw.
* Note that pure functions can throw.
*/
if (ce.f && ce.f == func)
return;
Type t = ce.e1.type.toBasetype();
auto tf = t.isTypeFunction();
if (tf && tf.isnothrow)
return;
else
{
auto td = t.isTypeDelegate();
if (td && td.nextOf().isTypeFunction().isnothrow)
return;
}
if (ce.f)
checkFuncThrows(ce, ce.f);
else if (mustNotThrow)
{
auto e1 = ce.e1;
if (auto pe = e1.isPtrExp()) // print 'fp' if e1 is (*fp)
e1 = pe.e1;
ce.error("`%s` is not `nothrow`", e1.toChars());
}
result |= CT.exception;
}
override void visit(NewExp ne)
{
if (ne.member)
{
// See if constructor call can throw
checkFuncThrows(ne, ne.member);
}
// regard storage allocation failures as not recoverable
}
override void visit(DeleteExp de)
{
Type tb = de.e1.type.toBasetype();
AggregateDeclaration ad = null;
switch (tb.ty)
{
case Tclass:
ad = tb.isTypeClass().sym;
break;
default:
assert(0); // error should have been detected by semantic()
}
if (ad.dtor)
checkFuncThrows(de, ad.dtor);
}
override void visit(AssignExp ae)
{
// blit-init cannot throw
if (ae.op == EXP.blit)
return;
/* Element-wise assignment could invoke postblits.
*/
Type t;
if (ae.type.toBasetype().ty == Tsarray)
{
if (!ae.e2.isLvalue())
return;
t = ae.type;
}
else if (auto se = ae.e1.isSliceExp())
t = se.e1.type;
else
return;
if (auto ts = t.baseElemOf().isTypeStruct())
if (auto postblit = ts.sym.postblit)
checkFuncThrows(ae, postblit);
}
override void visit(ThrowExp te)
{
const res = checkThrow(te.loc, te.e1, mustNotThrow);
assert((res & ~(CT.exception | CT.error)) == 0);
result |= res;
}
override void visit(NewAnonClassExp)
{
assert(0); // should have been lowered by semantic()
}
}
scope CanThrow ct = new CanThrow(func, mustNotThrow);
walkPostorder(e, ct);
return ct.result;
}
/**************************************
* Does symbol, when initialized, throw?
* Mirrors logic in Dsymbol_toElem().
*/
private CT Dsymbol_canThrow(Dsymbol s, FuncDeclaration func, bool mustNotThrow)
{
CT result;
int symbolDg(Dsymbol s)
{
result |= Dsymbol_canThrow(s, func, mustNotThrow);
return 0;
}
//printf("Dsymbol_toElem() %s\n", s.toChars());
if (auto vd = s.isVarDeclaration())
{
s = s.toAlias();
if (s != vd)
return Dsymbol_canThrow(s, func, mustNotThrow);
if (vd.storage_class & STC.manifest)
{
}
else if (vd.isStatic() || vd.storage_class & (STC.extern_ | STC.gshared))
{
}
else
{
if (vd._init)
{
if (auto ie = vd._init.isExpInitializer())
result |= canThrow(ie.exp, func, mustNotThrow);
}
if (vd.needsScopeDtor())
result |= canThrow(vd.edtor, func, mustNotThrow);
}
}
else if (auto ad = s.isAttribDeclaration())
{
ad.include(null).foreachDsymbol(&symbolDg);
}
else if (auto tm = s.isTemplateMixin())
{
tm.members.foreachDsymbol(&symbolDg);
}
else if (auto td = s.isTupleDeclaration())
{
td.foreachVar(&symbolDg);
}
return result;
}