Skip to content

Commit 7b704cb

Browse files
disambiguate clang2py's generic anonymous-field names in vmlinux-gen.py
Prevents ctypes _fields_ assignment collisions when multiple anonymous struct/union members across different structs get the same generic '_N' name from clang2py.
1 parent 03b1232 commit 7b704cb

1 file changed

Lines changed: 98 additions & 0 deletions

File tree

tools/vmlinux-gen.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,11 +261,109 @@ def repl(m):
261261
for name in invalid_ctypes:
262262
data = re.sub(rf"\bctypes\.{name}\b", name, data)
263263

264+
data = self.disambiguate_anonymous_field_names(data)
265+
264266
with open(self.output_file, "w") as f:
265267
f.write(data)
266268

267269
self.log(f"Saved final output to {self.output_file}")
268270

271+
def disambiguate_anonymous_field_names(self, data):
272+
"""Make clang2py's generic '_N' field names globally unique per struct/union.
273+
274+
clang2py names both (a) unnamed anonymous struct/union members that go
275+
into `_anonymous_`, and (b) other unnamed/reserved members, using the
276+
same per-struct sequential scheme ('_0', '_1', ...). ctypes flattens
277+
anonymous members by promoting their field names onto the parent class,
278+
so if a struct has an anonymous member (say '_0') whose type itself has
279+
a field also named '_1', and that same struct has ANOTHER anonymous
280+
member named '_1', the promoted name collides with the parent's own
281+
field key. ctypes then fails with a cryptic
282+
"type object 'c_ulong' has no attribute '_fields_'" (or similar) when
283+
`_fields_` is assigned. Prefixing every generic '_N' key with its
284+
owning struct/union name keeps these names unique across the whole
285+
file, so promoted names can never collide with a sibling's own key.
286+
"""
287+
self.log("Disambiguating generic anonymous-field names...")
288+
289+
def sanitize(name):
290+
return re.sub(r"[^0-9A-Za-z_]", "_", name)
291+
292+
lines = data.split("\n")
293+
class_re = re.compile(r"^class\s+(\w+)\(")
294+
stmt_fields_re = re.compile(r"^(\w+)\._fields_\s*=\s*\[\s*$")
295+
inline_fields_re = re.compile(r"^\s+_fields_\s*=\s*\[\s*$")
296+
anon_stmt_re = re.compile(r"^(\w+)\._anonymous_\s*=\s*\((.*)\)\s*$")
297+
anon_inline_re = re.compile(r"^(\s+)_anonymous_\s*=\s*\((.*)\)\s*$")
298+
key_re = re.compile(r"(\('_)([0-9]+)(',)")
299+
300+
last_class = None
301+
out = []
302+
i = 0
303+
n = len(lines)
304+
renamed_count = 0
305+
while i < n:
306+
line = lines[i]
307+
308+
m_class = class_re.match(line)
309+
if m_class:
310+
last_class = m_class.group(1)
311+
out.append(line)
312+
i += 1
313+
continue
314+
315+
m_anon = anon_stmt_re.match(line)
316+
if m_anon:
317+
cls, body = m_anon.group(1), m_anon.group(2)
318+
prefix = sanitize(cls)
319+
new_body, cnt = re.subn(
320+
r"'_([0-9]+)'", lambda mm: f"'_{prefix}_{mm.group(1)}'", body
321+
)
322+
renamed_count += cnt
323+
out.append(f"{cls}._anonymous_ = ({new_body})")
324+
i += 1
325+
continue
326+
327+
m_anon_inline = anon_inline_re.match(line)
328+
if m_anon_inline and last_class:
329+
indent, body = m_anon_inline.group(1), m_anon_inline.group(2)
330+
prefix = sanitize(last_class)
331+
new_body, cnt = re.subn(
332+
r"'_([0-9]+)'", lambda mm: f"'_{prefix}_{mm.group(1)}'", body
333+
)
334+
renamed_count += cnt
335+
out.append(f"{indent}_anonymous_ = ({new_body})")
336+
i += 1
337+
continue
338+
339+
m_stmt = stmt_fields_re.match(line)
340+
m_inline = inline_fields_re.match(line) if not m_stmt else None
341+
if m_stmt or m_inline:
342+
cls = m_stmt.group(1) if m_stmt else last_class
343+
prefix = sanitize(cls) if cls else None
344+
out.append(line)
345+
i += 1
346+
while i < n and lines[i].strip() != "]":
347+
fl = lines[i]
348+
if prefix:
349+
fl, cnt = key_re.subn(
350+
lambda mm: f"{mm.group(1)}{prefix}_{mm.group(2)}{mm.group(3)}",
351+
fl,
352+
)
353+
renamed_count += cnt
354+
out.append(fl)
355+
i += 1
356+
if i < n:
357+
out.append(lines[i]) # closing ']'
358+
i += 1
359+
continue
360+
361+
out.append(line)
362+
i += 1
363+
364+
self.log(f"Renamed {renamed_count} generic field keys")
365+
return "\n".join(out)
366+
269367
def cleanup(self):
270368
"""Remove temporary files if not keeping them."""
271369
if not self.keep_intermediate and self.temp_dir != ".":

0 commit comments

Comments
 (0)