Fix 18127 - ImportC: redeclaration of struct in different translation unit doesn’t check compatibility (#21224)

Fixes: https://github.com/dlang/dmd/issues/18127

When merging struct definitions from different C imports, check that the
structs are actually compatible according to the C rules. If they are
not, issue an error.
This commit is contained in:
drpriver 2025-04-13 23:35:20 -07:00 committed by GitHub
parent 605fb8bf5b
commit 8a8746f318
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 661 additions and 4 deletions

View file

@ -3091,10 +3091,18 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor
StructDeclaration sym = ts.sym;
if (sd.isCsymbol() && sym.isCsymbol())
{
/* This is two structs imported from different C files.
* Just ignore sd, the second one. The first one will always
* be found when going through the type.
*/
if (!isCCompatible(sd, sym))
{
// Already issued an error.
errorSupplemental(sd.loc, "C %ss with the same name from different imports are merged", sd.kind);
}
else {
/* This is two structs imported from different C files.
* Just ignore sd, the second one. The first one will always
* be found when going through the type.
*/
}
}
else
{
@ -3134,6 +3142,234 @@ private extern(C++) final class DsymbolSemanticVisitor : Visitor
//printf("-StructDeclaration::semantic(this=%p, '%s', sizeok = %d)\n", sd, sd.toPrettyChars(), sd.sizeok);
}
//
// Checks if two structs are compatible
// Implements the rules according to C23 6.2.7
//
static bool isCCompatible(StructDeclaration a, StructDeclaration b)
{
// Get the name of a type, while avoiding exposing "__tagXXX" anonymous structs
static const(char)* typeName(Type t)
{
if (TypeStruct ts = t.isTypeStruct())
{
if (ts.sym.ident.toString().startsWith("__tag"))
return ts.sym.isUnionDeclaration() ? "(anonymous union)".ptr: "(anonymous struct)".ptr;
}
return t.toChars();
}
void incompatError()
{
.error(a.loc, "%s `%s` already exists with an incompatible definition.",
a.kind, typeName(a.type));
errorSupplemental(b.loc, "previously declared here");
}
// For recursive calls into unnamed structs (so Type.equals() doesn't work).
static bool isCCompatibleUnnamedStruct(Type a, Type b)
{
TypeStruct ats = a.isTypeStruct();
if (!ats) return false;
TypeStruct bts = b.isTypeStruct();
if (!bts) return false;
// Hack, anonymous structs within a struct are given
// an anonymous id starting with __tag.
if (!ats.sym.ident.toString().startsWith("__tag"))
return false;
if (!bts.sym.ident.toString().startsWith("__tag"))
return false;
return isCCompatible(ats.sym, bts.sym);
}
if (a.fields.length != b.fields.length)
{
incompatError();
errorSupplemental(a.loc, "`%s` has %zu field(s) while `%s` has %zu field(s)",
a.toPrettyChars(), a.fields.length, b.toPrettyChars(), b.fields.length);
return false;
}
// both are structs or both are unions
if ((a.isUnionDeclaration() is null) != (b.isUnionDeclaration() is null))
{
incompatError();
errorSupplemental(a.loc, "`%s` is a %s while `%s` is a %s",
a.toPrettyChars(), a.kind, b.toPrettyChars(), b.kind);
return false;
}
if (a.alignment != b.alignment)
{
incompatError();
errorSupplemental(a.loc, "`%s` has different alignment or packing", a.toPrettyChars());
if (a.alignment.isDefault() && ! b.alignment.isDefault())
{
errorSupplemental(a.loc, "`%s` alignment: default", a.toPrettyChars());
errorSupplemental(b.loc, "`%s` alignment: %u",
b.toPrettyChars(), cast(uint)b.alignment.get());
}
else if (!a.alignment.isDefault() && b.alignment.isDefault())
{
errorSupplemental(a.loc, "`%s` alignment: %u",
a.toPrettyChars(), cast(uint)a.alignment.get());
errorSupplemental(b.loc, "`%s` alignment: default",
b.toPrettyChars());
}
else if (a.alignment.get() != b.alignment.get())
{
errorSupplemental(a.loc, "`%s` alignment: %u",
a.toPrettyChars(), cast(uint)a.alignment.get());
errorSupplemental(b.loc, "`%s` alignment: %u",
b.toPrettyChars(), cast(uint)b.alignment.get());
}
if (a.alignment.isPack() != b.alignment.isPack())
{
errorSupplemental(a.loc, "`%s` packed: %s",
a.toPrettyChars(), a.alignment.isPack()?"true".ptr:"false".ptr);
errorSupplemental(b.loc, "`%s` packed: %s",
b.toPrettyChars(), b.alignment.isPack()?"true".ptr:"false".ptr);
}
return false;
}
foreach (size_t i, VarDeclaration a_field; a.fields[])
{
VarDeclaration b_field = b.fields[i];
//
// — there shall be a one-to-one correspondence between
// their members such that each pair of corresponding
// members are declared with compatible types;
//
if (!a_field.type.equals(b_field.type) && !isCCompatibleUnnamedStruct(a_field.type, b_field.type))
{
// Already errored, just bail
incompatError();
if (a_field.type.isTypeError()) return false;
if (b_field.type.isTypeError()) return false;
errorSupplemental(a_field.loc, "Field %zu differs in type", i);
errorSupplemental(a_field.loc, "typeof(%s): %s",
a_field.toChars(), typeName(a_field.type));
errorSupplemental(b_field.loc, "typeof(%s): %s",
b_field.toChars(), typeName(b_field.type));
return false;
}
//
// — if one member of the pair is declared with an
// alignment specifier, the second is declared with an
// equivalent alignment specifier;
//
if (a_field.alignment != b_field.alignment)
{
incompatError();
errorSupplemental(a_field.loc, "Field %zu differs in alignment or packing", i);
if (a_field.alignment.isDefault() && ! b_field.alignment.isDefault())
{
errorSupplemental(a_field.loc, "`%s.%s` alignment: default",
a.toPrettyChars(),a_field.toChars());
errorSupplemental(b_field.loc, "`%s.%s` alignment: %u",
b.toPrettyChars(), b_field.toChars(), cast(uint)b_field.alignment.get());
}
else if (!a_field.alignment.isDefault() && b_field.alignment.isDefault())
{
errorSupplemental(a_field.loc, "`%s.%s` alignment: %u",
a.toPrettyChars(), a_field.toChars(), cast(uint)a_field.alignment.get());
errorSupplemental(b_field.loc, "`%s.%s` alignment: default",
b.toPrettyChars(), b_field.toChars());
}
else if (a_field.alignment.get() != b_field.alignment.get())
{
errorSupplemental(a_field.loc, "`%s.%s` alignment: %u",
a.toPrettyChars(), a_field.toChars(),
cast(uint)a_field.alignment.get());
errorSupplemental(b_field.loc, "`%s.%s` alignment: %u",
b.toPrettyChars(), b_field.toChars(),
cast(uint)b_field.alignment.get());
}
if (a_field.alignment.isPack() != b_field.alignment.isPack())
{
errorSupplemental(a_field.loc, "`%s.%s` packed: %s",
a.toPrettyChars(), a_field.toChars(),
a_field.alignment.isPack()?"true".ptr:"false".ptr);
errorSupplemental(b_field.loc, "`%s.%s` packed: %s",
b.toPrettyChars(), b_field.toChars(),
b_field.alignment.isPack()?"true".ptr:"false".ptr);
}
return false;
}
//
// - and, if one member of the pair is declared with a
// name, the second is declared with the same name.
//
if (a_field.ident.isAnonymous())
{
if (!b_field.ident.isAnonymous())
{
incompatError();
errorSupplemental(a_field.loc, "Field %zu differs in name", i);
errorSupplemental(a_field.loc, "(anonymous)", a_field.ident.toChars());
errorSupplemental(b_field.loc, "%s", b_field.ident.toChars());
return false;
}
}
else if (b_field.ident.isAnonymous())
{
incompatError();
errorSupplemental(a_field.loc, "Field %zu differs in name", i);
errorSupplemental(a_field.loc, "%s", a_field.ident.toChars());
errorSupplemental(b_field.loc, "(anonymous)");
return false;
}
else if (a_field.ident != b_field.ident)
{
incompatError();
errorSupplemental(a_field.loc, "Field %zu differs in name", i);
errorSupplemental(a_field.loc, "%s", a_field.ident.toChars());
errorSupplemental(b_field.loc, "%s", b_field.ident.toChars());
return false;
}
//
// For two structures or unions, corresponding bit-fields shall have the same widths.
//
BitFieldDeclaration bfa = a_field.isBitFieldDeclaration();
BitFieldDeclaration bfb = b_field.isBitFieldDeclaration();
if ((bfa is null) != (bfb is null))
{
incompatError();
errorSupplemental(a_field.loc, "Field %zu differs in being a bitfield", i);
if (bfa is null)
{
errorSupplemental(a_field.loc, "`%s.%s` is not a bitfield",
a.toPrettyChars(), a_field.toChars());
errorSupplemental(b_field.loc, "`%s.%s` is a bitfield",
b.toPrettyChars(), b_field.toChars());
}
else if (bfb is null)
{
errorSupplemental(a_field.loc, "`%s.%s` *is a bitfield",
a.toPrettyChars(), a_field.toChars());
errorSupplemental(b_field.loc, "`%s.%s` is not a bitfield",
b.toPrettyChars(), b_field.toChars());
}
return false;
}
if (bfa !is null && bfb !is null)
{
if (bfa.fieldWidth != bfb.fieldWidth)
{
incompatError();
errorSupplemental(a_field.loc, "Field %zu differs in bitfield width", i);
errorSupplemental(a_field.loc, "`%s.%s`: %u",
a.toPrettyChars(), a_field.toChars(), bfa.fieldWidth);
errorSupplemental(b_field.loc, "`%s.%s`: %u",
b.toPrettyChars(), b_field.toChars(), bfb.fieldWidth);
return false;
}
}
}
return true;
}
void interfaceSemantic(ClassDeclaration cd)
{
cd.vtblInterfaces = new BaseClasses();

View file

@ -0,0 +1,74 @@
// https://github.com/dlang/dmd/issues/18127
union struct_or_union {
int x;
};
struct S_n_fields {
int x, y;
};
struct S_types {
float x;
};
struct S_names {
float x;
};
struct B {
int x;
};
struct S_b {
struct B b;
};
struct S_contains_anon_named {
struct {
int x;
} a;
};
struct S_contains_anon_unnamed {
struct {
int x;
};
};
struct S_bitfields_mismatch1 {
unsigned x: 3;
unsigned y: 1;
};
struct S_bitfields_mismatch2 {
unsigned x;
unsigned y: 1;
};
struct S_bitfields_widths {
unsigned x: 3;
unsigned y: 1;
};
struct S_bitfields_anon {
unsigned x: 3;
unsigned : 1;
};
struct S_alignas {
_Alignas(8) float x;
};
struct S_aligned {
float x;
}__attribute__((aligned(8)));
struct __attribute__((packed)) S_pack_1 {
float x;
char c;
};
#pragma pack(push)
#pragma pack(1)
struct S_pack_2 {
float x;
char c;
};
#pragma pack(pop)

View file

@ -0,0 +1,74 @@
// https://github.com/dlang/dmd/issues/18127
union struct_or_union {
int x;
};
struct S_n_fields {
int x, y;
};
struct S_types {
float x;
};
struct S_names {
float x;
};
struct B {
int x;
};
struct S_b {
struct B b;
};
struct S_contains_anon_named {
struct {
int x;
} a;
};
struct S_contains_anon_unnamed {
struct {
int x;
};
};
struct S_bitfields_mismatch1 {
unsigned x: 3;
unsigned y: 1;
};
struct S_bitfields_mismatch2 {
unsigned x;
unsigned y: 1;
};
struct S_bitfields_widths {
unsigned x: 3;
unsigned y: 1;
};
struct S_bitfields_anon {
unsigned x: 3;
unsigned : 1;
};
struct S_alignas {
_Alignas(8) float x;
};
struct S_aligned {
float x;
}__attribute__((aligned(8)));
struct __attribute__((packed)) S_pack_1 {
float x;
char c;
};
#pragma pack(push)
#pragma pack(1)
struct S_pack_2 {
float x;
char c;
};
#pragma pack(pop)

View file

@ -0,0 +1,4 @@
// https://github.com/dlang/dmd/issues/18127
import imports.imp18127a;
import imports.imp18127b;

View file

@ -0,0 +1,83 @@
// https://github.com/dlang/dmd/issues/18127
// union in here, struct in other
union struct_or_union {
int x;
};
// mismatching number of fields
struct S_n_fields {
int x, y;
};
// mismatched types
struct S_types {
float x;
};
// mismatched names
struct S_names {
float x;
};
struct B {
int x;
};
// Contains a struct that is incompatible
struct S_b {
struct B b;
};
// mismatched anonymous struct
struct S_contains_anon_named {
struct {
int x;
} a;
};
struct S_contains_anon_unnamed {
struct {
int x;
};
};
// bitfields
struct S_bitfields_mismatch1 {
unsigned x: 3;
unsigned y: 1;
};
struct S_bitfields_mismatch2 {
unsigned x;
unsigned y: 1;
};
struct S_bitfields_widths {
unsigned x: 3;
unsigned y: 1;
};
struct S_bitfields_anon {
unsigned x: 3;
unsigned : 1;
};
// mismatched alignment
struct S_alignas {
_Alignas(8) float x;
};
struct S_aligned {
float x;
}__attribute__((aligned(8)));
// mismatched packing
struct __attribute__((packed)) S_pack_1 {
float x;
char c;
};
#pragma pack(push)
#pragma pack(1)
struct S_pack_2 {
float x;
char c;
};
#pragma pack(pop)

View file

@ -0,0 +1,80 @@
// https://github.com/dlang/dmd/issues/18127
// struct in here, union in other
struct struct_or_union {
int x;
};
// mismatching number of fields
struct S_n_fields {
int x;
};
// mismatched types
struct S_types {
int x;
};
// mismatched names
struct S_names {
float y;
};
struct B {
float x;
};
// Contains a struct that is incompatible
struct S_b {
struct B b;
};
// mismatched anonymous struct
struct S_contains_anon_named {
struct {
float x;
} a;
};
struct S_contains_anon_unnamed {
struct {
float x;
};
};
// bitfields
struct S_bitfields_mismatch1 {
unsigned x: 3;
unsigned y;
};
struct S_bitfields_mismatch2 {
unsigned x: 3;
unsigned y: 1;
};
struct S_bitfields_widths {
unsigned x: 3;
unsigned y: 2;
};
struct S_bitfields_anon {
unsigned x: 3;
unsigned y: 1;
};
// mismatched alignment
struct S_alignas {
float x;
};
struct S_aligned {
float x;
}__attribute__((aligned(4)));
// mismatched packing
struct S_pack_1 {
float x;
char c;
};
struct S_pack_2 {
float x;
char c;
};

View file

@ -0,0 +1,106 @@
/*
TEST_OUTPUT:
---
fail_compilation/imports/imp18127b.c(3): Error: struct `struct_or_union` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(3): previously declared here
fail_compilation/imports/imp18127b.c(3): `imp18127b.struct_or_union` is a struct while `imp18127a.struct_or_union` is a union
fail_compilation/imports/imp18127b.c(3): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(8): Error: struct `S_n_fields` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(8): previously declared here
fail_compilation/imports/imp18127b.c(8): `imp18127b.S_n_fields` has 1 field(s) while `imp18127a.S_n_fields` has 2 field(s)
fail_compilation/imports/imp18127b.c(8): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(13): Error: struct `S_types` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(13): previously declared here
fail_compilation/imports/imp18127b.c(14): Field 0 differs in type
fail_compilation/imports/imp18127b.c(14): typeof(x): int
fail_compilation/imports/imp18127a.c(14): typeof(x): float
fail_compilation/imports/imp18127b.c(13): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(18): Error: struct `S_names` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(18): previously declared here
fail_compilation/imports/imp18127b.c(19): Field 0 differs in name
fail_compilation/imports/imp18127b.c(19): y
fail_compilation/imports/imp18127a.c(19): x
fail_compilation/imports/imp18127b.c(18): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(22): Error: struct `B` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(22): previously declared here
fail_compilation/imports/imp18127b.c(23): Field 0 differs in type
fail_compilation/imports/imp18127b.c(23): typeof(x): float
fail_compilation/imports/imp18127a.c(23): typeof(x): int
fail_compilation/imports/imp18127b.c(22): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(27): Error: struct `S_b` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(27): previously declared here
fail_compilation/imports/imp18127b.c(27): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(33): Error: struct `(anonymous struct)` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(33): previously declared here
fail_compilation/imports/imp18127b.c(34): Field 0 differs in type
fail_compilation/imports/imp18127b.c(34): typeof(x): float
fail_compilation/imports/imp18127a.c(34): typeof(x): int
fail_compilation/imports/imp18127b.c(32): Error: struct `S_contains_anon_named` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(32): previously declared here
fail_compilation/imports/imp18127b.c(35): Field 0 differs in type
fail_compilation/imports/imp18127b.c(35): typeof(a): (anonymous struct)
fail_compilation/imports/imp18127a.c(35): typeof(a): (anonymous struct)
fail_compilation/imports/imp18127b.c(32): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(38): Error: struct `S_contains_anon_unnamed` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(38): previously declared here
fail_compilation/imports/imp18127b.c(40): Field 0 differs in type
fail_compilation/imports/imp18127b.c(40): typeof(x): float
fail_compilation/imports/imp18127a.c(40): typeof(x): int
fail_compilation/imports/imp18127b.c(38): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(45): Error: struct `S_bitfields_mismatch1` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(45): previously declared here
fail_compilation/imports/imp18127b.c(47): Field 1 differs in being a bitfield
fail_compilation/imports/imp18127b.c(47): `imp18127b.S_bitfields_mismatch1.y` is not a bitfield
fail_compilation/imports/imp18127a.c(47): `imp18127a.S_bitfields_mismatch1.y` is a bitfield
fail_compilation/imports/imp18127b.c(45): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(49): Error: struct `S_bitfields_mismatch2` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(49): previously declared here
fail_compilation/imports/imp18127b.c(50): Field 0 differs in being a bitfield
fail_compilation/imports/imp18127b.c(50): `imp18127b.S_bitfields_mismatch2.x` *is a bitfield
fail_compilation/imports/imp18127a.c(50): `imp18127a.S_bitfields_mismatch2.x` is not a bitfield
fail_compilation/imports/imp18127b.c(49): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(54): Error: struct `S_bitfields_widths` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(54): previously declared here
fail_compilation/imports/imp18127b.c(56): Field 1 differs in bitfield width
fail_compilation/imports/imp18127b.c(56): `imp18127b.S_bitfields_widths.y`: 2
fail_compilation/imports/imp18127a.c(56): `imp18127a.S_bitfields_widths.y`: 1
fail_compilation/imports/imp18127b.c(54): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(59): Error: struct `S_bitfields_anon` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(59): previously declared here
fail_compilation/imports/imp18127b.c(61): Field 1 differs in name
fail_compilation/imports/imp18127b.c(61): y
fail_compilation/imports/imp18127a.c(61): (anonymous)
fail_compilation/imports/imp18127b.c(59): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(65): Error: struct `S_alignas` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(65): previously declared here
fail_compilation/imports/imp18127b.c(66): Field 0 differs in alignment or packing
fail_compilation/imports/imp18127b.c(66): `imp18127b.S_alignas.x` alignment: default
fail_compilation/imports/imp18127a.c(66): `imp18127a.S_alignas.x` alignment: 8
fail_compilation/imports/imp18127b.c(65): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(68): Error: struct `S_aligned` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(68): previously declared here
fail_compilation/imports/imp18127b.c(68): `imp18127b.S_aligned` has different alignment or packing
fail_compilation/imports/imp18127b.c(68): `imp18127b.S_aligned` alignment: 4
fail_compilation/imports/imp18127a.c(68): `imp18127a.S_aligned` alignment: 8
fail_compilation/imports/imp18127b.c(68): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(73): Error: struct `S_pack_1` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(73): previously declared here
fail_compilation/imports/imp18127b.c(73): `imp18127b.S_pack_1` has different alignment or packing
fail_compilation/imports/imp18127b.c(73): `imp18127b.S_pack_1` alignment: default
fail_compilation/imports/imp18127a.c(73): `imp18127a.S_pack_1` alignment: 1
fail_compilation/imports/imp18127b.c(73): `imp18127b.S_pack_1` packed: false
fail_compilation/imports/imp18127a.c(73): `imp18127a.S_pack_1` packed: true
fail_compilation/imports/imp18127b.c(73): C structs with the same name from different imports are merged
fail_compilation/imports/imp18127b.c(77): Error: struct `S_pack_2` already exists with an incompatible definition.
fail_compilation/imports/imp18127a.c(79): previously declared here
fail_compilation/imports/imp18127b.c(77): `imp18127b.S_pack_2` has different alignment or packing
fail_compilation/imports/imp18127b.c(77): `imp18127b.S_pack_2` alignment: default
fail_compilation/imports/imp18127a.c(79): `imp18127a.S_pack_2` alignment: 1
fail_compilation/imports/imp18127b.c(77): C structs with the same name from different imports are merged
---
*/
// https://github.com/dlang/dmd/issues/18127
import imports.imp18127a;
import imports.imp18127b;