| /** | |
| * Find side-effects of expressions. | |
| * | |
| * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved | |
| * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) | |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) | |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/sideeffect.d, _sideeffect.d) | |
| * Documentation: https://dlang.org/phobos/dmd_sideeffect.html | |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/sideeffect.d | |
| */ | |
| module dmd.sideeffect; | |
| import dmd.apply; | |
| import dmd.astenums; | |
| import dmd.declaration; | |
| import dmd.dscope; | |
| import dmd.expression; | |
| import dmd.expressionsem; | |
| import dmd.func; | |
| import dmd.globals; | |
| import dmd.identifier; | |
| import dmd.init; | |
| import dmd.mtype; | |
| import dmd.tokens; | |
| import dmd.visitor; | |
| /************************************************** | |
| * Front-end expression rewriting should create temporary variables for | |
| * non trivial sub-expressions in order to: | |
| * 1. save evaluation order | |
| * 2. prevent sharing of sub-expression in AST | |
| */ | |
| extern (C++) bool isTrivialExp(Expression e) | |
| { | |
| extern (C++) final class IsTrivialExp : StoppableVisitor | |
| { | |
| alias visit = typeof(super).visit; | |
| public: | |
| extern (D) this() | |
| { | |
| } | |
| override void visit(Expression e) | |
| { | |
| /* https://issues.dlang.org/show_bug.cgi?id=11201 | |
| * CallExp is always non trivial expression, | |
| * especially for inlining. | |
| */ | |
| if (e.op == EXP.call) | |
| { | |
| stop = true; | |
| return; | |
| } | |
| // stop walking if we determine this expression has side effects | |
| stop = lambdaHasSideEffect(e); | |
| } | |
| } | |
| scope IsTrivialExp v = new IsTrivialExp(); | |
| return walkPostorder(e, v) == false; | |
| } | |
| /******************************************** | |
| * Determine if Expression has any side effects. | |
| * | |
| * Params: | |
| * e = the expression | |
| * assumeImpureCalls = whether function calls should always be assumed to | |
| * be impure (e.g. debug is allowed to violate purity) | |
| */ | |
| extern (C++) bool hasSideEffect(Expression e, bool assumeImpureCalls = false) | |
| { | |
| extern (C++) final class LambdaHasSideEffect : StoppableVisitor | |
| { | |
| alias visit = typeof(super).visit; | |
| public: | |
| extern (D) this() | |
| { | |
| } | |
| override void visit(Expression e) | |
| { | |
| // stop walking if we determine this expression has side effects | |
| stop = lambdaHasSideEffect(e, assumeImpureCalls); | |
| } | |
| } | |
| scope LambdaHasSideEffect v = new LambdaHasSideEffect(); | |
| return walkPostorder(e, v); | |
| } | |
| /******************************************** | |
| * Determine if the call of f, or function type or delegate type t1, has any side effects. | |
| * Returns: | |
| * 0 has any side effects | |
| * 1 nothrow + strongly pure | |
| * 2 nothrow + strongly pure + only immutable indirections in the return | |
| * type | |
| */ | |
| int callSideEffectLevel(FuncDeclaration f) | |
| { | |
| /* https://issues.dlang.org/show_bug.cgi?id=12760 | |
| * ctor call always has side effects. | |
| */ | |
| if (f.isCtorDeclaration()) | |
| return 0; | |
| assert(f.type.ty == Tfunction); | |
| TypeFunction tf = cast(TypeFunction)f.type; | |
| if (!tf.isnothrow) | |
| return 0; | |
| final switch (f.isPure()) | |
| { | |
| case PURE.impure: | |
| case PURE.fwdref: | |
| case PURE.weak: | |
| return 0; | |
| case PURE.const_: | |
| return mutabilityOfType(tf.isref, tf.next) == 2 ? 2 : 1; | |
| } | |
| } | |
| int callSideEffectLevel(Type t) | |
| { | |
| t = t.toBasetype(); | |
| TypeFunction tf; | |
| if (t.ty == Tdelegate) | |
| tf = cast(TypeFunction)(cast(TypeDelegate)t).next; | |
| else | |
| { | |
| assert(t.ty == Tfunction); | |
| tf = cast(TypeFunction)t; | |
| } | |
| if (!tf.isnothrow) // function can throw | |
| return 0; | |
| tf.purityLevel(); | |
| PURE purity = tf.purity; | |
| if (t.ty == Tdelegate && purity > PURE.weak) | |
| { | |
| if (tf.isMutable()) | |
| purity = PURE.weak; | |
| else if (!tf.isImmutable()) | |
| purity = PURE.const_; | |
| } | |
| if (purity == PURE.const_) | |
| return mutabilityOfType(tf.isref, tf.next) == 2 ? 2 : 1; | |
| return 0; | |
| } | |
| private bool lambdaHasSideEffect(Expression e, bool assumeImpureCalls = false) | |
| { | |
| switch (e.op) | |
| { | |
| // Sort the cases by most frequently used first | |
| case EXP.assign: | |
| case EXP.plusPlus: | |
| case EXP.minusMinus: | |
| case EXP.declaration: | |
| case EXP.construct: | |
| case EXP.blit: | |
| case EXP.addAssign: | |
| case EXP.minAssign: | |
| case EXP.concatenateAssign: | |
| case EXP.concatenateElemAssign: | |
| case EXP.concatenateDcharAssign: | |
| case EXP.mulAssign: | |
| case EXP.divAssign: | |
| case EXP.modAssign: | |
| case EXP.leftShiftAssign: | |
| case EXP.rightShiftAssign: | |
| case EXP.unsignedRightShiftAssign: | |
| case EXP.andAssign: | |
| case EXP.orAssign: | |
| case EXP.xorAssign: | |
| case EXP.powAssign: | |
| case EXP.in_: | |
| case EXP.remove: | |
| case EXP.assert_: | |
| case EXP.halt: | |
| case EXP.throw_: | |
| case EXP.delete_: | |
| case EXP.new_: | |
| case EXP.newAnonymousClass: | |
| return true; | |
| case EXP.call: | |
| { | |
| if (assumeImpureCalls) | |
| return true; | |
| if (e.type && e.type.ty == Tnoreturn) | |
| return true; | |
| CallExp ce = cast(CallExp)e; | |
| /* Calling a function or delegate that is pure nothrow | |
| * has no side effects. | |
| */ | |
| if (ce.e1.type) | |
| { | |
| Type t = ce.e1.type.toBasetype(); | |
| if (t.ty == Tdelegate) | |
| t = (cast(TypeDelegate)t).next; | |
| if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0) | |
| { | |
| } | |
| else | |
| return true; | |
| } | |
| break; | |
| } | |
| case EXP.cast_: | |
| { | |
| CastExp ce = cast(CastExp)e; | |
| /* if: | |
| * cast(classtype)func() // because it may throw | |
| */ | |
| if (ce.to.ty == Tclass && ce.e1.op == EXP.call && ce.e1.type.ty == Tclass) | |
| return true; | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| return false; | |
| } | |
| /*********************************** | |
| * The result of this expression will be discarded. | |
| * Print error messages if the operation has no side effects (and hence is meaningless). | |
| * Returns: | |
| * true if expression has no side effects | |
| */ | |
| bool discardValue(Expression e) | |
| { | |
| if (lambdaHasSideEffect(e)) // check side-effect shallowly | |
| return false; | |
| switch (e.op) | |
| { | |
| case EXP.cast_: | |
| { | |
| CastExp ce = cast(CastExp)e; | |
| if (ce.to.equals(Type.tvoid)) | |
| { | |
| /* | |
| * Don't complain about an expression with no effect if it was cast to void | |
| */ | |
| return false; | |
| } | |
| break; // complain | |
| } | |
| case EXP.error: | |
| return false; | |
| case EXP.variable: | |
| { | |
| VarDeclaration v = (cast(VarExp)e).var.isVarDeclaration(); | |
| if (v && (v.storage_class & STC.temp)) | |
| { | |
| // https://issues.dlang.org/show_bug.cgi?id=5810 | |
| // Don't complain about an internal generated variable. | |
| return false; | |
| } | |
| break; | |
| } | |
| case EXP.call: | |
| /* Issue 3882: */ | |
| if (global.params.warnings != DiagnosticReporting.off && !global.gag) | |
| { | |
| CallExp ce = cast(CallExp)e; | |
| if (e.type.ty == Tvoid) | |
| { | |
| /* Don't complain about calling void-returning functions with no side-effect, | |
| * because purity and nothrow are inferred, and because some of the | |
| * runtime library depends on it. Needs more investigation. | |
| * | |
| * One possible solution is to restrict this message to only be called in hierarchies that | |
| * never call assert (and or not called from inside unittest blocks) | |
| */ | |
| } | |
| else if (ce.e1.type) | |
| { | |
| Type t = ce.e1.type.toBasetype(); | |
| if (t.ty == Tdelegate) | |
| t = (cast(TypeDelegate)t).next; | |
| if (t.ty == Tfunction && (ce.f ? callSideEffectLevel(ce.f) : callSideEffectLevel(ce.e1.type)) > 0) | |
| { | |
| const(char)* s; | |
| if (ce.f) | |
| s = ce.f.toPrettyChars(); | |
| else if (ce.e1.op == EXP.star) | |
| { | |
| // print 'fp' if ce.e1 is (*fp) | |
| s = (cast(PtrExp)ce.e1).e1.toChars(); | |
| } | |
| else | |
| s = ce.e1.toChars(); | |
| e.warning("calling `%s` without side effects discards return value of type `%s`; prepend a `cast(void)` if intentional", s, e.type.toChars()); | |
| } | |
| } | |
| } | |
| return false; | |
| case EXP.andAnd: | |
| case EXP.orOr: | |
| { | |
| LogicalExp aae = cast(LogicalExp)e; | |
| return discardValue(aae.e2); | |
| } | |
| case EXP.question: | |
| { | |
| CondExp ce = cast(CondExp)e; | |
| /* https://issues.dlang.org/show_bug.cgi?id=6178 | |
| * https://issues.dlang.org/show_bug.cgi?id=14089 | |
| * Either CondExp::e1 or e2 may have | |
| * redundant expression to make those types common. For example: | |
| * | |
| * struct S { this(int n); int v; alias v this; } | |
| * S[int] aa; | |
| * aa[1] = 0; | |
| * | |
| * The last assignment statement will be rewitten to: | |
| * | |
| * 1 in aa ? aa[1].value = 0 : (aa[1] = 0, aa[1].this(0)).value; | |
| * | |
| * The last DotVarExp is necessary to take assigned value. | |
| * | |
| * int value = (aa[1] = 0); // value = aa[1].value | |
| * | |
| * To avoid false error, discardValue() should be called only when | |
| * the both tops of e1 and e2 have actually no side effects. | |
| */ | |
| if (!lambdaHasSideEffect(ce.e1) && !lambdaHasSideEffect(ce.e2)) | |
| { | |
| return discardValue(ce.e1) | | |
| discardValue(ce.e2); | |
| } | |
| return false; | |
| } | |
| case EXP.comma: | |
| { | |
| CommaExp ce = cast(CommaExp)e; | |
| // Don't complain about compiler-generated comma expressions | |
| if (ce.isGenerated) | |
| return false; | |
| // Don't check e1 until we cast(void) the a,b code generation. | |
| // This is concretely done in expressionSemantic, if a CommaExp has Tvoid as type | |
| return discardValue(ce.e2); | |
| } | |
| case EXP.tuple: | |
| /* Pass without complaint if any of the tuple elements have side effects. | |
| * Ideally any tuple elements with no side effects should raise an error, | |
| * this needs more investigation as to what is the right thing to do. | |
| */ | |
| if (!hasSideEffect(e)) | |
| break; | |
| return false; | |
| default: | |
| break; | |
| } | |
| e.error("`%s` has no effect", e.toChars()); | |
| return true; | |
| } | |
| /************************************************** | |
| * Build a temporary variable to copy the value of e into. | |
| * Params: | |
| * stc = storage classes will be added to the made temporary variable | |
| * name = name for temporary variable | |
| * e = original expression | |
| * Returns: | |
| * Newly created temporary variable. | |
| */ | |
| VarDeclaration copyToTemp(StorageClass stc, const char[] name, Expression e) | |
| { | |
| assert(name[0] == '_' && name[1] == '_'); | |
| auto vd = new VarDeclaration(e.loc, e.type, | |
| Identifier.generateId(name), | |
| new ExpInitializer(e.loc, e)); | |
| vd.storage_class = stc | STC.temp | STC.ctfe; // temporary is always CTFEable | |
| return vd; | |
| } | |
| /************************************************** | |
| * Build a temporary variable to extract e's evaluation, if e is not trivial. | |
| * Params: | |
| * sc = scope | |
| * name = name for temporary variable | |
| * e0 = a new side effect part will be appended to it. | |
| * e = original expression | |
| * alwaysCopy = if true, build new temporary variable even if e is trivial. | |
| * Returns: | |
| * When e is trivial and alwaysCopy == false, e itself is returned. | |
| * Otherwise, a new VarExp is returned. | |
| * Note: | |
| * e's lvalue-ness will be handled well by STC.ref_ or STC.rvalue. | |
| */ | |
| Expression extractSideEffect(Scope* sc, const char[] name, | |
| ref Expression e0, Expression e, bool alwaysCopy = false) | |
| { | |
| //printf("extractSideEffect(e: %s)\n", e.toChars()); | |
| /* The trouble here is that if CTFE is running, extracting the side effect | |
| * results in an assignment, and then the interpreter says it cannot evaluate the | |
| * side effect assignment variable. But we don't have to worry about side | |
| * effects in function calls anyway, because then they won't CTFE. | |
| * https://issues.dlang.org/show_bug.cgi?id=17145 | |
| */ | |
| if (!alwaysCopy && | |
| ((sc.flags & SCOPE.ctfe) ? !hasSideEffect(e) : isTrivialExp(e))) | |
| return e; | |
| auto vd = copyToTemp(0, name, e); | |
| vd.storage_class |= e.isLvalue() ? STC.ref_ : STC.rvalue; | |
| e0 = Expression.combine(e0, new DeclarationExp(vd.loc, vd) | |
| .expressionSemantic(sc)); | |
| return new VarExp(vd.loc, vd) | |
| .expressionSemantic(sc); | |
| } |