From 405393963e0e64a0d993044ff3267e28e14a1b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EC=A3=BC?= Date: Tue, 10 Nov 2026 10:22:08 +0900 Subject: [PATCH] Honor DWARF reference forms in TypeMatch [AUTOMATED] Delegate type and abstract-origin references to pyelftools so absolute offsets and signatures use their encoded form. Invalidate cached scores. --- decbench/metrics/type_match.py | 25 ++++++++++--------------- docs/metrics.md | 4 ++-- tests/test_metrics.py | 31 +++++++++++++++++++++++++++---- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/decbench/metrics/type_match.py b/decbench/metrics/type_match.py index d39f87d..51ac9d3 100644 --- a/decbench/metrics/type_match.py +++ b/decbench/metrics/type_match.py @@ -461,11 +461,8 @@ def _parse_variable_die( return None if "DW_AT_abstract_origin" in die.attributes: - try: - attr = die.attributes["DW_AT_abstract_origin"] - die = dwarfinfo.get_DIE_from_refaddr(attr.value + die.cu.cu_offset) - except Exception: - pass + with contextlib.suppress(Exception): + die = die.get_DIE_from_attribute("DW_AT_abstract_origin") name = "" if "DW_AT_name" in die.attributes: @@ -475,7 +472,7 @@ def _parse_variable_die( size = 0 with contextlib.suppress(Exception): - type_names, size = _parse_type_die(die, dwarfinfo) + type_names, size = _parse_type_die(die) if not type_names: return None @@ -550,14 +547,12 @@ def _get_location(die: Any, dwarfinfo: Any) -> tuple[list[int], bool]: return offsets, has_location -def _parse_type_die(die: Any, dwarfinfo: Any) -> tuple[list[str], int]: +def _parse_type_die(die: Any) -> tuple[list[str], int]: """Recursively parse type DIEs to extract type name(s) and size.""" if "DW_AT_type" not in die.attributes: return ["void"], 0 - attr = die.attributes["DW_AT_type"] - type_offset = attr.value + die.cu.cu_offset - type_die = dwarfinfo.get_DIE_from_refaddr(type_offset) + type_die = die.get_DIE_from_attribute("DW_AT_type") tag = type_die.tag if tag == "DW_TAG_base_type": @@ -572,7 +567,7 @@ def _parse_type_die(die: Any, dwarfinfo: Any) -> tuple[list[str], int]: names = [] if "DW_AT_name" in type_die.attributes: names.append(type_die.attributes["DW_AT_name"].value.decode("utf-8", "replace")) - child_names, size = _parse_type_die(type_die, dwarfinfo) + child_names, size = _parse_type_die(type_die) names.extend(child_names) return names, size @@ -583,7 +578,7 @@ def _parse_type_die(die: Any, dwarfinfo: Any) -> tuple[list[str], int]: ): # A reference is a pointer in the ABI and every decompiler renders it as # one; without this arm every reference-typed GT variable became "void". - child_names, _ = _parse_type_die(type_die, dwarfinfo) + child_names, _ = _parse_type_die(type_die) if not child_names: child_names = ["void"] ptr_names = [n + "*" for n in child_names] @@ -593,14 +588,14 @@ def _parse_type_die(die: Any, dwarfinfo: Any) -> tuple[list[str], int]: return ptr_names, size elif tag == "DW_TAG_array_type": - child_names, elem_size = _parse_type_die(type_die, dwarfinfo) + child_names, elem_size = _parse_type_die(type_die) dims = _get_array_dims(type_die) length = dims[0] if dims and dims[0] else 1 arr_names = [f"{n}[{length}]" for n in child_names] return child_names + arr_names, elem_size * length elif tag in ("DW_TAG_const_type", "DW_TAG_volatile_type", "DW_TAG_restrict_type"): - return _parse_type_die(type_die, dwarfinfo) + return _parse_type_die(type_die) elif tag == "DW_TAG_structure_type": names = [] @@ -841,7 +836,7 @@ class TypeMatchMetric(Metric): display_name = "Type Correctness" description = "Accuracy of variable type recovery vs DWARF ground truth" - cache_version = "18" + cache_version = "19" weight = 1.0 lower_is_better = False diff --git a/docs/metrics.md b/docs/metrics.md index fc97b88..be3ed5d 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -118,8 +118,8 @@ Ground truth is the function's OWN parameters and locals: the variables of an inlined callee (`DW_TAG_inlined_subroutine`, at `-O2` mostly the parameters of glibc's `_FORTIFY_SOURCE` wrappers such as `printf`'s `__fmt`) are not scored, since the compiled function usually has no storage for them. -Current `cache_version="18"`, bumped for scoped syntax-aware declaration parsing, -aggregate/qualifier normalization, and DWARF restrict-type passthrough. +Current `cache_version="19"`, bumped to resolve DWARF type and abstract-origin +references according to their encoded reference form. The per-function key covers source and decompiler address evidence, argument/stack anchors, recovered types, source selection diagnostics, and the matching thresholds. diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 7aafa35..46b31a8 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -474,9 +474,18 @@ def test_qualifier_tokens_at_pointer_boundaries(self) -> None: assert normalize_type(name) == {name} assert normalize_type("struct") == {"struct"} - def test_restrict_die_preserves_typedef_pointer_and_size(self) -> None: + @pytest.mark.parametrize( + ("form", "reference"), + [("DW_FORM_ref4", 4), ("DW_FORM_ref_addr", 104), ("DW_FORM_ref_sig8", 0xA1B2)], + ) + def test_restrict_die_preserves_typedef_pointer_and_size( + self, form: str, reference: int + ) -> None: + from functools import partial from types import SimpleNamespace + from elftools.dwarf.die import DIE + from decbench.metrics.type_match import _parse_type_die cu = SimpleNamespace(cu_offset=100) @@ -493,12 +502,26 @@ def test_restrict_die_preserves_typedef_pointer_and_size(self) -> None: + index: SimpleNamespace( tag=tag, cu=cu, - attributes={key: SimpleNamespace(value=value) for key, value in attrs.items()}, + attributes={ + key: SimpleNamespace(value=value, raw_value=value, form="DW_FORM_ref4") + for key, value in attrs.items() + }, ) for index, (tag, attrs) in enumerate(rows) } - dwarf = SimpleNamespace(get_DIE_from_refaddr=dies.__getitem__) - assert _parse_type_die(dies[105], dwarf) == (["IntPointer", "int*"], 8) + dwarf = SimpleNamespace( + get_DIE_from_refaddr=dies.__getitem__, + get_DIE_by_sig8={0xA1B2: dies[104]}.__getitem__, + ) + cu.dwarfinfo = dwarf + cu.get_DIE_from_refaddr = dies.__getitem__ + for die in dies.values(): + die.get_DIE_from_attribute = partial(DIE.get_DIE_from_attribute, die) + variable = dies[105] + variable.attributes["DW_AT_type"] = SimpleNamespace( + value=reference, raw_value=reference, form=form + ) + assert _parse_type_die(variable) == (["IntPointer", "int*"], 8) def test_type_match_with_matching_types(self) -> None: from decbench.metrics.type_match import TypeMatchMetric