blob: 661bd43134c8ee5c2d44b818ab2fcafe56a3a9db [file] [log] [blame]
/* Compiler implementation of the D programming language
* Copyright (C) 1999-2021 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);
/**************************************************
* 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:
case TOKoror:
{
LogicalExp *aae = (LogicalExp *)e;
return discardValue(aae->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 = expressionSemantic(de, sc);
ve = expressionSemantic(ve, sc);
*e0 = Expression::combine(*e0, de);
return ve;
}