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 }