diff --git a/decompiler/pipeline/controlflowanalysis/loop_utility_methods.py b/decompiler/pipeline/controlflowanalysis/loop_utility_methods.py index f9390fecf..99de6adfd 100644 --- a/decompiler/pipeline/controlflowanalysis/loop_utility_methods.py +++ b/decompiler/pipeline/controlflowanalysis/loop_utility_methods.py @@ -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 @@ -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) diff --git a/decompiler/pipeline/controlflowanalysis/readability_based_refinement.py b/decompiler/pipeline/controlflowanalysis/readability_based_refinement.py index c48621eb3..f979803c7 100644 --- a/decompiler/pipeline/controlflowanalysis/readability_based_refinement.py +++ b/decompiler/pipeline/controlflowanalysis/readability_based_refinement.py @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/tests/pipeline/controlflowanalysis/test_readability_based_refinement.py b/tests/pipeline/controlflowanalysis/test_readability_based_refinement.py index 7cd0c82f4..1bf2a57ee 100644 --- a/tests/pipeline/controlflowanalysis/test_readability_based_refinement.py +++ b/tests/pipeline/controlflowanalysis/test_readability_based_refinement.py @@ -3,6 +3,7 @@ import pytest from decompiler.pipeline.controlflowanalysis.loop_utility_methods import ( _find_continuation_instruction, + _get_last_definition_index_of, _has_deep_requirement, _initialization_reaches_loop_node, ) @@ -21,6 +22,7 @@ ImportedFunctionSymbol, ListOperation, OperationType, + UnaryOperation, Variable, ) from decompiler.structures.pseudo.operations import OperationType @@ -848,8 +850,10 @@ def test_separated_by_loop_node_4(self, ast_while_in_else): assert _initialization_reaches_loop_node(init_code_node, inner_while) is False - def test_skip_for_loop_recovery_if_continue_in_while(self): + def test_for_loop_recovery_if_continue_in_while_1(self): """ + Test for loop recovery if a continue occurs in a while loop and the last definition is a simple binary operation. + a = 0 while(a < 10) { if(a == 2) { @@ -894,10 +898,220 @@ def test_skip_for_loop_recovery_if_continue_in_while(self): ) WhileLoopReplacer(ast, _generate_options()).run() - assert not any(isinstance(loop_node, ForLoopNode) for loop_node in list(ast.get_loop_nodes_post_order())) - def test_skip_for_loop_recovery_if_continue_in_nested_while(self): + for loop_node in list(ast.get_loop_nodes_post_order()): + assert isinstance(loop_node, ForLoopNode) + assert loop_node.modification == Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("a"), Constant(1)])) + + condition_nodes = list(ast.get_condition_nodes_post_order()) + last_definition = condition_nodes[0].true_branch_child.instructions[ + _get_last_definition_index_of(condition_nodes[0].true_branch_child, Variable("a")) + ] + assert last_definition == Assignment( + Variable("a"), + BinaryOperation(OperationType.minus, [BinaryOperation(OperationType.plus, [Variable("a"), Constant(2)]), Constant(1)]), + ) + + def test_for_loop_recovery_if_continue_in_while_2(self): + """ + Test for loop recovery if a continue occurs in a while loop and the last definition is a constant. + + a = 0 + while(a < 10) { + if(a == 2) { + a = 4 + continue + } + a = a + 1 + } + """ + true_value = LogicCondition.initialize_true(context := LogicCondition.generate_new_context()) + ast = AbstractSyntaxTree( + root := SeqNode(true_value), + condition_map={ + logic_cond("x1", context): Condition(OperationType.less, [Variable("a"), Constant(10)]), + logic_cond("x2", context): Condition(OperationType.equal, [Variable("a"), Constant(2)]), + }, + ) + + true_branch = ast._add_code_node([Assignment(Variable("a"), Constant(4)), Continue()]) + if_condition = ast._add_condition_node_with(logic_cond("x2", context), true_branch) + + init_code_node = ast._add_code_node([Assignment(Variable("a"), Constant(0))]) + + while_loop = ast.factory.create_while_loop_node(logic_cond("x1", context)) + while_loop_body = ast.factory.create_seq_node() + while_loop_iteration = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("a"), Constant(1)]))] + ) + ast._add_node(while_loop) + ast._add_node(while_loop_body) + + ast._add_edges_from( + [ + (root, init_code_node), + (root, while_loop), + (while_loop, while_loop_body), + (while_loop_body, if_condition), + (while_loop_body, while_loop_iteration), + ] + ) + + WhileLoopReplacer(ast, _generate_options()).run() + assert all(isinstance(loop_node, ForLoopNode) for loop_node in list(ast.get_loop_nodes_post_order())) + + condition_nodes = list(ast.get_condition_nodes_post_order()) + last_definition = condition_nodes[0].true_branch_child.instructions[ + _get_last_definition_index_of(condition_nodes[0].true_branch_child, Variable("a")) + ] + assert last_definition == Assignment(Variable("a"), BinaryOperation(OperationType.minus, [Constant(4), Constant(1)])) + + def test_for_loop_recovery_if_continue_in_while_3(self): + """ + Test for loop recovery if a continue occurs in a while loop, the last definition is a simple binary operation and + the continuation-instruction has a negated used variable. + + a = 0 + while(a < 10) { + if(a == 2) { + a = a + 1 + continue + } + a = -a + 10 + } """ + true_value = LogicCondition.initialize_true(context := LogicCondition.generate_new_context()) + ast = AbstractSyntaxTree( + root := SeqNode(true_value), + condition_map={ + logic_cond("x1", context): Condition(OperationType.less, [Variable("a"), Constant(10)]), + logic_cond("x2", context): Condition(OperationType.equal, [Variable("a"), Constant(2)]), + }, + ) + + true_branch = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("a"), Constant(1)])), Continue()] + ) + if_condition = ast._add_condition_node_with(logic_cond("x2", context), true_branch) + + init_code_node = ast._add_code_node([Assignment(Variable("a"), Constant(0))]) + + while_loop = ast.factory.create_while_loop_node(logic_cond("x1", context)) + while_loop_body = ast.factory.create_seq_node() + while_loop_iteration = ast._add_code_node( + [ + Assignment( + Variable("a"), + BinaryOperation(OperationType.plus, [UnaryOperation(OperationType.negate, [Variable("a")]), Constant(10)]), + ) + ] + ) + ast._add_node(while_loop) + ast._add_node(while_loop_body) + + ast._add_edges_from( + [ + (root, init_code_node), + (root, while_loop), + (while_loop, while_loop_body), + (while_loop_body, if_condition), + (while_loop_body, while_loop_iteration), + ] + ) + + WhileLoopReplacer(ast, _generate_options()).run() + assert all(isinstance(loop_node, ForLoopNode) for loop_node in list(ast.get_loop_nodes_post_order())) + + condition_nodes = list(ast.get_condition_nodes_post_order()) + last_definition = condition_nodes[0].true_branch_child.instructions[ + _get_last_definition_index_of(condition_nodes[0].true_branch_child, Variable("a")) + ] + assert last_definition == Assignment( + Variable("a"), + UnaryOperation( + OperationType.negate, + [BinaryOperation(OperationType.minus, [BinaryOperation(OperationType.plus, [Variable("a"), Constant(1)]), Constant(10)])], + ), + ) + + def test_for_loop_recovery_if_continue_in_while_4(self): + """ + Test for loop recovery if a continue occurs in a while loop, the last definition is a simple binary operation, + the continuation-instruction has a negated constant and a not unified form like 'var = var + const'. + + a = 0; + while(a < 10) { + if(a == 2) { + a = a + 1 + continue + } + a = -10 - -a + } + """ + true_value = LogicCondition.initialize_true(context := LogicCondition.generate_new_context()) + ast = AbstractSyntaxTree( + root := SeqNode(true_value), + condition_map={ + logic_cond("x1", context): Condition(OperationType.less, [Variable("a"), Constant(10)]), + logic_cond("x2", context): Condition(OperationType.equal, [Variable("a"), Constant(2)]), + }, + ) + + true_branch = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("a"), Constant(1)])), Continue()] + ) + if_condition = ast._add_condition_node_with(logic_cond("x2", context), true_branch) + + init_code_node = ast._add_code_node([Assignment(Variable("a"), Constant(0))]) + + while_loop = ast.factory.create_while_loop_node(logic_cond("x1", context)) + while_loop_body = ast.factory.create_seq_node() + while_loop_iteration = ast._add_code_node( + [ + Assignment( + Variable("a"), + BinaryOperation(OperationType.minus, [Constant(-10), UnaryOperation(OperationType.negate, [Variable("a")])]), + ) + ] + ) + ast._add_node(while_loop) + ast._add_node(while_loop_body) + + ast._add_edges_from( + [ + (root, init_code_node), + (root, while_loop), + (while_loop, while_loop_body), + (while_loop_body, if_condition), + (while_loop_body, while_loop_iteration), + ] + ) + + WhileLoopReplacer(ast, _generate_options()).run() + + for loop_node in list(ast.get_loop_nodes_post_order()): + assert isinstance(loop_node, ForLoopNode) + assert loop_node.modification == Assignment( + Variable("a"), + BinaryOperation( + OperationType.plus, + [UnaryOperation(OperationType.negate, [UnaryOperation(OperationType.negate, [Variable("a")])]), Constant(-10)], + ), + ) + + condition_nodes = list(ast.get_condition_nodes_post_order()) + last_definition = condition_nodes[0].true_branch_child.instructions[ + _get_last_definition_index_of(condition_nodes[0].true_branch_child, Variable("a")) + ] + assert last_definition == Assignment( + Variable("a"), + BinaryOperation(OperationType.minus, [BinaryOperation(OperationType.plus, [Variable("a"), Constant(1)]), Constant(-10)]), + ) + + def test_for_loop_recovery_if_continue_in_nested_while(self): + """ + Test for loop recovery if a continue occurs in a nested while loop and the last definition is a simple binary operation. + while(a < 5) { a = a + b while(b < 10) { @@ -958,5 +1172,260 @@ def test_skip_for_loop_recovery_if_continue_in_nested_while(self): ) WhileLoopReplacer(ast, _generate_options()).run() - loop_nodes = list(ast.get_loop_nodes_post_order()) - assert not isinstance(loop_nodes[0], ForLoopNode) and isinstance(loop_nodes[1], ForLoopNode) + assert all(isinstance(loop_node, ForLoopNode) for loop_node in list(ast.get_loop_nodes_post_order())) + + condition_nodes = list(ast.get_condition_nodes_post_order()) + last_definition = condition_nodes[0].true_branch_child.instructions[ + _get_last_definition_index_of(condition_nodes[0].true_branch_child, Variable("b")) + ] + assert last_definition == Assignment( + Variable("b"), + BinaryOperation(OperationType.minus, [BinaryOperation(OperationType.plus, [Variable("b"), Constant(2)]), Constant(1)]), + ) + + def test_skip_for_loop_recovery_if_continue_in_while_1(self): + """ + Test skip of for loop recovery if a continue occurs in a while loop, because the continuation instruction is no simple binary operation. + + a = 0 + while(a < 10) { + if(a == 2) { + a = a + 2 + continue + } + a = a * 2 + } + """ + true_value = LogicCondition.initialize_true(context := LogicCondition.generate_new_context()) + ast = AbstractSyntaxTree( + root := SeqNode(true_value), + condition_map={ + logic_cond("x1", context): Condition(OperationType.less, [Variable("a"), Constant(10)]), + logic_cond("x2", context): Condition(OperationType.equal, [Variable("a"), Constant(2)]), + }, + ) + + true_branch = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("a"), Constant(2)])), Continue()] + ) + if_condition = ast._add_condition_node_with(logic_cond("x2", context), true_branch) + + init_code_node = ast._add_code_node([Assignment(Variable("a"), Constant(0))]) + + while_loop = ast.factory.create_while_loop_node(logic_cond("x1", context)) + while_loop_body = ast.factory.create_seq_node() + while_loop_iteration = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.multiply, [Variable("a"), Constant(2)]))] + ) + ast._add_node(while_loop) + ast._add_node(while_loop_body) + + ast._add_edges_from( + [ + (root, init_code_node), + (root, while_loop), + (while_loop, while_loop_body), + (while_loop_body, if_condition), + (while_loop_body, while_loop_iteration), + ] + ) + + WhileLoopReplacer(ast, _generate_options()).run() + assert not any(isinstance(loop_node, ForLoopNode) for loop_node in list(ast.get_loop_nodes_post_order())) + + def test_skip_for_loop_recovery_if_continue_in_while_2(self): + """ + Test skip of for loop recovery if a continue occurs in a while loop, because the last definition is no simple binary operation. + + a = 0 + while(a < 10) { + if(a == 2) { + a = a * 2 + continue + } + a = a + 1 + } + """ + true_value = LogicCondition.initialize_true(context := LogicCondition.generate_new_context()) + ast = AbstractSyntaxTree( + root := SeqNode(true_value), + condition_map={ + logic_cond("x1", context): Condition(OperationType.less, [Variable("a"), Constant(10)]), + logic_cond("x2", context): Condition(OperationType.equal, [Variable("a"), Constant(2)]), + }, + ) + + true_branch = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.multiply, [Variable("a"), Constant(2)])), Continue()] + ) + if_condition = ast._add_condition_node_with(logic_cond("x2", context), true_branch) + + init_code_node = ast._add_code_node([Assignment(Variable("a"), Constant(0))]) + + while_loop = ast.factory.create_while_loop_node(logic_cond("x1", context)) + while_loop_body = ast.factory.create_seq_node() + while_loop_iteration = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("a"), Constant(1)]))] + ) + ast._add_node(while_loop) + ast._add_node(while_loop_body) + + ast._add_edges_from( + [ + (root, init_code_node), + (root, while_loop), + (while_loop, while_loop_body), + (while_loop_body, if_condition), + (while_loop_body, while_loop_iteration), + ] + ) + + WhileLoopReplacer(ast, _generate_options()).run() + assert not any(isinstance(loop_node, ForLoopNode) for loop_node in list(ast.get_loop_nodes_post_order())) + + def test_skip_for_loop_recovery_if_continue_in_while_3(self): + """ + Test skip of for loop recovery if a continue occurs in a while loop, because no last definition exists. + + a = 0 + while(a < 10) { + if(a == 2) { + continue + } + a = a + 1 + } + """ + true_value = LogicCondition.initialize_true(context := LogicCondition.generate_new_context()) + ast = AbstractSyntaxTree( + root := SeqNode(true_value), + condition_map={ + logic_cond("x1", context): Condition(OperationType.less, [Variable("a"), Constant(10)]), + logic_cond("x2", context): Condition(OperationType.equal, [Variable("a"), Constant(2)]), + }, + ) + + true_branch = ast._add_code_node([Continue()]) + if_condition = ast._add_condition_node_with(logic_cond("x2", context), true_branch) + + init_code_node = ast._add_code_node([Assignment(Variable("a"), Constant(0))]) + + while_loop = ast.factory.create_while_loop_node(logic_cond("x1", context)) + while_loop_body = ast.factory.create_seq_node() + while_loop_iteration = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("a"), Constant(1)]))] + ) + ast._add_node(while_loop) + ast._add_node(while_loop_body) + + ast._add_edges_from( + [ + (root, init_code_node), + (root, while_loop), + (while_loop, while_loop_body), + (while_loop_body, if_condition), + (while_loop_body, while_loop_iteration), + ] + ) + + WhileLoopReplacer(ast, _generate_options()).run() + assert not any(isinstance(loop_node, ForLoopNode) for loop_node in list(ast.get_loop_nodes_post_order())) + + def test_skip_for_loop_recovery_if_continue_in_while_4(self): + """ + Test skip of for loop recovery if a continue occurs in a while loop and the continuation-statement defines + and uses different variables. + + a = 0 + while(a < 10){ + if(a == 2) { + a = a + 3 + continue + } + a = b + 2 + } + """ + true_value = LogicCondition.initialize_true(context := LogicCondition.generate_new_context()) + ast = AbstractSyntaxTree( + root := SeqNode(true_value), + condition_map={ + logic_cond("x1", context): Condition(OperationType.less, [Variable("a"), Constant(10)]), + logic_cond("x2", context): Condition(OperationType.equal, [Variable("a"), Constant(2)]), + }, + ) + + true_branch = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("a"), Constant(3)])), Continue()] + ) + if_condition = ast._add_condition_node_with(logic_cond("x2", context), true_branch) + + init_code_node = ast._add_code_node([Assignment(Variable("a"), Constant(0))]) + + while_loop = ast.factory.create_while_loop_node(logic_cond("x1", context)) + while_loop_body = ast.factory.create_seq_node() + while_loop_iteration = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("b"), Constant(2)]))] + ) + ast._add_node(while_loop) + ast._add_node(while_loop_body) + + ast._add_edges_from( + [ + (root, init_code_node), + (root, while_loop), + (while_loop, while_loop_body), + (while_loop_body, if_condition), + (while_loop_body, while_loop_iteration), + ] + ) + + WhileLoopReplacer(ast, _generate_options()).run() + assert not any(isinstance(loop_node, ForLoopNode) for loop_node in list(ast.get_loop_nodes_post_order())) + + def test_skip_for_loop_recovery_if_continue_in_while_5(self): + """ + Test skip of for loop recovery if a continue occurs in a while loop, because the continuation instruction is + an assignment with a constant and is therefore not a simple binary operation. + + a = 0 + while(a < 10){ + if(a == 2) { + a = a + 1 + continue + } + a = 4 + } + """ + true_value = LogicCondition.initialize_true(context := LogicCondition.generate_new_context()) + ast = AbstractSyntaxTree( + root := SeqNode(true_value), + condition_map={ + logic_cond("x1", context): Condition(OperationType.less, [Variable("a"), Constant(10)]), + logic_cond("x2", context): Condition(OperationType.equal, [Variable("a"), Constant(2)]), + }, + ) + + true_branch = ast._add_code_node( + [Assignment(Variable("a"), BinaryOperation(OperationType.plus, [Variable("a"), Constant(1)])), Continue()] + ) + if_condition = ast._add_condition_node_with(logic_cond("x2", context), true_branch) + + init_code_node = ast._add_code_node([Assignment(Variable("a"), Constant(0))]) + + while_loop = ast.factory.create_while_loop_node(logic_cond("x1", context)) + while_loop_body = ast.factory.create_seq_node() + while_loop_iteration = ast._add_code_node([Assignment(Variable("a"), Constant(4))]) + ast._add_node(while_loop) + ast._add_node(while_loop_body) + + ast._add_edges_from( + [ + (root, init_code_node), + (root, while_loop), + (while_loop, while_loop_body), + (while_loop_body, if_condition), + (while_loop_body, while_loop_iteration), + ] + ) + + WhileLoopReplacer(ast, _generate_options()).run() + assert not any(isinstance(loop_node, ForLoopNode) for loop_node in list(ast.get_loop_nodes_post_order()))