Skip to content

Commit

Permalink
Merge branch 'main' into issue-390-_ValueError_identity_elimination_p…
Browse files Browse the repository at this point in the history
…y_278_ValueError_There_are_at_least_two_definitions_in_the_identity_group_var_58#0_=_var_58#0_+_&_var_58#0_var_58#0
  • Loading branch information
mari-mari authored Jun 25, 2024
2 parents e60a8f8 + 2bd0591 commit 7de33a3
Show file tree
Hide file tree
Showing 20 changed files with 773 additions and 176 deletions.
3 changes: 3 additions & 0 deletions decompiler/backend/cexpressiongenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ def visit_constant_composition(self, expr: expressions.ConstantComposition):
case CustomType(text="wchar16") | CustomType(text="wchar32"):
val = "".join([x.value for x in expr.value])
return f'L"{val}"' if len(val) <= MAX_GLOBAL_INIT_LENGTH else f'L"{val[:MAX_GLOBAL_INIT_LENGTH]}..."'
case Integer(size=8, signed=False):
val = "".join([f"\\x{x.value:02X}" for x in expr.value][:MAX_GLOBAL_INIT_LENGTH])
return f'"{val}"' if len(val) <= MAX_GLOBAL_INIT_LENGTH else f'"{val[:MAX_GLOBAL_INIT_LENGTH]}..."'
case Integer(8):
val = "".join([x.value for x in expr.value][:MAX_GLOBAL_INIT_LENGTH])
return f'"{val}"' if len(val) <= MAX_GLOBAL_INIT_LENGTH else f'"{val[:MAX_GLOBAL_INIT_LENGTH]}..."'
Expand Down
2 changes: 1 addition & 1 deletion decompiler/backend/variabledeclarations.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _generate_definitions(global_variables: set[GlobalVariable]) -> Iterator[str
match variable.type:
case ArrayType():
br, bl = "", ""
if not variable.type.type in [Integer.char(), CustomType.wchar16(), CustomType.wchar32()]:
if not variable.type.type in [Integer.char(), Integer.uint8_t(), CustomType.wchar16(), CustomType.wchar32()]:
br, bl = "{", "}"
yield f"{base}{variable.type.type} {variable.name}[{hex(variable.type.elements)}] = {br}{CExpressionGenerator().visit(variable.initial_value)}{bl};"
case _:
Expand Down
3 changes: 0 additions & 3 deletions decompiler/frontend/binaryninja/handlers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,6 @@ def lift_constant_pointer(self, pointer: mediumlevelil.MediumLevelILConstPtr, **
if isinstance(res, Constant): # BNinja Error case handling
return res

if isinstance(res.type, Pointer) and res.type.type == CustomType.void():
return res

if isinstance(pointer, mediumlevelil.MediumLevelILImport): # Temp fix for '&'
return res

Expand Down
4 changes: 3 additions & 1 deletion decompiler/frontend/binaryninja/handlers/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@ def _get_unknown_value(self, variable: DataVariable):
type = PseudoArrayType(self._lifter.lift(data[1]), len(data[0]))
data = ConstantComposition([Constant(x, type.type) for x in data[0]], type)
else:
data, type = get_raw_bytes(variable.address, self._view), Pointer(CustomType.void(), self._view.address_size * BYTE_SIZE)
rbytes = get_raw_bytes(variable.address, self._view)
type = PseudoArrayType(Integer.uint8_t(), len(rbytes))
data = ConstantComposition([Constant(b, type.type) for b in rbytes], type)
return data, type

def _get_unknown_pointer_value(self, variable: DataVariable, callers: list[int] = None):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ def constant_fold(operation: OperationType, constants: list[Constant], result_ty
)


def _constant_fold_arithmetic_binary(constants: list[Constant], fun: Callable[[int, int], int], norm_sign: Optional[bool] = None) -> int:
def _constant_fold_arithmetic_binary(
constants: list[Constant],
fun: Callable[[int, int], int],
norm_sign: Optional[bool] = None,
allow_mismatched_sizes: bool = False,
) -> int:
"""
Fold an arithmetic binary operation with constants as operands.
Expand All @@ -84,7 +89,7 @@ def _constant_fold_arithmetic_binary(constants: list[Constant], fun: Callable[[i

if len(constants) != 2:
raise IncompatibleOperandCount(f"Expected exactly 2 constants to fold, got {len(constants)}.")
if not all(constant.type.size == constants[0].type.size for constant in constants):
if not allow_mismatched_sizes and not all(constant.type.size == constants[0].type.size for constant in constants):
raise UnsupportedMismatchedSizes(f"Can not fold constants with different sizes: {[constant.type for constant in constants]}")

left, right = constants
Expand Down Expand Up @@ -137,13 +142,19 @@ def _constant_fold_shift(constants: list[Constant], fun: Callable[[int, int], in
return fun(normalize_int(left.value, left.type.size, norm_signed), right.value)


def remainder(n, d):
return (-1 if n < 0 else 1) * (n % d)


_OPERATION_TO_FOLD_FUNCTION: dict[OperationType, Callable[[list[Constant]], int]] = {
OperationType.minus: partial(_constant_fold_arithmetic_binary, fun=operator.sub),
OperationType.plus: partial(_constant_fold_arithmetic_binary, fun=operator.add),
OperationType.multiply: partial(_constant_fold_arithmetic_binary, fun=operator.mul, norm_sign=True),
OperationType.multiply_us: partial(_constant_fold_arithmetic_binary, fun=operator.mul, norm_sign=False),
OperationType.divide: partial(_constant_fold_arithmetic_binary, fun=operator.floordiv, norm_sign=True),
OperationType.divide_us: partial(_constant_fold_arithmetic_binary, fun=operator.floordiv, norm_sign=False),
OperationType.modulo: partial(_constant_fold_arithmetic_binary, fun=remainder, norm_sign=True, allow_mismatched_sizes=True),
OperationType.modulo_us: partial(_constant_fold_arithmetic_binary, fun=operator.mod, norm_sign=False, allow_mismatched_sizes=True),
OperationType.negate: partial(_constant_fold_arithmetic_unary, fun=operator.neg),
OperationType.left_shift: partial(_constant_fold_shift, fun=operator.lshift, signed=True),
OperationType.right_shift: partial(_constant_fold_shift, fun=operator.rshift, signed=True),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def find(cls, asforest: AbstractSyntaxForest, options: RestructuringOptions):
case_candidate_information.case_node, case_candidate_information.case_constants, case_candidate_information.switch_node
)
if case_candidate_information.in_sequence:
asforest.extract_switch_from_condition_sequence(case_candidate_information.switch_node, condition_node)
asforest.extract_switch_from_sequence(case_candidate_information.switch_node)
else:
asforest.replace_condition_node_by_single_branch(condition_node)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,46 @@
BaseClassConditionAwareRefinement,
)
from decompiler.pipeline.controlflowanalysis.restructuring_options import RestructuringOptions
from decompiler.structures.ast.ast_nodes import ConditionNode, FalseNode, SeqNode, TrueNode
from decompiler.structures.ast.ast_nodes import ConditionNode, FalseNode, SeqNode, SwitchNode, TrueNode
from decompiler.structures.ast.syntaxforest import AbstractSyntaxForest
from decompiler.structures.logic.logic_condition import LogicCondition


class SwitchExtractor(BaseClassConditionAwareRefinement):
"""Extract switch nodes from condition nodes if the condition node is irrelevant for the switch node."""

def __init__(self, asforest: AbstractSyntaxForest, options: RestructuringOptions):
"""
self.current_cond_node: The condition node which we consider to extract switch nodes.
"""
super().__init__(asforest, options)
self._current_cond_node: Optional[ConditionNode] = None

@classmethod
def extract(cls, asforest: AbstractSyntaxForest, options: RestructuringOptions):
"""
Extract switch nodes from condition nodes, i.e., if a switch node is a branch of a condition node whose condition is redundant for
the switch node, we extract it from the condition node.
"""
"""Extract switch nodes from condition nodes, or sequence-nodes with a non-trivial reaching-condition."""
switch_extractor = cls(asforest, options)
for condition_node in asforest.get_condition_nodes_post_order(asforest.current_root):
switch_extractor._current_cond_node = condition_node
switch_extractor._extract_switches_from_condition()
for switch_node in list(asforest.get_switch_nodes_post_order(asforest.current_root)):
while switch_extractor._successfully_extracts_switch_nodes(switch_node):
pass

def _extract_switches_from_condition(self) -> None:
"""Extract switch nodes in the true and false branch of the given condition node."""
if self._current_cond_node.false_branch:
self._try_to_extract_switch_from_branch(self._current_cond_node.false_branch)
if self._current_cond_node.true_branch:
self._try_to_extract_switch_from_branch(self._current_cond_node.true_branch)
if self._current_cond_node in self.asforest:
self._current_cond_node.clean()

def _try_to_extract_switch_from_branch(self, branch: Union[TrueNode, FalseNode]) -> None:
def _successfully_extracts_switch_nodes(self, switch_node: SwitchNode) -> bool:
"""
1. If the given branch of the condition node is a switch node,
then extract it if the reaching condition is redundant for the switch node.
2. If the given branch of the condition node is a sequence node whose first or last node is a switch node,
then extract it if the reaching condition is redundant for the switch node.
We extract the given switch-node, if possible, and return whether it was successfully extracted.
1. If the switch node has a sequence node as parent and is its first or last child
i) Sequence node has a non-trivial reaching-condition
--> extract the switch from the sequence node if the reaching-condition is redundant for the switch
ii) Sequence node has a trivial reaching-condition, and its parent is a branch of a condition node
--> extract the switch from the condition-node if the branch-condition is redundant for the switch
2. If the switch node has a branch of a condition-node as parent
--> extract the switch from the condition node if the branch-condition is redundant for the switch
"""
branch_condition = branch.branch_condition
if self._condition_is_redundant_for_switch_node(branch.child, branch_condition):
self._extract_switch_node_from_branch(branch)
elif isinstance(sequence_node := branch.child, SeqNode):
for switch_node in [sequence_node.children[0], sequence_node.children[-1]]:
if self._condition_is_redundant_for_switch_node(switch_node, branch_condition):
self.asforest.extract_switch_from_condition_sequence(switch_node, self._current_cond_node)
switch_parent = switch_node.parent
if isinstance(switch_parent, SeqNode):
if not switch_parent.reaching_condition.is_true:
return self._successfully_extract_switch_from_first_or_last_child_of(switch_parent, switch_parent.reaching_condition)
elif isinstance(branch := switch_parent.parent, TrueNode | FalseNode):
return self._successfully_extract_switch_from_first_or_last_child_of(switch_parent, branch.branch_condition)
elif isinstance(switch_parent, TrueNode | FalseNode) and self._condition_is_redundant_for_switch_node(
switch_node, switch_parent.branch_condition
):
self._extract_switch_node_from_branch(switch_parent)
return True
return False

def _extract_switch_node_from_branch(self, branch: Union[TrueNode, FalseNode]) -> None:
"""
Expand All @@ -64,7 +56,20 @@ def _extract_switch_node_from_branch(self, branch: Union[TrueNode, FalseNode]) -
:param branch: The branch from which we extract the switch node.
:return: If we introduce a new sequence node, then return this node, otherwise return None.
"""
if len(self._current_cond_node.children) != 2:
self.asforest.replace_condition_node_by_single_branch(self._current_cond_node)
assert isinstance(condition_node := branch.parent, ConditionNode), "The parent of a true/false-branch must be a condition node!"
if len(condition_node.children) != 2:
self.asforest.replace_condition_node_by_single_branch(condition_node)
else:
self.asforest.extract_branch_from_condition_node(self._current_cond_node, branch, False)
self.asforest.extract_branch_from_condition_node(condition_node, branch, False)

def _successfully_extract_switch_from_first_or_last_child_of(self, sequence_node: SeqNode, condition: LogicCondition) -> bool:
"""
Check whether the first or last child of the sequence node is a switch-node for which the given condition is redundant.
If this is the case, extract the switch-node from the sequence.
"""
for switch_node in [sequence_node.children[0], sequence_node.children[-1]]:
if self._condition_is_redundant_for_switch_node(switch_node, condition):
assert isinstance(switch_node, SwitchNode), f"The node {switch_node} must be a switch-node!"
self.asforest.extract_switch_from_sequence(switch_node)
return True
return False
Loading

0 comments on commit 7de33a3

Please sign in to comment.