Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 10 additions & 15 deletions decbench/metrics/type_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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":
Expand All @@ -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

Expand All @@ -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]
Expand All @@ -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 = []
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 27 additions & 4 deletions tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down