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