fix: guard get_operand_expressions against empty expression tree

`_build_expression_tree` already returns `[]` for the Ghidra bug where
an operand has no expressions (see
https://github.com/NationalSecurityAgency/ghidra/issues/6817), but
`get_operand_expressions` then called the recursive walker
unconditionally with `tree_index=0`, which indexed into the empty list
and raised `IndexError`. Add an early-return guard so callers receive
`[]` instead.
This commit is contained in:
Willi Ballenthin
2026-04-22 18:31:37 +03:00
committed by Willi Ballenthin
parent 5d43fc8fe3
commit 197a84d267
3 changed files with 23 additions and 0 deletions
+1
View File
@@ -32,6 +32,7 @@
- fix: loader.py reads entire file for magic byte check @williballenthin #3029
- fix: freeze/__init__.py: logically impossible condition @williballenthin #3030
- fix: EXTENSIONS_ELF never referenced @williballenthin #3031
- fix: guard get_operand_expressions against empty expression tree so Ghidra-exported BinExport2 files with empty operands no longer raise IndexError @williballenthin
- fix: add return after zero-offset yield in extract_insn_offset_features so Offset(0) is not emitted twice @williballenthin
- fix: use f-string in binexport2 extractor so unexpected global feature value appears in ValueError message @williballenthin
- fix: correct scale/displacement expressions in get_operand_phrase_info 5-expression branch (used expression3 operator instead of expression4 value) @williballenthin
@@ -333,6 +333,8 @@ def _fill_operand_expression_list(
def get_operand_expressions(be2: BinExport2, op: BinExport2.Operand) -> list[BinExport2.Expression]:
tree = _build_expression_tree(be2, op)
if not tree:
return []
expressions: list[BinExport2.Expression] = []
_fill_operand_expression_list(be2, op, tree, 0, expressions)
+20
View File
@@ -340,6 +340,26 @@ BE2 = ParseDict(
)
def test_get_operand_expressions_empty_operand():
be2 = ParseDict(
{
"expression": [
{"type": BinExport2.Expression.REGISTER, "symbol": "x0"},
],
"operand": [
{"expression_index": [0]},
{},
],
},
BinExport2(),
)
normal_op = be2.operand[0]
empty_op = be2.operand[1]
assert len(get_operand_expressions(be2, normal_op)) == 1
assert get_operand_expressions(be2, empty_op) == []
def test_is_stack_register_expression():
mov = ParseDict(BE2_DICT["instruction"][0], BinExport2.Instruction())
add = ParseDict(BE2_DICT["instruction"][2], BinExport2.Instruction())