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