diff --git a/tools/qfcc/include/symtab.h b/tools/qfcc/include/symtab.h index c0c064f3e..53b42f150 100644 --- a/tools/qfcc/include/symtab.h +++ b/tools/qfcc/include/symtab.h @@ -45,6 +45,7 @@ typedef enum vis_e { vis_public, vis_protected, vis_private, + vis_anonymous, } vis_t; typedef enum { diff --git a/tools/qfcc/source/qc-parse.y b/tools/qfcc/source/qc-parse.y index 3fac0f926..17316e79f 100644 --- a/tools/qfcc/source/qc-parse.y +++ b/tools/qfcc/source/qc-parse.y @@ -42,6 +42,7 @@ #include #include +#include #include "class.h" #include "debug.h" @@ -264,6 +265,26 @@ default_type (specifier_t spec, symbol_t *sym) return spec; } +static int +is_anonymous_struct (specifier_t spec) +{ + if (spec.sym) { + return 0; + } + if (!is_struct (spec.type)) { + return 0; + } + if (spec.type->t.symtab->parent) { + return 0; + } + // struct and union type names always begin with "tag ". Untagged s/u + // are "tag ..". + if (spec.type->name[4] != '.') { + return 0; + } + return 1; +} + %} %expect 0 @@ -675,15 +696,27 @@ struct_def : type struct_decl_list | type { - if ($1.sym) { + if ($1.sym && $1.sym->type != $1.type) { // a type name (id, typedef, etc) was used as a field name. // this is allowed in C - print_type ($1.type); - printf ("%s\n", $1.sym->name); $1.sym = new_symbol ($1.sym->name); $1.sym->type = $1.type; $1.sym->sy_type = sy_var; symtab_addsymbol (current_symtab, $1.sym); + if (!$1.sym->table) { + error (0, "duplicate field `%s'", $1.sym->name); + } + } else if (is_anonymous_struct ($1)) { + // anonymous struct/union + // type->name always begins with "tag " + $1.sym = new_symbol (va (".anonymous.%s", $1.type->name + 4)); + $1.sym->type = $1.type; + $1.sym->sy_type = sy_var; + $1.sym->visibility = vis_anonymous; + symtab_addsymbol (current_symtab, $1.sym); + if (!$1.sym->table) { + error (0, "duplicate field `%s'", $1.sym->name); + } } else { // bare type warning (0, "declaration does not declare anything"); diff --git a/tools/qfcc/source/struct.c b/tools/qfcc/source/struct.c index 989b298ef..7af7f26b2 100644 --- a/tools/qfcc/source/struct.c +++ b/tools/qfcc/source/struct.c @@ -114,6 +114,7 @@ build_struct (int su, symbol_t *tag, symtab_t *symtab, type_t *type) symbol_t *sym = find_struct (su, tag, type); symbol_t *s; int alignment = 1; + symbol_t *as; symtab->parent = 0; // disconnect struct's symtab from parent scope @@ -140,6 +141,28 @@ build_struct (int su, symbol_t *tag, symtab_t *symtab, type_t *type) if (s->type->alignment > alignment) { alignment = s->type->alignment; } + if (s->visibility == vis_anonymous) { + symtab_t *anonymous; + symbol_t *t = s->next; + int offset = s->s.offset; + + if (!is_struct (s->type)) { + internal_error (0, "non-struct/union anonymous field"); + } + anonymous = s->type->t.symtab; + for (as = anonymous->symbols; as; as = as->next) { + if (Hash_Find (symtab->tab, as->name)) { + error (0, "ambiguous field `%s' in anonymous %s", + as->name, su == 's' ? "struct" : "union"); + } else { + s->next = copy_symbol (as); + s = s->next; + s->s.offset += offset; + Hash_Add (symtab->tab, s); + } + } + s->next = t; + } } if (!type) sym->type = find_type (sym->type); // checks the tag, not the symtab diff --git a/tools/qfcc/source/symtab.c b/tools/qfcc/source/symtab.c index f26a690e7..377671e8a 100644 --- a/tools/qfcc/source/symtab.c +++ b/tools/qfcc/source/symtab.c @@ -152,7 +152,7 @@ symtab_removesymbol (symtab_t *symtab, symbol_t *symbol) for (s = &symtab->symbols; *s && *s != symbol; s = & (*s)->next) ; if (!*s) - internal_error (0, "symtab_removesymbol"); + internal_error (0, "attempt to remove symbol not in symtab"); *s = (*s)->next; if (symtab->symtail == &symbol->next) symtab->symtail = s; diff --git a/tools/qfcc/test/Makefile.am b/tools/qfcc/test/Makefile.am index fc6f7692b..2e1001c11 100644 --- a/tools/qfcc/test/Makefile.am +++ b/tools/qfcc/test/Makefile.am @@ -31,6 +31,7 @@ fail_bins= test_progs_dat=\ address-cast.dat \ alignment.dat \ + anonstruct.dat \ chewed-alias.dat \ chewed-return.dat \ comma-expr.dat \ @@ -119,6 +120,15 @@ alignment.run: Makefile build-run include ./$(DEPDIR)/alignment.Qo # am--include-marker r_depfiles_remade += ./$(DEPDIR)/alignment.Qo +anonstruct_dat_SOURCES=anonstruct.r +anonstruct_obj=$(anonstruct_dat_SOURCES:.r=.qfo) +anonstruct.dat$(EXEEXT): $(anonstruct_obj) $(QFCC_DEP) + $(QFCC) $(QCFLAGS) -o $@ $(anonstruct_obj) +anonstruct.run: Makefile build-run + @$(srcdir)/build-run $@ +include ./$(DEPDIR)/anonstruct.Qo # am--include-marker +r_depfiles_remade += ./$(DEPDIR)/anonstruct.Qo + chewed_alias_dat_SOURCES=chewed-alias.r chewed_alias_obj=$(chewed_alias_dat_SOURCES:.r=.qfo) chewed-alias.dat$(EXEEXT): $(chewed_alias_obj) $(QFCC_DEP) diff --git a/tools/qfcc/test/anonstruct.r b/tools/qfcc/test/anonstruct.r new file mode 100644 index 000000000..a426d8f61 --- /dev/null +++ b/tools/qfcc/test/anonstruct.r @@ -0,0 +1,57 @@ +void printf (string fmt, ...) = #0; + +typedef struct xyzzy_s { + int magic; +} xyzzy_t; + +typedef struct anon_s { + int foo; + int id; + struct { + int bar; + int baz; + }; + union { + int snafu; + float fizzle; + }; +} anon_t; + +union { + int xsnafu; + float xfizzle; +}; + +int foo (float f) +{ + anon_t anon; + anon.fizzle = f; + return anon.snafu; +} + +int main() +{ + anon_t anon; + int ret = 0; + if (&anon.snafu != &anon.fizzle) { + printf ("anon union broken: %p %p\n", + &anon.snafu, &anon.fizzle); + ret |= 1; + } + if (&anon.snafu - &anon.baz != 1) { + printf ("snafu and baz not adjacant: snafu:%p baz:%p\n", + &anon.snafu, &anon.baz); + ret |= 1; + } + if (&anon.baz - &anon.bar != 1) { + printf ("baz and bar not adjacant: baz:%p bar:%p\n", + &anon.baz, &anon.bar); + ret |= 1; + } + if (&anon.bar - &anon.id != 1) { + printf ("bar not after id: bar:%p id:%p\n", + &anon.bar, &anon.id); + ret |= 1; + } + return ret; +}