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