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