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 26 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
108 changes: 104 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,85 @@ 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, 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
fnhartmann marked this conversation as resolved.
Show resolved Hide resolved
if (last_definition_index := _get_last_definition_index_of(code_node, continuation.instruction.destination)) == -1:
return None
else:
last_definition = code_node.instructions[last_definition_index]
if not (
isinstance(last_definition.value, Constant)
or _is_expression_simple_binary_operation(last_definition.value)
and continuation.instruction.destination
== _get_variable_in_binary_operation(continuation.instruction.value)
fnhartmann marked this conversation as resolved.
Show resolved Hide resolved
== _get_variable_in_binary_operation(last_definition.value)
fnhartmann marked this conversation as resolved.
Show resolved Hide resolved
):
return None

_unify_binary_operation_in_assignment(continuation.instruction)
equalizable_nodes.append(last_definition)
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) or _is_negated_constant_variable(operand, Constant) for operand in expression.operands)
and any(isinstance(operand, Variable) or _is_negated_constant_variable(operand, Variable) for operand in expression.operands)
)


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, assignment.value.right]))
assignment.value.substitute(assignment.value.right, UnaryOperation(OperationType.negate, [assignment.value.right]))
fnhartmann marked this conversation as resolved.
Show resolved Hide resolved

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
"""
last_definition.substitute(
last_definition.value, 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, [last_definition.value]))
fnhartmann marked this conversation as resolved.
Show resolved Hide resolved
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