1 /** 2 * Perform checks for `nothrow`. 3 * 4 * Specification: $(LINK2 https://dlang.org/spec/function.html#nothrow-functions, Nothrow Functions) 5 * 6 * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved 7 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) 8 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/canthrow.d, _canthrow.d) 10 * Documentation: https://dlang.org/phobos/dmd_canthrow.html 11 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/canthrow.d 12 */ 13 14 module dmd.canthrow; 15 16 import dmd.aggregate; 17 import dmd.apply; 18 import dmd.arraytypes; 19 import dmd.attrib; 20 import dmd.astenums; 21 import dmd.blockexit : BE, checkThrow; 22 import dmd.declaration; 23 import dmd.dsymbol; 24 import dmd.expression; 25 import dmd.func; 26 import dmd.globals; 27 import dmd.init; 28 import dmd.mtype; 29 import dmd.root.rootobject; 30 import dmd.tokens; 31 import dmd.visitor; 32 33 /** 34 * Status indicating what kind of throwable might be caused by an expression. 35 * 36 * This is a subset of `BE` restricted to the values actually used by `canThrow`. 37 */ 38 enum CT : BE 39 { 40 /// Never throws an `Exception` or `Throwable` 41 none = BE.none, 42 43 /// Might throw an `Exception` 44 exception = BE.throw_, 45 46 // Might throw an `Error` 47 error = BE.errthrow, 48 } 49 50 /******************************************** 51 * Returns true if the expression may throw exceptions. 52 * If 'mustNotThrow' is true, generate an error if it throws 53 */ 54 extern (C++) /* CT */ BE canThrow(Expression e, FuncDeclaration func, bool mustNotThrow) 55 { 56 //printf("Expression::canThrow(%d) %s\n", mustNotThrow, e.toChars()); 57 // stop walking if we determine this expression can throw 58 extern (C++) final class CanThrow : StoppableVisitor 59 { 60 alias visit = typeof(super).visit; 61 FuncDeclaration func; 62 bool mustNotThrow; 63 CT result; 64 65 public: 66 extern (D) this(FuncDeclaration func, bool mustNotThrow) scope 67 { 68 this.func = func; 69 this.mustNotThrow = mustNotThrow; 70 } 71 72 void checkFuncThrows(Expression e, FuncDeclaration f) 73 { 74 auto tf = f.type.toBasetype().isTypeFunction(); 75 if (tf && !tf.isnothrow) 76 { 77 if (mustNotThrow) 78 { 79 e.error("%s `%s` is not `nothrow`", f.kind(), f.toPrettyChars()); 80 if (!f.isDtorDeclaration()) 81 errorSupplementalInferredAttr(f, 10, false, STC.nothrow_); 82 83 e.checkOverridenDtor(null, f, dd => dd.type.toTypeFunction().isnothrow, "not nothrow"); 84 } 85 else if (func) 86 { 87 func.setThrowCall(e.loc, f); 88 } 89 result |= CT.exception; 90 } 91 } 92 93 override void visit(Expression) 94 { 95 } 96 97 override void visit(DeclarationExp de) 98 { 99 result |= Dsymbol_canThrow(de.declaration, func, mustNotThrow); 100 } 101 102 override void visit(CallExp ce) 103 { 104 if (ce.inDebugStatement) 105 return; 106 107 if (global.errors && !ce.e1.type) 108 return; // error recovery 109 110 if (ce.f && ce.arguments.length > 0) 111 { 112 Type tb = (*ce.arguments)[0].type.toBasetype(); 113 auto tbNext = tb.nextOf(); 114 if (tbNext) 115 { 116 auto ts = tbNext.baseElemOf().isTypeStruct(); 117 if (ts) 118 { 119 auto sd = ts.sym; 120 const id = ce.f.ident; 121 if (sd.postblit && isArrayConstructionOrAssign(id)) 122 { 123 checkFuncThrows(ce, sd.postblit); 124 return; 125 } 126 } 127 } 128 } 129 130 /* If calling a function or delegate that is typed as nothrow, 131 * then this expression cannot throw. 132 * Note that pure functions can throw. 133 */ 134 if (ce.f && ce.f == func) 135 return; 136 Type t = ce.e1.type.toBasetype(); 137 auto tf = t.isTypeFunction(); 138 if (tf && tf.isnothrow) 139 return; 140 else 141 { 142 auto td = t.isTypeDelegate(); 143 if (td && td.nextOf().isTypeFunction().isnothrow) 144 return; 145 } 146 147 if (ce.f) 148 checkFuncThrows(ce, ce.f); 149 else if (mustNotThrow) 150 { 151 auto e1 = ce.e1; 152 if (auto pe = e1.isPtrExp()) // print 'fp' if e1 is (*fp) 153 e1 = pe.e1; 154 ce.error("`%s` is not `nothrow`", e1.toChars()); 155 } 156 result |= CT.exception; 157 } 158 159 override void visit(NewExp ne) 160 { 161 if (ne.member) 162 { 163 // See if constructor call can throw 164 checkFuncThrows(ne, ne.member); 165 } 166 // regard storage allocation failures as not recoverable 167 } 168 169 override void visit(DeleteExp de) 170 { 171 Type tb = de.e1.type.toBasetype(); 172 AggregateDeclaration ad = null; 173 switch (tb.ty) 174 { 175 case Tclass: 176 ad = tb.isTypeClass().sym; 177 break; 178 179 default: 180 assert(0); // error should have been detected by semantic() 181 } 182 183 if (ad.dtor) 184 checkFuncThrows(de, ad.dtor); 185 } 186 187 override void visit(AssignExp ae) 188 { 189 // blit-init cannot throw 190 if (ae.op == EXP.blit) 191 return; 192 /* Element-wise assignment could invoke postblits. 193 */ 194 Type t; 195 if (ae.type.toBasetype().ty == Tsarray) 196 { 197 if (!ae.e2.isLvalue()) 198 return; 199 t = ae.type; 200 } 201 else if (auto se = ae.e1.isSliceExp()) 202 t = se.e1.type; 203 else 204 return; 205 206 if (auto ts = t.baseElemOf().isTypeStruct()) 207 if (auto postblit = ts.sym.postblit) 208 checkFuncThrows(ae, postblit); 209 } 210 211 override void visit(ThrowExp te) 212 { 213 const res = checkThrow(te.loc, te.e1, mustNotThrow, func); 214 assert((res & ~(CT.exception | CT.error)) == 0); 215 result |= res; 216 } 217 218 override void visit(NewAnonClassExp) 219 { 220 assert(0); // should have been lowered by semantic() 221 } 222 } 223 224 scope CanThrow ct = new CanThrow(func, mustNotThrow); 225 walkPostorder(e, ct); 226 return ct.result; 227 } 228 229 /************************************** 230 * Does symbol, when initialized, throw? 231 * Mirrors logic in Dsymbol_toElem(). 232 */ 233 private CT Dsymbol_canThrow(Dsymbol s, FuncDeclaration func, bool mustNotThrow) 234 { 235 CT result; 236 237 int symbolDg(Dsymbol s) 238 { 239 result |= Dsymbol_canThrow(s, func, mustNotThrow); 240 return 0; 241 } 242 243 //printf("Dsymbol_toElem() %s\n", s.toChars()); 244 if (auto vd = s.isVarDeclaration()) 245 { 246 s = s.toAlias(); 247 if (s != vd) 248 return Dsymbol_canThrow(s, func, mustNotThrow); 249 if (vd.storage_class & STC.manifest) 250 { 251 } 252 else if (vd.isStatic() || vd.storage_class & (STC.extern_ | STC.gshared)) 253 { 254 } 255 else 256 { 257 if (vd._init) 258 { 259 if (auto ie = vd._init.isExpInitializer()) 260 result |= canThrow(ie.exp, func, mustNotThrow); 261 } 262 if (vd.needsScopeDtor()) 263 result |= canThrow(vd.edtor, func, mustNotThrow); 264 } 265 } 266 else if (auto ad = s.isAttribDeclaration()) 267 { 268 ad.include(null).foreachDsymbol(&symbolDg); 269 } 270 else if (auto tm = s.isTemplateMixin()) 271 { 272 tm.members.foreachDsymbol(&symbolDg); 273 } 274 else if (auto td = s.isTupleDeclaration()) 275 { 276 td.foreachVar(&symbolDg); 277 } 278 return result; 279 }