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-2022 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 */
canThrow(Expression e,FuncDeclaration func,bool mustNotThrow)54 extern (C++) /* CT */ BE canThrow(Expression e, FuncDeclaration func, bool mustNotThrow)
55 {
56 //printf("Expression::canThrow(%d) %s\n", mustNotThrow, 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)
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`",
80 f.kind(), f.toPrettyChars());
81
82 e.checkOverridenDtor(null, f, dd => dd.type.toTypeFunction().isnothrow, "not nothrow");
83 }
84 result |= CT.exception;
85 }
86 }
87
88 override void visit(Expression)
89 {
90 }
91
92 override void visit(DeclarationExp de)
93 {
94 result |= Dsymbol_canThrow(de.declaration, func, mustNotThrow);
95 }
96
97 override void visit(CallExp ce)
98 {
99 if (ce.inDebugStatement)
100 return;
101
102 if (global.errors && !ce.e1.type)
103 return; // error recovery
104
105 if (ce.f && ce.arguments.dim > 0)
106 {
107 Type tb = (*ce.arguments)[0].type.toBasetype();
108 auto tbNext = tb.nextOf();
109 if (tbNext)
110 {
111 auto ts = tbNext.baseElemOf().isTypeStruct();
112 if (ts)
113 {
114 import dmd.id : Id;
115
116 auto sd = ts.sym;
117 if (sd.postblit &&
118 (ce.f.ident == Id._d_arrayctor || ce.f.ident == Id._d_arraysetctor))
119 {
120 checkFuncThrows(ce, sd.postblit);
121 return;
122 }
123 }
124 }
125 }
126
127 /* If calling a function or delegate that is typed as nothrow,
128 * then this expression cannot throw.
129 * Note that pure functions can throw.
130 */
131 if (ce.f && ce.f == func)
132 return;
133 Type t = ce.e1.type.toBasetype();
134 auto tf = t.isTypeFunction();
135 if (tf && tf.isnothrow)
136 return;
137 else
138 {
139 auto td = t.isTypeDelegate();
140 if (td && td.nextOf().isTypeFunction().isnothrow)
141 return;
142 }
143
144 if (ce.f)
145 checkFuncThrows(ce, ce.f);
146 else if (mustNotThrow)
147 {
148 auto e1 = ce.e1;
149 if (auto pe = e1.isPtrExp()) // print 'fp' if e1 is (*fp)
150 e1 = pe.e1;
151 ce.error("`%s` is not `nothrow`", e1.toChars());
152 }
153 result |= CT.exception;
154 }
155
156 override void visit(NewExp ne)
157 {
158 if (ne.member)
159 {
160 // See if constructor call can throw
161 checkFuncThrows(ne, ne.member);
162 }
163 // regard storage allocation failures as not recoverable
164 }
165
166 override void visit(DeleteExp de)
167 {
168 Type tb = de.e1.type.toBasetype();
169 AggregateDeclaration ad = null;
170 switch (tb.ty)
171 {
172 case Tclass:
173 ad = tb.isTypeClass().sym;
174 break;
175
176 default:
177 assert(0); // error should have been detected by semantic()
178 }
179
180 if (ad.dtor)
181 checkFuncThrows(de, ad.dtor);
182 }
183
184 override void visit(AssignExp ae)
185 {
186 // blit-init cannot throw
187 if (ae.op == EXP.blit)
188 return;
189 /* Element-wise assignment could invoke postblits.
190 */
191 Type t;
192 if (ae.type.toBasetype().ty == Tsarray)
193 {
194 if (!ae.e2.isLvalue())
195 return;
196 t = ae.type;
197 }
198 else if (auto se = ae.e1.isSliceExp())
199 t = se.e1.type;
200 else
201 return;
202
203 if (auto ts = t.baseElemOf().isTypeStruct())
204 if (auto postblit = ts.sym.postblit)
205 checkFuncThrows(ae, postblit);
206 }
207
208 override void visit(ThrowExp te)
209 {
210 const res = checkThrow(te.loc, te.e1, mustNotThrow);
211 assert((res & ~(CT.exception | CT.error)) == 0);
212 result |= res;
213 }
214
215 override void visit(NewAnonClassExp)
216 {
217 assert(0); // should have been lowered by semantic()
218 }
219 }
220
221 scope CanThrow ct = new CanThrow(func, mustNotThrow);
222 walkPostorder(e, ct);
223 return ct.result;
224 }
225
226 /**************************************
227 * Does symbol, when initialized, throw?
228 * Mirrors logic in Dsymbol_toElem().
229 */
Dsymbol_canThrow(Dsymbol s,FuncDeclaration func,bool mustNotThrow)230 private CT Dsymbol_canThrow(Dsymbol s, FuncDeclaration func, bool mustNotThrow)
231 {
232 CT result;
233
234 int symbolDg(Dsymbol s)
235 {
236 result |= Dsymbol_canThrow(s, func, mustNotThrow);
237 return 0;
238 }
239
240 //printf("Dsymbol_toElem() %s\n", s.toChars());
241 if (auto vd = s.isVarDeclaration())
242 {
243 s = s.toAlias();
244 if (s != vd)
245 return Dsymbol_canThrow(s, func, mustNotThrow);
246 if (vd.storage_class & STC.manifest)
247 {
248 }
249 else if (vd.isStatic() || vd.storage_class & (STC.extern_ | STC.gshared))
250 {
251 }
252 else
253 {
254 if (vd._init)
255 {
256 if (auto ie = vd._init.isExpInitializer())
257 result |= canThrow(ie.exp, func, mustNotThrow);
258 }
259 if (vd.needsScopeDtor())
260 result |= canThrow(vd.edtor, func, mustNotThrow);
261 }
262 }
263 else if (auto ad = s.isAttribDeclaration())
264 {
265 ad.include(null).foreachDsymbol(&symbolDg);
266 }
267 else if (auto tm = s.isTemplateMixin())
268 {
269 tm.members.foreachDsymbol(&symbolDg);
270 }
271 else if (auto td = s.isTupleDeclaration())
272 {
273 for (size_t i = 0; i < td.objects.dim; i++)
274 {
275 RootObject o = (*td.objects)[i];
276 if (o.dyncast() == DYNCAST.expression)
277 {
278 Expression eo = cast(Expression)o;
279 if (auto se = eo.isDsymbolExp())
280 {
281 result |= Dsymbol_canThrow(se.s, func, mustNotThrow);
282 }
283 }
284 }
285 }
286 return result;
287 }
288