From 7d7b74d3177601db6cbaab07c207dd6834c02bd6 Mon Sep 17 00:00:00 2001 From: ebehner Date: Tue, 18 Jul 2023 14:30:56 +0000 Subject: [PATCH 1/9] Create draft PR for #35 From 29ede4ee702d045a407f80d1eff278c35be2c094 Mon Sep 17 00:00:00 2001 From: Eva-Maria Behner Date: Tue, 18 Jul 2023 17:18:16 +0200 Subject: [PATCH 2/9] mark problems and places where we have to handle this --- .../initial_switch_node_constructer.py | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py index 42a03d7d9..691f19f79 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py @@ -1,8 +1,9 @@ import operator +from collections import defaultdict from dataclasses import dataclass from functools import reduce from itertools import chain, combinations, permutations -from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple +from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, DefaultDict from decompiler.pipeline.controlflowanalysis.restructuring_commons.condition_aware_refinement_commons.base_class_car import ( BaseClassConditionAwareRefinement, @@ -10,7 +11,8 @@ ) from decompiler.pipeline.controlflowanalysis.restructuring_options import RestructuringOptions from decompiler.structures.ast.ast_nodes import AbstractSyntaxTreeNode, CaseNode, CodeNode, ConditionNode, SeqNode, SwitchNode, TrueNode -from decompiler.structures.ast.reachability_graph import CaseDependencyGraph, LinearOrderDependency, SiblingReachability +from decompiler.structures.ast.reachability_graph import CaseDependencyGraph, LinearOrderDependency, SiblingReachability, \ + SiblingReachabilityGraph from decompiler.structures.ast.switch_node_handler import ExpressionUsages from decompiler.structures.ast.syntaxforest import AbstractSyntaxForest from decompiler.structures.logic.logic_condition import LogicCondition @@ -181,7 +183,7 @@ def _get_possible_switch_nodes_for(self, seq_node: SeqNode) -> List[SwitchNodeCa case_candidate.expression.expression, InsertionOrderedSet([case_candidate]) ) - self._remove_case_candidates_with_same_condition(switch_candidate_for.values()) + self._remove_case_candidates_with_same_condition(switch_candidate_for.values(), seq_node) return list(switch_candidate_for.values()) def _get_possible_case_candidate_for(self, ast_node: AbstractSyntaxTreeNode) -> Optional[CaseNodeCandidate]: @@ -533,25 +535,37 @@ def _can_place_switch_node(switch_node_candidate: SwitchNodeCandidate, sibling_r :param switch_node_candidate: The switch node candidate that we want to place. :param sibling_reachability: The reachability of all children of the sequence node. """ + # TODO Why not deleting some nodes to still get the switch? If only one is the problem??? copy_sibling_reachability = sibling_reachability.copy() new_node = SwitchNode(switch_node_candidate.expression, LogicCondition.generate_new_context()) copy_sibling_reachability.merge_siblings_to(new_node, [case_candidate.node for case_candidate in switch_node_candidate.cases]) return copy_sibling_reachability.sorted_nodes() is not None - @staticmethod - def _remove_case_candidates_with_same_condition(switch_candidates: Iterable[SwitchNodeCandidate]) -> None: + def _remove_case_candidates_with_same_condition(self, switch_candidates: Iterable[SwitchNodeCandidate], seq_node: SeqNode) -> None: """ Remove one of two case candidates if they have the same condition. Since they were not combined before, they can not be combined, and we do not know which to pick. """ + # TODO: not optimal, but a first version + sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(seq_node) + reachability_graph = SiblingReachabilityGraph(sibling_reachability) for switch_candidate in switch_candidates: - considered_conditions = set() - for case_candidate in list(switch_candidate.cases): - if case_candidate.condition in considered_conditions: - switch_candidate.cases.remove(case_candidate) - else: - considered_conditions.add(case_candidate.condition) + multiple_cases_of_condition = InitialSwitchNodeConstructor.__get_conditions_with_multiple_cases(switch_candidate) + copy_sibling_reachability = sibling_reachability.copy() + tmp = SwitchNode(switch_candidate.expression, LogicCondition.generate_new_context()) + copy_sibling_reachability.merge_siblings_to(tmp, [candidate.node for candidate in switch_candidate.cases if not any(candidate in multiple_case for multiple_case in multiple_cases_of_condition.values())]) + # TODO + + + + + @staticmethod + def __get_conditions_with_multiple_cases(switch_candidate: SwitchNodeCandidate): + cases_of_condition: DefaultDict[LogicCondition, Set[CaseNodeCandidate]] = defaultdict(set) + for case_candidate in switch_candidate.cases: + cases_of_condition[case_candidate.condition].add(case_candidate) + return {condition: cases for condition, cases in cases_of_condition.items() if len(cases) > 1} def _clean_up_reaching_conditions(self, switch_node: SwitchNode) -> None: """ From 69facf3c549552d5e47691be08a554de28e747d4 Mon Sep 17 00:00:00 2001 From: Eva-Maria Behner Date: Wed, 19 Jul 2023 16:54:46 +0200 Subject: [PATCH 3/9] only found problems --- .../initial_switch_node_constructer.py | 50 +++++++++++++------ .../structures/ast/reachability_graph.py | 2 +- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py index 691f19f79..8e6258305 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py @@ -153,10 +153,9 @@ def _try_to_construct_initial_switch_node_for(self, seq_node: SeqNode) -> None: """ for possible_switch_node in self._get_possible_switch_nodes_for(seq_node): sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(seq_node) - self._clean_up_reachability(possible_switch_node, sibling_reachability) + self._process_switch_node(possible_switch_node, sibling_reachability) + if len(possible_switch_node.cases) > 1: - self._remove_too_nested_cases(possible_switch_node, sibling_reachability) - if len(possible_switch_node.cases) > 1 and self._can_place_switch_node(possible_switch_node, sibling_reachability): switch_cases = list(possible_switch_node.construct_switch_cases()) switch_node = self.asforest.create_switch_node_with(possible_switch_node.expression, switch_cases) case_dependency = CaseDependencyGraph.construct_case_dependency_for( @@ -171,7 +170,7 @@ def _get_possible_switch_nodes_for(self, seq_node: SeqNode) -> List[SwitchNodeCa Return a list of all possible switch candidates for the given sequence node. A switch candidate is a node whose reaching condition (for condition nodes combination of condition and reaching condition) - is an disjunction of a conjunction of comparisons with the switch-expression and an arbitrary condition, that can be empty. + is a disjunction of a conjunction of comparisons with the switch-expression and an arbitrary condition, that can be empty. """ switch_candidate_for: Dict[ExpressionUsages, SwitchNodeCandidate] = dict() for child in seq_node.children: @@ -182,9 +181,7 @@ def _get_possible_switch_nodes_for(self, seq_node: SeqNode) -> List[SwitchNodeCa switch_candidate_for[case_candidate.expression] = SwitchNodeCandidate( case_candidate.expression.expression, InsertionOrderedSet([case_candidate]) ) - - self._remove_case_candidates_with_same_condition(switch_candidate_for.values(), seq_node) - return list(switch_candidate_for.values()) + return list(candidate for candidate in switch_candidate_for.values() if len(candidate.cases) > 1) def _get_possible_case_candidate_for(self, ast_node: AbstractSyntaxTreeNode) -> Optional[CaseNodeCandidate]: """ @@ -206,6 +203,25 @@ def _get_possible_case_candidate_for(self, ast_node: AbstractSyntaxTreeNode) -> return None + def _process_switch_node(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): + """ + Process the possible switch node such that we can insert it a switch-node, if possible. + + 1. clean-up reachability, i.e., remove reachability between case-nodes that are not reachable from each other due to their condition + 2. remove case-candidates with the exact same constants, leaving the once that are most suitable (consider reachability) + 3. remove too nested cases, i.e., we do not want to insert too many conditions into the switch-cases + 4. Check whether we can place the switch-node and delete cases in order to make it insertable. + """ + self._clean_up_reachability(possible_switch_node, sibling_reachability) + self._initialize_not_same_switch_nodes(possible_switch_node, sibling_reachability) + self._remove_case_candidates_with_same_condition(possible_switch_node, sibling_reachability) + + if len(possible_switch_node.cases) < 1: + return + self._remove_too_nested_cases(possible_switch_node, sibling_reachability) + if len(possible_switch_node.cases) < 1: + self._can_place_switch_node(possible_switch_node, sibling_reachability) + def _clean_up_reachability(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): """ If two possible switch-cases reach each other, but they have no common possible cases, then we can remove the reachability. @@ -220,6 +236,14 @@ def _clean_up_reachability(self, possible_switch_node: SwitchNodeCandidate, sibl self.asforest._code_node_reachability_graph.remove_reachability_between([candidate_1.node, candidate_2.node]) sibling_reachability.remove_reachability_between([candidate_1.node, candidate_2.node]) + def _initialize_not_same_switch_nodes(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): + """We do not care about siblings that are no code-nodes and reach all cases or are reached by all cases.""" + for node in sibling_reachability.sorted_nodes(): + if node in possible_switch_node: + continue + if sibling_reachability.reachable_siblings_of(node) or not sibling_reachability.siblings_reaching(node): + sibling_reachability.remove_sibling(node) + def _update_reaching_condition_for_case_node_children(self, switch_node: SwitchNode): """ Update the reaching condition for each case-node child. @@ -541,20 +565,18 @@ def _can_place_switch_node(switch_node_candidate: SwitchNodeCandidate, sibling_r copy_sibling_reachability.merge_siblings_to(new_node, [case_candidate.node for case_candidate in switch_node_candidate.cases]) return copy_sibling_reachability.sorted_nodes() is not None - def _remove_case_candidates_with_same_condition(self, switch_candidates: Iterable[SwitchNodeCandidate], seq_node: SeqNode) -> None: + def _remove_case_candidates_with_same_condition(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability) -> None: """ Remove one of two case candidates if they have the same condition. Since they were not combined before, they can not be combined, and we do not know which to pick. """ # TODO: not optimal, but a first version - sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(seq_node) reachability_graph = SiblingReachabilityGraph(sibling_reachability) - for switch_candidate in switch_candidates: - multiple_cases_of_condition = InitialSwitchNodeConstructor.__get_conditions_with_multiple_cases(switch_candidate) - copy_sibling_reachability = sibling_reachability.copy() - tmp = SwitchNode(switch_candidate.expression, LogicCondition.generate_new_context()) - copy_sibling_reachability.merge_siblings_to(tmp, [candidate.node for candidate in switch_candidate.cases if not any(candidate in multiple_case for multiple_case in multiple_cases_of_condition.values())]) + multiple_cases_of_condition = InitialSwitchNodeConstructor.__get_conditions_with_multiple_cases(possible_switch_node) + copy_sibling_reachability = sibling_reachability.copy() + tmp = SwitchNode(possible_switch_node.expression, LogicCondition.generate_new_context()) + copy_sibling_reachability.merge_siblings_to(tmp, [candidate.node for candidate in possible_switch_node.cases if not any(candidate in multiple_case for multiple_case in multiple_cases_of_condition.values())]) # TODO diff --git a/decompiler/structures/ast/reachability_graph.py b/decompiler/structures/ast/reachability_graph.py index 676228acf..57b040add 100644 --- a/decompiler/structures/ast/reachability_graph.py +++ b/decompiler/structures/ast/reachability_graph.py @@ -306,7 +306,7 @@ def get_cross_nodes_of(self, considered_nodes: Iterable[AbstractSyntaxTreeNode]) """ Returns a list of all cross nodes of the reachability graph that are contained in the given set of AST-nodes. - -> A cross node is a node where the in-degree or out-degree is larger then one. + -> A cross node is a node where the in-degree or out-degree is larger than one. """ return [case for case in considered_nodes if self.in_degree(case) > 1 or self.out_degree(case) > 1] From 6fab3e8bf55bcf0e414c9c9e38a1ad4fce280485 Mon Sep 17 00:00:00 2001 From: Eva-Maria Behner Date: Thu, 20 Jul 2023 16:04:47 +0200 Subject: [PATCH 4/9] some progress, but still a lot of work --- .../initial_switch_node_constructer.py | 13 +++++++++++++ .../missing_case_finder_sequence.py | 1 + 2 files changed, 14 insertions(+) diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py index 8e6258305..d34909fc9 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py @@ -238,7 +238,20 @@ def _clean_up_reachability(self, possible_switch_node: SwitchNodeCandidate, sibl def _initialize_not_same_switch_nodes(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): """We do not care about siblings that are no code-nodes and reach all cases or are reached by all cases.""" + # TODO: Find a way to construct the conflict graph + # maximal independent set is wanted + reaching_node: DefaultDict[AbstractSyntaxTreeNode, Set[AbstractSyntaxTreeNode]] = defaultdict(set) + reachable_from_node: Dict[AbstractSyntaxTreeNode, Set[AbstractSyntaxTreeNode]] = dict() + for node in reversed(sibling_reachability.sorted_nodes()): + reachable_from_node[node] = set().union(*(reachable_from_node[succ].union({succ}) for succ in sibling_reachability.reachable_siblings_of(node))) + for node, reachable_nodes in reachable_from_node.items(): + for reaching in reachable_nodes: + reaching_node[reaching].add(node) + + for node in sibling_reachability.sorted_nodes(): + pass + if node in possible_switch_node: continue if sibling_reachability.reachable_siblings_of(node) or not sibling_reachability.siblings_reaching(node): diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/missing_case_finder_sequence.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/missing_case_finder_sequence.py index aff46d911..b8469e162 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/missing_case_finder_sequence.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/missing_case_finder_sequence.py @@ -95,6 +95,7 @@ def _can_combine_switch_nodes(self, switch_nodes: List[SwitchNode]) -> bool: :param switch_nodes: A list of switch nodes we want to combine. """ + # TODO What if only some of them are combinable??? sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(self._current_seq_node) # The switch cases are all different, thus which switch comes first is irrelevant for the switch-nodes, but maybe not for the other children sibling_reachability.remove_reachability_between(switch_nodes) From eb8b987d82ec1e97c943e68244460b7c39ce2d5d Mon Sep 17 00:00:00 2001 From: Eva-Maria Behner Date: Fri, 21 Jul 2023 13:39:21 +0200 Subject: [PATCH 5/9] a first, but not complete version --- .../initial_switch_node_constructer.py | 154 ++++++++++++------ .../structures/ast/reachability_graph.py | 10 +- 2 files changed, 115 insertions(+), 49 deletions(-) diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py index d34909fc9..955435d95 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py @@ -2,8 +2,8 @@ from collections import defaultdict from dataclasses import dataclass from functools import reduce -from itertools import chain, combinations, permutations -from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, DefaultDict +from itertools import chain, combinations, permutations, product +from typing import DefaultDict, Dict, Iterable, Iterator, List, Optional, Set, Tuple from decompiler.pipeline.controlflowanalysis.restructuring_commons.condition_aware_refinement_commons.base_class_car import ( BaseClassConditionAwareRefinement, @@ -11,13 +11,18 @@ ) from decompiler.pipeline.controlflowanalysis.restructuring_options import RestructuringOptions from decompiler.structures.ast.ast_nodes import AbstractSyntaxTreeNode, CaseNode, CodeNode, ConditionNode, SeqNode, SwitchNode, TrueNode -from decompiler.structures.ast.reachability_graph import CaseDependencyGraph, LinearOrderDependency, SiblingReachability, \ - SiblingReachabilityGraph +from decompiler.structures.ast.reachability_graph import ( + CaseDependencyGraph, + LinearOrderDependency, + SiblingReachability, + SiblingReachabilityGraph, +) from decompiler.structures.ast.switch_node_handler import ExpressionUsages from decompiler.structures.ast.syntaxforest import AbstractSyntaxForest from decompiler.structures.logic.logic_condition import LogicCondition from decompiler.structures.pseudo import Break, Constant, Expression from decompiler.util.insertion_ordered_set import InsertionOrderedSet +from networkx import Graph, contracted_nodes, relabel_nodes @dataclass @@ -155,7 +160,8 @@ def _try_to_construct_initial_switch_node_for(self, seq_node: SeqNode) -> None: sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(seq_node) self._process_switch_node(possible_switch_node, sibling_reachability) - if len(possible_switch_node.cases) > 1: + # TODO: also do it after removeing to nested nodes. + if len(possible_switch_node.cases) > 1 and self._can_place_switch_node(possible_switch_node, sibling_reachability): switch_cases = list(possible_switch_node.construct_switch_cases()) switch_node = self.asforest.create_switch_node_with(possible_switch_node.expression, switch_cases) case_dependency = CaseDependencyGraph.construct_case_dependency_for( @@ -214,13 +220,10 @@ def _process_switch_node(self, possible_switch_node: SwitchNodeCandidate, siblin """ self._clean_up_reachability(possible_switch_node, sibling_reachability) self._initialize_not_same_switch_nodes(possible_switch_node, sibling_reachability) - self._remove_case_candidates_with_same_condition(possible_switch_node, sibling_reachability) - if len(possible_switch_node.cases) < 1: + if len(possible_switch_node.cases) <= 1: return self._remove_too_nested_cases(possible_switch_node, sibling_reachability) - if len(possible_switch_node.cases) < 1: - self._can_place_switch_node(possible_switch_node, sibling_reachability) def _clean_up_reachability(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): """ @@ -238,24 +241,83 @@ def _clean_up_reachability(self, possible_switch_node: SwitchNodeCandidate, sibl def _initialize_not_same_switch_nodes(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): """We do not care about siblings that are no code-nodes and reach all cases or are reached by all cases.""" - # TODO: Find a way to construct the conflict graph - # maximal independent set is wanted - reaching_node: DefaultDict[AbstractSyntaxTreeNode, Set[AbstractSyntaxTreeNode]] = defaultdict(set) - reachable_from_node: Dict[AbstractSyntaxTreeNode, Set[AbstractSyntaxTreeNode]] = dict() - for node in reversed(sibling_reachability.sorted_nodes()): - reachable_from_node[node] = set().union(*(reachable_from_node[succ].union({succ}) for succ in sibling_reachability.reachable_siblings_of(node))) - for node, reachable_nodes in reachable_from_node.items(): - for reaching in reachable_nodes: - reaching_node[reaching].add(node) - - - for node in sibling_reachability.sorted_nodes(): - pass - - if node in possible_switch_node: - continue - if sibling_reachability.reachable_siblings_of(node) or not sibling_reachability.siblings_reaching(node): - sibling_reachability.remove_sibling(node) + sorted_siblings = sibling_reachability.sorted_nodes() + cases_with_same_condition: Dict[LogicCondition, Set[CaseNodeCandidate]] = self.__get_conditions_with_multiple_cases( + possible_switch_node + ) + if not cases_with_same_condition and self._can_place_switch_node(possible_switch_node, sibling_reachability): + return + transitive_closure = sibling_reachability.transitive_closure() + interfering_cases = self.__generate_interfering_case_nodes(possible_switch_node, sorted_siblings, transitive_closure) + self.__remove_duplicated_conditions( + cases_with_same_condition, interfering_cases, possible_switch_node, sibling_reachability, transitive_closure + ) + + switch_nodes = self.__get_switch_nodes(interfering_cases) + possible_switch_node.cases = switch_nodes + + def __generate_interfering_case_nodes(self, possible_switch_node, sorted_siblings, transitive_closure): + switch_cases = {case.node: case for case in possible_switch_node.cases} + interfering_cases = Graph() + interfering_cases.add_nodes_from(switch_cases.values()) + for node in self.__get_non_case_nodes(sorted_siblings, switch_cases): + before_cases = [switch_cases[reaching] for reaching in transitive_closure.siblings_reaching(node) if reaching in switch_cases] + after_cases = [ + switch_cases[reachable] for reachable in transitive_closure.reachable_siblings_of(node) if reachable in switch_cases + ] + if before_cases and after_cases: + interfering_cases.add_edges_from(product(before_cases, after_cases)) + return interfering_cases + + def __get_non_case_nodes(self, siblings: Tuple[AbstractSyntaxTreeNode], switch_cases: Dict[AbstractSyntaxTreeNode, CaseNodeCandidate]): + for sibling in siblings: + if sibling not in switch_cases: + yield sibling + + def __remove_duplicated_conditions( + self, cases_with_same_condition, interfering_cases, possible_switch_node, sibling_reachability, transitive_closure + ): + for condition, same_condition_cases in cases_with_same_condition.items(): + non_interfering_cases = [ + (case1, case2) + for case1, case2 in combinations(same_condition_cases, 2) + if not interfering_cases.has_edge(case1.node, case2.node) + ] + while non_interfering_cases: + case1, case2 = non_interfering_cases.pop() + if case1 not in possible_switch_node.cases or case2 not in possible_switch_node.cases: + continue + if sibling_reachability.can_group_siblings([case1.node, case2.node]): + cond_node = self.asforest.create_condition_node_with(condition, [case1.node, case2.node], []) + sibling_reachability.merge_siblings_to(cond_node, [case1.node, case2.node]) + transitive_closure.merge_siblings_to(cond_node, [case1.node, case2.node]) + contracted_nodes(interfering_cases, case1, case2, copy=False) + relabel_nodes(interfering_cases, {case1: cond_node}, copy=False) + possible_switch_node.cases.remove(case1) + possible_switch_node.cases.remove(case2) + possible_switch_node.cases.add(CaseNodeCandidate(cond_node, case1.expression, condition)) + else: + # TODO: not done yet -> check when to use CaseNodeCandidate and when to use ASTNode!!! + before_cases1 = [ + reaching for reaching in transitive_closure.siblings_reaching(case1.node) if reaching in interfering_cases + ] + after_cases2 = [ + reachable for reachable in transitive_closure.reachable_siblings_of(case2.node) if reachable in interfering_cases + ] + if len(before_cases1) > len(after_cases2): + before_cases2 = [ + reaching for reaching in transitive_closure.siblings_reaching(case2.node) if reaching in interfering_cases + ] + possible_switch_node.cases.remove(case2.node) + interfering_cases.add_edges_from(product(before_cases2, after_cases2)) + else: + after_cases1 = [ + reachable + for reachable in transitive_closure.reachable_siblings_of(case1.node) + if reachable in interfering_cases + ] + possible_switch_node.cases.remove(case1.node) + interfering_cases.add_edges_from(product(before_cases1, after_cases1)) def _update_reaching_condition_for_case_node_children(self, switch_node: SwitchNode): """ @@ -573,27 +635,7 @@ def _can_place_switch_node(switch_node_candidate: SwitchNodeCandidate, sibling_r :param sibling_reachability: The reachability of all children of the sequence node. """ # TODO Why not deleting some nodes to still get the switch? If only one is the problem??? - copy_sibling_reachability = sibling_reachability.copy() - new_node = SwitchNode(switch_node_candidate.expression, LogicCondition.generate_new_context()) - copy_sibling_reachability.merge_siblings_to(new_node, [case_candidate.node for case_candidate in switch_node_candidate.cases]) - return copy_sibling_reachability.sorted_nodes() is not None - - def _remove_case_candidates_with_same_condition(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability) -> None: - """ - Remove one of two case candidates if they have the same condition. - - Since they were not combined before, they can not be combined, and we do not know which to pick. - """ - # TODO: not optimal, but a first version - reachability_graph = SiblingReachabilityGraph(sibling_reachability) - multiple_cases_of_condition = InitialSwitchNodeConstructor.__get_conditions_with_multiple_cases(possible_switch_node) - copy_sibling_reachability = sibling_reachability.copy() - tmp = SwitchNode(possible_switch_node.expression, LogicCondition.generate_new_context()) - copy_sibling_reachability.merge_siblings_to(tmp, [candidate.node for candidate in possible_switch_node.cases if not any(candidate in multiple_case for multiple_case in multiple_cases_of_condition.values())]) - # TODO - - - + return sibling_reachability.can_group_siblings([case.node for case in switch_node_candidate.cases]) @staticmethod def __get_conditions_with_multiple_cases(switch_candidate: SwitchNodeCandidate): @@ -622,3 +664,19 @@ def __parent_conditions(self, second_case_node: AbstractSyntaxTreeNode, cond_nod current_node = second_case_node while (current_node := current_node.parent) != cond_node: yield current_node.reaching_condition + + def __get_switch_nodes(self, interfering_cases): + degree_map = {node: degree for node, degree in interfering_cases.degree()} + final_case_nodes = InsertionOrderedSet() + while interfering_cases: + case = min(degree_map, key=lambda x: degree_map[x]) + final_case_nodes.add(case) + for neighbor in list(interfering_cases.neighbors(case)): + for n in interfering_cases.neighbors(neighbor): + degree_map[n] -= 1 + interfering_cases.remove_node(neighbor) + del degree_map[neighbor] + + interfering_cases.remove_node(case) + del degree_map[case] + return final_case_nodes diff --git a/decompiler/structures/ast/reachability_graph.py b/decompiler/structures/ast/reachability_graph.py index 57b040add..5beeb0710 100644 --- a/decompiler/structures/ast/reachability_graph.py +++ b/decompiler/structures/ast/reachability_graph.py @@ -6,7 +6,7 @@ from decompiler.structures.pseudo.expressions import Constant from decompiler.util.insertion_ordered_set import InsertionOrderedSet -from networkx import DiGraph, NetworkXUnfeasible, has_path, topological_sort, weakly_connected_components +from networkx import DiGraph, NetworkXUnfeasible, has_path, topological_sort, transitive_closure, weakly_connected_components if TYPE_CHECKING: from decompiler.structures.ast.ast_nodes import AbstractSyntaxTreeNode, CaseNode, CodeNode, SwitchNode @@ -102,6 +102,14 @@ def sorted_nodes(self) -> Optional[Tuple[AbstractSyntaxTreeNode, ...]]: except NetworkXUnfeasible: return None + def transitive_closure(self): + return SiblingReachability(transitive_closure(self._sibling_reachability_graph)) + + def can_group_siblings(self, grouping_siblings: List[AbstractSyntaxTreeNode]): + copy_sibling_reachability = self.copy() + copy_sibling_reachability.merge_siblings_to("X", grouping_siblings) + return copy_sibling_reachability.sorted_nodes() is not None + class ReachabilityGraph: """Class in charge of handling the code node reachability of Abstract-Syntax-Forests and Trees.""" From 4c924b2082d6693ef66b071d0f43eba4713e9866 Mon Sep 17 00:00:00 2001 From: Eva-Maria Behner Date: Tue, 25 Jul 2023 18:36:48 +0200 Subject: [PATCH 6/9] some changes --- .../initial_switch_node_constructer.py | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py index 955435d95..d8a6b9c9a 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py @@ -219,11 +219,27 @@ def _process_switch_node(self, possible_switch_node: SwitchNodeCandidate, siblin 4. Check whether we can place the switch-node and delete cases in order to make it insertable. """ self._clean_up_reachability(possible_switch_node, sibling_reachability) - self._initialize_not_same_switch_nodes(possible_switch_node, sibling_reachability) + sorted_siblings = sibling_reachability.sorted_nodes() + cases_with_same_condition: Dict[LogicCondition, Set[CaseNodeCandidate]] = self.__get_conditions_with_multiple_cases( + possible_switch_node + ) + if not cases_with_same_condition and self._can_place_switch_node(possible_switch_node, sibling_reachability): + return + transitive_closure = sibling_reachability.transitive_closure() + interfering_cases = self.__generate_interfering_case_nodes(possible_switch_node, sorted_siblings, transitive_closure) + self.__remove_duplicated_conditions( + cases_with_same_condition, interfering_cases, possible_switch_node, sibling_reachability, transitive_closure + ) + + switch_nodes = self.__get_switch_cases(interfering_cases) + possible_switch_node.cases = switch_nodes if len(possible_switch_node.cases) <= 1: return - self._remove_too_nested_cases(possible_switch_node, sibling_reachability) + removed_nodes = set(self._remove_too_nested_cases(possible_switch_node, sibling_reachability)) + if removed_nodes and not self._can_place_switch_node(possible_switch_node, sibling_reachability): + pass + # add to interfering graph and get new case-nodes!!!! def _clean_up_reachability(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): """ @@ -239,22 +255,6 @@ def _clean_up_reachability(self, possible_switch_node: SwitchNodeCandidate, sibl self.asforest._code_node_reachability_graph.remove_reachability_between([candidate_1.node, candidate_2.node]) sibling_reachability.remove_reachability_between([candidate_1.node, candidate_2.node]) - def _initialize_not_same_switch_nodes(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): - """We do not care about siblings that are no code-nodes and reach all cases or are reached by all cases.""" - sorted_siblings = sibling_reachability.sorted_nodes() - cases_with_same_condition: Dict[LogicCondition, Set[CaseNodeCandidate]] = self.__get_conditions_with_multiple_cases( - possible_switch_node - ) - if not cases_with_same_condition and self._can_place_switch_node(possible_switch_node, sibling_reachability): - return - transitive_closure = sibling_reachability.transitive_closure() - interfering_cases = self.__generate_interfering_case_nodes(possible_switch_node, sorted_siblings, transitive_closure) - self.__remove_duplicated_conditions( - cases_with_same_condition, interfering_cases, possible_switch_node, sibling_reachability, transitive_closure - ) - - switch_nodes = self.__get_switch_nodes(interfering_cases) - possible_switch_node.cases = switch_nodes def __generate_interfering_case_nodes(self, possible_switch_node, sorted_siblings, transitive_closure): switch_cases = {case.node: case for case in possible_switch_node.cases} @@ -613,7 +613,7 @@ def _order_parallel_cases(self, case_nodes: Set[CaseNode], linear_ordering_start return ordered_cases[0], ordered_cases[-1] @staticmethod - def _remove_too_nested_cases(possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability) -> None: + def _remove_too_nested_cases(possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability) -> Iterable[AbstractSyntaxTreeNode]: """ Check whether the cases are too nested. If this is the case, then we remove the cases that cause this problem. @@ -625,6 +625,7 @@ def _remove_too_nested_cases(possible_switch_node: SwitchNodeCandidate, sibling_ case_dependency_graph = CaseDependencyGraph(sibling_reachability, tuple(poss_case.node for poss_case in possible_switch_node.cases)) for cross_node in case_dependency_graph.get_too_nested_cases(): possible_switch_node.cases.remove(cross_node) + yield cross_node @staticmethod def _can_place_switch_node(switch_node_candidate: SwitchNodeCandidate, sibling_reachability: SiblingReachability) -> bool: @@ -665,7 +666,7 @@ def __parent_conditions(self, second_case_node: AbstractSyntaxTreeNode, cond_nod while (current_node := current_node.parent) != cond_node: yield current_node.reaching_condition - def __get_switch_nodes(self, interfering_cases): + def __get_switch_cases(self, interfering_cases): degree_map = {node: degree for node, degree in interfering_cases.degree()} final_case_nodes = InsertionOrderedSet() while interfering_cases: From 7b2c498a2f5bb9514d2c506ec9115b14929bf5b2 Mon Sep 17 00:00:00 2001 From: Eva-Maria Behner Date: Wed, 26 Jul 2023 16:17:46 +0200 Subject: [PATCH 7/9] first complete version --- .../initial_switch_node_constructer.py | 367 +++++++++--------- .../condition_based_refinement.py | 4 +- .../structures/ast/reachability_graph.py | 3 +- decompiler/util/insertion_ordered_set.py | 3 + 4 files changed, 198 insertions(+), 179 deletions(-) diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py index d8a6b9c9a..ee3e09386 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py @@ -2,9 +2,11 @@ from collections import defaultdict from dataclasses import dataclass from functools import reduce -from itertools import chain, combinations, permutations, product +from itertools import combinations, permutations, product from typing import DefaultDict, Dict, Iterable, Iterator, List, Optional, Set, Tuple +from networkx import Graph + from decompiler.pipeline.controlflowanalysis.restructuring_commons.condition_aware_refinement_commons.base_class_car import ( BaseClassConditionAwareRefinement, CaseNodeCandidate, @@ -15,14 +17,12 @@ CaseDependencyGraph, LinearOrderDependency, SiblingReachability, - SiblingReachabilityGraph, ) from decompiler.structures.ast.switch_node_handler import ExpressionUsages from decompiler.structures.ast.syntaxforest import AbstractSyntaxForest from decompiler.structures.logic.logic_condition import LogicCondition -from decompiler.structures.pseudo import Break, Constant, Expression +from decompiler.structures.pseudo import Constant, Expression from decompiler.util.insertion_ordered_set import InsertionOrderedSet -from networkx import Graph, contracted_nodes, relabel_nodes @dataclass @@ -38,6 +38,184 @@ def construct_switch_cases(self) -> Iterator[Tuple[CaseNode, AbstractSyntaxTreeN yield case_candidate.construct_case_node(self.expression), case_candidate.node +class SwitchNodeProcessor: + """Class for processing a possible switch node""" + + def __init__(self, asforest: AbstractSyntaxForest): + self.asforest: AbstractSyntaxForest = asforest + self.switch_candidate: Optional[SwitchNodeCandidate] = None + self._sibling_reachability: Optional[SiblingReachability] = None + self._switch_cases: Optional[Dict[AbstractSyntaxTreeNode, CaseNodeCandidate]] = None + self._transitive_closure: Optional[SiblingReachability] = None + + @property + def sibling_reachability(self) -> SiblingReachability: + return self._sibling_reachability + + @property + def transitive_closure(self) -> SiblingReachability: + if self._transitive_closure is None and self.sibling_reachability is not None: + self._transitive_closure = self.sibling_reachability.transitive_closure() + return self._transitive_closure + + @property + def switch_cases(self) -> Dict[AbstractSyntaxTreeNode, CaseNodeCandidate]: + if self._switch_cases is None or len(self._switch_cases) != len(self.switch_candidate.cases): + self._switch_cases = {case.node: case for case in self.switch_candidate.cases} + return self._switch_cases + + def process(self, possible_switch_node: SwitchNodeCandidate, seq_node: SeqNode) -> bool: + """ + Process the possible switch node such that we can insert it a switch-node, if possible. + + 1. clean-up reachability, i.e., remove reachability between case-nodes that are not reachable from each other due to their condition + 2. remove case-candidates with the exact same constants, leaving the once that are most suitable (consider reachability) + 3. remove too nested cases, i.e., we do not want to insert too many conditions into the switch-cases + 4. Check whether we can place the switch-node and delete cases in order to make it insertable. + + Return whether the candidate is a switch-node. + """ + self.switch_candidate = possible_switch_node + self._sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(seq_node) + self._clean_up_reachability() + self._remove_too_nested_cases() + cases_with_same_condition: Dict[LogicCondition, InsertionOrderedSet[CaseNodeCandidate]] = self._get_conditions_with_multiple_cases() + if cases_with_same_condition or not self._can_place_switch_node(): + self._remove_contradicting_cases(cases_with_same_condition) + + if len(possible_switch_node.cases) <= 1 or not self._can_place_switch_node(): + return False + return True + + def _clean_up_reachability(self): + """ + If two possible switch-cases reach each other, but they have no common possible cases, then we can remove the reachability. + + In these cases, the order is irrelevant and if one is executed the other will not be executed. + """ + for candidate_1, candidate_2 in permutations(self.switch_candidate.cases, 2): + if self.sibling_reachability.reaches(candidate_1.node, candidate_2.node) and not ( + set(self.asforest.switch_node_handler.get_constants_for(candidate_1.condition)) + & set(self.asforest.switch_node_handler.get_constants_for(candidate_2.condition)) + ): + self.asforest._code_node_reachability_graph.remove_reachability_between([candidate_1.node, candidate_2.node]) + self.sibling_reachability.remove_reachability_between([candidate_1.node, candidate_2.node]) + + def _get_conditions_with_multiple_cases(self) -> Dict[LogicCondition, InsertionOrderedSet[CaseNodeCandidate]]: + """Return a dictionary mapping the case-conditions of cases with the same condition to these cases.""" + cases_of_condition: DefaultDict[LogicCondition, InsertionOrderedSet[CaseNodeCandidate]] = defaultdict(InsertionOrderedSet) + for case_candidate in self.switch_candidate.cases: + cases_of_condition[case_candidate.condition].add(case_candidate) + return {condition: cases for condition, cases in cases_of_condition.items() if len(cases) > 1} + + def _remove_contradicting_cases(self, cases_with_same_condition: Dict[LogicCondition, InsertionOrderedSet[CaseNodeCandidate]]): + """Remove switch-cases in order to be able to insert the possible case node.""" + interfering_cases = self._generate_interfering_cases_graph() + self._remove_duplicated_conditions(cases_with_same_condition, interfering_cases) + self._get_final_switch_cases(interfering_cases) + + def _generate_interfering_cases_graph(self) -> Graph: + """ + Generate a graph whose nodes are the possible switch-cases, i.e., CaseNodeCandidates, + and where there is an edge between two case-nodes if they can not be in the same switch node. + """ + interfering_cases = Graph() + interfering_cases.add_nodes_from(self.switch_cases.values()) + for node in self.__get_non_case_nodes(): + before_cases = self._cases_reaching(node) + after_cases = self._cases_reachable_from(node) + if before_cases and after_cases: + interfering_cases.add_edges_from(product(before_cases, after_cases)) + return interfering_cases + + def __get_non_case_nodes(self): + """Return all nodes not in the given set of cases.""" + for sibling in self.sibling_reachability.nodes: + if sibling not in self.switch_cases: + yield sibling + + def _remove_duplicated_conditions( + self, cases_with_same_condition: Dict[LogicCondition, InsertionOrderedSet[CaseNodeCandidate]], interfering_cases: Graph + ): + for condition, same_condition_cases in cases_with_same_condition.items(): + non_interfering_cases = [ + (case1, case2) + for case1, case2 in combinations(same_condition_cases, 2) + if not interfering_cases.has_edge(case1.node, case2.node) + ] + while non_interfering_cases: + case1, case2 = non_interfering_cases.pop() + if case1 not in self.switch_candidate.cases or case2 not in self.switch_candidate.cases: + continue + before_cases1 = self._cases_reaching(case1.node) + after_cases2 = self._cases_reachable_from(case2.node) + if len(before_cases1) > len(after_cases2): + self._remove_case_candidate(case2, interfering_cases, self._cases_reaching(case2.node), after_cases2) + else: + self._remove_case_candidate(case1, interfering_cases, before_cases1, self._cases_reachable_from(case1.node)) + + def _remove_case_candidate( + self, case: CaseNodeCandidate, interfering_cases: Graph, before_cases: List[CaseNodeCandidate], after_cases: List[CaseNodeCandidate] + ): + """Remove the case-candidate as a case for the switch-node candidate""" + self.switch_candidate.cases.remove(case) + interfering_cases.add_edges_from(product(before_cases, after_cases)) + interfering_cases.remove_node(case) + del self._switch_cases[case.node] + + def _cases_reaching(self, node: AbstractSyntaxTreeNode) -> List[CaseNodeCandidate]: + """Switch Cases reaching the given node.""" + return [ + self.switch_cases[reaching] for reaching in self.transitive_closure.siblings_reaching(node) if reaching in self.switch_cases + ] + + def _cases_reachable_from(self, node: AbstractSyntaxTreeNode) -> List[CaseNodeCandidate]: + """Switch Cases reachable from the given node.""" + return [ + self.switch_cases[reachable] + for reachable in self.transitive_closure.reachable_siblings_of(node) + if reachable in self.switch_cases + ] + + def _get_final_switch_cases(self, interfering_cases: Graph): + """Get the final set of switch-cases, respectively, remove switch-cases untill we can order them.""" + assert len(self.switch_candidate.cases) == len(interfering_cases.nodes) and all( + node in self.switch_candidate.cases for node in interfering_cases + ) + degree_map = {node: degree for node, degree in interfering_cases.degree()} + while interfering_cases: + case = min(degree_map, key=lambda x: degree_map[x]) + for neighbor in list(interfering_cases.neighbors(case)): + for n in interfering_cases.neighbors(neighbor): + degree_map[n] -= 1 + interfering_cases.remove_node(neighbor) + del degree_map[neighbor] + self.switch_candidate.cases.discard(neighbor) + + interfering_cases.remove_node(case) + del degree_map[case] + + def _remove_too_nested_cases(self) -> Iterable[AbstractSyntaxTreeNode]: + """ + Check whether the cases are too nested. If this is the case, then we remove the cases that cause this problem. + + The sibling reachability tells us which ast-nodes must be reachable from their siblings node. + If we have case nodes, say c1, c2, c3, c4 and c5 s.t. c1 reaches c3 and c4, c2 reaches c3 and c5 and c3 reaches c4 and c5 + then it is impossible to sort the cases without adding too many additional conditions. + If they are too nested, then we remove the cases from the SwitchNodeCandidate that cause this problem. + """ + case_dependency_graph = CaseDependencyGraph( + self.sibling_reachability, tuple(poss_case.node for poss_case in self.switch_candidate.cases) + ) + for cross_node in case_dependency_graph.get_too_nested_cases(): + self.switch_candidate.cases.remove(cross_node) + yield cross_node + + def _can_place_switch_node(self) -> bool: + """Check whether we can construct a switch node for the switch node candidate.""" + return self.sibling_reachability.can_group_siblings([case.node for case in self.switch_candidate.cases]) + + class InitialSwitchNodeConstructor(BaseClassConditionAwareRefinement): """Class that constructs switch nodes.""" @@ -156,20 +334,18 @@ def _try_to_construct_initial_switch_node_for(self, seq_node: SeqNode) -> None: 3. If there exists an expression that belongs to at least two possible case candidates, then we construct a switch node. 4. Then we place the switch node if possible. """ + switch_node_processor = SwitchNodeProcessor(self.asforest) for possible_switch_node in self._get_possible_switch_nodes_for(seq_node): sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(seq_node) - self._process_switch_node(possible_switch_node, sibling_reachability) - - # TODO: also do it after removeing to nested nodes. - if len(possible_switch_node.cases) > 1 and self._can_place_switch_node(possible_switch_node, sibling_reachability): - switch_cases = list(possible_switch_node.construct_switch_cases()) - switch_node = self.asforest.create_switch_node_with(possible_switch_node.expression, switch_cases) - case_dependency = CaseDependencyGraph.construct_case_dependency_for( - self.asforest.children(switch_node), sibling_reachability - ) - self._update_reaching_condition_for_case_node_children(switch_node) - self._add_constants_to_cases(switch_node, case_dependency) - switch_node.sort_cases() + if switch_node_processor.process(possible_switch_node, seq_node) is False: + continue + + switch_cases = list(possible_switch_node.construct_switch_cases()) + switch_node = self.asforest.create_switch_node_with(possible_switch_node.expression, switch_cases) + case_dependency = CaseDependencyGraph.construct_case_dependency_for(self.asforest.children(switch_node), sibling_reachability) + self._update_reaching_condition_for_case_node_children(switch_node) + self._add_constants_to_cases(switch_node, case_dependency) + switch_node.sort_cases() def _get_possible_switch_nodes_for(self, seq_node: SeqNode) -> List[SwitchNodeCandidate]: """ @@ -209,116 +385,6 @@ def _get_possible_case_candidate_for(self, ast_node: AbstractSyntaxTreeNode) -> return None - def _process_switch_node(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): - """ - Process the possible switch node such that we can insert it a switch-node, if possible. - - 1. clean-up reachability, i.e., remove reachability between case-nodes that are not reachable from each other due to their condition - 2. remove case-candidates with the exact same constants, leaving the once that are most suitable (consider reachability) - 3. remove too nested cases, i.e., we do not want to insert too many conditions into the switch-cases - 4. Check whether we can place the switch-node and delete cases in order to make it insertable. - """ - self._clean_up_reachability(possible_switch_node, sibling_reachability) - sorted_siblings = sibling_reachability.sorted_nodes() - cases_with_same_condition: Dict[LogicCondition, Set[CaseNodeCandidate]] = self.__get_conditions_with_multiple_cases( - possible_switch_node - ) - if not cases_with_same_condition and self._can_place_switch_node(possible_switch_node, sibling_reachability): - return - transitive_closure = sibling_reachability.transitive_closure() - interfering_cases = self.__generate_interfering_case_nodes(possible_switch_node, sorted_siblings, transitive_closure) - self.__remove_duplicated_conditions( - cases_with_same_condition, interfering_cases, possible_switch_node, sibling_reachability, transitive_closure - ) - - switch_nodes = self.__get_switch_cases(interfering_cases) - possible_switch_node.cases = switch_nodes - - if len(possible_switch_node.cases) <= 1: - return - removed_nodes = set(self._remove_too_nested_cases(possible_switch_node, sibling_reachability)) - if removed_nodes and not self._can_place_switch_node(possible_switch_node, sibling_reachability): - pass - # add to interfering graph and get new case-nodes!!!! - - def _clean_up_reachability(self, possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability): - """ - If two possible switch-cases reach each other, but they have no common possible cases, then we can remove the reachability. - - In these cases, the order is irrelevant and if one is executed the other will not be executed. - """ - for candidate_1, candidate_2 in permutations(possible_switch_node.cases, 2): - if sibling_reachability.reaches(candidate_1.node, candidate_2.node) and not ( - set(self.asforest.switch_node_handler.get_constants_for(candidate_1.condition)) - & set(self.asforest.switch_node_handler.get_constants_for(candidate_2.condition)) - ): - self.asforest._code_node_reachability_graph.remove_reachability_between([candidate_1.node, candidate_2.node]) - sibling_reachability.remove_reachability_between([candidate_1.node, candidate_2.node]) - - - def __generate_interfering_case_nodes(self, possible_switch_node, sorted_siblings, transitive_closure): - switch_cases = {case.node: case for case in possible_switch_node.cases} - interfering_cases = Graph() - interfering_cases.add_nodes_from(switch_cases.values()) - for node in self.__get_non_case_nodes(sorted_siblings, switch_cases): - before_cases = [switch_cases[reaching] for reaching in transitive_closure.siblings_reaching(node) if reaching in switch_cases] - after_cases = [ - switch_cases[reachable] for reachable in transitive_closure.reachable_siblings_of(node) if reachable in switch_cases - ] - if before_cases and after_cases: - interfering_cases.add_edges_from(product(before_cases, after_cases)) - return interfering_cases - - def __get_non_case_nodes(self, siblings: Tuple[AbstractSyntaxTreeNode], switch_cases: Dict[AbstractSyntaxTreeNode, CaseNodeCandidate]): - for sibling in siblings: - if sibling not in switch_cases: - yield sibling - - def __remove_duplicated_conditions( - self, cases_with_same_condition, interfering_cases, possible_switch_node, sibling_reachability, transitive_closure - ): - for condition, same_condition_cases in cases_with_same_condition.items(): - non_interfering_cases = [ - (case1, case2) - for case1, case2 in combinations(same_condition_cases, 2) - if not interfering_cases.has_edge(case1.node, case2.node) - ] - while non_interfering_cases: - case1, case2 = non_interfering_cases.pop() - if case1 not in possible_switch_node.cases or case2 not in possible_switch_node.cases: - continue - if sibling_reachability.can_group_siblings([case1.node, case2.node]): - cond_node = self.asforest.create_condition_node_with(condition, [case1.node, case2.node], []) - sibling_reachability.merge_siblings_to(cond_node, [case1.node, case2.node]) - transitive_closure.merge_siblings_to(cond_node, [case1.node, case2.node]) - contracted_nodes(interfering_cases, case1, case2, copy=False) - relabel_nodes(interfering_cases, {case1: cond_node}, copy=False) - possible_switch_node.cases.remove(case1) - possible_switch_node.cases.remove(case2) - possible_switch_node.cases.add(CaseNodeCandidate(cond_node, case1.expression, condition)) - else: - # TODO: not done yet -> check when to use CaseNodeCandidate and when to use ASTNode!!! - before_cases1 = [ - reaching for reaching in transitive_closure.siblings_reaching(case1.node) if reaching in interfering_cases - ] - after_cases2 = [ - reachable for reachable in transitive_closure.reachable_siblings_of(case2.node) if reachable in interfering_cases - ] - if len(before_cases1) > len(after_cases2): - before_cases2 = [ - reaching for reaching in transitive_closure.siblings_reaching(case2.node) if reaching in interfering_cases - ] - possible_switch_node.cases.remove(case2.node) - interfering_cases.add_edges_from(product(before_cases2, after_cases2)) - else: - after_cases1 = [ - reachable - for reachable in transitive_closure.reachable_siblings_of(case1.node) - if reachable in interfering_cases - ] - possible_switch_node.cases.remove(case1.node) - interfering_cases.add_edges_from(product(before_cases1, after_cases1)) - def _update_reaching_condition_for_case_node_children(self, switch_node: SwitchNode): """ Update the reaching condition for each case-node child. @@ -612,39 +678,6 @@ def _order_parallel_cases(self, case_nodes: Set[CaseNode], linear_ordering_start return ordered_cases[0], ordered_cases[-1] - @staticmethod - def _remove_too_nested_cases(possible_switch_node: SwitchNodeCandidate, sibling_reachability: SiblingReachability) -> Iterable[AbstractSyntaxTreeNode]: - """ - Check whether the cases are too nested. If this is the case, then we remove the cases that cause this problem. - - The sibling reachability tells us which ast-nodes must be reachable from their siblings node. - If we have case nodes, say c1, c2, c3, c4 and c5 s.t. c1 reaches c3 and c4, c2 reaches c3 and c5 and c3 reaches c4 and c5 - then it is impossible to sort the cases without adding too many additional conditions. - If they are too nested, then we remove the cases from the SwitchNodeCandidate that cause this problem. - """ - case_dependency_graph = CaseDependencyGraph(sibling_reachability, tuple(poss_case.node for poss_case in possible_switch_node.cases)) - for cross_node in case_dependency_graph.get_too_nested_cases(): - possible_switch_node.cases.remove(cross_node) - yield cross_node - - @staticmethod - def _can_place_switch_node(switch_node_candidate: SwitchNodeCandidate, sibling_reachability: SiblingReachability) -> bool: - """ - Check whether we can construct a switch node for the switch node candidate. - - :param switch_node_candidate: The switch node candidate that we want to place. - :param sibling_reachability: The reachability of all children of the sequence node. - """ - # TODO Why not deleting some nodes to still get the switch? If only one is the problem??? - return sibling_reachability.can_group_siblings([case.node for case in switch_node_candidate.cases]) - - @staticmethod - def __get_conditions_with_multiple_cases(switch_candidate: SwitchNodeCandidate): - cases_of_condition: DefaultDict[LogicCondition, Set[CaseNodeCandidate]] = defaultdict(set) - for case_candidate in switch_candidate.cases: - cases_of_condition[case_candidate.condition].add(case_candidate) - return {condition: cases for condition, cases in cases_of_condition.items() if len(cases) > 1} - def _clean_up_reaching_conditions(self, switch_node: SwitchNode) -> None: """ Remove the reaching condition of each case node of the given switch node. @@ -665,19 +698,3 @@ def __parent_conditions(self, second_case_node: AbstractSyntaxTreeNode, cond_nod current_node = second_case_node while (current_node := current_node.parent) != cond_node: yield current_node.reaching_condition - - def __get_switch_cases(self, interfering_cases): - degree_map = {node: degree for node, degree in interfering_cases.degree()} - final_case_nodes = InsertionOrderedSet() - while interfering_cases: - case = min(degree_map, key=lambda x: degree_map[x]) - final_case_nodes.add(case) - for neighbor in list(interfering_cases.neighbors(case)): - for n in interfering_cases.neighbors(neighbor): - degree_map[n] -= 1 - interfering_cases.remove_node(neighbor) - del degree_map[neighbor] - - interfering_cases.remove_node(case) - del degree_map[case] - return final_case_nodes diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_based_refinement.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_based_refinement.py index 2cab9c905..2fe08d824 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_based_refinement.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_based_refinement.py @@ -235,9 +235,7 @@ def _can_place_condition_node_with_branches(branches: List[AbstractSyntaxTreeNod :param sibling_reachability: :return: """ - copy_sibling_reachability = sibling_reachability.copy() - copy_sibling_reachability.merge_siblings_to(SeqNode(LogicCondition.generate_new_context()), branches) - return copy_sibling_reachability.sorted_nodes() is not None + return sibling_reachability.can_group_siblings(branches) @staticmethod def _all_subsets(arguments: List[LogicCondition]) -> Iterator[Tuple[LogicCondition]]: diff --git a/decompiler/structures/ast/reachability_graph.py b/decompiler/structures/ast/reachability_graph.py index 5beeb0710..ef54a4dc8 100644 --- a/decompiler/structures/ast/reachability_graph.py +++ b/decompiler/structures/ast/reachability_graph.py @@ -102,10 +102,11 @@ def sorted_nodes(self) -> Optional[Tuple[AbstractSyntaxTreeNode, ...]]: except NetworkXUnfeasible: return None - def transitive_closure(self): + def transitive_closure(self) -> SiblingReachability: return SiblingReachability(transitive_closure(self._sibling_reachability_graph)) def can_group_siblings(self, grouping_siblings: List[AbstractSyntaxTreeNode]): + """Check whether the given siblings can be grouped into one node.""" copy_sibling_reachability = self.copy() copy_sibling_reachability.merge_siblings_to("X", grouping_siblings) return copy_sibling_reachability.sorted_nodes() is not None diff --git a/decompiler/util/insertion_ordered_set.py b/decompiler/util/insertion_ordered_set.py index 32d18aabc..17628f7e1 100644 --- a/decompiler/util/insertion_ordered_set.py +++ b/decompiler/util/insertion_ordered_set.py @@ -15,6 +15,9 @@ def __init__(self, iterable: Optional[Iterable[T]] = None, **kwargs: Dict[str, A def __iter__(self) -> Iterator[T]: yield from self.keys() + def __len__(self) -> int: + return len(self.keys()) + def update(self, *args: Iterable[T], **kwargs: Dict[str, Any]): # type: ignore # Type checking ignored as we cannot satisfy the dict.update signature. if kwargs: From dd7858ef574a78a84c1e4df0c8687278c3b276ac Mon Sep 17 00:00:00 2001 From: Eva-Maria Behner Date: Wed, 26 Jul 2023 17:15:10 +0200 Subject: [PATCH 8/9] fix tests --- .../initial_switch_node_constructer.py | 14 +-- .../test_condition_aware_refinement.py | 94 ++++++++----------- 2 files changed, 43 insertions(+), 65 deletions(-) diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py index ee3e09386..1950c543b 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/initial_switch_node_constructer.py @@ -5,24 +5,19 @@ from itertools import combinations, permutations, product from typing import DefaultDict, Dict, Iterable, Iterator, List, Optional, Set, Tuple -from networkx import Graph - from decompiler.pipeline.controlflowanalysis.restructuring_commons.condition_aware_refinement_commons.base_class_car import ( BaseClassConditionAwareRefinement, CaseNodeCandidate, ) from decompiler.pipeline.controlflowanalysis.restructuring_options import RestructuringOptions from decompiler.structures.ast.ast_nodes import AbstractSyntaxTreeNode, CaseNode, CodeNode, ConditionNode, SeqNode, SwitchNode, TrueNode -from decompiler.structures.ast.reachability_graph import ( - CaseDependencyGraph, - LinearOrderDependency, - SiblingReachability, -) +from decompiler.structures.ast.reachability_graph import CaseDependencyGraph, LinearOrderDependency, SiblingReachability from decompiler.structures.ast.switch_node_handler import ExpressionUsages from decompiler.structures.ast.syntaxforest import AbstractSyntaxForest from decompiler.structures.logic.logic_condition import LogicCondition from decompiler.structures.pseudo import Constant, Expression from decompiler.util.insertion_ordered_set import InsertionOrderedSet +from networkx import Graph @dataclass @@ -195,7 +190,7 @@ def _get_final_switch_cases(self, interfering_cases: Graph): interfering_cases.remove_node(case) del degree_map[case] - def _remove_too_nested_cases(self) -> Iterable[AbstractSyntaxTreeNode]: + def _remove_too_nested_cases(self) -> None: """ Check whether the cases are too nested. If this is the case, then we remove the cases that cause this problem. @@ -209,7 +204,6 @@ def _remove_too_nested_cases(self) -> Iterable[AbstractSyntaxTreeNode]: ) for cross_node in case_dependency_graph.get_too_nested_cases(): self.switch_candidate.cases.remove(cross_node) - yield cross_node def _can_place_switch_node(self) -> bool: """Check whether we can construct a switch node for the switch node candidate.""" @@ -336,10 +330,10 @@ def _try_to_construct_initial_switch_node_for(self, seq_node: SeqNode) -> None: """ switch_node_processor = SwitchNodeProcessor(self.asforest) for possible_switch_node in self._get_possible_switch_nodes_for(seq_node): - sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(seq_node) if switch_node_processor.process(possible_switch_node, seq_node) is False: continue + sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(seq_node) switch_cases = list(possible_switch_node.construct_switch_cases()) switch_node = self.asforest.create_switch_node_with(possible_switch_node.expression, switch_cases) case_dependency = CaseDependencyGraph.construct_case_dependency_for(self.asforest.children(switch_node), sibling_reachability) diff --git a/tests/pipeline/controlflowanalysis/restructuring_commons/test_condition_aware_refinement.py b/tests/pipeline/controlflowanalysis/restructuring_commons/test_condition_aware_refinement.py index e67837083..46dacb68f 100644 --- a/tests/pipeline/controlflowanalysis/restructuring_commons/test_condition_aware_refinement.py +++ b/tests/pipeline/controlflowanalysis/restructuring_commons/test_condition_aware_refinement.py @@ -3400,14 +3400,12 @@ def test_too_nested(task): PatternIndependentRestructuring().run(task) # initial part - assert isinstance(seq_node := task._ast.root, SeqNode) and len(seq_node.children) == 12 + assert isinstance(seq_node := task._ast.root, SeqNode) and len(seq_node.children) == 5 assert isinstance(seq_node.children[0], CodeNode) and seq_node.children[0].instructions == vertices[0].instructions[:-1] assert isinstance(cond_node := seq_node.children[1], ConditionNode) - assert all( - isinstance(child, ConditionNode) and child.false_branch is None and isinstance(child.true_branch_child, CodeNode) - for child in seq_node.children[2:11] - ) - assert isinstance(seq_node.children[11], CodeNode) and seq_node.children[11].instructions == vertices[3].instructions + assert isinstance(switch1 := seq_node.children[2], SwitchNode) + assert isinstance(switch2 := seq_node.children[3], SwitchNode) + assert isinstance(seq_node.children[4], CodeNode) and seq_node.children[4].instructions == vertices[3].instructions # condition node if (cond := cond_node.condition).is_symbol: @@ -3424,59 +3422,45 @@ def test_too_nested(task): # before_switch branch assert isinstance(switch_branch, CodeNode) and switch_branch.instructions == vertices[2].instructions[:-1] - # all switch-cases - assert seq_node.children[2].true_branch_child.instructions == vertices[4].instructions - assert task._ast.condition_map[seq_node.children[2].condition] == Condition( - OperationType.equal, [var_1_1, Constant(0, Integer(32, True))] + # switch1 + assert switch1.expression == var_1_1 and len(switch1.children) == 5 + assert isinstance(case1 := switch1.cases[0], CaseNode) and case1.constant == Constant(0, Integer(32, True)) and case1.break_case + assert isinstance(case2 := switch1.cases[1], CaseNode) and case2.constant == Constant(1, Integer(32, True)) and case2.break_case + assert ( + isinstance(case3 := switch1.cases[2], CaseNode) and case3.constant == Constant(3, Integer(32, True)) and case3.break_case is False + ) + assert isinstance(case4 := switch1.cases[3], CaseNode) and case4.constant == Constant(4, Integer(32, True)) and case4.break_case + assert isinstance(case5 := switch1.cases[4], CaseNode) and case5.constant == Constant(5, Integer(32, True)) and case5.break_case + + # children of cases + assert isinstance(case1.child, CodeNode) and case1.child.instructions == vertices[4].instructions + assert isinstance(case2.child, CodeNode) and case2.child.instructions == vertices[5].instructions + assert isinstance(case3.child, CodeNode) and case3.child.instructions == vertices[7].instructions + assert isinstance(case4.child, CodeNode) and case4.child.instructions == vertices[8].instructions + assert isinstance(case5.child, CodeNode) and case5.child.instructions == vertices[12].instructions + + # switch2 + assert switch2.expression == var_1_1 and len(switch2.children) == 5 + assert ( + isinstance(case1 := switch2.cases[0], CaseNode) and case1.constant == Constant(0, Integer(32, True)) and case1.break_case is False ) - assert seq_node.children[3].true_branch_child.instructions == vertices[5].instructions - assert task._ast.condition_map[seq_node.children[3].condition] == Condition( - OperationType.equal, [var_1_1, Constant(1, Integer(32, True))] + assert ( + isinstance(case2 := switch2.cases[1], CaseNode) and case2.constant == Constant(1, Integer(32, True)) and case2.break_case is False ) - assert seq_node.children[4].true_branch_child.instructions == vertices[12].instructions - assert task._ast.condition_map[seq_node.children[4].condition] == Condition( - OperationType.equal, [var_1_1, Constant(5, Integer(32, True))] + assert ( + isinstance(case3 := switch2.cases[2], CaseNode) and case3.constant == Constant(2, Integer(32, True)) and case3.break_case is False ) - assert seq_node.children[5].true_branch_child.instructions == vertices[7].instructions - assert task._ast.condition_map[seq_node.children[5].condition] == Condition( - OperationType.equal, [var_1_1, Constant(3, Integer(32, True))] + assert ( + isinstance(case4 := switch2.cases[3], CaseNode) and case4.constant == Constant(6, Integer(32, True)) and case4.break_case is False ) - assert seq_node.children[6].true_branch_child.instructions == vertices[11].instructions - assert seq_node.children[6].condition.is_disjunction and len(arguments := seq_node.children[6].condition.operands) == 2 - assert {task._ast.condition_map[arg] for arg in arguments} == { - Condition(OperationType.equal, [var_1_1, Constant(0, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(1, Integer(32, True))]), - } - assert seq_node.children[7].true_branch_child.instructions == vertices[8].instructions - assert seq_node.children[7].condition.is_disjunction and len(arguments := seq_node.children[7].condition.operands) == 2 - assert {task._ast.condition_map[arg] for arg in arguments} == { - Condition(OperationType.equal, [var_1_1, Constant(3, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(4, Integer(32, True))]), - } - assert seq_node.children[8].true_branch_child.instructions == vertices[6].instructions - assert seq_node.children[8].condition.is_disjunction and len(arguments := seq_node.children[8].condition.operands) == 3 - assert {task._ast.condition_map[arg] for arg in arguments} == { - Condition(OperationType.equal, [var_1_1, Constant(0, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(1, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(2, Integer(32, True))]), - } - assert seq_node.children[9].true_branch_child.instructions == vertices[9].instructions - assert seq_node.children[9].condition.is_disjunction and len(arguments := seq_node.children[9].condition.operands) == 4 - assert {task._ast.condition_map[arg] for arg in arguments} == { - Condition(OperationType.equal, [var_1_1, Constant(0, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(1, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(2, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(6, Integer(32, True))]), - } - assert seq_node.children[10].true_branch_child.instructions == vertices[10].instructions - assert seq_node.children[10].condition.is_disjunction and len(arguments := seq_node.children[10].condition.operands) == 5 - assert {task._ast.condition_map[arg] for arg in arguments} == { - Condition(OperationType.equal, [var_1_1, Constant(0, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(1, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(2, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(5, Integer(32, True))]), - Condition(OperationType.equal, [var_1_1, Constant(6, Integer(32, True))]), - } + assert isinstance(case5 := switch2.cases[4], CaseNode) and case5.constant == Constant(5, Integer(32, True)) and case5.break_case + + # children of cases + assert isinstance(case1.child, CodeNode) and case1.child.instructions == [] + assert isinstance(case2.child, CodeNode) and case2.child.instructions == vertices[11].instructions + assert isinstance(case3.child, CodeNode) and case3.child.instructions == vertices[6].instructions + assert isinstance(case4.child, CodeNode) and case4.child.instructions == vertices[9].instructions + assert isinstance(case5.child, CodeNode) and case5.child.instructions == vertices[10].instructions def test_combine_switch_nodes(task): From 2fa0c52509abbac33c4617b2d8c6743433ebfe55 Mon Sep 17 00:00:00 2001 From: Eva-Maria Behner Date: Thu, 27 Jul 2023 08:34:48 +0200 Subject: [PATCH 9/9] minor fixes --- .../missing_case_finder_sequence.py | 5 +- .../test_condition_aware_refinement.py | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/missing_case_finder_sequence.py b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/missing_case_finder_sequence.py index b8469e162..7023ccc77 100644 --- a/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/missing_case_finder_sequence.py +++ b/decompiler/pipeline/controlflowanalysis/restructuring_commons/condition_aware_refinement_commons/missing_case_finder_sequence.py @@ -95,13 +95,10 @@ def _can_combine_switch_nodes(self, switch_nodes: List[SwitchNode]) -> bool: :param switch_nodes: A list of switch nodes we want to combine. """ - # TODO What if only some of them are combinable??? sibling_reachability = self.asforest.get_sibling_reachability_of_children_of(self._current_seq_node) # The switch cases are all different, thus which switch comes first is irrelevant for the switch-nodes, but maybe not for the other children sibling_reachability.remove_reachability_between(switch_nodes) - new_node = self.asforest.factory.create_switch_node(switch_nodes[0].expression) - sibling_reachability.merge_siblings_to(new_node, switch_nodes) - return sibling_reachability.sorted_nodes() is not None + return sibling_reachability.can_group_siblings(switch_nodes) def _add_missing_cases(self) -> None: """ diff --git a/tests/pipeline/controlflowanalysis/restructuring_commons/test_condition_aware_refinement.py b/tests/pipeline/controlflowanalysis/restructuring_commons/test_condition_aware_refinement.py index 46dacb68f..a9ede12fe 100644 --- a/tests/pipeline/controlflowanalysis/restructuring_commons/test_condition_aware_refinement.py +++ b/tests/pipeline/controlflowanalysis/restructuring_commons/test_condition_aware_refinement.py @@ -5548,3 +5548,86 @@ def test_nested_cases_unnecessary_condition_not_all_irrelevant_2(task): assert isinstance(case5.child, CodeNode) and case5.child.instructions == vertices[12].instructions assert isinstance(case6.child, CodeNode) and case6.child.instructions == vertices[14].instructions assert isinstance(case7.child, CodeNode) and case7.child.instructions == vertices[16].instructions + + +def test_intersecting_cases(task): + """test_condition test18""" + var_0_0 = Variable("var_0", Integer(32, True), None, True, Variable("var_10", Integer(32, True), 0, True, None)) + var_0_2 = Variable("var_0", Integer(32, True), None, True, Variable("var_10", Integer(32, True), 2, True, None)) + arg1_0 = Variable("arg1", Integer(32, True), None, True, Variable("arg1", Integer(32, True), 0, True, None)) + arg1_1 = Variable("arg1", Integer(32, True), None, True, Variable("arg1", Integer(32, True), 1, True, None)) + arg1_2 = Variable("arg1", Integer(32, True), None, True, Variable("arg1", Integer(32, True), 2, True, None)) + task.graph.add_nodes_from( + vertices := [ + BasicBlock( + 0, + [ + Assignment(ListOperation([]), print_call("Enter week number(1-7): ", 1)), + Assignment( + ListOperation([]), + scanf_call( + UnaryOperation(OperationType.address, [var_0_0], Pointer(Integer(32, True), 32), None, False), 0x134524965, 2 + ), + ), + Branch(Condition(OperationType.not_equal, [var_0_2, Constant(1, Integer(32, True))], CustomType("bool", 1))), + ], + ), + BasicBlock(1, [Branch(Condition(OperationType.not_equal, [var_0_2, Constant(2, Integer(32, True))], CustomType("bool", 1)))]), + BasicBlock( + 2, + [ + Assignment(ListOperation([]), print_call("Possible switch case but not the one we want", 1)), + Branch(Condition(OperationType.greater, [arg1_0, Constant(5, Integer(32, True))], CustomType("bool", 1))), + ], + ), + BasicBlock(3, [Branch(Condition(OperationType.not_equal, [var_0_2, Constant(3, Integer(32, True))], CustomType("bool", 1)))]), + BasicBlock(6, [Assignment(arg1_1, BinaryOperation(OperationType.plus, [arg1_0, Constant(5, Integer.int32_t())]))]), + BasicBlock(7, [Branch(Condition(OperationType.not_equal, [var_0_2, Constant(4, Integer(32, True))], CustomType("bool", 1)))]), + BasicBlock( + 9, + [ + Assignment(ListOperation([]), print_call("Tuesday", 1)), + Branch(Condition(OperationType.less_or_equal, [arg1_2, Constant(10, Integer(32, True))], CustomType("bool", 1))), + ], + ), + BasicBlock(10, [Assignment(ListOperation([]), print_call("Monday", 3))]), + BasicBlock( + 13, + [ + Assignment(ListOperation([]), print_call("Wednesday", 1)), + Branch(Condition(OperationType.not_equal, [var_0_2, Constant(1, Integer(32, True))], CustomType("bool", 1))), + ], + ), + BasicBlock(16, [Return(ListOperation([Constant(0, Integer(32, True))]))]), + BasicBlock(17, [Assignment(ListOperation([]), print_call("Thursday", 3))]), + ] + ) + task.graph.add_edges_from( + [ + TrueCase(vertices[0], vertices[1]), + FalseCase(vertices[0], vertices[2]), + TrueCase(vertices[1], vertices[3]), + FalseCase(vertices[1], vertices[6]), + TrueCase(vertices[2], vertices[7]), + FalseCase(vertices[2], vertices[4]), + TrueCase(vertices[3], vertices[5]), + FalseCase(vertices[3], vertices[8]), + UnconditionalEdge(vertices[4], vertices[6]), + TrueCase(vertices[5], vertices[9]), + FalseCase(vertices[5], vertices[10]), + TrueCase(vertices[6], vertices[9]), + FalseCase(vertices[6], vertices[8]), + UnconditionalEdge(vertices[7], vertices[9]), + TrueCase(vertices[8], vertices[10]), + FalseCase(vertices[8], vertices[7]), + UnconditionalEdge(vertices[10], vertices[9]), + ] + ) + + PatternIndependentRestructuring().run(task) + + assert len(list(task._ast.get_switch_nodes_post_order())) == 2 + assert isinstance(seq_node := task._ast.root, SeqNode) and len(children := seq_node.children) == 6 + assert isinstance(children[0], CodeNode) and isinstance(children[5], CodeNode) + assert all(isinstance(child, ConditionNode) for child in children[1:4]) + assert isinstance(children[4], SwitchNode) and isinstance(children[3].true_branch_child, SwitchNode)