Skip to content

Commit ff397a1

Browse files
Fix #42: Support attributes on anonymous struct types (#96)
This commit adds support for parsing and generating C code with attributes on anonymous struct types, such as `struct __attribute__((packed)) { ... };` and `struct { ... } __attribute__((packed));`. Implementation details: - Added StructExt class to extend c_ast.Struct with attribute support - Added three parser rules to handle different attribute positions: * Attributes before anonymous struct body * Attributes before named struct body * Attributes after anonymous struct in declarations - Updated generator with visit_StructExt method to output attributes - Enhanced test cases with round-trip verification The fix handles both syntactic forms and ensures proper code generation and round-trip parsing. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 834b63e commit ff397a1

File tree

3 files changed

+194
-0
lines changed

3 files changed

+194
-0
lines changed

pycparserext/ext_c_generator.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ def visit_TypeList(self, n):
162162
def visit_RangeExpression(self, n):
163163
return "%s ... %s" % (self.visit(n.first), self.visit(n.last))
164164

165+
def visit_Struct(self, n):
166+
"""Generate code for struct, handling attributes if present."""
167+
s = self._generate_struct_union_enum(n, "struct")
168+
# If this is a StructExt with attributes, add them
169+
if hasattr(n, "attrib") and n.attrib:
170+
s += " " + self.visit(n.attrib)
171+
return s
172+
173+
def visit_StructExt(self, n):
174+
"""Generate code for StructExt with attributes."""
175+
return self.visit_Struct(n)
176+
165177

166178
class GNUCGenerator(GnuCGenerator):
167179
def __init__(self):

pycparserext/ext_c_parser.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,18 @@ def from_pycparser(ad):
216216
)
217217

218218

219+
class StructExt(c_ast.Struct):
220+
"""Extended Struct that can hold attributes."""
221+
@staticmethod
222+
def from_pycparser(st):
223+
assert isinstance(st, c_ast.Struct)
224+
return StructExt(
225+
name=st.name,
226+
decls=st.decls,
227+
coord=st.coord
228+
)
229+
230+
219231
def to_decl_ext(d):
220232
if isinstance(d, c_ast.TypeDecl):
221233
return TypeDeclExt.from_pycparser(d)
@@ -589,6 +601,44 @@ def p_struct_declaration_list_1(self, p):
589601
""" struct_declaration_list : empty """
590602
p[0] = None
591603

604+
# Support attributes on anonymous struct/union declarations
605+
# e.g., struct { int a; } __attribute__((packed));
606+
def p_struct_declaration_anonymous_with_attr(self, p):
607+
""" struct_declaration : specifier_qualifier_list attributes_opt SEMI
608+
"""
609+
spec = p[1]
610+
attr_decl = p[2]
611+
assert "typedef" not in spec["storage"]
612+
613+
# Anonymous struct/union with attributes
614+
if len(spec["type"]) == 1:
615+
node = spec["type"][0]
616+
if isinstance(node, c_ast.Node):
617+
decl_type = node
618+
619+
# If we have attributes and this is a Struct/Union, attach them
620+
if attr_decl and attr_decl.exprs and isinstance(node, c_ast.Struct):
621+
# Convert to StructExt and attach attributes
622+
if not isinstance(node, StructExt):
623+
struct_ext = StructExt.from_pycparser(node)
624+
struct_ext.attrib = AttributeSpecifier(attr_decl)
625+
decl_type = struct_ext
626+
else:
627+
node.attrib = AttributeSpecifier(attr_decl)
628+
else:
629+
decl_type = c_ast.IdentifierType(node)
630+
631+
decls = self._build_declarations(
632+
spec=spec,
633+
decls=[{"decl": decl_type}])
634+
else:
635+
# Structure/union members can have the same names as typedefs
636+
decls = self._build_declarations(
637+
spec=spec,
638+
decls=[{"decl": None, "init": None}])
639+
640+
p[0] = decls
641+
592642
def p_range_designator(self, p):
593643
""" designator : LBRACKET constant_expression \
594644
ELLIPSIS constant_expression RBRACKET
@@ -611,6 +661,70 @@ def p_unified_volatile_gnu(self, p):
611661
| __VOLATILE__
612662
"""
613663
p[0] = p[1]
664+
665+
# Support attributes on struct/union types
666+
# struct __attribute__((packed)) { int a; };
667+
def p_struct_or_union_specifier_with_attr_1(self, p):
668+
"""struct_or_union_specifier : struct_or_union attributes_opt \
669+
brace_open struct_declaration_list brace_close
670+
| struct_or_union attributes_opt \
671+
brace_open brace_close
672+
"""
673+
klass = self._select_struct_union_class(p[1])
674+
attr_decl = p[2]
675+
676+
if len(p) == 5:
677+
# Empty struct/union
678+
struct = klass(
679+
name=None,
680+
decls=[],
681+
coord=self._token_coord(p, 1))
682+
else:
683+
struct = klass(
684+
name=None,
685+
decls=p[4],
686+
coord=self._token_coord(p, 1))
687+
688+
# Attach attributes if present
689+
if attr_decl and attr_decl.exprs:
690+
struct_ext = StructExt.from_pycparser(struct)
691+
struct_ext.attrib = AttributeSpecifier(attr_decl)
692+
p[0] = struct_ext
693+
else:
694+
p[0] = struct
695+
696+
def p_struct_or_union_specifier_with_attr_2(self, p):
697+
"""struct_or_union_specifier : struct_or_union ID attributes_opt \
698+
brace_open struct_declaration_list brace_close
699+
| struct_or_union ID attributes_opt \
700+
brace_open brace_close
701+
| struct_or_union TYPEID \
702+
attributes_opt brace_open struct_declaration_list brace_close
703+
| struct_or_union TYPEID \
704+
attributes_opt brace_open brace_close
705+
"""
706+
klass = self._select_struct_union_class(p[1])
707+
attr_decl = p[3]
708+
709+
if len(p) == 6:
710+
# Empty struct/union with name
711+
struct = klass(
712+
name=p[2],
713+
decls=[],
714+
coord=self._token_coord(p, 2))
715+
else:
716+
struct = klass(
717+
name=p[2],
718+
decls=p[5],
719+
coord=self._token_coord(p, 2))
720+
721+
# Attach attributes if present
722+
if attr_decl and attr_decl.exprs:
723+
struct_ext = StructExt.from_pycparser(struct)
724+
struct_ext.attrib = AttributeSpecifier(attr_decl)
725+
p[0] = struct_ext
726+
else:
727+
p[0] = struct
614728
# }}}
615729

616730

test/test_pycparserext.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,74 @@ def test_pointer_to_function_with_attribute():
687687
assert _round_trip_matches(src)
688688

689689

690+
def test_packed_anonymous_struct_in_struct():
691+
from pycparser import c_ast
692+
693+
from pycparserext.ext_c_parser import GnuCParser
694+
parser = GnuCParser()
695+
code = """
696+
struct foo_t {
697+
struct __attribute__((packed)) {
698+
int a;
699+
char b;
700+
};
701+
};
702+
"""
703+
ast = parser.parse(code)
704+
assert isinstance(ast, c_ast.FileAST)
705+
assert len(ast.ext) == 1
706+
decl = ast.ext[0]
707+
assert isinstance(decl, c_ast.Decl)
708+
struct_foo = decl.type
709+
assert isinstance(struct_foo, c_ast.Struct)
710+
assert struct_foo.name == "foo_t"
711+
assert len(struct_foo.decls) == 1
712+
anon_struct_decl = struct_foo.decls[0]
713+
assert isinstance(anon_struct_decl, c_ast.Decl)
714+
anon_struct = anon_struct_decl.type
715+
assert isinstance(anon_struct, c_ast.Struct)
716+
assert anon_struct.name is None
717+
assert hasattr(anon_struct, "attrib")
718+
assert anon_struct.attrib is not None
719+
720+
# Verify round-trip code generation
721+
assert _round_trip_matches(code)
722+
723+
724+
def test_packed_anonymous_struct_in_struct_after():
725+
from pycparser import c_ast
726+
727+
from pycparserext.ext_c_parser import GnuCParser
728+
parser = GnuCParser()
729+
code = """
730+
struct foo_t {
731+
struct {
732+
int a;
733+
char b;
734+
} __attribute__((packed));
735+
};
736+
"""
737+
ast = parser.parse(code)
738+
assert isinstance(ast, c_ast.FileAST)
739+
assert len(ast.ext) == 1
740+
decl = ast.ext[0]
741+
assert isinstance(decl, c_ast.Decl)
742+
struct_foo = decl.type
743+
assert isinstance(struct_foo, c_ast.Struct)
744+
assert struct_foo.name == "foo_t"
745+
assert len(struct_foo.decls) == 1
746+
anon_struct_decl = struct_foo.decls[0]
747+
assert isinstance(anon_struct_decl, c_ast.Decl)
748+
anon_struct = anon_struct_decl.type
749+
assert isinstance(anon_struct, c_ast.Struct)
750+
assert anon_struct.name is None
751+
assert hasattr(anon_struct, "attrib")
752+
assert anon_struct.attrib is not None
753+
754+
# Verify round-trip code generation
755+
assert _round_trip_matches(code)
756+
757+
690758
if __name__ == "__main__":
691759
import sys
692760
if len(sys.argv) > 1:

0 commit comments

Comments
 (0)