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 1
        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 1
            if (e.op == TOK.call)
50
            {
51 1
                stop = true;
52 1
                return;
53
            }
54
            // stop walking if we determine this expression has side effects
55 1
            stop = lambdaHasSideEffect(e);
56
        }
57
    }
58

59 1
    scope IsTrivialExp v = new IsTrivialExp();
60 1
    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 1
        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 1
            stop = lambdaHasSideEffect(e);
80
        }
81
    }
82

83 1
    scope LambdaHasSideEffect v = new LambdaHasSideEffect();
84 1
    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 1
    if (f.isCtorDeclaration())
100 1
        return 0;
101 1
    assert(f.type.ty == Tfunction);
102 1
    TypeFunction tf = cast(TypeFunction)f.type;
103 1
    if (tf.isnothrow)
104
    {
105 1
        PURE purity = f.isPure();
106 1
        if (purity == PURE.strong)
107 1
            return 2;
108 1
        if (purity == PURE.const_)
109 1
            return 1;
110
    }
111 1
    return 0;
112
}
113

114
int callSideEffectLevel(Type t)
115
{
116 1
    t = t.toBasetype();
117 1
    TypeFunction tf;
118 1
    if (t.ty == Tdelegate)
119 1
        tf = cast(TypeFunction)(cast(TypeDelegate)t).next;
120
    else
121
    {
122 1
        assert(t.ty == Tfunction);
123 1
        tf = cast(TypeFunction)t;
124
    }
125 1
    if (!tf.isnothrow)  // function can throw
126 1
        return 0;
127

128 1
    tf.purityLevel();
129 1
    PURE purity = tf.purity;
130 1
    if (t.ty == Tdelegate && purity > PURE.weak)
131
    {
132 1
        if (tf.isMutable())
133 1
            purity = PURE.weak;
134 0
        else if (!tf.isImmutable())
135 0
            purity = PURE.const_;
136
    }
137

138 1
    if (purity == PURE.strong)
139 1
        return 2;
140 1
    if (purity == PURE.const_)
141 1
        return 1;
142 1
    return 0;
143
}
144

145
private bool lambdaHasSideEffect(Expression e)
146
{
147 1
    switch (e.op)
148
    {
149
    // Sort the cases by most frequently used first
150 1
    case TOK.assign:
151 1
    case TOK.plusPlus:
152 1
    case TOK.minusMinus:
153 1
    case TOK.declaration:
154 1
    case TOK.construct:
155 1
    case TOK.blit:
156 1
    case TOK.addAssign:
157 1
    case TOK.minAssign:
158 1
    case TOK.concatenateAssign:
159 1
    case TOK.concatenateElemAssign:
160 1
    case TOK.concatenateDcharAssign:
161 1
    case TOK.mulAssign:
162 1
    case TOK.divAssign:
163 1
    case TOK.modAssign:
164 1
    case TOK.leftShiftAssign:
165 1
    case TOK.rightShiftAssign:
166 1
    case TOK.unsignedRightShiftAssign:
167 1
    case TOK.andAssign:
168 1
    case TOK.orAssign:
169 1
    case TOK.xorAssign:
170 1
    case TOK.powAssign:
171 1
    case TOK.in_:
172 1
    case TOK.remove:
173 1
    case TOK.assert_:
174 1
    case TOK.halt:
175 1
    case TOK.delete_:
176 1
    case TOK.new_:
177 1
    case TOK.newAnonymousClass:
178 1
        return true;
179 1
    case TOK.call:
180
        {
181 1
            CallExp ce = cast(CallExp)e;
182
            /* Calling a function or delegate that is pure nothrow
183
             * has no side effects.
184
             */
185 1
            if (ce.e1.type)
186
            {
187 1
                Type t = ce.e1.type.toBasetype();
188 1
                if (t.ty == Tdelegate)
189 1
                    t = (cast(TypeDelegate)t).next;
190 1
                if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0)
191
                {
192
                }
193
                else
194 1
                    return true;
195
            }
196 1
            break;
197
        }
198 1
    case TOK.cast_:
199
        {
200 1
            CastExp ce = cast(CastExp)e;
201
            /* if:
202
             *  cast(classtype)func()  // because it may throw
203
             */
204 1
            if (ce.to.ty == Tclass && ce.e1.op == TOK.call && ce.e1.type.ty == Tclass)
205 1
                return true;
206 1
            break;
207
        }
208 1
    default:
209 1
        break;
210
    }
211 1
    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 1
    if (lambdaHasSideEffect(e)) // check side-effect shallowly
223 1
        return false;
224 1
    switch (e.op)
225
    {
226 1
    case TOK.cast_:
227
        {
228 1
            CastExp ce = cast(CastExp)e;
229 1
            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 1
                return false;
235
            }
236 0
            break; // complain
237
        }
238 1
    case TOK.error:
239 1
        return false;
240 1
    case TOK.variable:
241
        {
242 1
            VarDeclaration v = (cast(VarExp)e).var.isVarDeclaration();
243 1
            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 1
                return false;
248
            }
249 1
            break;
250
        }
251 1
    case TOK.call:
252
        /* Issue 3882: */
253 1
        if (global.params.warnings != DiagnosticReporting.off && !global.gag)
254
        {
255 1
            CallExp ce = cast(CallExp)e;
256 1
            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 1
            else if (ce.e1.type)
267
            {
268 1
                Type t = ce.e1.type.toBasetype();
269 1
                if (t.ty == Tdelegate)
270 0
                    t = (cast(TypeDelegate)t).next;
271 1
                if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0)
272
                {
273 1
                    const(char)* s;
274 1
                    if (ce.f)
275 1
                        s = ce.f.toPrettyChars();
276 1
                    else if (ce.e1.op == TOK.star)
277
                    {
278
                        // print 'fp' if ce.e1 is (*fp)
279 1
                        s = (cast(PtrExp)ce.e1).e1.toChars();
280
                    }
281
                    else
282 0
                        s = ce.e1.toChars();
283 1
                    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 1
        return false;
288 1
    case TOK.andAnd:
289 1
    case TOK.orOr:
290
        {
291 1
            LogicalExp aae = cast(LogicalExp)e;
292 1
            return discardValue(aae.e2);
293
        }
294 1
    case TOK.question:
295
        {
296 1
            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 1
            if (!lambdaHasSideEffect(ce.e1) && !lambdaHasSideEffect(ce.e2))
318
            {
319 1
                return discardValue(ce.e1) |
320
                       discardValue(ce.e2);
321
            }
322 1
            return false;
323
        }
324 1
    case TOK.comma:
325
        {
326 1
            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 1
            auto fc = firstComma(ce);
333 1
            if (fc.op == TOK.declaration && ce.e2.op == TOK.variable && (cast(DeclarationExp)fc).declaration == (cast(VarExp)ce.e2).var)
334
            {
335 1
                return false;
336
            }
337
            // Don't check e1 until we cast(void) the a,b code generation.
338
            // This is concretely done in expressionSemantic, if a CommaExp has Tvoid as type
339 1
            return discardValue(ce.e2);
340
        }
341 1
    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 1
        if (!hasSideEffect(e))
347 1
            break;
348 1
        return false;
349 1
    default:
350 1
        break;
351
    }
352 1
    e.error("`%s` has no effect", e.toChars());
353 1
    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 1
    assert(name[0] == '_' && name[1] == '_');
368 1
    auto vd = new VarDeclaration(e.loc, e.type,
369
        Identifier.generateId(name),
370
        new ExpInitializer(e.loc, e));
371 1
    vd.storage_class = stc | STC.temp | STC.ctfe; // temporary is always CTFEable
372 1
    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 1
    if (!alwaysCopy &&
401 1
        ((sc.flags & SCOPE.ctfe) ? !hasSideEffect(e) : isTrivialExp(e)))
402 1
        return e;
403

404 1
    auto vd = copyToTemp(0, name, e);
405 1
    vd.storage_class |= e.isLvalue() ? STC.ref_ : STC.rvalue;
406

407 1
    e0 = Expression.combine(e0, new DeclarationExp(vd.loc, vd)
408
                                .expressionSemantic(sc));
409

410 1
    return new VarExp(vd.loc, vd)
411
           .expressionSemantic(sc);
412
}

Read our documentation on viewing source code .

Loading