Method dispatch axis: map id -> body via Runner::run switch
Recovers how a script method id maps to its implementation, the foundation for body-level normalisation. Each CMC_*_Runner::run is a switch(id) (vtable slot 17); every case is the method body — inline (MSVC6) or a tail-call to a separate show()/load() (MSVC8). The extractor parses the jump table at the disassembly level (Ghidra's decompiler jump-table recovery silently dropped the big runners), fingerprints each case by its ordered CALL anchors (Class::method / vtbl+0xNN), and expands thin wrappers one level so MSVC8 lines up with MSVC6. Validated on the golden pair: Animo SHOW..RESUME (id 1-4) yield identical leaves (getAnimo + vtbl+0xa0/0xa4/0x4c/0x50) across both compilers. Coverage 30/32 runners; Piklib 475 / BlooMoo 619 dispatch rows. - extract_engine_surface.py: extract_method_dispatch (schema_version -> 4) - snapshots regenerated with the method_dispatch axis - ams: Snapshot.method_dispatch; diff axis keyed (owner,id) on [impl,calls] with method-name join; render METHOD BODIES section; cli --only dispatch; owner filter - UI: "Ciała metod" diff axis + browse tab - tests: body-change unit + cross-compiler vtbl assertion -> 29/29 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
20
ams/diff.py
20
ams/diff.py
@@ -69,6 +69,22 @@ def _detect_method_moves(old_m: list[Item], new_m: list[Item]) -> list[Item]:
|
||||
return moves
|
||||
|
||||
|
||||
def _dispatch_key(x: Item) -> Hashable:
|
||||
return (x["owner"], x["id"])
|
||||
|
||||
|
||||
def _dispatch_with_names(snap: Snapshot) -> list[Item]:
|
||||
"""Attach the method name (from the methods axis, joined on owner+id) to each dispatch row,
|
||||
so a body-level diff reads as 'SHOW body changed' rather than 'CMC_Animo id 1 changed'."""
|
||||
name_by = {(m["owner"], m.get("id")): m["name"] for m in snap.methods}
|
||||
out = []
|
||||
for r in snap.method_dispatch:
|
||||
rr = dict(r)
|
||||
rr["name"] = name_by.get((r["owner"], r["id"]))
|
||||
out.append(rr)
|
||||
return out
|
||||
|
||||
|
||||
def compute_diff(old: Snapshot, new: Snapshot) -> dict[str, Any]:
|
||||
return {
|
||||
"binary": {"from": old.binary, "to": new.binary},
|
||||
@@ -78,6 +94,8 @@ def compute_diff(old: Snapshot, new: Snapshot) -> dict[str, Any]:
|
||||
"fields": keyed_diff(old.fields, new.fields, _owner_name_key, ["type"]),
|
||||
"struct_layout": keyed_diff(old.struct_layout, new.struct_layout, _layout_key,
|
||||
["size", "is_vtable"]),
|
||||
"method_dispatch": keyed_diff(_dispatch_with_names(old), _dispatch_with_names(new),
|
||||
_dispatch_key, ["impl", "calls"]),
|
||||
"method_inheritance": keyed_diff(old.method_inheritance, new.method_inheritance,
|
||||
lambda x: x["runner"], ["base_runner"]),
|
||||
"field_inheritance": keyed_diff(old.field_inheritance, new.field_inheritance,
|
||||
@@ -90,7 +108,7 @@ def compute_diff(old: Snapshot, new: Snapshot) -> dict[str, Any]:
|
||||
def _item_owner(axis: str, item: Item) -> str | None:
|
||||
if axis == "types":
|
||||
return item.get("cpp_class")
|
||||
if axis in ("methods", "events", "fields", "struct_layout"):
|
||||
if axis in ("methods", "events", "fields", "struct_layout", "method_dispatch"):
|
||||
return item.get("owner")
|
||||
if axis == "method_inheritance":
|
||||
return item.get("runner")
|
||||
|
||||
Reference in New Issue
Block a user