Skip to content

Commit

Permalink
[ValueError@identity_elimination.py:226] ValueError: At least two var…
Browse files Browse the repository at this point in the history
…iables in the identity group {eax_1#5 (type: char * aliased: False), eax_1#7 (type: char * aliased: False), ebx#0 (type: char * aliased: False), ebx#2 (type: char * aliased: False), eax_1#4 (type: char * aliased: False)} have out degree zero, namely eax_1#5 and eax_1#4, i.e., these set of vertices is not an identity group (#389)

* Create draft PR for #388

* fix that it does not crash when the group is not a identity group

* add pytest

* fix format

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eva-Maria Behner <[email protected]>
  • Loading branch information
github-actions[bot] and ebehner authored Feb 26, 2024
1 parent b26c6ed commit 50d6b52
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 18 deletions.
41 changes: 23 additions & 18 deletions decompiler/pipeline/dataflowanalysis/identity_elimination.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,49 @@
from __future__ import annotations

from collections import defaultdict, namedtuple
from dataclasses import dataclass
from logging import error, info
from typing import DefaultDict, Dict, Iterator, List, Optional, Set, Tuple, Union

from decompiler.pipeline.stage import PipelineStage
from decompiler.structures.graphs.cfg import BasicBlock
from decompiler.structures.pseudo.expressions import Constant, GlobalVariable, UnknownExpression, Variable
from decompiler.structures.pseudo.instructions import Assignment, Instruction, Phi, Relation
from decompiler.structures.pseudo.instructions import Assignment, BaseAssignment, Instruction, Phi, Relation
from decompiler.task import DecompilerTask
from networkx import DiGraph, node_disjoint_paths, weakly_connected_components
from networkx.exception import NetworkXNoPath


@dataclass
class DefinitionLocation:
block: BasicBlock
definition: BaseAssignment


class _IdentityDataflow:
"""
Class implementing helper methods for IdentityGraph to extract dataflow information from a given graph.
Implemented for usage in IdentityElimination and VariableReplacer only.
"""

DefinitionLocation = namedtuple("DefinitionLocation", ["block", "definition"])

def __init__(self):
"""Generate a new IdentityDataflow object."""
self._use_map: DefaultDict[Variable, List[Instruction]] = defaultdict(list)
self._def_map: Dict[Variable, _IdentityDataflow.DefinitionLocation] = dict()
self._def_map: Dict[Variable, DefinitionLocation] = dict()

def parse_dataflow(self, instruction: Instruction, basic_block: BasicBlock):
"""Parse the dataflow information of the given instruction."""
for required_variable in instruction.requirements:
self._use_map[required_variable].append(instruction)
for defined_value in instruction.definitions:
self._def_map[defined_value] = self.DefinitionLocation(basic_block, instruction)
assert isinstance(instruction, BaseAssignment), f"The Instruction {instruction} must be an Assignment if it has a Definition."
self._def_map[defined_value] = DefinitionLocation(basic_block, instruction)

def get_usages(self, variable: Variable) -> Iterator[Instruction]:
"""Yield all parsed usages for the given Variable."""
yield from self._use_map[variable]

def get_definition(self, variable: Variable) -> Optional[_IdentityDataflow.DefinitionLocation]:
def get_definition(self, variable: Variable) -> Optional[DefinitionLocation]:
"""Get the DefinitionLocation of the given variable."""
return self._def_map.get(variable, None)

Expand Down Expand Up @@ -205,36 +211,34 @@ def _is_aliased_variable_in(required_values: List[Union[Constant, Variable]]) ->
for required_variable in required_values
)

def find_replacement_variable_of_group(self, identity_group: Set[Variable]) -> Variable:
def find_replacement_variable_of_group(self, identity_group: Set[Variable]) -> Optional[Variable]:
"""Returns the variable of the identity group that is initially defined."""
replacement_variable = None
optional_variable = None
for variable in identity_group:
if self.out_degree(variable):
if self.out_degree(variable) > 0:
continue
if not self._is_defining_value(variable):
optional_variable = variable
continue
if replacement_variable is None:
replacement_variable = variable
else:
message = (
info(
f"At least two variables in the identity group {identity_group} have out degree zero, namely "
f"{replacement_variable} and {variable}, i.e., these set of vertices is not an identity group"
)
error(message)
raise ValueError(message)
return None
if replacement_variable:
return replacement_variable
elif optional_variable:
return optional_variable
else:
message = (
f"No variable in the identity group {identity_group} has out degree zero, i.e., these set of vertices has no initial"
f"definition."
info(
f"No variable in the identity group {identity_group} has out degree zero, "
f"thus this set of Variables has no initial definition."
)
error(message)
raise ValueError(message)
return None


class _VariableReplacer:
Expand Down Expand Up @@ -310,11 +314,12 @@ def run(self, task: DecompilerTask):
identity_graph, dataflow = self._parse_cfg(task)
variable_replacer = _VariableReplacer(dataflow)
for identity_group in identity_graph.yield_identities():
variable_replacer.replace_variables(identity_group, identity_graph.find_replacement_variable_of_group(identity_group))
if replacement_variable := identity_graph.find_replacement_variable_of_group(identity_group):
variable_replacer.replace_variables(identity_group, replacement_variable)

@staticmethod
def _parse_cfg(task: DecompilerTask) -> Tuple[_IdentityGraph, _IdentityDataflow]:
"""Setup the IdentityGraph and The IdentityDataflow objects in a single iteration of all instructions."""
"""Set up the IdentityGraph and The IdentityDataflow objects in a single iteration of all instructions."""
dataflow = _IdentityDataflow()
identity_graph = _IdentityGraph(task.function_parameters)
for basic_block in task.graph:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2396,3 +2396,48 @@ def test_do_not_identify_relations():
assert vertices[6].instructions == [instructions[15]]
assert vertices[7].instructions == [instructions[16]]
assert vertices[8].instructions == [instructions[17]]


def test_do_not_crash_if_no_identity():
"""
+----------------------------------------------------------------------+
v |
+------------------+ +------------------+ +-------------------+ |
| 4. | | 2. | | 0. | |
| z#2 = ϕ(z#0,y#2) | | y#2 = a_1#0 | | if(a_0#0 < a_1#0) | |
| return z#2 | <-- | if(a_1#0 < 0x14) | <-- | | |
+------------------+ +------------------+ +-------------------+ |
| | |
| | |
| v |
| +-------------------+ |
| | 1. | |
| | y#1 = a_0#0 | |
| | if(a_0#0 > 0xa) | -+
| +-------------------+
| |
| |
| v
| +-------------------+
| | 3. |
| | z#1 = ϕ(z#0,y#1) |
+--------------------> | return z#1 |
+-------------------+
"""
y0, y1, y2 = [Variable("y", Integer.int32_t(), i) for i in range(3)]
z0, z1, z2 = [Variable("z", Integer.int32_t(), i) for i in range(3)]
a_0, a_1 = [Variable(f"a_{i}", ssa_label=0) for i in range(2)]
cfg = ControlFlowGraph()
cfg.add_nodes_from(
[
head := BasicBlock(0, instructions=[Branch(Condition(OperationType.less, [a_0, a_1]))]),
true := BasicBlock(1, [Assignment(y1, a_0), Branch(Condition(OperationType.greater, [a_0, Constant(10, Integer.int32_t())]))]),
false := BasicBlock(2, [Assignment(y2, a_1), Branch(Condition(OperationType.less, [a_1, Constant(20, Integer.int32_t())]))]),
r1 := BasicBlock(3, [Phi(z1.copy(), [z0.copy(), y1.copy()]), Return([z1])]),
r2 := BasicBlock(4, [Phi(z2.copy(), [z0.copy(), y2.copy()]), Return([z2])]),
]
)
cfg.add_edges_from(
[TrueCase(head, true), FalseCase(head, false), TrueCase(true, r1), FalseCase(true, r2), TrueCase(false, r2), FalseCase(false, r1)]
)
IdentityElimination().run(DecompilerTask("test", cfg, function_parameters=[a_0, a_1]))

0 comments on commit 50d6b52

Please sign in to comment.