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-2021 by The D Language Foundation, All Rights Reserved
7  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
8  * License:     $(LINK2 http://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.declaration;
21 import dmd.dsymbol;
22 import dmd.expression;
23 import dmd.func;
24 import dmd.globals;
25 import dmd.init;
26 import dmd.mtype;
27 import dmd.root.rootobject;
28 import dmd.tokens;
29 import dmd.visitor;
30 
31 /********************************************
32  * Returns true if the expression may throw exceptions.
33  * If 'mustNotThrow' is true, generate an error if it throws
34  */
35 extern (C++) bool canThrow(Expression e, FuncDeclaration func, bool mustNotThrow)
36 {
37     //printf("Expression::canThrow(%d) %s\n", mustNotThrow, toChars());
38     // stop walking if we determine this expression can throw
39     extern (C++) final class CanThrow : StoppableVisitor
40     {
41         alias visit = typeof(super).visit;
42         FuncDeclaration func;
43         bool mustNotThrow;
44 
45     public:
46         extern (D) this(FuncDeclaration func, bool mustNotThrow)
47         {
48             this.func = func;
49             this.mustNotThrow = mustNotThrow;
50         }
51 
52         void checkFuncThrows(Expression e, FuncDeclaration f)
53         {
54             auto tf = f.type.toBasetype().isTypeFunction();
55             if (tf && !tf.isnothrow)
56             {
57                 if (mustNotThrow)
58                 {
59                     e.error("%s `%s` is not `nothrow`",
60                         f.kind(), f.toPrettyChars());
61 
62                     e.checkOverridenDtor(null, f, dd => dd.type.toTypeFunction().isnothrow, "not nothrow");
63                 }
64                 stop = true;  // if any function throws, then the whole expression throws
65             }
66         }
67 
68         override void visit(Expression)
69         {
70         }
71 
72         override void visit(DeclarationExp de)
73         {
74             stop = Dsymbol_canThrow(de.declaration, func, mustNotThrow);
75         }
76 
77         override void visit(CallExp ce)
78         {
79             if (ce.inDebugStatement)
80                 return;
81 
82             if (global.errors && !ce.e1.type)
83                 return; // error recovery
84             /* If calling a function or delegate that is typed as nothrow,
85              * then this expression cannot throw.
86              * Note that pure functions can throw.
87              */
88             if (ce.f && ce.f == func)
89                 return;
90             Type t = ce.e1.type.toBasetype();
91             auto tf = t.isTypeFunction();
92             if (tf && tf.isnothrow)
93                 return;
94             else
95             {
96                 auto td = t.isTypeDelegate();
97                 if (td && td.nextOf().isTypeFunction().isnothrow)
98                     return;
99             }
100 
101             if (ce.f)
102                 checkFuncThrows(ce, ce.f);
103             else if (mustNotThrow)
104             {
105                 auto e1 = ce.e1;
106                 if (auto pe = e1.isPtrExp())   // print 'fp' if e1 is (*fp)
107                     e1 = pe.e1;
108                 ce.error("`%s` is not `nothrow`", e1.toChars());
109             }
110             stop = true;
111         }
112 
113         override void visit(NewExp ne)
114         {
115             if (ne.member)
116             {
117                 if (ne.allocator)
118                     // https://issues.dlang.org/show_bug.cgi?id=14407
119                     checkFuncThrows(ne, ne.allocator);
120 
121                 // See if constructor call can throw
122                 checkFuncThrows(ne, ne.member);
123             }
124             // regard storage allocation failures as not recoverable
125         }
126 
127         override void visit(DeleteExp de)
128         {
129             Type tb = de.e1.type.toBasetype();
130             AggregateDeclaration ad = null;
131             switch (tb.ty)
132             {
133             case Tclass:
134                 ad = tb.isTypeClass().sym;
135                 break;
136 
137             case Tpointer:
138             case Tarray:
139                 auto ts = tb.nextOf().baseElemOf().isTypeStruct();
140                 if (!ts)
141                     return;
142                 ad = ts.sym;
143                 break;
144 
145             default:
146                 assert(0);  // error should have been detected by semantic()
147             }
148 
149             if (ad.dtor)
150                 checkFuncThrows(de, ad.dtor);
151         }
152 
153         override void visit(AssignExp ae)
154         {
155             // blit-init cannot throw
156             if (ae.op == TOK.blit)
157                 return;
158             /* Element-wise assignment could invoke postblits.
159              */
160             Type t;
161             if (ae.type.toBasetype().ty == Tsarray)
162             {
163                 if (!ae.e2.isLvalue())
164                     return;
165                 t = ae.type;
166             }
167             else if (auto se = ae.e1.isSliceExp())
168                 t = se.e1.type;
169             else
170                 return;
171 
172             if (auto ts = t.baseElemOf().isTypeStruct())
173                 if (auto postblit = ts.sym.postblit)
174                     checkFuncThrows(ae, postblit);
175         }
176 
177         override void visit(NewAnonClassExp)
178         {
179             assert(0); // should have been lowered by semantic()
180         }
181     }
182 
183     scope CanThrow ct = new CanThrow(func, mustNotThrow);
184     return walkPostorder(e, ct);
185 }
186 
187 /**************************************
188  * Does symbol, when initialized, throw?
189  * Mirrors logic in Dsymbol_toElem().
190  */
191 private bool Dsymbol_canThrow(Dsymbol s, FuncDeclaration func, bool mustNotThrow)
192 {
193     int symbolDg(Dsymbol s)
194     {
195         return Dsymbol_canThrow(s, func, mustNotThrow);
196     }
197 
198     //printf("Dsymbol_toElem() %s\n", s.toChars());
199     if (auto vd = s.isVarDeclaration())
200     {
201         s = s.toAlias();
202         if (s != vd)
203             return Dsymbol_canThrow(s, func, mustNotThrow);
204         if (vd.storage_class & STC.manifest)
205         {
206         }
207         else if (vd.isStatic() || vd.storage_class & (STC.extern_ | STC.tls | STC.gshared))
208         {
209         }
210         else
211         {
212             if (vd._init)
213             {
214                 if (auto ie = vd._init.isExpInitializer())
215                     if (canThrow(ie.exp, func, mustNotThrow))
216                         return true;
217             }
218             if (vd.needsScopeDtor())
219                 return canThrow(vd.edtor, func, mustNotThrow);
220         }
221     }
222     else if (auto ad = s.isAttribDeclaration())
223     {
224         return ad.include(null).foreachDsymbol(&symbolDg) != 0;
225     }
226     else if (auto tm = s.isTemplateMixin())
227     {
228         return tm.members.foreachDsymbol(&symbolDg) != 0;
229     }
230     else if (auto td = s.isTupleDeclaration())
231     {
232         for (size_t i = 0; i < td.objects.dim; i++)
233         {
234             RootObject o = (*td.objects)[i];
235             if (o.dyncast() == DYNCAST.expression)
236             {
237                 Expression eo = cast(Expression)o;
238                 if (auto se = eo.isDsymbolExp())
239                 {
240                     if (Dsymbol_canThrow(se.s, func, mustNotThrow))
241                         return true;
242                 }
243             }
244         }
245     }
246     return false;
247 }