mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-08 11:56:12 +03:00
349 lines
10 KiB
D
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());
|
|
}
|