Skip to content

Commit e38e2c8

Browse files
retifII Assistant
andauthored
Fix: Support __attribute__ within struct declarations (#95)
This commit fixes issue #75 where __attribute__ specifiers within struct declarations were not parsed correctly, leading to a ParseError. Example code that now parses correctly: typedef struct { __attribute__((__deprecated__)) int test1; } test2; Changes: - Extended specifier_qualifier_list rule in GnuCParser to handle function_specifier nodes, allowing __attribute__ within structs - Added _p_specifier_qualifier_list_left_recursion method to correctly handle AttributeSpecifier nodes in the funcspec list - Modified _generate_decl in GnuCGenerator to handle funcspec list with AttributeSpecifier nodes - Improved AttributeSpecifier.__eq__ to use structural comparison of AST nodes instead of object identity - Added round-trip test to verify the fix Fixes #75 Co-authored-by: II Assistant <[email protected]>
1 parent ff397a1 commit e38e2c8

File tree

3 files changed

+101
-6
lines changed

3 files changed

+101
-6
lines changed

pycparserext/ext_c_generator.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ def _generate_type(self, n, modifiers=None, emit_declname=True):
127127
else:
128128
return self.visit(n)
129129

130+
def visit_AttributeSpecifier(self, n):
131+
return "__attribute__((" + self.visit(n.exprlist) + "))"
132+
133+
134+
class GnuCGenerator(AsmAndAttributesMixin, CGeneratorBase):
130135
def _generate_decl(self, n):
131136
""" Generation from a Decl node.
132137
"""
@@ -145,11 +150,6 @@ def funcspec_to_str(i):
145150
s += self._generate_type(n.type)
146151
return s
147152

148-
def visit_AttributeSpecifier(self, n):
149-
return " __attribute__((" + self.visit(n.exprlist) + "))"
150-
151-
152-
class GnuCGenerator(AsmAndAttributesMixin, CGeneratorBase):
153153
def visit_TypeOfDeclaration(self, n):
154154
return "%s(%s)" % (n.typeof_keyword, self.visit(n.declaration))
155155

pycparserext/ext_c_parser.py

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import pycparser.c_ast as c_ast
32
import pycparser.c_parser
43

@@ -59,6 +58,39 @@ class AttributeSpecifier(c_ast.Node):
5958
def __init__(self, exprlist):
6059
self.exprlist = exprlist
6160

61+
def __eq__(self, other):
62+
if not isinstance(other, AttributeSpecifier):
63+
return False
64+
# Recursively compare the exprlist nodes
65+
return self._compare_ast_nodes(self.exprlist, other.exprlist)
66+
67+
def _compare_ast_nodes(self, node1, node2):
68+
"""Recursively compare two AST nodes for structural equality."""
69+
if type(node1) is not type(node2):
70+
return False
71+
72+
# Compare attributes
73+
if hasattr(node1, "attr_names"):
74+
for attr in node1.attr_names:
75+
val1 = getattr(node1, attr)
76+
val2 = getattr(node2, attr)
77+
if val1 != val2:
78+
return False
79+
80+
# Compare children
81+
if hasattr(node1, "children"):
82+
children1 = node1.children()
83+
children2 = node2.children()
84+
if len(children1) != len(children2):
85+
return False
86+
for (name1, child1), (name2, child2) in zip(children1, children2):
87+
if name1 != name2:
88+
return False
89+
if not self._compare_ast_nodes(child1, child2):
90+
return False
91+
92+
return True
93+
6294
def children(self):
6395
return [("exprlist", self.exprlist)]
6496

@@ -71,6 +103,32 @@ def __iter__(self):
71103
attr_names = ()
72104

73105

106+
class DeclExt(c_ast.Decl):
107+
@staticmethod
108+
def from_pycparser(decl):
109+
assert isinstance(decl, c_ast.Decl)
110+
new_decl = DeclExt(
111+
name=decl.name,
112+
quals=decl.quals,
113+
align=decl.align,
114+
storage=decl.storage,
115+
funcspec=decl.funcspec,
116+
type=decl.type,
117+
init=decl.init,
118+
bitsize=decl.bitsize,
119+
coord=decl.coord,
120+
)
121+
if hasattr(decl, "attributes"):
122+
new_decl.attributes = decl.attributes
123+
return new_decl
124+
125+
def children(self):
126+
nodelist = super().children()
127+
if hasattr(self, "attributes"):
128+
nodelist = (*nodelist, ("attributes", self.attributes))
129+
return nodelist
130+
131+
74132
class Asm(c_ast.Node):
75133
def __init__(self, asm_keyword, template, output_operands,
76134
input_operands, clobbered_regs, coord=None):
@@ -580,6 +638,33 @@ def p_gnu_unary_expression(self, p):
580638
p[2] if len(p) == 3 else p[3],
581639
self._token_coord(p, 1))
582640

641+
def p_specifier_qualifier_list_fs(self, p):
642+
""" specifier_qualifier_list : function_specifier specifier_qualifier_list
643+
"""
644+
self._p_specifier_qualifier_list_left_recursion(p)
645+
646+
def _p_specifier_qualifier_list_left_recursion(self, p):
647+
# The PLY documentation says that left-recursive rules are supported,
648+
# but it keeps complaining about reduce/reduce conflicts.
649+
#
650+
# See `_p_specifier_qualifier_list_right_recursion` for a non-complaining
651+
# version.
652+
spec = p[1]
653+
spec_dict = p[2]
654+
655+
if isinstance(spec, AttributeSpecifier):
656+
spec_dict["function"].append(spec)
657+
elif isinstance(spec, str):
658+
spec_dict["qual"].append(spec)
659+
elif isinstance(spec, c_ast.Node):
660+
if "type" not in spec_dict:
661+
spec_dict["type"] = []
662+
spec_dict["type"].append(spec)
663+
else:
664+
raise TypeError(f"Unknown specifier {spec!r} of type {type(spec)}")
665+
666+
p[0] = spec_dict
667+
583668
def p_statement(self, p):
584669
""" statement : labeled_statement
585670
| expression_statement

test/test_pycparserext.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,16 @@ def test_packed_anonymous_struct_in_struct_after():
755755
assert _round_trip_matches(code)
756756

757757

758+
def test_attribute_in_struct():
759+
# https://github.com/inducer/pycparserext/issues/75
760+
src = """
761+
typedef struct {
762+
__attribute__((__deprecated__)) int test1;
763+
} test2;
764+
"""
765+
assert _round_trip_matches(src)
766+
767+
758768
if __name__ == "__main__":
759769
import sys
760770
if len(sys.argv) > 1:

0 commit comments

Comments
 (0)