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.arraytypes;
18 import dmd.attrib;
19 import dmd.astenums;
20 import dmd.blockexit : BE, checkThrow;
21 import dmd.declaration;
22 import dmd.dsymbol;
23 import dmd.errorsink;
24 import dmd.expression;
25 import dmd.func;
26 import dmd.globals;
27 import dmd.init;
28 import dmd.mtype;
29 import dmd.postordervisitor;
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  * If `eSink` is not null, generate an error if `e` throws
52  * Params:
53  *      e = expression to check for throwing
54  *      func = function
55  *      eSink = if !null, then send error messages to eSink
56  * Returns: `CT.exception` or `CT.error` if the expression may throw exceptions.
57  */
58 CT canThrow(Expression e, FuncDeclaration func, ErrorSink eSink)
59 {
60     //printf("Expression::canThrow(%d) %s\n", mustNotThrow, e.toChars());
61     // stop walking if we determine this expression can throw
62     extern (C++) final class CanThrow : StoppableVisitor
63     {
64         alias visit = typeof(super).visit;
65         CT result;
66 
67     public:
68         extern (D) this() scope @safe
69         {
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 (eSink)
78                 {
79                     eSink.error(e.loc, "%s `%s` is not `nothrow`", f.kind(), f.toPrettyChars());
80                     if (!f.isDtorDeclaration())
81                         errorSupplementalInferredAttr(f, 10, false, STC.nothrow_);
82 
83                     e.checkOverriddenDtor(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, eSink);
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 && isArrayConstruction(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             const tf = ce.calledFunctionType();
137             if (tf && tf.isnothrow)
138                 return;
139 
140             if (ce.f)
141                 checkFuncThrows(ce, ce.f);
142             else if (eSink)
143             {
144                 auto e1 = ce.e1;
145                 if (auto pe = e1.isPtrExp())   // print 'fp' if e1 is (*fp)
146                     e1 = pe.e1;
147                 eSink.error(ce.loc, "`%s` is not `nothrow`", e1.toChars());
148             }
149             result |= CT.exception;
150         }
151 
152         override void visit(NewExp ne)
153         {
154             if (ne.member)
155             {
156                 // See if constructor call can throw
157                 checkFuncThrows(ne, ne.member);
158             }
159             // regard storage allocation failures as not recoverable
160         }
161 
162         override void visit(DeleteExp de)
163         {
164             Type tb = de.e1.type.toBasetype();
165             AggregateDeclaration ad = null;
166             switch (tb.ty)
167             {
168             case Tclass:
169                 ad = tb.isTypeClass().sym;
170                 break;
171 
172             default:
173                 assert(0);  // error should have been detected by semantic()
174             }
175 
176             if (ad.dtor)
177                 checkFuncThrows(de, ad.dtor);
178         }
179 
180         override void visit(AssignExp ae)
181         {
182             // blit-init cannot throw
183             if (ae.op == EXP.blit)
184                 return;
185             /* Element-wise assignment could invoke postblits.
186              */
187             Type t;
188             if (ae.type.toBasetype().ty == Tsarray)
189             {
190                 if (!ae.e2.isLvalue())
191                     return;
192                 t = ae.type;
193             }
194             else if (auto se = ae.e1.isSliceExp())
195                 t = se.e1.type;
196             else
197                 return;
198 
199             if (auto ts = t.baseElemOf().isTypeStruct())
200                 if (auto postblit = ts.sym.postblit)
201                     checkFuncThrows(ae, postblit);
202         }
203 
204         override void visit(ThrowExp te)
205         {
206             const res = checkThrow(te.loc, te.e1, func, eSink);
207             assert((res & ~(CT.exception | CT.error)) == 0);
208             result |= res;
209         }
210 
211         override void visit(NewAnonClassExp)
212         {
213             assert(0); // should have been lowered by semantic()
214         }
215     }
216 
217     scope CanThrow ct = new CanThrow();
218     walkPostorder(e, ct);
219     return ct.result;
220 }
221 
222 /**************************************
223  * Does symbol `s`, when initialized, throw?
224  * Mirrors logic in Dsymbol_toElem().
225  */
226 private CT Dsymbol_canThrow(Dsymbol s, FuncDeclaration func, ErrorSink eSink)
227 {
228     CT result;
229 
230     int symbolDg(Dsymbol s)
231     {
232         result |= Dsymbol_canThrow(s, func, eSink);
233         return 0;
234     }
235 
236     //printf("Dsymbol_toElem() %s\n", s.toChars());
237     if (auto vd = s.isVarDeclaration())
238     {
239         s = s.toAlias();
240         if (s != vd)
241             return Dsymbol_canThrow(s, func, eSink);
242         if (vd.storage_class & STC.manifest)
243         {
244         }
245         else if (vd.isStatic() || vd.storage_class & (STC.extern_ | STC.gshared))
246         {
247         }
248         else
249         {
250             if (vd._init)
251             {
252                 if (auto ie = vd._init.isExpInitializer())
253                     result |= canThrow(ie.exp, func, eSink);
254             }
255             if (vd.needsScopeDtor())
256                 result |= canThrow(vd.edtor, func, eSink);
257         }
258     }
259     else if (auto ad = s.isAttribDeclaration())
260     {
261         ad.include(null).foreachDsymbol(&symbolDg);
262     }
263     else if (auto tm = s.isTemplateMixin())
264     {
265         tm.members.foreachDsymbol(&symbolDg);
266     }
267     else if (auto td = s.isTupleDeclaration())
268     {
269         td.foreachVar(&symbolDg);
270     }
271     return result;
272 }