1 /**
2  * Compiler implementation of the
3  * $(LINK2 http://www.dlang.org, D programming language).
4  *
5  * Copyright:   Copyright (c) 1999-2016 by Digital Mars, All Rights Reserved
6  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
7  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8  * Source:      $(DMDSRC _sideeffect.d)
9  */
10 
11 module ddmd.sideeffect;
12 
13 import ddmd.apply;
14 import ddmd.declaration;
15 import ddmd.dscope;
16 import ddmd.expression;
17 import ddmd.func;
18 import ddmd.globals;
19 import ddmd.identifier;
20 import ddmd.init;
21 import ddmd.mtype;
22 import ddmd.tokens;
23 import ddmd.visitor;
24 
25 /**************************************************
26  * Front-end expression rewriting should create temporary variables for
27  * non trivial sub-expressions in order to:
28  *  1. save evaluation order
29  *  2. prevent sharing of sub-expression in AST
30  */
31 extern (C++) bool isTrivialExp(Expression e)
32 {
33     extern (C++) final class IsTrivialExp : StoppableVisitor
34     {
35         alias visit = super.visit;
36     public:
37         extern (D) this()
38         {
39         }
40 
41         override void visit(Expression e)
42         {
43             /* Bugzilla 11201: CallExp is always non trivial expression,
44              * especially for inlining.
45              */
46             if (e.op == TOKcall)
47             {
48                 stop = true;
49                 return;
50             }
51             // stop walking if we determine this expression has side effects
52             stop = lambdaHasSideEffect(e);
53         }
54     }
55 
56     scope IsTrivialExp v = new IsTrivialExp();
57     return walkPostorder(e, v) == false;
58 }
59 
60 /********************************************
61  * Determine if Expression has any side effects.
62  */
63 extern (C++) bool hasSideEffect(Expression e)
64 {
65     extern (C++) final class LambdaHasSideEffect : StoppableVisitor
66     {
67         alias visit = super.visit;
68     public:
69         extern (D) this()
70         {
71         }
72 
73         override void visit(Expression e)
74         {
75             // stop walking if we determine this expression has side effects
76             stop = lambdaHasSideEffect(e);
77         }
78     }
79 
80     scope LambdaHasSideEffect v = new LambdaHasSideEffect();
81     return walkPostorder(e, v);
82 }
83 
84 /********************************************
85  * Determine if the call of f, or function type or delegate type t1, has any side effects.
86  * Returns:
87  *      0   has any side effects
88  *      1   nothrow + constant purity
89  *      2   nothrow + strong purity
90  */
91 extern (C++) int callSideEffectLevel(FuncDeclaration f)
92 {
93     /* Bugzilla 12760: ctor call always has side effects.
94      */
95     if (f.isCtorDeclaration())
96         return 0;
97     assert(f.type.ty == Tfunction);
98     TypeFunction tf = cast(TypeFunction)f.type;
99     if (tf.isnothrow)
100     {
101         PURE purity = f.isPure();
102         if (purity == PUREstrong)
103             return 2;
104         if (purity == PUREconst)
105             return 1;
106     }
107     return 0;
108 }
109 
110 extern (C++) int callSideEffectLevel(Type t)
111 {
112     t = t.toBasetype();
113     TypeFunction tf;
114     if (t.ty == Tdelegate)
115         tf = cast(TypeFunction)(cast(TypeDelegate)t).next;
116     else
117     {
118         assert(t.ty == Tfunction);
119         tf = cast(TypeFunction)t;
120     }
121     tf.purityLevel();
122     PURE purity = tf.purity;
123     if (t.ty == Tdelegate && purity > PUREweak)
124     {
125         if (tf.isMutable())
126             purity = PUREweak;
127         else if (!tf.isImmutable())
128             purity = PUREconst;
129     }
130     if (tf.isnothrow)
131     {
132         if (purity == PUREstrong)
133             return 2;
134         if (purity == PUREconst)
135             return 1;
136     }
137     return 0;
138 }
139 
140 extern (C++) bool lambdaHasSideEffect(Expression e)
141 {
142     switch (e.op)
143     {
144     // Sort the cases by most frequently used first
145     case TOKassign:
146     case TOKplusplus:
147     case TOKminusminus:
148     case TOKdeclaration:
149     case TOKconstruct:
150     case TOKblit:
151     case TOKaddass:
152     case TOKminass:
153     case TOKcatass:
154     case TOKmulass:
155     case TOKdivass:
156     case TOKmodass:
157     case TOKshlass:
158     case TOKshrass:
159     case TOKushrass:
160     case TOKandass:
161     case TOKorass:
162     case TOKxorass:
163     case TOKpowass:
164     case TOKin:
165     case TOKremove:
166     case TOKassert:
167     case TOKhalt:
168     case TOKdelete:
169     case TOKnew:
170     case TOKnewanonclass:
171         return true;
172     case TOKcall:
173         {
174             CallExp ce = cast(CallExp)e;
175             /* Calling a function or delegate that is pure nothrow
176              * has no side effects.
177              */
178             if (ce.e1.type)
179             {
180                 Type t = ce.e1.type.toBasetype();
181                 if (t.ty == Tdelegate)
182                     t = (cast(TypeDelegate)t).next;
183                 if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0)
184                 {
185                 }
186                 else
187                     return true;
188             }
189             break;
190         }
191     case TOKcast:
192         {
193             CastExp ce = cast(CastExp)e;
194             /* if:
195              *  cast(classtype)func()  // because it may throw
196              */
197             if (ce.to.ty == Tclass && ce.e1.op == TOKcall && ce.e1.type.ty == Tclass)
198                 return true;
199             break;
200         }
201     default:
202         break;
203     }
204     return false;
205 }
206 
207 /***********************************
208  * The result of this expression will be discarded.
209  * Complain if the operation has no side effects (and hence is meaningless).
210  */
211 extern (C++) void discardValue(Expression e)
212 {
213     if (lambdaHasSideEffect(e)) // check side-effect shallowly
214         return;
215     switch (e.op)
216     {
217     case TOKcast:
218         {
219             CastExp ce = cast(CastExp)e;
220             if (ce.to.equals(Type.tvoid))
221             {
222                 /*
223                  * Don't complain about an expression with no effect if it was cast to void
224                  */
225                 return;
226             }
227             break; // complain
228         }
229     case TOKerror:
230         return;
231     case TOKvar:
232         {
233             VarDeclaration v = (cast(VarExp)e).var.isVarDeclaration();
234             if (v && (v.storage_class & STCtemp))
235             {
236                 // Bugzilla 5810: Don't complain about an internal generated variable.
237                 return;
238             }
239             break;
240         }
241     case TOKcall:
242         /* Issue 3882: */
243         if (global.params.warnings && !global.gag)
244         {
245             CallExp ce = cast(CallExp)e;
246             if (e.type.ty == Tvoid)
247             {
248                 /* Don't complain about calling void-returning functions with no side-effect,
249                  * because purity and nothrow are inferred, and because some of the
250                  * runtime library depends on it. Needs more investigation.
251                  *
252                  * One possible solution is to restrict this message to only be called in hierarchies that
253                  * never call assert (and or not called from inside unittest blocks)
254                  */
255             }
256             else if (ce.e1.type)
257             {
258                 Type t = ce.e1.type.toBasetype();
259                 if (t.ty == Tdelegate)
260                     t = (cast(TypeDelegate)t).next;
261                 if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0)
262                 {
263                     const(char)* s;
264                     if (ce.f)
265                         s = ce.f.toPrettyChars();
266                     else if (ce.e1.op == TOKstar)
267                     {
268                         // print 'fp' if ce->e1 is (*fp)
269                         s = (cast(PtrExp)ce.e1).e1.toChars();
270                     }
271                     else
272                         s = ce.e1.toChars();
273                     e.warning("calling %s without side effects discards return value of type %s, prepend a cast(void) if intentional", s, e.type.toChars());
274                 }
275             }
276         }
277         return;
278     case TOKscope:
279         e.error("%s has no effect", e.toChars());
280         return;
281     case TOKandand:
282         {
283             AndAndExp aae = cast(AndAndExp)e;
284             discardValue(aae.e2);
285             return;
286         }
287     case TOKoror:
288         {
289             OrOrExp ooe = cast(OrOrExp)e;
290             discardValue(ooe.e2);
291             return;
292         }
293     case TOKquestion:
294         {
295             CondExp ce = cast(CondExp)e;
296             /* Bugzilla 6178 & 14089: Either CondExp::e1 or e2 may have
297              * redundant expression to make those types common. For example:
298              *
299              *  struct S { this(int n); int v; alias v this; }
300              *  S[int] aa;
301              *  aa[1] = 0;
302              *
303              * The last assignment statement will be rewitten to:
304              *
305              *  1 in aa ? aa[1].value = 0 : (aa[1] = 0, aa[1].this(0)).value;
306              *
307              * The last DotVarExp is necessary to take assigned value.
308              *
309              *  int value = (aa[1] = 0);    // value = aa[1].value
310              *
311              * To avoid false error, discardValue() should be called only when
312              * the both tops of e1 and e2 have actually no side effects.
313              */
314             if (!lambdaHasSideEffect(ce.e1) && !lambdaHasSideEffect(ce.e2))
315             {
316                 discardValue(ce.e1);
317                 discardValue(ce.e2);
318             }
319             return;
320         }
321     case TOKcomma:
322         {
323             CommaExp ce = cast(CommaExp)e;
324             /* Check for compiler-generated code of the form  auto __tmp, e, __tmp;
325              * In such cases, only check e for side effect (it's OK for __tmp to have
326              * no side effect).
327              * See Bugzilla 4231 for discussion
328              */
329             CommaExp firstComma = ce;
330             while (firstComma.e1.op == TOKcomma)
331                 firstComma = cast(CommaExp)firstComma.e1;
332             if (firstComma.e1.op == TOKdeclaration && ce.e2.op == TOKvar && (cast(DeclarationExp)firstComma.e1).declaration == (cast(VarExp)ce.e2).var)
333             {
334                 return;
335             }
336             // Don't check e1 until we cast(void) the a,b code generation
337             //discardValue(ce->e1);
338             discardValue(ce.e2);
339             return;
340         }
341     case TOKtuple:
342         /* Pass without complaint if any of the tuple elements have side effects.
343          * Ideally any tuple elements with no side effects should raise an error,
344          * this needs more investigation as to what is the right thing to do.
345          */
346         if (!hasSideEffect(e))
347             break;
348         return;
349     default:
350         break;
351     }
352     e.error("%s has no effect in expression (%s)", Token.toChars(e.op), e.toChars());
353 }
354 
355 /**************************************************
356  * Build a temporary variable to copy the value of e into.
357  * Params:
358  *  stc = storage classes will be added to the made temporary variable
359  *  name = name for temporary variable
360  *  e = original expression
361  * Returns:
362  *  Newly created temporary variable.
363  */
364 VarDeclaration copyToTemp(StorageClass stc, const char* name, Expression e)
365 {
366     assert(name && name[0] == '_' && name[1] == '_');
367     auto id = Identifier.generateId(name);
368     auto ez = new ExpInitializer(e.loc, e);
369     auto vd = new VarDeclaration(e.loc, e.type, id, ez);
370     vd.storage_class = stc;
371     vd.storage_class |= STCtemp;
372     vd.storage_class |= STCctfe; // temporary is always CTFEable
373     return vd;
374 }
375 
376 /**************************************************
377  * Build a temporary variable to extract e's evaluation, if e is not trivial.
378  * Params:
379  *  sc = scope
380  *  name = name for temporary variable
381  *  e0 = a new side effect part will be appended to it.
382  *  e = original expression
383  *  alwaysCopy = if true, build new temporary variable even if e is trivial.
384  * Returns:
385  *  When e is trivial and alwaysCopy == false, e itself is returned.
386  *  Otherwise, a new VarExp is returned.
387  * Note:
388  *  e's lvalue-ness will be handled well by STCref or STCrvalue.
389  */
390 Expression extractSideEffect(Scope* sc, const char* name,
391     ref Expression e0, Expression e, bool alwaysCopy = false)
392 {
393     if (!alwaysCopy && isTrivialExp(e))
394         return e;
395 
396     auto vd = copyToTemp(0, name, e);
397     if (e.isLvalue())
398         vd.storage_class |= STCref;
399     else
400         vd.storage_class |= STCrvalue;
401 
402     Expression de = new DeclarationExp(vd.loc, vd);
403     Expression ve = new VarExp(vd.loc, vd);
404     de = de.semantic(sc);
405     ve = ve.semantic(sc);
406 
407     e0 = Expression.combine(e0, de);
408     return ve;
409 }