Fix IDA v9 XML exporter datatype comment handling

Fixes #8832. Use single-comment model for structures, enums, unions, and members. Omit empty comments and stop emitting spurious REPEATABLE_CMT elements.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Abdul Samad
2026-06-26 16:16:54 +05:00
parent b8dde537d5
commit 56a7fa78c9

View File

@@ -877,9 +877,8 @@ class XmlExporter(IdaXml):
cname = idc.get_enum_member_name(cid) cname = idc.get_enum_member_name(cid)
if cname is None or len(cname) == 0: if cname is None or len(cname) == 0:
return return
regcmt = idc.get_enum_member_cmt(cid, False) cmt = idc.get_enum_member_cmt(cid, False)
rptcmt = idc.get_enum_member_cmt(cid, True) has_comment = self._normalize_comment_text(cmt) is not None
has_comment = regcmt is not None
self.start_element(ENUM_ENTRY) self.start_element(ENUM_ENTRY)
self.write_attribute(NAME, cname) self.write_attribute(NAME, cname)
value = idc.get_enum_member_value(cid) value = idc.get_enum_member_value(cid)
@@ -888,11 +887,8 @@ class XmlExporter(IdaXml):
#if bf: #if bf:
# self.write_numeric_attribute(BIT_MASK, mask) # self.write_numeric_attribute(BIT_MASK, mask)
self.close_tag(has_comment) self.close_tag(has_comment)
if regcmt is not None and len(regcmt) > 0: if has_comment:
self.export_regular_cmt(regcmt) self._export_datatype_comment(cmt)
if rptcmt is not None and len(rptcmt) > 0:
self.export_repeatable_cmt(rptcmt)
if (has_comment):
self.end_element(ENUM_ENTRY) self.end_element(ENUM_ENTRY)
@@ -990,12 +986,10 @@ class XmlExporter(IdaXml):
# BIT_FIELD attribute not supported for ENUM export # BIT_FIELD attribute not supported for ENUM export
#if bf: #if bf:
# self.write_attribute(BIT_FIELD, "yes") # self.write_attribute(BIT_FIELD, "yes")
regcmt = idc.get_enum_cmt(eid) enum_cmt = idc.get_enum_cmt(eid)
tif = ida_typeinf.tinfo_t() has_enum_cmt = self._normalize_comment_text(enum_cmt) is not None
tif.get_type_by_tid(tid=eid)
rptcmt = tif.get_type_rptcmt()
has_children = ((idc.get_enum_size(eid) > 0) or has_children = ((idc.get_enum_size(eid) > 0) or
(regcmt is not None) or (rptcmt is not None) or has_enum_cmt or
(ida_bytes.get_radix(eflags, 0) != 16) or (ida_bytes.get_radix(eflags, 0) != 16) or
(self.is_signed_data(eflags))) (self.is_signed_data(eflags)))
self.close_tag(has_children) self.close_tag(has_children)
@@ -1007,10 +1001,8 @@ class XmlExporter(IdaXml):
if self.is_signed_data(eflags): if self.is_signed_data(eflags):
self.write_attribute(SIGNED, "yes") self.write_attribute(SIGNED, "yes")
self.close_tag() self.close_tag()
if regcmt is not None: if has_enum_cmt:
self.export_regular_cmt(regcmt) self._export_datatype_comment(enum_cmt)
if rptcmt is not None:
self.export_repeatable_cmt(rptcmt)
self.export_enum_members(eid, bf, eflags) self.export_enum_members(eid, bf, eflags)
if (has_children): if (has_children):
self.end_element(ENUM) self.end_element(ENUM)
@@ -1205,15 +1197,10 @@ class XmlExporter(IdaXml):
dtype = "%s[%d]" % (arraytype, size//msize) dtype = "%s[%d]" % (arraytype, size//msize)
self.write_attribute(DATATYPE, dtype) self.write_attribute(DATATYPE, dtype)
self.write_numeric_attribute(SIZE, size*self.cbsize) self.write_numeric_attribute(SIZE, size*self.cbsize)
regcmt = m.cmt if m.is_regcmt() else None hascmt = self._normalize_comment_text(m.cmt) is not None
rptcmt = m.cmt if not m.is_regcmt() else None
hascmt = regcmt is not None or rptcmt is not None
self.close_tag(hascmt) self.close_tag(hascmt)
if (hascmt): if hascmt:
if regcmt is not None: self._export_datatype_comment(m.cmt)
self.export_regular_cmt(regcmt)
if rptcmt is not None:
self.export_repeatable_cmt(rptcmt)
self.end_element(MEMBER) self.end_element(MEMBER)
@@ -1514,6 +1501,35 @@ class XmlExporter(IdaXml):
self.display_cpu_time(timer) self.display_cpu_time(timer)
def _normalize_comment_text(self, cmt):
"""
Returns stripped comment text, or None if empty or unset.
IDA v8+ uses a single comment field for datatypes; empty strings must
not be treated as present comments.
"""
if cmt is None:
return None
text = ida_lines.tag_remove(cmt + " ").strip()
if len(text) == 0:
return None
return text
def _export_datatype_comment(self, cmt):
"""
Exports a datatype comment using IDA v9's single-comment model.
Datatype comments are written as REGULAR_CMT only (no REPEATABLE_CMT).
Returns True if a comment element was written.
"""
text = self._normalize_comment_text(cmt)
if text is not None:
self.export_regular_cmt(text)
return True
return False
def export_regular_cmt(self, cmt: str) -> None: def export_regular_cmt(self, cmt: str) -> None:
""" """
Exports the regular comment for an item. Exports the regular comment for an item.
@@ -1679,15 +1695,13 @@ class XmlExporter(IdaXml):
self.write_numeric_attribute(SIZE, size) self.write_numeric_attribute(SIZE, size)
if s.is_varstruct(): if s.is_varstruct():
self.write_attribute(VARIABLE_LENGTH, "y") self.write_attribute(VARIABLE_LENGTH, "y")
regcmt = s.get_type_cmt() type_cmt = s.get_type_cmt()
rptcmt = s.get_type_rptcmt() has_type_cmt = self._normalize_comment_text(type_cmt) is not None
has_contents = regcmt is not None or rptcmt is not None or s.get_udt_nmembers() > 0 has_contents = has_type_cmt or s.get_udt_nmembers() > 0
self.close_tag(has_contents) self.close_tag(has_contents)
if (has_contents): if has_contents:
if regcmt is not None: if has_type_cmt:
self.export_regular_cmt(regcmt) self._export_datatype_comment(type_cmt)
if rptcmt is not None:
self.export_repeatable_cmt(rptcmt)
if s.get_udt_nmembers() > 0: if s.get_udt_nmembers() > 0:
self.export_members(s) self.export_members(s)
self.end_element(stype) self.end_element(stype)