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