blob: a3953af6ce87435887837029d660ce0e05fa4707 [file] [log] [blame]
/**
* Manage flow analysis for constructors.
*
* Copyright: Copyright (C) 1999-2023 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/ctorflow.d, _ctorflow.d)
* Documentation: https://dlang.org/phobos/dmd_ctorflow.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/ctorflow.d
*/
module dmd.ctorflow;
import core.stdc.stdio;
import dmd.root.rmem;
import dmd.location;
enum CSX : ushort
{
none = 0,
this_ctor = 0x01, /// called this()
super_ctor = 0x02, /// called super()
label = 0x04, /// seen a label
return_ = 0x08, /// seen a return statement
any_ctor = 0x10, /// either this() or super() was called
halt = 0x20, /// assert(0)
}
/// Individual field in the Ctor with information about its callees and location.
struct FieldInit
{
CSX csx; /// information about the field's callees
Loc loc; /// location of the field initialization
}
/***********
* Primitive flow analysis for constructors
*/
struct CtorFlow
{
CSX callSuper; /// state of calling other constructors
FieldInit[] fieldinit; /// state of field initializations
void allocFieldinit(size_t dim)
{
fieldinit = (cast(FieldInit*)mem.xcalloc(FieldInit.sizeof, dim))[0 .. dim];
}
void freeFieldinit()
{
if (fieldinit.ptr)
mem.xfree(fieldinit.ptr);
fieldinit = null;
}
/***********************
* Create a deep copy of `this`
* Returns:
* a copy
*/
CtorFlow clone()
{
return CtorFlow(callSuper, fieldinit.arraydup);
}
/**********************************
* Set CSX bits in flow analysis state
* Params:
* csx = bits to set
*/
void orCSX(CSX csx) nothrow pure
{
callSuper |= csx;
foreach (ref u; fieldinit)
u.csx |= csx;
}
/******************************
* OR CSX bits to `this`
* Params:
* ctorflow = bits to OR in
*/
void OR(const ref CtorFlow ctorflow) pure nothrow
{
callSuper |= ctorflow.callSuper;
if (fieldinit.length && ctorflow.fieldinit.length)
{
assert(fieldinit.length == ctorflow.fieldinit.length);
foreach (i, u; ctorflow.fieldinit)
{
auto fi = &fieldinit[i];
fi.csx |= u.csx;
if (fi.loc is Loc.init)
fi.loc = u.loc;
}
}
}
}
/****************************************
* Merge `b` flow analysis results into `a`.
* Params:
* a = the path to merge `b` into
* b = the other path
* Returns:
* false means one of the paths skips construction
*/
bool mergeCallSuper(ref CSX a, const CSX b) pure nothrow
{
// This does a primitive flow analysis to support the restrictions
// regarding when and how constructors can appear.
// It merges the results of two paths.
// The two paths are `a` and `b`; the result is merged into `a`.
if (b == a)
return true;
// Have ALL branches called a constructor?
const aAll = (a & (CSX.this_ctor | CSX.super_ctor)) != 0;
const bAll = (b & (CSX.this_ctor | CSX.super_ctor)) != 0;
// Have ANY branches called a constructor?
const aAny = (a & CSX.any_ctor) != 0;
const bAny = (b & CSX.any_ctor) != 0;
// Have any branches returned?
const aRet = (a & CSX.return_) != 0;
const bRet = (b & CSX.return_) != 0;
// Have any branches halted?
const aHalt = (a & CSX.halt) != 0;
const bHalt = (b & CSX.halt) != 0;
if (aHalt && bHalt)
{
a = CSX.halt;
}
else if ((!bHalt && bRet && !bAny && aAny) || (!aHalt && aRet && !aAny && bAny))
{
// If one has returned without a constructor call, there must not
// be ctor calls in the other.
return false;
}
else if (bHalt || bRet && bAll)
{
// If one branch has called a ctor and then exited, anything the
// other branch has done is OK (except returning without a
// ctor call, but we already checked that).
a |= b & (CSX.any_ctor | CSX.label);
}
else if (aHalt || aRet && aAll)
{
a = cast(CSX)(b | (a & (CSX.any_ctor | CSX.label)));
}
else if (aAll != bAll) // both branches must have called ctors, or both not
return false;
else
{
// If one returned without a ctor, remember that
if (bRet && !bAny)
a |= CSX.return_;
a |= b & (CSX.any_ctor | CSX.label);
}
return true;
}
/****************************************
* Merge `b` flow analysis results into `a`.
* Params:
* a = the path to merge `b` into
* b = the other path
* Returns:
* false means either `a` or `b` skips initialization
*/
bool mergeFieldInit(ref CSX a, const CSX b) pure nothrow
{
if (b == a)
return true;
// Have any branches returned?
const aRet = (a & CSX.return_) != 0;
const bRet = (b & CSX.return_) != 0;
// Have any branches halted?
const aHalt = (a & CSX.halt) != 0;
const bHalt = (b & CSX.halt) != 0;
if (aHalt && bHalt)
{
a = CSX.halt;
return true;
}
// The logic here is to prefer the branch that neither halts nor returns.
bool ok;
if (!bHalt && bRet)
{
// Branch b returns, no merging required.
ok = (b & CSX.this_ctor);
}
else if (!aHalt && aRet)
{
// Branch a returns, but b doesn't, b takes precedence.
ok = (a & CSX.this_ctor);
a = b;
}
else if (bHalt)
{
// Branch b halts, no merging required.
ok = (a & CSX.this_ctor);
}
else if (aHalt)
{
// Branch a halts, but b doesn't, b takes precedence.
ok = (b & CSX.this_ctor);
a = b;
}
else
{
// Neither branch returns nor halts, merge flags.
ok = !((a ^ b) & CSX.this_ctor);
a |= b;
}
return ok;
}