Skip to content

Commit

Permalink
Moved methods in new structure
Browse files Browse the repository at this point in the history
  • Loading branch information
fnhartmann committed Nov 14, 2023
1 parent 42782bd commit fd1076f
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 84 deletions.
84 changes: 81 additions & 3 deletions decompiler/pipeline/controlflowanalysis/loop_utility_methods.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, Optional
from typing import Dict, List, Optional

from decompiler.structures.ast.ast_nodes import AbstractSyntaxTreeNode, CaseNode, CodeNode, ConditionNode, LoopNode, SeqNode, SwitchNode
from decompiler.structures.ast.ast_nodes import AbstractSyntaxTreeNode, CaseNode, CodeNode, ConditionNode, LoopNode, SeqNode, SwitchNode, WhileLoopNode
from decompiler.structures.ast.syntaxtree import AbstractSyntaxTree
from decompiler.structures.logic.logic_condition import LogicCondition
from decompiler.structures.pseudo import Assignment, Condition, Variable
from decompiler.structures.pseudo import Assignment, BinaryOperation, Condition, Constant, Expression, OperationType, UnaryOperation, Variable
from decompiler.structures.visitors.assignment_visitor import AssignmentVisitor


Expand Down Expand Up @@ -210,3 +210,81 @@ def _requirement_without_reinitialization(ast: AbstractSyntaxTree, node: Abstrac
return True
elif variable in assignment.requirements:
return True

def _get_continue_nodes_with_equalizable_definition(loop_node: WhileLoopNode, continuation: AstInstruction, variable_init: AstInstruction) -> List[CodeNode]:
"""
Finds code nodes of a while loop containing continue statements and a definition of the continuation instruction, which can be easily equalized.
:param loop_node: While-loop to search in
:param continuation: Instruction defining the for-loops modification
:param variable_init: Instruction defining the for-loops declaration
:return: List of continue code nodes that has equalizable definitions, None if at least one continue node does not match the requirements
"""
equalizable_nodes = []
for code_node in (node for node in loop_node.body.get_descendant_code_nodes_interrupting_ancestor_loop() if node.does_end_with_continue):
if not _is_expression_simple_binary_operation(continuation.instruction.value):
return None
if (last_definition_index := _get_last_definition_index_of(code_node, variable_init.instruction.destination)) == -1:
return None
if not (
isinstance(code_node.instructions[last_definition_index].value, Constant)
or _is_expression_simple_binary_operation(code_node.instructions[last_definition_index].value)
and _get_variable_in_binary_operation(continuation.instruction.value)
== _get_variable_in_binary_operation(code_node.instructions[last_definition_index].value)
):
return None

_unify_binary_operation(continuation.instruction.value)
if not isinstance(code_node.instructions[last_definition_index].value, Constant):
_unify_binary_operation(code_node.instructions[last_definition_index].value)

equalizable_nodes.append(code_node)
return equalizable_nodes

def _is_expression_simple_binary_operation(expression: Expression) -> bool:
"""Checks if an expression is a simple binary operation. Meaning it includes a variable and a constant and uses plus or minus as operation type."""
return (
isinstance(expression, BinaryOperation)
and expression.operation in {OperationType.plus, OperationType.minus}
and any(isinstance(operand, Constant) for operand in expression.subexpressions())
and any(isinstance(operand, Variable) for operand in expression.subexpressions())
)

def _get_variable_in_binary_operation(binaryoperation: BinaryOperation) -> Variable:
"""Returns the used variable of a binary operation if available."""
for operand in binaryoperation.subexpressions():
if isinstance(operand, Variable):
return operand
return None

def _count_unaryoperations_negations(expression: Expression) -> int:
"""Counts the amount of UnaryOperation negations of an expression."""
negations = 0
for subexpression in expression.subexpressions():
if isinstance(subexpression, UnaryOperation) and subexpression.operation == OperationType.negate:
negations += 1
return negations

def _unify_binary_operation(binaryoperation: BinaryOperation):
"""Brings a simple binary operation into a unified representation like 'var + const'."""
if not binaryoperation.operation == OperationType.plus:
binaryoperation.operation = OperationType.plus
binaryoperation.substitute(binaryoperation.right, UnaryOperation(OperationType.negate, [binaryoperation.right]))

if any(isinstance(operand, Constant) for operand in binaryoperation.left.subexpressions()):
binaryoperation.swap_operands()

def _substract_continuation_from_last_definition(code_node: CodeNode, continuation: AstInstruction, variable_init: AstInstruction):
"""
Substracts the value of the continuation instruction from the last definition, which must be a simple binary operation or a constant,
defining the same value as the continuation instruction in the given code node.
:param code_node: Code node whose last instruction is to be changed
:param continuation: Instruction defining the for-loops modification
:param variable_init: Instruction defining the for-loops declaration
"""
last_definition = code_node.instructions[_get_last_definition_index_of(code_node, variable_init.instruction.destination)]
last_definition.substitute(last_definition.value, BinaryOperation(OperationType.minus, [last_definition.value, continuation.instruction.value.right]))

if _count_unaryoperations_negations(continuation.instruction.value.left) % 2 != 0:
last_definition.substitute(last_definition.value, UnaryOperation(OperationType.negate, [last_definition.value]))
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
from decompiler.pipeline.controlflowanalysis.loop_utility_methods import (
AstInstruction,
_find_continuation_instruction,
_get_continue_nodes_with_equalizable_definition,
_get_variable_initialisation,
_initialization_reaches_loop_node,
_is_single_instruction_loop_node,
_single_defininition_reaches_node,
_substract_continuation_from_last_definition,
)
from decompiler.pipeline.stage import PipelineStage
from decompiler.structures.ast.ast_nodes import ConditionNode, DoWhileLoopNode, ForLoopNode, WhileLoopNode
Expand Down Expand Up @@ -101,10 +103,10 @@ def run(self):
continue
if not self._force_for_loops and continuation.instruction.complexity > self._modification_max_complexity:
continue
if (equalizable_continue_nodes := self._get_continue_nodes_with_equalizable_definition(loop_node, continuation, variable_init)) is None:
if (equalizable_continue_nodes := _get_continue_nodes_with_equalizable_definition(loop_node, continuation, variable_init)) is None:
break
for node in equalizable_continue_nodes:
self._substract_continuation_from_last_definition(node, continuation, variable_init)
_substract_continuation_from_last_definition(node, continuation, variable_init)
self._replace_with_for_loop(loop_node, continuation, variable_init)
break

Expand All @@ -120,85 +122,6 @@ def run(self):
),
)

def _get_continue_nodes_with_equalizable_definition(self, loop_node: WhileLoopNode, continuation: AstInstruction, variable_init: AstInstruction) -> List[CodeNode]:
"""
Finds code nodes of a while loop containing continue statements and a definition of the continuation instruction, which can be easily equalized.
:param loop_node: While-loop to search in
:param continuation: Instruction defining the for-loops modification
:param variable_init: Instruction defining the for-loops declaration
:return: List of continue code nodes that has equalizable definitions, None if at least one continue node does not match the requirements
"""
equalizable_nodes = []
for code_node in (node for node in loop_node.body.get_descendant_code_nodes_interrupting_ancestor_loop() if node.does_end_with_continue):
if not self._is_expression_simple_binary_operation(continuation.instruction.value):
return None
if (last_definition_index := _get_last_definition_index_of(code_node, variable_init.instruction.destination)) == -1:
return None
if not (
isinstance(code_node.instructions[last_definition_index].value, Constant)
or self._is_expression_simple_binary_operation(code_node.instructions[last_definition_index].value)
and self._get_variable_in_binary_operation(continuation.instruction.value)
== self._get_variable_in_binary_operation(code_node.instructions[last_definition_index].value)
):
return None

self._unify_binary_operation(continuation.instruction.value)
if not isinstance(code_node.instructions[last_definition_index].value, Constant):
self._unify_binary_operation(code_node.instructions[last_definition_index].value)

equalizable_nodes.append(code_node)
return equalizable_nodes

def _is_expression_simple_binary_operation(self, expression: Expression) -> bool:
"""Checks if an expression is a simple binary operation. Meaning it includes a variable and a constant and uses plus or minus as operation type."""
return (
isinstance(expression, BinaryOperation)
and expression.operation in {OperationType.plus, OperationType.minus}
and any(isinstance(operand, Constant) for operand in expression.subexpressions())
and any(isinstance(operand, Variable) for operand in expression.subexpressions())
)

def _get_variable_in_binary_operation(self, binaryoperation: BinaryOperation) -> Variable:
"""Returns the used variable of a binary operation if available."""
for operand in binaryoperation.subexpressions():
if isinstance(operand, Variable):
return operand
return None

def _count_unaryoperations_negations(self, expression: Expression) -> int:
"""Counts the amount of UnaryOperation negations of an expression."""
negations = 0
for subexpression in expression.subexpressions():
if isinstance(subexpression, UnaryOperation) and subexpression.operation == OperationType.negate:
negations += 1
return negations

def _unify_binary_operation(self, binaryoperation: BinaryOperation):
"""Brings a simple binary operation into a unified representation like 'var + const'."""
print(binaryoperation)
if not binaryoperation.operation == OperationType.plus:
binaryoperation.operation = OperationType.plus
binaryoperation.substitute(binaryoperation.right, UnaryOperation(OperationType.negate, [binaryoperation.right]))

if any(isinstance(operand, Constant) for operand in binaryoperation.left.subexpressions()):
binaryoperation.swap_operands()

def _substract_continuation_from_last_definition(self, code_node: CodeNode, continuation: AstInstruction, variable_init: AstInstruction):
"""
Substracts the value of the continuation instruction from the last definition, which must be a simple binary operation or a constant,
defining the same value as the continuation instruction in the given code node.
:param code_node: Code node whose last instruction is to be changed
:param continuation: Instruction defining the for-loops modification
:param variable_init: Instruction defining the for-loops declaration
"""
last_definition = code_node.instructions[_get_last_definition_index_of(code_node, variable_init.instruction.destination)]
last_definition.substitute(last_definition.value, BinaryOperation(OperationType.minus, [last_definition.value, continuation.instruction.value.right]))

if self._count_unaryoperations_negations(continuation.instruction.value.left) % 2 != 0:
last_definition.substitute(last_definition.value, UnaryOperation(OperationType.negate, [last_definition.value]))

def _replace_with_for_loop(self, loop_node: WhileLoopNode, continuation: AstInstruction, init: AstInstruction):
"""
Replaces a given WhileLoopNode with a ForLoopNode.
Expand Down

0 comments on commit fd1076f

Please sign in to comment.