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 }