| /** |
| * 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; |
| } |