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 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 extern (C++) bool hasSideEffect(Expression e) 67 { 68 extern (C++) final class LambdaHasSideEffect : StoppableVisitor 69 { 70 alias visit = typeof(super).visit; 71 public: 72 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 stop = lambdaHasSideEffect(e); 80 } 81 } 82 83 scope LambdaHasSideEffect v = new LambdaHasSideEffect(); 84 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 if (f.isCtorDeclaration()) 100 return 0; 101 assert(f.type.ty == Tfunction); 102 TypeFunction tf = cast(TypeFunction)f.type; 103 if (tf.isnothrow) 104 { 105 PURE purity = f.isPure(); 106 if (purity == PURE.strong) 107 return 2; 108 if (purity == PURE.const_) 109 return 1; 110 } 111 return 0; 112 } 113 114 int callSideEffectLevel(Type t) 115 { 116 t = t.toBasetype(); 117 TypeFunction tf; 118 if (t.ty == Tdelegate) 119 tf = cast(TypeFunction)(cast(TypeDelegate)t).next; 120 else 121 { 122 assert(t.ty == Tfunction); 123 tf = cast(TypeFunction)t; 124 } 125 if (!tf.isnothrow) // function can throw 126 return 0; 127 128 tf.purityLevel(); 129 PURE purity = tf.purity; 130 if (t.ty == Tdelegate && purity > PURE.weak) 131 { 132 if (tf.isMutable()) 133 purity = PURE.weak; 134 else if (!tf.isImmutable()) 135 purity = PURE.const_; 136 } 137 138 if (purity == PURE.strong) 139 return 2; 140 if (purity == PURE.const_) 141 return 1; 142 return 0; 143 } 144 145 private bool lambdaHasSideEffect(Expression e) 146 { 147 switch (e.op) 148 { 149 // Sort the cases by most frequently used first 150 case TOK.assign: 151 case TOK.plusPlus: 152 case TOK.minusMinus: 153 case TOK.declaration: 154 case TOK.construct: 155 case TOK.blit: 156 case TOK.addAssign: 157 case TOK.minAssign: 158 case TOK.concatenateAssign: 159 case TOK.concatenateElemAssign: 160 case TOK.concatenateDcharAssign: 161 case TOK.mulAssign: 162 case TOK.divAssign: 163 case TOK.modAssign: 164 case TOK.leftShiftAssign: 165 case TOK.rightShiftAssign: 166 case TOK.unsignedRightShiftAssign: 167 case TOK.andAssign: 168 case TOK.orAssign: 169 case TOK.xorAssign: 170 case TOK.powAssign: 171 case TOK.in_: 172 case TOK.remove: 173 case TOK.assert_: 174 case TOK.halt: 175 case TOK.delete_: 176 case TOK.new_: 177 case TOK.newAnonymousClass: 178 return true; 179 case TOK.call: 180 { 181 CallExp ce = cast(CallExp)e; 182 /* Calling a function or delegate that is pure nothrow 183 * has no side effects. 184 */ 185 if (ce.e1.type) 186 { 187 Type t = ce.e1.type.toBasetype(); 188 if (t.ty == Tdelegate) 189 t = (cast(TypeDelegate)t).next; 190 if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0) 191 { 192 } 193 else 194 return true; 195 } 196 break; 197 } 198 case TOK.cast_: 199 { 200 CastExp ce = cast(CastExp)e; 201 /* if: 202 * cast(classtype)func() // because it may throw 203 */ 204 if (ce.to.ty == Tclass && ce.e1.op == TOK.call && ce.e1.type.ty == Tclass) 205 return true; 206 break; 207 } 208 default: 209 break; 210 } 211 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 if (lambdaHasSideEffect(e)) // check side-effect shallowly 223 return false; 224 switch (e.op) 225 { 226 case TOK.cast_: 227 { 228 CastExp ce = cast(CastExp)e; 229 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 return false; 235 } 236 break; // complain 237 } 238 case TOK.error: 239 return false; 240 case TOK.variable: 241 { 242 VarDeclaration v = (cast(VarExp)e).var.isVarDeclaration(); 243 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 return false; 248 } 249 break; 250 } 251 case TOK.call: 252 /* Issue 3882: */ 253 if (global.params.warnings != DiagnosticReporting.off && !global.gag) 254 { 255 CallExp ce = cast(CallExp)e; 256 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 else if (ce.e1.type) 267 { 268 Type t = ce.e1.type.toBasetype(); 269 if (t.ty == Tdelegate) 270 t = (cast(TypeDelegate)t).next; 271 if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0) 272 { 273 const(char)* s; 274 if (ce.f) 275 s = ce.f.toPrettyChars(); 276 else if (ce.e1.op == TOK.star) 277 { 278 // print 'fp' if ce.e1 is (*fp) 279 s = (cast(PtrExp)ce.e1).e1.toChars(); 280 } 281 else 282 s = ce.e1.toChars(); 283 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 return false; 288 case TOK.andAnd: 289 case TOK.orOr: 290 { 291 LogicalExp aae = cast(LogicalExp)e; 292 return discardValue(aae.e2); 293 } 294 case TOK.question: 295 { 296 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 if (!lambdaHasSideEffect(ce.e1) && !lambdaHasSideEffect(ce.e2)) 318 { 319 return discardValue(ce.e1) | 320 discardValue(ce.e2); 321 } 322 return false; 323 } 324 case TOK.comma: 325 { 326 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 auto fc = firstComma(ce); 333 if (fc.op == TOK.declaration && ce.e2.op == TOK.variable && (cast(DeclarationExp)fc).declaration == (cast(VarExp)ce.e2).var) 334 { 335 return false; 336 } 337 // Don't check e1 until we cast(void) the a,b code generation 338 //discardValue(ce.e1); 339 return discardValue(ce.e2); 340 } 341 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 if (!hasSideEffect(e)) 347 break; 348 return false; 349 default: 350 break; 351 } 352 e.error("`%s` has no effect", e.toChars()); 353 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 assert(name[0] == '_' && name[1] == '_'); 368 auto vd = new VarDeclaration(e.loc, e.type, 369 Identifier.generateId(name), 370 new ExpInitializer(e.loc, e)); 371 vd.storage_class = stc | STC.temp | STC.ctfe; // temporary is always CTFEable 372 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 if (!alwaysCopy && 401 ((sc.flags & SCOPE.ctfe) ? !hasSideEffect(e) : isTrivialExp(e))) 402 return e; 403 404 auto vd = copyToTemp(0, name, e); 405 vd.storage_class |= e.isLvalue() ? STC.ref_ : STC.rvalue; 406 407 e0 = Expression.combine(e0, new DeclarationExp(vd.loc, vd) 408 .expressionSemantic(sc)); 409 410 return new VarExp(vd.loc, vd) 411 .expressionSemantic(sc); 412 }