blob: 51d6fc45953946427ad92d2bd0c1b699d0e58616 [file] [log] [blame]
/**
* Defines a `Dsymbol` representing an aggregate, which is a `struct`, `union` or `class`.
*
* Specification: $(LINK2 https://dlang.org/spec/struct.html, Structs, Unions),
* $(LINK2 https://dlang.org/spec/class.html, Class).
*
* Copyright: Copyright (C) 1999-2025 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/compiler/src/dmd/aggregate.d, _aggregate.d)
* Documentation: https://dlang.org/phobos/dmd_aggregate.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/aggregate.d
*/
module dmd.aggregate;
import core.stdc.stdio;
import core.checkedint;
import dmd.aliasthis;
import dmd.arraytypes;
import dmd.astenums;
import dmd.attrib;
import dmd.declaration;
import dmd.dscope;
import dmd.dstruct;
import dmd.dsymbol;
import dmd.dsymbolsem : dsymbolSemantic, determineFields, search, determineSize, include;
import dmd.dtemplate;
import dmd.errors;
import dmd.expression;
import dmd.func;
import dmd.globals;
import dmd.hdrgen;
import dmd.id;
import dmd.identifier;
import dmd.location;
import dmd.mtype;
import dmd.tokens;
import dmd.typesem : defaultInit, addMod, size;
import dmd.visitor;
/**
* The ClassKind enum is used in AggregateDeclaration AST nodes to
* specify the linkage type of the struct/class/interface or if it
* is an anonymous class. If the class is anonymous it is also
* considered to be a D class.
*/
enum ClassKind : ubyte
{
/// the aggregate is a d(efault) class
d,
/// the aggregate is a C++ struct/class/interface
cpp,
/// the aggregate is an Objective-C class/interface
objc,
/// the aggregate is a C struct
c,
}
/**
* Give a nice string for a class kind for error messages
* Params:
* c = class kind
* Returns:
* 0-terminated string for `c`
*/
const(char)* ClassKindToChars(ClassKind c) @safe
{
final switch (c)
{
case ClassKind.d:
return "D";
case ClassKind.cpp:
return "C++";
case ClassKind.objc:
return "Objective-C";
case ClassKind.c:
return "C";
}
}
/**
* If an aggregate has a pargma(mangle, ...) this holds the information
* to mangle.
*/
struct MangleOverride
{
Dsymbol agg; // The symbol to copy template parameters from (if any)
Identifier id; // the name to override the aggregate's with, defaults to agg.ident
}
/***********************************************************
* Abstract aggregate as a common ancestor for Class- and StructDeclaration.
*/
extern (C++) abstract class AggregateDeclaration : ScopeDsymbol
{
Type type; ///
STC storage_class; ///
uint structsize; /// size of struct
uint alignsize; /// size of struct for alignment purposes
VarDeclarations fields; /// VarDeclaration fields including flattened AnonDeclaration members
Dsymbol deferred; /// any deferred semantic2() or semantic3() symbol
/// specifies whether this is a D, C++, Objective-C or anonymous struct/class/interface
ClassKind classKind;
/// Specify whether to mangle the aggregate as a `class` or a `struct`
/// This information is used by the MSVC mangler
/// Only valid for class and struct. TODO: Merge with ClassKind ?
CPPMANGLE cppmangle;
/// overridden symbol with pragma(mangle, "...") if not null
MangleOverride* pMangleOverride;
/**
* !=null if is nested
* pointing to the dsymbol that directly enclosing it.
* 1. The function that enclosing it (nested struct and class)
* 2. The class that enclosing it (nested class only)
* 3. If enclosing aggregate is template, its enclosing dsymbol.
*
* See AggregateDeclaraton::makeNested for the details.
*/
Dsymbol enclosing;
VarDeclaration vthis; /// 'this' parameter if this aggregate is nested
VarDeclaration vthis2; /// 'this' parameter if this aggregate is a template and is nested
// Special member functions
FuncDeclarations invs; /// Array of invariants
FuncDeclaration inv; /// Merged invariant calling all members of invs
/// CtorDeclaration or TemplateDeclaration
Dsymbol ctor;
/// default constructor - should have no arguments, because
/// it would be stored in TypeInfo_Class.defaultConstructor
CtorDeclaration defaultCtor;
AliasThis aliasthis; /// forward unresolved lookups to aliasthis
DtorDeclarations userDtors; /// user-defined destructors (`~this()`) - mixins can yield multiple ones
DtorDeclaration aggrDtor; /// aggregate destructor calling userDtors and fieldDtor (and base class aggregate dtor for C++ classes)
DtorDeclaration dtor; /// the aggregate destructor exposed as `__xdtor` alias
/// (same as aggrDtor, except for C++ classes with virtual dtor on Windows)
DtorDeclaration tidtor; /// aggregate destructor used in TypeInfo (must have extern(D) ABI)
DtorDeclaration fieldDtor; /// function destructing (non-inherited) fields
Expression getRTInfo; /// pointer to GC info generated by object.RTInfo(this)
///
Visibility visibility;
bool noDefaultCtor; /// no default construction
bool disableNew; /// disallow allocations using `new`
Sizeok sizeok = Sizeok.none; /// set when structsize contains valid data
final extern (D) this(Loc loc, Identifier id)
{
super(loc, id);
visibility = Visibility(Visibility.Kind.public_);
}
/***************************************
* Create a new scope from sc.
* semantic, semantic2 and semantic3 will use this for aggregate members.
*/
Scope* newScope(Scope* sc)
{
auto sc2 = sc.push(this);
sc2.stc &= STC.flowThruAggregate;
sc2.parent = this;
sc2.inunion = isUnionDeclaration();
sc2.visibility = Visibility(Visibility.Kind.public_);
sc2.explicitVisibility = 0;
sc2.aligndecl = null;
sc2.userAttribDecl = null;
sc2.namespace = null;
return sc2;
}
/***************************************
* Returns:
* The total number of fields minus the number of hidden fields.
*/
extern (D) final size_t nonHiddenFields()
{
return fields.length - isNested() - (vthis2 !is null);
}
abstract void finalizeSize();
override final uinteger_t size(Loc loc)
{
//printf("+AggregateDeclaration::size() %s, scope = %p, sizeok = %d\n", toChars(), _scope, sizeok);
bool ok = determineSize(this, loc);
//printf("-AggregateDeclaration::size() %s, scope = %p, sizeok = %d\n", toChars(), _scope, sizeok);
return ok ? structsize : SIZE_INVALID;
}
/***************************************
* Calculate field[i].overlapped and overlapUnsafe, and check that all of explicit
* field initializers have unique memory space on instance.
* Returns:
* true if any errors happen.
*/
extern (D) final bool checkOverlappedFields()
{
//printf("AggregateDeclaration::checkOverlappedFields() %s\n", toChars());
assert(sizeok == Sizeok.done);
size_t nfields = fields.length;
if (isNested())
{
auto cd = isClassDeclaration();
if (!cd || !cd.baseClass || !cd.baseClass.isNested())
nfields--;
if (vthis2 && !(cd && cd.baseClass && cd.baseClass.vthis2))
nfields--;
}
bool errors = false;
// Fill in missing any elements with default initializers
foreach (i; 0 .. nfields)
{
auto vd = fields[i];
if (vd.errors)
{
errors = true;
continue;
}
const vdIsVoidInit = vd._init && vd._init.isVoidInitializer();
// Find overlapped fields with the hole [vd.offset .. vd.offset.size()].
foreach (j; 0 .. nfields)
{
if (i == j)
continue;
auto v2 = fields[j];
if (v2.errors)
{
errors = true;
continue;
}
if (!vd.isOverlappedWith(v2))
continue;
// vd and v2 are overlapping.
vd.overlapped = true;
v2.overlapped = true;
if (!MODimplicitConv(vd.type.mod, v2.type.mod))
v2.overlapUnsafe = true;
if (!MODimplicitConv(v2.type.mod, vd.type.mod))
vd.overlapUnsafe = true;
if (i > j)
continue;
if (!v2._init)
continue;
if (v2._init.isVoidInitializer())
continue;
if (vd._init && !vdIsVoidInit && v2._init)
{
.error(loc, "overlapping default initialization for field `%s` and `%s`", v2.toChars(), vd.toChars());
errors = true;
}
else if (v2._init && i < j)
{
.error(v2.loc, "union field `%s` with default initialization `%s` must be before field `%s`",
v2.toChars(), dmd.hdrgen.toChars(v2._init), vd.toChars());
errors = true;
}
}
}
return errors;
}
override final Type getType()
{
/* Apply storage classes to forward references. (Issue 22254)
* Note: Avoid interfaces for now. Implementing qualifiers on interface
* definitions exposed some issues in their TypeInfo generation in DMD.
* Related PR: https://github.com/dlang/dmd/pull/13312
*/
if (semanticRun == PASS.initial && !isInterfaceDeclaration())
{
auto stc = storage_class;
if (_scope)
stc |= _scope.stc;
type = type.addSTC(stc);
}
return type;
}
// is aggregate deprecated?
override final bool isDeprecated() const
{
return !!(this.storage_class & STC.deprecated_);
}
/// Flag this aggregate as deprecated
extern (D) final void setDeprecated()
{
this.storage_class |= STC.deprecated_;
}
/****************************************
* Returns true if there's an extra member which is the 'this'
* pointer to the enclosing context (enclosing aggregate or function)
*/
final bool isNested() const
{
return enclosing !is null;
}
/* Append vthis field (this.tupleof[$-1]) to make this aggregate type nested.
*/
extern (D) final void makeNested()
{
if (enclosing) // if already nested
return;
if (sizeok == Sizeok.done)
return;
if (isUnionDeclaration() || isInterfaceDeclaration())
return;
if (storage_class & STC.static_)
return;
// If nested struct, add in hidden 'this' pointer to outer scope
auto s = toParentLocal();
if (!s)
s = toParent2();
if (!s)
return;
Type t = null;
if (auto fd = s.isFuncDeclaration())
{
enclosing = fd;
/* https://issues.dlang.org/show_bug.cgi?id=14422
* If a nested class parent is a function, its
* context pointer (== `outer`) should be void* always.
*/
t = Type.tvoidptr;
}
else if (auto ad = s.isAggregateDeclaration())
{
if (isClassDeclaration() && ad.isClassDeclaration())
{
enclosing = ad;
}
else if (isStructDeclaration())
{
if (auto ti = ad.parent.isTemplateInstance())
{
enclosing = ti.enclosing;
}
}
t = ad.handleType();
}
if (enclosing)
{
//printf("makeNested %s, enclosing = %s\n", toChars(), enclosing.toChars());
assert(t);
if (t.ty == Tstruct)
t = Type.tvoidptr; // t should not be a ref type
assert(!vthis);
vthis = new ThisDeclaration(loc, t);
//vthis.storage_class |= STC.ref_;
// Emulate vthis.addMember()
members.push(vthis);
// Emulate vthis.dsymbolSemantic()
vthis.storage_class |= STC.field;
vthis.parent = this;
vthis.visibility = Visibility(Visibility.Kind.public_);
vthis.alignment = t.alignment();
vthis.semanticRun = PASS.semanticdone;
if (sizeok == Sizeok.fwd)
fields.push(vthis);
makeNested2();
}
}
/* Append vthis2 field (this.tupleof[$-1]) to add a second context pointer.
*/
extern (D) final void makeNested2()
{
if (vthis2)
return;
if (!vthis)
makeNested(); // can't add second before first
if (!vthis)
return;
if (sizeok == Sizeok.done)
return;
if (isUnionDeclaration() || isInterfaceDeclaration())
return;
if (storage_class & STC.static_)
return;
auto s0 = toParentLocal();
auto s = toParent2();
if (!s || !s0 || s == s0)
return;
auto cd = s.isClassDeclaration();
Type t = cd ? cd.type : Type.tvoidptr;
vthis2 = new ThisDeclaration(loc, t);
//vthis2.storage_class |= STC.ref_;
// Emulate vthis2.addMember()
members.push(vthis2);
// Emulate vthis2.dsymbolSemantic()
vthis2.storage_class |= STC.field;
vthis2.parent = this;
vthis2.visibility = Visibility(Visibility.Kind.public_);
vthis2.alignment = t.alignment();
vthis2.semanticRun = PASS.semanticdone;
if (sizeok == Sizeok.fwd)
fields.push(vthis2);
}
override final bool isExport() const
{
return visibility.kind == Visibility.Kind.export_;
}
/*******************************************
* Look for constructor declaration.
*/
extern (D) final Dsymbol searchCtor()
{
auto s = this.search(Loc.initial, Id.ctor);
if (s)
{
if (!(s.isCtorDeclaration() ||
s.isTemplateDeclaration() ||
s.isOverloadSet()))
{
error(s.loc, "%s name `__ctor` is not allowed", s.kind);
errorSupplemental(s.loc, "identifiers starting with `__` are reserved for internal use");
errors = true;
s = null;
}
}
if (s && s.toParent() != this)
s = null; // search() looks through ancestor classes
if (s)
{
// Finish all constructors semantics to determine this.noDefaultCtor.
static int searchCtor(Dsymbol s, void*)
{
auto f = s.isCtorDeclaration();
if (f && f.semanticRun == PASS.initial)
f.dsymbolSemantic(null);
return 0;
}
for (size_t i = 0; i < members.length; i++)
{
auto sm = (*members)[i];
sm.apply(&searchCtor, null);
}
}
return s;
}
override final Visibility visible() pure nothrow @nogc @safe
{
return visibility;
}
// 'this' type
final Type handleType()
{
return type;
}
// Does this class have an invariant function?
final bool hasInvariant()
{
return invs.length != 0;
}
// Back end
void* sinit; /// initializer symbol
override void accept(Visitor v)
{
v.visit(this);
}
}
/*********************************
* Iterate this dsymbol or members of this scoped dsymbol, then
* call `fp` with the found symbol and `params`.
* Params:
* symbol = the dsymbol or parent of members to call fp on
* fp = function pointer to process the iterated symbol.
* If it returns nonzero, the iteration will be aborted.
* ctx = context parameter passed to fp.
* Returns:
* nonzero if the iteration is aborted by the return value of fp,
* or 0 if it's completed.
*/
int apply(Dsymbol symbol, int function(Dsymbol, void*) fp, void* ctx)
{
if (auto nd = symbol.isNspace())
{
return nd.members.foreachDsymbol( (s) { return s && s.apply(fp, ctx); } );
}
if (auto ad = symbol.isAttribDeclaration())
{
return ad.include(ad._scope).foreachDsymbol( (s) { return s && s.apply(fp, ctx); } );
}
if (auto tm = symbol.isTemplateMixin())
{
if (tm._scope) // if fwd reference
dsymbolSemantic(tm, null); // try to resolve it
return tm.members.foreachDsymbol( (s) { return s && s.apply(fp, ctx); } );
}
return fp(symbol, ctx);
}
/****************************
* Do byte or word alignment as necessary.
* Align sizes of 0, as we may not know array sizes yet.
* Params:
* alignment = struct alignment that is in effect
* memalignsize = natural alignment of field
* offset = offset to be aligned
* Returns:
* aligned offset
*/
public uint alignmember(structalign_t alignment, uint memalignsize, uint offset) pure nothrow @safe
{
//debug printf("alignment = %u %d, size = %u, offset = %u\n", alignment.get(), alignment.isPack(), memalignsize, offset);
uint alignvalue;
if (alignment.isDefault())
{
// Alignment in Target::fieldalignsize must match what the
// corresponding C compiler's default alignment behavior is.
alignvalue = memalignsize;
}
else if (alignment.isPack()) // #pragma pack semantics
{
alignvalue = alignment.get();
if (memalignsize < alignvalue)
alignvalue = memalignsize; // align to min(memalignsize, alignment)
}
else if (alignment.get() > 1)
{
// Align on alignment boundary, which must be a positive power of 2
alignvalue = alignment.get();
}
else
return offset;
assert(alignvalue && !(alignvalue & (alignvalue - 1))); // non-zero and power of 2
return (offset + alignvalue - 1) & ~(alignvalue - 1);
}
/****************************************
* Place a field (mem) into an aggregate (agg), which can be a struct, union or class
* Params:
* loc = source location for error messages
* nextoffset = location just past the end of the previous field in the aggregate.
* Updated to be just past the end of this field to be placed, i.e. the future nextoffset
* memsize = size of field
* memalignsize = natural alignment of field
* alignment = alignment in effect for this field
* aggsize = size of aggregate (updated)
* aggalignsize = alignment of aggregate (updated)
* isunion = the aggregate is a union
* Returns:
* aligned offset to place field at
*
*/
public uint placeField(Loc loc, ref uint nextoffset, uint memsize, uint memalignsize,
structalign_t alignment, ref uint aggsize, ref uint aggalignsize, bool isunion) @trusted nothrow
{
static if (0)
{
printf("placeField() nextoffset: %u\n", nextoffset);
printf(": memsize: %u\n", memsize);
printf(": memalignsize: %u\n", memalignsize);
printf(": alignment: %u\n", alignment.get());
printf(": aggsize: %u\n", aggsize);
printf(": aggalignsize: %u\n", aggalignsize);
printf(": isunion: %d\n", isunion);
}
uint ofs = nextoffset;
const uint actualAlignment =
alignment.isDefault() || alignment.isPack() && memalignsize < alignment.get()
? memalignsize : alignment.get();
// Ensure no overflow for (memsize + actualAlignment + ofs)
bool overflow;
const sz = addu(memsize, actualAlignment, overflow);
addu(ofs, sz, overflow);
if (overflow)
{
error(loc, "max object size %u exceeded from adding field size %u + alignment adjustment %u + field offset %u when placing field in aggregate",
uint.max, memsize, actualAlignment, ofs);
return 0;
}
// Skip no-op for noreturn without custom aligment
if (memalignsize != 0 || !alignment.isDefault())
ofs = alignmember(alignment, memalignsize, ofs);
uint memoffset = ofs;
ofs += memsize;
if (ofs > aggsize)
aggsize = ofs;
if (!isunion)
{
nextoffset = ofs;
//printf(" revised nextoffset: %u\n", ofs);
}
if (aggalignsize < actualAlignment)
aggalignsize = actualAlignment;
return memoffset;
}