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