/* Compiler implementation of the D programming language * Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved * written by Walter Bright * http://www.digitalmars.com * Distributed under the Boost Software License, Version 1.0. * http://www.boost.org/LICENSE_1_0.txt * https://github.com/D-Programming-Language/dmd/blob/master/src/sideeffect.c */ #include "root/dsystem.h" #include "mars.h" #include "init.h" #include "expression.h" #include "template.h" #include "statement.h" #include "mtype.h" #include "utf.h" #include "declaration.h" #include "aggregate.h" #include "scope.h" #include "attrib.h" #include "tokens.h" bool walkPostorder(Expression *e, StoppableVisitor *v); bool lambdaHasSideEffect(Expression *e); Expression *semantic(Expression *e, Scope *sc); /************************************************** * 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 */ bool isTrivialExp(Expression *e) { class IsTrivialExp : public StoppableVisitor { public: IsTrivialExp() {} void visit(Expression *e) { /* Bugzilla 11201: CallExp is always non trivial expression, * especially for inlining. */ if (e->op == TOKcall) { stop = true; return; } // stop walking if we determine this expression has side effects stop = lambdaHasSideEffect(e); } }; IsTrivialExp v; return walkPostorder(e, &v) == false; } /******************************************** * Determine if Expression has any side effects. */ bool hasSideEffect(Expression *e) { class LambdaHasSideEffect : public StoppableVisitor { public: LambdaHasSideEffect() {} void visit(Expression *e) { // stop walking if we determine this expression has side effects stop = lambdaHasSideEffect(e); } }; LambdaHasSideEffect v; 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 + constant purity * 2 nothrow + strong purity */ int callSideEffectLevel(FuncDeclaration *f) { /* Bugzilla 12760: ctor call always has side effects. */ if (f->isCtorDeclaration()) return 0; assert(f->type->ty == Tfunction); TypeFunction *tf = (TypeFunction *)f->type; if (tf->isnothrow) { PURE purity = f->isPure(); if (purity == PUREstrong) return 2; if (purity == PUREconst) return 1; } return 0; } int callSideEffectLevel(Type *t) { t = t->toBasetype(); TypeFunction *tf; if (t->ty == Tdelegate) tf = (TypeFunction *)((TypeDelegate *)t)->next; else { assert(t->ty == Tfunction); tf = (TypeFunction *)t; } tf->purityLevel(); PURE purity = tf->purity; if (t->ty == Tdelegate && purity > PUREweak) { if (tf->isMutable()) purity = PUREweak; else if (!tf->isImmutable()) purity = PUREconst; } if (tf->isnothrow) { if (purity == PUREstrong) return 2; if (purity == PUREconst) return 1; } return 0; } bool lambdaHasSideEffect(Expression *e) { switch (e->op) { // Sort the cases by most frequently used first case TOKassign: case TOKplusplus: case TOKminusminus: case TOKdeclaration: case TOKconstruct: case TOKblit: case TOKaddass: case TOKminass: case TOKcatass: case TOKmulass: case TOKdivass: case TOKmodass: case TOKshlass: case TOKshrass: case TOKushrass: case TOKandass: case TOKorass: case TOKxorass: case TOKpowass: case TOKin: case TOKremove: case TOKassert: case TOKhalt: case TOKdelete: case TOKnew: case TOKnewanonclass: return true; case TOKcall: { CallExp *ce = (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 = ((TypeDelegate *)t)->next; if (t->ty == Tfunction && (ce->f ? callSideEffectLevel(ce->f) : callSideEffectLevel(ce->e1->type)) > 0) { } else return true; } break; } case TOKcast: { CastExp *ce = (CastExp *)e; /* if: * cast(classtype)func() // because it may throw */ if (ce->to->ty == Tclass && ce->e1->op == TOKcall && 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 TOKcast: { CastExp *ce = (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 TOKerror: return false; case TOKvar: { VarDeclaration *v = ((VarExp *)e)->var->isVarDeclaration(); if (v && (v->storage_class & STCtemp)) { // Bugzilla 5810: Don't complain about an internal generated variable. return false; } break; } case TOKcall: /* Issue 3882: */ if (global.params.warnings != DIAGNOSTICoff && !global.gag) { CallExp *ce = (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 = ((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 == TOKstar) { // print 'fp' if ce->e1 is (*fp) s = ((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 TOKscope: e->error("%s has no effect", e->toChars()); return true; case TOKandand: { AndAndExp *aae = (AndAndExp *)e; return discardValue(aae->e2); } case TOKoror: { OrOrExp *ooe = (OrOrExp *)e; return discardValue(ooe->e2); } case TOKquestion: { CondExp *ce = (CondExp *)e; /* Bugzilla 6178 & 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 TOKcomma: { CommaExp *ce = (CommaExp *)e; /* Check for compiler-generated code of the form auto __tmp, e, __tmp; * In such cases, only check e for side effect (it's OK for __tmp to have * no side effect). * See Bugzilla 4231 for discussion */ CommaExp *firstComma = ce; while (firstComma->e1->op == TOKcomma) firstComma = (CommaExp *)firstComma->e1; if (firstComma->e1->op == TOKdeclaration && ce->e2->op == TOKvar && ((DeclarationExp *)firstComma->e1)->declaration == ((VarExp*)ce->e2)->var) { return false; } // Don't check e1 until we cast(void) the a,b code generation //discardValue(ce->e1); return discardValue(ce->e2); } case TOKtuple: /* 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 in expression (%s)", Token::toChars(e->op), 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 && name[0] == '_' && name[1] == '_'); Identifier *id = Identifier::generateId(name); ExpInitializer *ez = new ExpInitializer(e->loc, e); VarDeclaration *vd = new VarDeclaration(e->loc, e->type, id, ez); vd->storage_class = stc; vd->storage_class |= STCtemp; vd->storage_class |= STCctfe; // 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 STCref or STCrvalue. */ Expression *extractSideEffect(Scope *sc, const char *name, Expression **e0, Expression *e, bool alwaysCopy = false) { if (!alwaysCopy && isTrivialExp(e)) return e; VarDeclaration *vd = copyToTemp(0, name, e); if (e->isLvalue()) vd->storage_class |= STCref; else vd->storage_class |= STCrvalue; Expression *de = new DeclarationExp(vd->loc, vd); Expression *ve = new VarExp(vd->loc, vd); de = semantic(de, sc); ve = semantic(ve, sc); *e0 = Expression::combine(*e0, de); return ve; }