| /** |
| * Perform checks for `nothrow`. |
| * |
| * Specification: $(LINK2 https://dlang.org/spec/function.html#nothrow-functions, Nothrow Functions) |
| * |
| * Copyright: Copyright (C) 1999-2022 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) |
| { |
| 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.dim > 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; |
| } |