ldc/ddmd/sideeffect.d
2016-02-14 19:35:40 +01:00

349 lines
10 KiB
D

// Compiler implementation of the D programming language
// Copyright (c) 1999-2015 by Digital Mars
// 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
module ddmd.sideeffect;
import ddmd.apply;
import ddmd.declaration;
import ddmd.expression;
import ddmd.func;
import ddmd.globals;
import ddmd.mtype;
import ddmd.tokens;
import ddmd.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 = super.visit;
public:
extern (D) this()
{
}
override 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);
}
}
scope IsTrivialExp v = new IsTrivialExp();
return walkPostorder(e, v) == false;
}
/********************************************
* Determine if Expression has any side effects.
*/
extern (C++) bool hasSideEffect(Expression e)
{
extern (C++) final class LambdaHasSideEffect : StoppableVisitor
{
alias visit = super.visit;
public:
extern (D) this()
{
}
override void visit(Expression e)
{
// stop walking if we determine this expression has side effects
stop = lambdaHasSideEffect(e);
}
}
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 + constant purity
* 2 nothrow + strong purity
*/
extern (C++) int callSideEffectLevel(FuncDeclaration f)
{
/* Bugzilla 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)
{
PURE purity = f.isPure();
if (purity == PUREstrong)
return 2;
if (purity == PUREconst)
return 1;
}
return 0;
}
extern (C++) 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;
}
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;
}
extern (C++) 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 = 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 TOKcast:
{
CastExp ce = cast(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.
* Complain if the operation has no side effects (and hence is meaningless).
*/
extern (C++) void discardValue(Expression e)
{
if (lambdaHasSideEffect(e)) // check side-effect shallowly
return;
switch (e.op)
{
case TOKcast:
{
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;
}
break;
// complain
}
case TOKerror:
return;
case TOKvar:
{
VarDeclaration v = (cast(VarExp)e).var.isVarDeclaration();
if (v && (v.storage_class & STCtemp))
{
// Bugzilla 5810: Don't complain about an internal generated variable.
return;
}
break;
}
case TOKcall:
/* Issue 3882: */
if (global.params.warnings && !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 == TOKstar)
{
// 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;
case TOKscope:
e.error("%s has no effect", e.toChars());
return;
case TOKandand:
{
AndAndExp aae = cast(AndAndExp)e;
discardValue(aae.e2);
return;
}
case TOKoror:
{
OrOrExp ooe = cast(OrOrExp)e;
discardValue(ooe.e2);
return;
}
case TOKquestion:
{
CondExp ce = cast(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))
{
discardValue(ce.e1);
discardValue(ce.e2);
}
return;
}
case TOKcomma:
{
CommaExp ce = cast(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 = cast(CommaExp)firstComma.e1;
if (firstComma.e1.op == TOKdeclaration && ce.e2.op == TOKvar && (cast(DeclarationExp)firstComma.e1).declaration == (cast(VarExp)ce.e2).var)
{
return;
}
// Don't check e1 until we cast(void) the a,b code generation
//discardValue(ce->e1);
discardValue(ce.e2);
return;
}
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;
default:
break;
}
e.error("%s has no effect in expression (%s)", Token.toChars(e.op), e.toChars());
}