Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[For-Loop Reconstruction] Follow up: Not handling "continue" instructions correctly #326

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
99b75ba
Create draft PR for #325
fnhartmann Aug 30, 2023
d698580
Merge branch 'main' into issue-325-_For-Loop_Reconstruction_Follow_up…
fnhartmann Sep 14, 2023
3e392cc
Equalize last defintion in continue node and reconstruct for loop
fnhartmann Sep 18, 2023
635faae
Changed and added tests
fnhartmann Sep 20, 2023
6767cf7
Changed condition in remove_continuation + comments
fnhartmann Sep 20, 2023
b34d013
Syntax and other small changes
fnhartmann Oct 12, 2023
ec1ef9d
Added check for uniform variables in continuation and last definition
fnhartmann Oct 16, 2023
6997fd5
Moved check for uniform variables, added check for commutativity
fnhartmann Oct 18, 2023
fe90534
Unify instructions, applied changing cases
fnhartmann Nov 13, 2023
42782bd
Merge branch 'main' into issue-325-_For-Loop_Reconstruction_Follow_up…
fnhartmann Nov 14, 2023
fd1076f
Moved methods in new structure
fnhartmann Nov 14, 2023
2e2fdf2
Clean up tests for new structure
fnhartmann Nov 14, 2023
560b5d0
Changed current tests to new recovery output
fnhartmann Nov 14, 2023
72be886
Black formatting
fnhartmann Nov 14, 2023
7799db5
Unified form only needed for continuation
fnhartmann Nov 15, 2023
b4cfe63
Replaced setter functions with substitutes
fnhartmann Nov 16, 2023
67f325a
Clean up imports
fnhartmann Nov 16, 2023
0c2d455
Implemented tests
fnhartmann Nov 16, 2023
a7f4d76
Fix comments
fnhartmann Nov 16, 2023
55ead2e
Shortened if statement
fnhartmann Nov 16, 2023
29ec9fa
Updated suggestions
fnhartmann Dec 6, 2023
4efd39a
Black formatting, added comment
fnhartmann Dec 6, 2023
13dc250
Fix substitute in unify
fnhartmann Dec 7, 2023
3176e98
Operate on last definitions
fnhartmann Dec 7, 2023
0d5b1df
Updated comments
fnhartmann Dec 7, 2023
795c68e
Only reconstruct if defined and used variables are the same
fnhartmann Dec 12, 2023
d4d5dd7
Added skip testcase for continuation with only constant
fnhartmann Dec 19, 2023
6a9e4bc
Adjusted check for simple binary operation and variable usage
fnhartmann Dec 19, 2023
459504d
Fix order of check for continue nodes
fnhartmann Dec 19, 2023
aa02657
Merged substitutes
fnhartmann Dec 19, 2023
83370e4
Fix missing brackets
fnhartmann Dec 19, 2023
9dc780e
Added check for correct continuation instruction
fnhartmann Dec 19, 2023
9515867
Merged substitutes
fnhartmann Dec 19, 2023
4a2f72d
Merge branch 'main' into issue-325-_For-Loop_Reconstruction_Follow_up…
ebehner Dec 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 111 additions & 4 deletions decompiler/pipeline/controlflowanalysis/loop_utility_methods.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, Optional

from decompiler.structures.ast.ast_nodes import AbstractSyntaxTreeNode, CaseNode, CodeNode, ConditionNode, LoopNode, SeqNode, SwitchNode
from typing import Dict, List, Optional

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 +228,92 @@ def _requirement_without_reinitialization(ast: AbstractSyntaxTree, node: Abstrac
return True
elif variable in assignment.requirements:
return True


def _get_equalizable_last_definitions(loop_node: WhileLoopNode, continuation: AstInstruction) -> List[CodeNode]:
"""
Finds equalizable last definitions of the continuation instruction in the code nodes of a while loop containing continue statements.

:param loop_node: While-loop to search in
:param continuation: Instruction defining the for-loops modification
:return: List of equalizable last definitions, Empty list if no continue nodes or no equalizable nodes
:return: None if at least one continue node does not match the requirements
"""
if not (
continue_nodes := [
node for node in loop_node.body.get_descendant_code_nodes_interrupting_ancestor_loop() if node.does_end_with_continue
]
):
return continue_nodes

if not (_is_assignment_with_simple_binary_operation(continuation.instruction)):
return None

equalizable_nodes = []
for code_node in continue_nodes:
if (last_definition_index := _get_last_definition_index_of(code_node, continuation.instruction.destination)) == -1:
return None

last_definition = code_node.instructions[last_definition_index]
if not (isinstance(last_definition.value, Constant) or _is_assignment_with_simple_binary_operation(last_definition)):
return None

_unify_binary_operation_in_assignment(continuation.instruction)
equalizable_nodes.append(last_definition)
return equalizable_nodes


def _is_assignment_with_simple_binary_operation(assignment: Assignment) -> bool:
"""
Checks if an assignment has a simple binary operation as value and the used and defined variable is the same. A simple binary
operation means that it includes a variable and a constant and uses plus or minus as operation type.
"""
return (
isinstance(assignment.value, BinaryOperation)
and assignment.value.operation in {OperationType.plus, OperationType.minus}
and any(isinstance(operand, Constant) or _is_negated_constant_variable(operand, Constant) for operand in assignment.value.operands)
and any(isinstance(operand, Variable) or _is_negated_constant_variable(operand, Variable) for operand in assignment.value.operands)
and assignment.destination == _get_variable_in_binary_operation(assignment.value)
)


def _is_negated_constant_variable(operand: Expression, expression: Constant | Variable) -> bool:
"""Checks if an operand (constant or variable) is negated."""
return isinstance(operand, UnaryOperation) and operand.operation == OperationType.negate and isinstance(operand.operand, expression)


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


def _unify_binary_operation_in_assignment(assignment: Assignment):
"""Brings a simple binary operation of an assignment into a unified representation like 'var = -var + const' instead of 'var = const - var'."""
if not assignment.value.operation == OperationType.plus:
assignment.substitute(
assignment.value,
BinaryOperation(OperationType.plus, [assignment.value.left, UnaryOperation(OperationType.negate, [assignment.value.right])]),
)

if any(isinstance(operand, Constant) for operand in assignment.value.left.subexpressions()):
assignment.substitute(assignment.value, BinaryOperation(OperationType.plus, [assignment.value.right, assignment.value.left]))


def _substract_continuation_from_last_definition(last_definition: Assignment, continuation: 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 last_definition: Last definition that is to be changed
:param continuation: Instruction defining the for-loops modification
"""
substracted_binary_operation = BinaryOperation(OperationType.minus, [last_definition.value, continuation.instruction.value.right])
if _is_negated_constant_variable(continuation.instruction.value.left, Variable):
last_definition.substitute(last_definition.value, UnaryOperation(OperationType.negate, [substracted_binary_operation]))
else:
last_definition.substitute(last_definition.value, substracted_binary_operation)
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_equalizable_last_definitions,
_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 @@ -76,6 +78,7 @@ def run(self):
-> loop condition complexity < condition complexity
-> possible modification complexity < modification complexity
-> if condition is only a symbol: check condition type for allowed one
-> has a continue statement which must and can be equalized

If 'force_for_loops' is enabled, the complexity options are ignored and every while loop after the
initial transformation will be forced into a for loop with an empty declaration/modification
Expand All @@ -90,9 +93,6 @@ def run(self):
):
continue

if any(node.does_end_with_continue for node in loop_node.body.get_descendant_code_nodes_interrupting_ancestor_loop()):
continue

if not self._force_for_loops and loop_node.condition.get_complexity(self._ast.condition_map) > self._condition_max_complexity:
continue

Expand All @@ -103,6 +103,10 @@ def run(self):
continue
if not self._force_for_loops and continuation.instruction.complexity > self._modification_max_complexity:
continue
if (equalizable_last_definitions := _get_equalizable_last_definitions(loop_node, continuation)) is None:
continue
for last_definition in equalizable_last_definitions:
_substract_continuation_from_last_definition(last_definition, continuation)
self._replace_with_for_loop(loop_node, continuation, variable_init)
break

Expand Down
Loading
Loading