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