From b90371d0c39ac03e0bdaa0e3185714cbd2bf9ec7 Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 12 Apr 2024 09:16:55 +0200 Subject: [PATCH] Rename father/son to predecessor/successor This is a breaking change in the API but for the moment, we keep the compatibility layer in the cfg/node.py. --- examples/scripts/taint_mapping.py | 4 +- .../analyses/write/are_variables_written.py | 8 +- slither/core/cfg/node.py | 156 +++++++++++------- slither/core/declarations/contract.py | 8 +- slither/core/declarations/function.py | 16 +- slither/core/dominators/utils.py | 20 +-- .../detectors/assembly/incorrect_return.py | 2 +- slither/detectors/functions/modifier.py | 8 +- .../functions/out_of_order_retryable.py | 18 +- .../operations/cache_array_length.py | 10 +- .../missing_zero_address_validation.py | 4 +- slither/detectors/reentrancy/reentrancy.py | 36 ++-- slither/detectors/statements/calls_in_loop.py | 4 +- .../statements/costly_operations_in_loop.py | 4 +- .../statements/delegatecall_in_loop.py | 4 +- .../statements/divide_before_multiply.py | 4 +- .../detectors/statements/msg_value_in_loop.py | 4 +- .../detectors/statements/write_after_write.py | 6 +- .../variables/predeclaration_usage_local.py | 4 +- .../uninitialized_local_variables.py | 35 ++-- .../uninitialized_storage_variables.py | 26 +-- slither/slithir/utils/ssa.py | 8 +- slither/solc_parsing/declarations/function.py | 106 ++++++------ slither/tools/read_storage/read_storage.py | 2 +- slither/utils/code_complexity.py | 10 +- slither/utils/upgradeability.py | 4 +- .../vyper_parsing/declarations/function.py | 4 +- tests/unit/slithir/test_implicit_returns.py | 40 ++--- tests/unit/slithir/test_ssa_generation.py | 14 +- .../unit/slithir/test_ternary_expressions.py | 17 +- 30 files changed, 326 insertions(+), 260 deletions(-) diff --git a/examples/scripts/taint_mapping.py b/examples/scripts/taint_mapping.py index 75ed3d8df3..1751118853 100644 --- a/examples/scripts/taint_mapping.py +++ b/examples/scripts/taint_mapping.py @@ -46,8 +46,8 @@ def visit_node(node, visited): node.function.compilation_unit.context[KEY] = list(set(taints)) - for son in node.sons: - visit_node(son, visited) + for successor in node.successors: + visit_node(successor, visited) def check_call(func, taints): diff --git a/slither/analyses/write/are_variables_written.py b/slither/analyses/write/are_variables_written.py index 2f8f83063d..abb895bfce 100644 --- a/slither/analyses/write/are_variables_written.py +++ b/slither/analyses/write/are_variables_written.py @@ -86,18 +86,18 @@ def _visit( lvalue = refs_lvalues ret: List[Variable] = [] - if not node.sons and node.type not in [NodeType.THROW, NodeType.RETURN]: + if not node.successors and node.type not in [NodeType.THROW, NodeType.RETURN]: ret += [v for v in variables_to_write if v not in variables_written] - # Explore sons if + # Explore successors if # - Before is none: its the first time we explored the node # - variables_written is not before: it means that this path has a configuration of set variables # that we haven't seen yet before = state.nodes[node] if node in state.nodes else None if before is None or variables_written not in before: state.nodes[node].append(variables_written) - for son in node.sons: - ret += _visit(son, state, variables_written, variables_to_write) + for successor in node.successors: + ret += _visit(successor, state, variables_written, variables_to_write) return ret diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 87d0e16a2e..c1e6ff0e2b 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -2,6 +2,7 @@ Node module """ from enum import Enum +import logging from typing import Optional, List, Set, Dict, Tuple, Union, TYPE_CHECKING from slither.all_exceptions import SlitherException @@ -64,6 +65,8 @@ ################################################################################### ################################################################################### +logger = logging.getLogger("SlitherNode") + class NodeType(Enum): ENTRYPOINT = "ENTRY_POINT" # no expression @@ -125,8 +128,8 @@ def __init__( self._node_type = node_type # TODO: rename to explicit CFG - self._sons: List["Node"] = [] - self._fathers: List["Node"] = [] + self._successors: List["Node"] = [] + self._predecessors: List["Node"] = [] ## Dominators info # Dominators nodes @@ -225,7 +228,7 @@ def type(self, new_type: NodeType) -> None: @property def will_return(self) -> bool: - if not self.sons and self.type != NodeType.THROW: + if not self.successors and self.type != NodeType.THROW: if SolidityFunction("revert()") not in self.solidity_calls: if SolidityFunction("revert(string)") not in self.solidity_calls: return True @@ -582,97 +585,97 @@ def add_inline_asm(self, asm: Union[str, Dict]) -> None: ################################################################################### ################################################################################### - def add_father(self, father: "Node") -> None: - """Add a father node + def add_predecessor(self, predecessor: "Node") -> None: + """Add a predecessor node Args: - father: father to add + predecessor: predecessor to add """ - self._fathers.append(father) + self._predecessors.append(predecessor) - def set_fathers(self, fathers: List["Node"]) -> None: - """Set the father nodes + def set_predecessors(self, predecessors: List["Node"]) -> None: + """Set the predecessors nodes Args: - fathers: list of fathers to add + predecessors: list of predecessors to add """ - self._fathers = fathers + self._predecessors = predecessors @property - def fathers(self) -> List["Node"]: - """Returns the father nodes + def predecessors(self) -> List["Node"]: + """Returns the predecessor nodes Returns: - list(Node): list of fathers + list(Node): list of predecessor """ - return list(self._fathers) + return list(self._predecessors) - def remove_father(self, father: "Node") -> None: - """Remove the father node. Do nothing if the node is not a father + def remove_predecessor(self, predecessor: "Node") -> None: + """Remove the predecessor node. Do nothing if the node is not a predecessor Args: - :param father: + :param predecessor: """ - self._fathers = [x for x in self._fathers if x.node_id != father.node_id] + self._predecessors = [x for x in self._predecessors if x.node_id != predecessor.node_id] - def remove_son(self, son: "Node") -> None: - """Remove the son node. Do nothing if the node is not a son + def remove_successor(self, successor: "Node") -> None: + """Remove the successor node. Do nothing if the node is not a successor Args: - :param son: + :param successor: """ - self._sons = [x for x in self._sons if x.node_id != son.node_id] + self._successors = [x for x in self._successors if x.node_id != successor.node_id] - def add_son(self, son: "Node") -> None: - """Add a son node + def add_successor(self, successor: "Node") -> None: + """Add a successor node Args: - son: son to add + successor: successor to add """ - self._sons.append(son) + self._successors.append(successor) - def replace_son(self, ori_son: "Node", new_son: "Node") -> None: - """Replace a son node. Do nothing if the node to replace is not a son + def replace_successor(self, old_successor: "Node", new_successor: "Node") -> None: + """Replace a successor node. Do nothing if the node to replace is not a successor Args: - ori_son: son to replace - new_son: son to replace with + old_successor: successor to replace + new_successor: successor to replace with """ - for i, s in enumerate(self._sons): - if s.node_id == ori_son.node_id: + for i, s in enumerate(self._successors): + if s.node_id == old_successor.node_id: idx = i break else: return - self._sons[idx] = new_son + self._successors[idx] = new_successor - def set_sons(self, sons: List["Node"]) -> None: - """Set the son nodes + def set_successors(self, successors: List["Node"]) -> None: + """Set the successor nodes Args: - sons: list of fathers to add + successors: list of successors to add """ - self._sons = sons + self._successors = successors @property - def sons(self) -> List["Node"]: - """Returns the son nodes + def successors(self) -> List["Node"]: + """Returns the successors nodes Returns: - list(Node): list of sons + list(Node): list of successors """ - return list(self._sons) + return list(self._successors) @property - def son_true(self) -> Optional["Node"]: + def successor_true(self) -> Optional["Node"]: if self.type in [NodeType.IF, NodeType.IFLOOP]: - return self._sons[0] + return self._successors[0] return None @property - def son_false(self) -> Optional["Node"]: - if self.type in [NodeType.IF, NodeType.IFLOOP] and len(self._sons) >= 1: - return self._sons[1] + def successor_false(self) -> Optional["Node"]: + if self.type in [NodeType.IF, NodeType.IFLOOP] and len(self._successors) >= 1: + return self._successors[1] return None # endregion @@ -1046,6 +1049,43 @@ def __str__(self) -> str: txt = str(self._node_type.value) + additional_info return txt + def __getattr__(self, item: str): + """Get attribute wrapper. + + Used for the breaking change to deprecate the usage of sons/fathers and use predecessors + and successors. + """ + replaced_functions = { + "add_son", + "remove_son", + "replace_son", + "son_false", + "son_true", + "sons", + "add_father", + "fathers", + "remove_father", + "set_fathers", + } + + if item not in replaced_functions: + raise AttributeError(item) + + if "son" in item: + new_function = item.replace("son", "successor") + elif "father" in item: + new_function = item.replace("father", "predecessor") + else: + raise AttributeError(item) + + proxied_function = getattr(self, new_function) + logger.warning( + "Function %s is deprecated and will be removed in a future version. Please use %s instead.", + item, + new_function, + ) + return proxied_function + # endregion ################################################################################### @@ -1056,18 +1096,18 @@ def __str__(self) -> str: def link_nodes(node1: Node, node2: Node) -> None: - node1.add_son(node2) - node2.add_father(node1) + node1.add_successor(node2) + node2.add_predecessor(node1) def insert_node(origin: Node, node_inserted: Node) -> None: - sons = origin.sons + successors = origin.successors link_nodes(origin, node_inserted) - for son in sons: - son.remove_father(origin) - origin.remove_son(son) + for successor in successors: + successor.remove_predecessor(origin) + origin.remove_successor(successor) - link_nodes(node_inserted, son) + link_nodes(node_inserted, successor) def recheable(node: Node) -> Set[Node]: @@ -1076,16 +1116,16 @@ def recheable(node: Node) -> Set[Node]: :param node: :return: set(Node) """ - nodes = node.sons + nodes = node.successors visited = set() while nodes: next_node = nodes[0] nodes = nodes[1:] if next_node not in visited: visited.add(next_node) - for son in next_node.sons: - if son not in visited: - nodes.append(son) + for successor in next_node.successors: + if successor not in visited: + nodes.append(successor) return visited diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index fe617b3313..bd7d0bdd8f 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -1480,8 +1480,8 @@ def add_constructor_variables(self) -> None: constructor_variable, counter, v, prev_node.scope ) v.node_initialization = next_node - prev_node.add_son(next_node) - next_node.add_father(prev_node) + prev_node.add_successor(next_node) + next_node.add_predecessor(prev_node) prev_node = next_node counter += 1 break @@ -1512,8 +1512,8 @@ def add_constructor_variables(self) -> None: constructor_variable, counter, v, prev_node.scope ) v.node_initialization = next_node - prev_node.add_son(next_node) - next_node.add_father(prev_node) + prev_node.add_successor(next_node) + next_node.add_predecessor(prev_node) prev_node = next_node counter += 1 diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 958a7d2199..772601ffdc 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -1397,8 +1397,8 @@ def cfg_to_dot(self, filename: str): f.write("digraph{\n") for node in self.nodes: f.write(f'{node.node_id}[label="{str(node)}"];\n') - for son in node.sons: - f.write(f"{node.node_id}->{son.node_id};\n") + for successor in node.successors: + f.write(f"{node.node_id}->{successor.node_id};\n") f.write("}\n") @@ -1453,15 +1453,15 @@ def slithir_cfg_to_dot_str(self, skip_expressions: bool = False) -> str: label += "\nIRs:\n" + "\n".join([str(ir) for ir in node.irs]) content += f'{node.node_id}[label="{label}"];\n' if node.type in [NodeType.IF, NodeType.IFLOOP]: - true_node = node.son_true + true_node = node.successor_true if true_node: content += f'{node.node_id}->{true_node.node_id}[label="True"];\n' - false_node = node.son_false + false_node = node.successor_false if false_node: content += f'{node.node_id}->{false_node.node_id}[label="False"];\n' else: - for son in node.sons: - content += f"{node.node_id}->{son.node_id};\n" + for successor in node.successors: + content += f"{node.node_id}->{successor.node_id};\n" content += "}\n" return content @@ -1761,8 +1761,8 @@ def _get_last_ssa_variable_instances( ret[name] = set() ret[name] |= instances - for son in node.sons: - to_explore.append((son, dict(values))) + for successor in node.successors: + to_explore.append((successor, dict(values))) return ret diff --git a/slither/core/dominators/utils.py b/slither/core/dominators/utils.py index 150c0c50e6..93890eadc2 100644 --- a/slither/core/dominators/utils.py +++ b/slither/core/dominators/utils.py @@ -7,20 +7,20 @@ def intersection_predecessor(node: "Node") -> Set["Node"]: - if not node.fathers: + if not node.predecessors: return set() - ret = node.fathers[0].dominators - for pred in node.fathers[1:]: + ret = node.predecessors[0].dominators + for pred in node.predecessors[1:]: ret = ret.intersection(pred.dominators) - if not any(father.is_reachable for father in node.fathers): + if not any(predecessor.is_reachable for predecessor in node.predecessors): return set() ret = set() - for pred in node.fathers: + for pred in node.predecessors: ret = ret.union(pred.dominators) - for pred in node.fathers: + for pred in node.predecessors: if pred.is_reachable: ret = ret.intersection(pred.dominators) return ret @@ -93,11 +93,11 @@ def compute_dominance_frontier(nodes: List["Node"]) -> None: Compute dominance frontier """ for node in nodes: - if len(node.fathers) >= 2: - for father in node.fathers: - if not father.is_reachable: + if len(node.predecessors) >= 2: + for predecessor in node.predecessors: + if not predecessor.is_reachable: continue - runner = father + runner = predecessor # Corner case: if there is a if without else # we need to add update the conditional node if ( diff --git a/slither/detectors/assembly/incorrect_return.py b/slither/detectors/assembly/incorrect_return.py index bd5a6d8449..0ff67b5509 100644 --- a/slither/detectors/assembly/incorrect_return.py +++ b/slither/detectors/assembly/incorrect_return.py @@ -72,7 +72,7 @@ def _detect(self) -> List[Output]: for f in c.functions_and_modifiers_declared: for node in f.nodes: - if node.sons: + if node.successors: for function_called in node.internal_calls: if isinstance(function_called, Function): found = _assembly_node(function_called) diff --git a/slither/detectors/functions/modifier.py b/slither/detectors/functions/modifier.py index 7f14872663..89e73793a5 100644 --- a/slither/detectors/functions/modifier.py +++ b/slither/detectors/functions/modifier.py @@ -26,10 +26,10 @@ def _get_false_son(node: Node) -> Node: Following this node stays on the outer scope of the function """ if node.type == NodeType.IF: - return node.sons[1] + return node.successors[1] if node.type == NodeType.IFLOOP: - return next(s for s in node.sons if s.type == NodeType.ENDLOOP) + return next(s for s in node.successors if s.type == NodeType.ENDLOOP) return None @@ -80,8 +80,8 @@ def _detect(self) -> List[Output]: break # Move down, staying on the outer scope in branches - if len(node.sons) > 0: - node = _get_false_son(node) if node.contains_if() else node.sons[0] + if len(node.successors) > 0: + node = _get_false_son(node) if node.contains_if() else node.successors[0] else: node = None else: diff --git a/slither/detectors/functions/out_of_order_retryable.py b/slither/detectors/functions/out_of_order_retryable.py index db9096f95f..5c58f0649e 100644 --- a/slither/detectors/functions/out_of_order_retryable.py +++ b/slither/detectors/functions/out_of_order_retryable.py @@ -81,23 +81,23 @@ def _detect_multiple_tickets( visited = visited + [node] - fathers_context = [] + predecessors_context = [] - for father in node.fathers: - if self.key in father.context: - fathers_context += father.context[self.key] + for predecessor in node.predecessors: + if self.key in predecessor.context: + predecessors_context += predecessor.context[self.key] # Exclude path that dont bring further information if node in self.visited_all_paths: - if all(f_c in self.visited_all_paths[node] for f_c in fathers_context): + if all(f_c in self.visited_all_paths[node] for f_c in predecessors_context): return else: self.visited_all_paths[node] = [] - self.visited_all_paths[node] = self.visited_all_paths[node] + fathers_context + self.visited_all_paths[node] = self.visited_all_paths[node] + predecessors_context if self.key not in node.context: - node.context[self.key] = fathers_context + node.context[self.key] = predecessors_context # include ops from internal function calls internal_ops = [] @@ -123,8 +123,8 @@ def _detect_multiple_tickets( self.results.append(node.context[self.key]) return - for son in node.sons: - self._detect_multiple_tickets(function, son, visited) + for successor in node.successors: + self._detect_multiple_tickets(function, successor, visited) def _detect(self) -> List[Output]: results = [] diff --git a/slither/detectors/operations/cache_array_length.py b/slither/detectors/operations/cache_array_length.py index eb4f43fa56..6bacddc522 100644 --- a/slither/detectors/operations/cache_array_length.py +++ b/slither/detectors/operations/cache_array_length.py @@ -141,9 +141,11 @@ def _is_loop_referencing_array_length( ): return True - for son in node.sons: - if son not in visited: - if CacheArrayLength._is_loop_referencing_array_length(son, visited, array, depth): + for successor in node.successors: + if successor not in visited: + if CacheArrayLength._is_loop_referencing_array_length( + successor, visited, array, depth + ): return True return False @@ -157,7 +159,7 @@ def _handle_loops(nodes: List[Node], non_optimal_array_len_usages: List[Node]) - """ for node in nodes: if node.type == NodeType.STARTLOOP: - if_node = node.sons[0] + if_node = node.successors[0] if if_node.type != NodeType.IFLOOP: continue if not isinstance(if_node.expression, BinaryOperation): diff --git a/slither/detectors/operations/missing_zero_address_validation.py b/slither/detectors/operations/missing_zero_address_validation.py index 4feac9d0ce..83202abe37 100644 --- a/slither/detectors/operations/missing_zero_address_validation.py +++ b/slither/detectors/operations/missing_zero_address_validation.py @@ -93,8 +93,8 @@ def _zero_address_validation( return True # Check recursively in all the parent nodes - for father in node.fathers: - if self._zero_address_validation(var, father, explored): + for predecessor in node.predecessors: + if self._zero_address_validation(var, predecessor, explored): return True return False diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index 8dd9aecc05..b6118bc408 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -110,7 +110,7 @@ def events(self) -> Dict[EventCall, Set[Node]]: def merge_fathers( self, node: Node, skip_father: Optional[Node], detector: "Reentrancy" ) -> None: - for father in node.fathers: + for father in node.predecessors: if detector.KEY in father.context: self._send_eth = union_dict( self._send_eth, @@ -249,36 +249,36 @@ def _explore(self, node: Optional[Node], skip_father: Optional[Node] = None) -> if node is None: return - fathers_context = AbstractState() - fathers_context.merge_fathers(node, skip_father, self) + predecessors_context = AbstractState() + predecessors_context.merge_fathers(node, skip_father, self) - # Exclude path that dont bring further information + # Exclude path that don't bring further information if node in self.visited_all_paths: - if self.visited_all_paths[node].does_not_bring_new_info(fathers_context): + if self.visited_all_paths[node].does_not_bring_new_info(predecessors_context): return else: self.visited_all_paths[node] = AbstractState() - self.visited_all_paths[node].add(fathers_context) + self.visited_all_paths[node].add(predecessors_context) - node.context[self.KEY] = fathers_context + node.context[self.KEY] = predecessors_context - contains_call = fathers_context.analyze_node(node, self) - node.context[self.KEY] = fathers_context + contains_call = predecessors_context.analyze_node(node, self) + node.context[self.KEY] = predecessors_context - sons = node.sons + successors = node.successors if contains_call and node.type in [NodeType.IF, NodeType.IFLOOP]: if _filter_if(node): - son = sons[0] - self._explore(son, skip_father=node) - sons = sons[1:] + successor = successors[0] + self._explore(successor, skip_father=node) + successors = successors[1:] else: - son = sons[1] - self._explore(son, skip_father=node) - sons = [sons[0]] + successor = successors[1] + self._explore(successor, skip_father=node) + successors = [successors[0]] - for son in sons: - self._explore(son) + for successor in successors: + self._explore(successor) def detect_reentrancy(self, contract: Contract) -> None: for function in contract.functions_and_modifiers_declared: diff --git a/slither/detectors/statements/calls_in_loop.py b/slither/detectors/statements/calls_in_loop.py index d40d18f599..7e22b7889e 100644 --- a/slither/detectors/statements/calls_in_loop.py +++ b/slither/detectors/statements/calls_in_loop.py @@ -51,8 +51,8 @@ def call_in_loop( assert ir.function call_in_loop(ir.function.entry_point, in_loop_counter, visited, ret) - for son in node.sons: - call_in_loop(son, in_loop_counter, visited, ret) + for successor in node.successors: + call_in_loop(successor, in_loop_counter, visited, ret) class MultipleCallsInLoop(AbstractDetector): diff --git a/slither/detectors/statements/costly_operations_in_loop.py b/slither/detectors/statements/costly_operations_in_loop.py index 53fa126477..44a1acfe96 100644 --- a/slither/detectors/statements/costly_operations_in_loop.py +++ b/slither/detectors/statements/costly_operations_in_loop.py @@ -46,8 +46,8 @@ def costly_operations_in_loop( if isinstance(ir, (InternalCall)) and ir.function: costly_operations_in_loop(ir.function.entry_point, in_loop_counter, visited, ret) - for son in node.sons: - costly_operations_in_loop(son, in_loop_counter, visited, ret) + for successor in node.successors: + costly_operations_in_loop(successor, in_loop_counter, visited, ret) class CostlyOperationsInLoop(AbstractDetector): diff --git a/slither/detectors/statements/delegatecall_in_loop.py b/slither/detectors/statements/delegatecall_in_loop.py index bdcf5dcae8..c4f59a947c 100644 --- a/slither/detectors/statements/delegatecall_in_loop.py +++ b/slither/detectors/statements/delegatecall_in_loop.py @@ -45,8 +45,8 @@ def delegatecall_in_loop( if isinstance(ir, (InternalCall)) and ir.function: delegatecall_in_loop(ir.function.entry_point, in_loop_counter, visited, results) - for son in node.sons: - delegatecall_in_loop(son, in_loop_counter, visited, results) + for successor in node.successors: + delegatecall_in_loop(successor, in_loop_counter, visited, results) class DelegatecallInLoop(AbstractDetector): diff --git a/slither/detectors/statements/divide_before_multiply.py b/slither/detectors/statements/divide_before_multiply.py index 1f6ccd87e1..fcb995e4d9 100644 --- a/slither/detectors/statements/divide_before_multiply.py +++ b/slither/detectors/statements/divide_before_multiply.py @@ -113,8 +113,8 @@ def _explore( if not (is_assert(node) and equality_found): f_results.append(node_results) - for son in node.sons: - to_explore.append(son) + for successor in node.successors: + to_explore.append(successor) def detect_divide_before_multiply( diff --git a/slither/detectors/statements/msg_value_in_loop.py b/slither/detectors/statements/msg_value_in_loop.py index 290447aa8e..92832955df 100644 --- a/slither/detectors/statements/msg_value_in_loop.py +++ b/slither/detectors/statements/msg_value_in_loop.py @@ -59,8 +59,8 @@ def msg_value_in_loop( if isinstance(ir, (InternalCall)): msg_value_in_loop(ir.function.entry_point, in_loop_counter, visited, results) - for son in node.sons: - msg_value_in_loop(son, in_loop_counter, visited, results) + for successor in node.successors: + msg_value_in_loop(successor, in_loop_counter, visited, results) class MsgValueInLoop(AbstractDetector): diff --git a/slither/detectors/statements/write_after_write.py b/slither/detectors/statements/write_after_write.py index 1f11921cb4..37a6b66ff9 100644 --- a/slither/detectors/statements/write_after_write.py +++ b/slither/detectors/statements/write_after_write.py @@ -94,10 +94,10 @@ def _detect_write_after_write( for ir in node.irs: _handle_ir(ir, written, ret) - if len(node.sons) > 1: + if len(node.successors) > 1: written = {} - for son in node.sons: - _detect_write_after_write(son, explored, dict(written), ret) + for successor in node.successors: + _detect_write_after_write(successor, explored, dict(written), ret) class WriteAfterWrite(AbstractDetector): diff --git a/slither/detectors/variables/predeclaration_usage_local.py b/slither/detectors/variables/predeclaration_usage_local.py index 9816dd6e24..888273d141 100644 --- a/slither/detectors/variables/predeclaration_usage_local.py +++ b/slither/detectors/variables/predeclaration_usage_local.py @@ -103,8 +103,8 @@ def detect_predeclared_local_usage( if result not in results: results.append(result) - for son in node.sons: - self.detect_predeclared_local_usage(son, results, already_declared, visited) + for successor in node.successors: + self.detect_predeclared_local_usage(successor, results, already_declared, visited) def detect_predeclared_in_contract( self, contract: Contract diff --git a/slither/detectors/variables/uninitialized_local_variables.py b/slither/detectors/variables/uninitialized_local_variables.py index 0aa5579f88..a82445398c 100644 --- a/slither/detectors/variables/uninitialized_local_variables.py +++ b/slither/detectors/variables/uninitialized_local_variables.py @@ -49,44 +49,49 @@ def _detect_uninitialized( visited = visited + [node] - fathers_context = [] + predecessors_context = [] - for father in node.fathers: - if self.key in father.context: - fathers_context += father.context[self.key] + for predecessor in node.predecessors: + if self.key in predecessor.context: + predecessors_context += predecessor.context[self.key] # Exclude path that dont bring further information if node in self.visited_all_paths: - if all(f_c in self.visited_all_paths[node] for f_c in fathers_context): + if all(f_c in self.visited_all_paths[node] for f_c in predecessors_context): return else: self.visited_all_paths[node] = [] - self.visited_all_paths[node] = list(set(self.visited_all_paths[node] + fathers_context)) + self.visited_all_paths[node] = list( + set(self.visited_all_paths[node] + predecessors_context) + ) # Remove a local variable declared in a for loop header if ( node.type == NodeType.VARIABLE - and len(node.sons) == 1 # Should always be true for a node that has a STARTLOOP son - and node.sons[0].type == NodeType.STARTLOOP + and len(node.successors) + == 1 # Should always be true for a node that has a STARTLOOP successor + and node.successors[0].type == NodeType.STARTLOOP ): - if node.variable_declaration in fathers_context: - fathers_context.remove(node.variable_declaration) + if node.variable_declaration in predecessors_context: + predecessors_context.remove(node.variable_declaration) if self.key in node.context: - fathers_context += node.context[self.key] + predecessors_context += node.context[self.key] variables_read = node.variables_read - for uninitialized_local_variable in fathers_context: + for uninitialized_local_variable in predecessors_context: if uninitialized_local_variable in variables_read: self.results.append((function, uninitialized_local_variable)) # Only save the local variables that are not yet written - uninitialized_local_variables = list(set(fathers_context) - set(node.variables_written)) + uninitialized_local_variables = list( + set(predecessors_context) - set(node.variables_written) + ) node.context[self.key] = uninitialized_local_variables - for son in node.sons: - self._detect_uninitialized(function, son, visited) + for successor in node.successors: + self._detect_uninitialized(function, successor, visited) def _detect(self) -> List[Output]: """Detect uninitialized local variables diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index 9caa5b88fc..bb1327cde2 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -57,35 +57,39 @@ def _detect_uninitialized( visited = visited + [node] - fathers_context = [] + predecessors_context = [] - for father in node.fathers: - if self.key in father.context: - fathers_context += father.context[self.key] + for predecessor in node.predecessors: + if self.key in predecessor.context: + predecessors_context += predecessor.context[self.key] # Exclude paths that dont bring further information if node in self.visited_all_paths: - if all(f_c in self.visited_all_paths[node] for f_c in fathers_context): + if all(f_c in self.visited_all_paths[node] for f_c in predecessors_context): return else: self.visited_all_paths[node] = [] - self.visited_all_paths[node] = list(set(self.visited_all_paths[node] + fathers_context)) + self.visited_all_paths[node] = list( + set(self.visited_all_paths[node] + predecessors_context) + ) if self.key in node.context: - fathers_context += node.context[self.key] + predecessors_context += node.context[self.key] variables_read = node.variables_read - for uninitialized_storage_variable in fathers_context: + for uninitialized_storage_variable in predecessors_context: if uninitialized_storage_variable in variables_read: self.results.append((function, uninitialized_storage_variable)) # Only save the storage variables that are not yet written - uninitialized_storage_variables = list(set(fathers_context) - set(node.variables_written)) + uninitialized_storage_variables = list( + set(predecessors_context) - set(node.variables_written) + ) node.context[self.key] = uninitialized_storage_variables - for son in node.sons: - self._detect_uninitialized(function, son, visited) + for successor in node.successors: + self._detect_uninitialized(function, successor, visited) def _detect(self) -> List[Output]: """Detect uninitialized storage variables diff --git a/slither/slithir/utils/ssa.py b/slither/slithir/utils/ssa.py index 5e5d87325b..6cadba471a 100644 --- a/slither/slithir/utils/ssa.py +++ b/slither/slithir/utils/ssa.py @@ -227,7 +227,7 @@ def generate_ssa_irs( return if node.type in [NodeType.ENDIF, NodeType.ENDLOOP] and any( - not father in visited for father in node.fathers + not predecessor in visited for predecessor in node.predecessors ): return @@ -398,9 +398,9 @@ def is_used_later( for v in node.state_variables_written ): return False - for son in node.sons: - if not son in explored: - to_explore.add(son) + for successor in node.successors: + if not successor in explored: + to_explore.add(successor) return False diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 57f4b26f12..e00371f625 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -660,12 +660,12 @@ def _parse_dowhile(self, do_while_statement: Dict, node: NodeSolc, scope: Scope) link_underlying_nodes(node, node_startDoWhile) # empty block, loop from the start to the condition - if not node_condition.underlying_node.sons: + if not node_condition.underlying_node.successors: link_underlying_nodes(node_startDoWhile, node_condition) else: link_nodes( node_startDoWhile.underlying_node, - node_condition.underlying_node.sons[0], + node_condition.underlying_node.successors[0], ) link_underlying_nodes(statement, node_condition) link_underlying_nodes(node_condition, node_endDoWhile) @@ -1154,7 +1154,7 @@ def _update_reachability(self, node: Node) -> None: # fix point if not current.is_reachable: current.set_is_reachable(True) - worklist.extend(current.sons) + worklist.extend(current.successors) def _parse_cfg(self, cfg: Dict) -> None: @@ -1198,8 +1198,8 @@ def _find_end_loop(self, node: Node, visited: List[Node], counter: int) -> Optio counter += 1 visited = visited + [node] - for son in node.sons: - ret = self._find_end_loop(son, visited, counter) + for successor in node.successors: + ret = self._find_end_loop(successor, visited, counter) if ret: return ret @@ -1215,9 +1215,9 @@ def _find_if_loop(self, node: Node, visited: List[Node], skip_if_loop: int) -> O # and it's the index increment expression if node.type == NodeType.IFLOOP: if skip_if_loop == 0: - for father in node.fathers: - if father.type == NodeType.EXPRESSION: - return father + for predecessor in node.predecessors: + if predecessor.type == NodeType.EXPRESSION: + return predecessor return node # skip_if_loop works as explained above. @@ -1235,8 +1235,8 @@ def _find_if_loop(self, node: Node, visited: List[Node], skip_if_loop: int) -> O skip_if_loop += 1 visited = visited + [node] - for father in node.fathers: - ret = self._find_if_loop(father, visited, skip_if_loop) + for predecessor in node.predecessors: + ret = self._find_if_loop(predecessor, visited, skip_if_loop) if ret: return ret @@ -1253,10 +1253,10 @@ def _fix_break_node(self, node: Node) -> None: if not end_node: raise ParsingError(f"Break in no-loop context {node.function}") - for son in node.sons: - son.remove_father(node) - node.set_sons([end_node]) - end_node.add_father(node) + for successor in node.successors: + successor.remove_predecessor(node) + node.set_successors([end_node]) + end_node.add_predecessor(node) def _fix_continue_node(self, node: Node) -> None: if_loop_node = self._find_if_loop(node, [], 0) @@ -1264,10 +1264,10 @@ def _fix_continue_node(self, node: Node) -> None: if not if_loop_node: raise ParsingError(f"Continue in no-loop context {node.node_id}") - for son in node.sons: - son.remove_father(node) - node.set_sons([if_loop_node]) - if_loop_node.add_father(node) + for successor in node.successors: + successor.remove_predecessor(node) + node.set_successors([if_loop_node]) + if_loop_node.add_predecessor(node) # endregion ################################################################################### @@ -1277,20 +1277,20 @@ def _fix_continue_node(self, node: Node) -> None: ################################################################################### def _fix_try(self, node: Node) -> None: - end_node = next((son for son in node.sons if son.type != NodeType.CATCH), None) + end_node = next((son for son in node.successors if son.type != NodeType.CATCH), None) if end_node: - for son in node.sons: - if son.type == NodeType.CATCH: - self._fix_catch(son, end_node, set()) + for successor in node.successors: + if successor.type == NodeType.CATCH: + self._fix_catch(successor, end_node, set()) def _fix_catch(self, node: Node, end_node: Node, visited: Set[Node]) -> None: - if not node.sons: + if not node.successors: link_nodes(node, end_node) else: - for son in node.sons: - if son != end_node and son not in visited: - visited.add(son) - self._fix_catch(son, end_node, visited) + for successor in node.successors: + if successor != end_node and successor not in visited: + visited.add(successor) + self._fix_catch(successor, end_node, visited) # endregion ################################################################################### @@ -1428,13 +1428,13 @@ def _fix_implicit_return(self, return_params: Dict) -> None: NodeType.RETURN, return_params["src"], self.underlying_function ) for node, node_solc in self._node_to_nodesolc.items(): - if len(node.sons) == 0 and node.type not in [NodeType.RETURN, NodeType.THROW]: + if len(node.successors) == 0 and node.type not in [NodeType.RETURN, NodeType.THROW]: link_underlying_nodes(node_solc, return_node) for _, yul_block in self._node_to_yulobject.items(): for yul_node in yul_block.nodes: node = yul_node.underlying_node - if len(node.sons) == 0 and node.type not in [NodeType.RETURN, NodeType.THROW]: + if len(node.successors) == 0 and node.type not in [NodeType.RETURN, NodeType.THROW]: link_underlying_nodes(yul_node, return_node) if self.is_compact_ast: @@ -1552,9 +1552,9 @@ def _add_return_exp_legacy(self, return_node: NodeSolc, return_params: Dict) -> def _remove_incorrect_edges(self): for node in self._node_to_nodesolc: if node.type in [NodeType.RETURN, NodeType.THROW]: - for son in node.sons: - son.remove_father(node) - node.set_sons([]) + for successor in node.successors: + successor.remove_predecessor(node) + node.set_successors([]) if node.type in [NodeType.BREAK]: self._fix_break_node(node) if node.type in [NodeType.CONTINUE]: @@ -1569,18 +1569,24 @@ def _remove_incorrect_edges(self): if node.type in [NodeType.STARTLOOP]: # can we prune? only if after pruning, we have at least one son that isn't itself if ( - len([son for son in node.sons if son.type != NodeType.ENDLOOP and son != node]) + len( + [ + son + for son in node.successors + if son.type != NodeType.ENDLOOP and son != node + ] + ) == 0 ): continue - new_sons = [] - for son in node.sons: - if son.type != NodeType.ENDLOOP: - new_sons.append(son) + new_successors = [] + for successor in node.successors: + if successor.type != NodeType.ENDLOOP: + new_successors.append(successor) continue - son.remove_father(node) - node.set_sons(new_sons) + successor.remove_predecessor(node) + node.set_successors(new_successors) def _remove_alone_endif(self) -> None: """ @@ -1606,10 +1612,10 @@ def _remove_alone_endif(self) -> None: prev_nodes = self._node_to_nodesolc.keys() to_remove: List[Node] = [] for node in self._node_to_nodesolc: - if node.type == NodeType.ENDIF and not node.fathers: - for son in node.sons: - son.remove_father(node) - node.set_sons([]) + if node.type == NodeType.ENDIF and not node.predecessors: + for successor in node.successors: + successor.remove_predecessor(node) + node.set_successors([]) to_remove.append(node) self._function.nodes = [n for n in self._function.nodes if n not in to_remove] for remove in to_remove: @@ -1679,14 +1685,14 @@ def _split_ternary_node( endif_node = self._new_node(NodeType.ENDIF, node.source_mapping, node.scope) - for father in node.fathers: - father.replace_son(node, condition_node.underlying_node) - condition_node.underlying_node.add_father(father) + for predecessor in node.predecessors: + predecessor.replace_successor(node, condition_node.underlying_node) + condition_node.underlying_node.add_predecessor(predecessor) - for son in node.sons: - son.remove_father(node) - son.add_father(endif_node.underlying_node) - endif_node.underlying_node.add_son(son) + for successor in node.successors: + successor.remove_predecessor(node) + successor.add_predecessor(endif_node.underlying_node) + endif_node.underlying_node.add_successor(successor) link_underlying_nodes(condition_node, true_node_parser) link_underlying_nodes(condition_node, false_node_parser) diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index 728636f2e4..c7162aed3b 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -434,7 +434,7 @@ def find_hardcoded_slot_in_fallback(self, contract: Contract) -> Optional[StateV while len(queue) > 0: node = queue.pop(0) visited.append(node) - queue.extend(son for son in node.sons if son not in visited) + queue.extend(son for son in node.successors if son not in visited) if node.type == NodeType.ASSEMBLY and isinstance(node.inline_asm, str): return SlitherReadStorage.find_hardcoded_slot_in_asm_str(node.inline_asm, contract) if node.type == NodeType.EXPRESSION: diff --git a/slither/utils/code_complexity.py b/slither/utils/code_complexity.py index aa78384999..0c39d37706 100644 --- a/slither/utils/code_complexity.py +++ b/slither/utils/code_complexity.py @@ -16,7 +16,7 @@ def compute_number_edges(function: "Function") -> int: """ n = 0 for node in function.nodes: - n += len(node.sons) + n += len(node.successors) return n @@ -38,8 +38,8 @@ def compute_strongly_connected_components(function: "Function") -> List[List["No def visit(node: "Node") -> None: if not visited[node]: visited[node] = True - for son in node.sons: - visit(son) + for successor in node.successors: + visit(successor) l.append(node) for n in function.nodes: @@ -49,8 +49,8 @@ def assign(node: "Node", root: List["Node"]) -> None: if not assigned[node]: assigned[node] = True root.append(node) - for father in node.fathers: - assign(father, root) + for predecessor in node.predecessors: + assign(predecessor, root) for n in reversed(l): component: List["Node"] = [] diff --git a/slither/utils/upgradeability.py b/slither/utils/upgradeability.py index 22461dbcf6..5746f35db6 100644 --- a/slither/utils/upgradeability.py +++ b/slither/utils/upgradeability.py @@ -333,8 +333,8 @@ def is_function_modified(f1: Function, f2: Function) -> bool: node_f1 = queue_f1.pop(0) node_f2 = queue_f2.pop(0) visited.extend([node_f1, node_f2]) - queue_f1.extend(son for son in node_f1.sons if son not in visited) - queue_f2.extend(son for son in node_f2.sons if son not in visited) + queue_f1.extend(son for son in node_f1.successors if son not in visited) + queue_f2.extend(son for son in node_f2.successors if son not in visited) if len(node_f1.irs) != len(node_f2.irs): return True for i, ir in enumerate(node_f1.irs): diff --git a/slither/vyper_parsing/declarations/function.py b/slither/vyper_parsing/declarations/function.py index ca5f73b5b6..4e49bc331c 100644 --- a/slither/vyper_parsing/declarations/function.py +++ b/slither/vyper_parsing/declarations/function.py @@ -248,8 +248,8 @@ def _update_reachability(self, node: Node) -> None: if node.is_reachable: return node.set_is_reachable(True) - for son in node.sons: - self._update_reachability(son) + for successor in node.successors: + self._update_reachability(successor) # pylint: disable=too-many-branches,too-many-statements,protected-access,too-many-locals def _parse_cfg(self, cfg: List[ASTNode]) -> None: diff --git a/tests/unit/slithir/test_implicit_returns.py b/tests/unit/slithir/test_implicit_returns.py index 551f869185..0fc36a5b01 100644 --- a/tests/unit/slithir/test_implicit_returns.py +++ b/tests/unit/slithir/test_implicit_returns.py @@ -27,16 +27,16 @@ def test_with_explicit_return(slither_from_solidity_source, legacy) -> None: c: Contract = slither.get_contract_from_name("Contract")[0] f: Function = c.functions[0] node_if: Node = f.nodes[1] - node_true = node_if.son_true - node_false = node_if.son_false + node_true = node_if.successor_true + node_false = node_if.successor_false assert node_true.type == NodeType.RETURN assert isinstance(node_true.irs[0], Return) assert node_true.irs[0].values[0] == f.get_local_variable_from_name("x") - assert len(node_true.sons) == 0 - node_end_if = node_false.sons[0] + assert len(node_true.successors) == 0 + node_end_if = node_false.successors[0] assert node_end_if.type == NodeType.ENDIF - assert node_end_if.sons[0].type == NodeType.RETURN - node_ret = node_end_if.sons[0] + assert node_end_if.successors[0].type == NodeType.RETURN + node_ret = node_end_if.successors[0] assert isinstance(node_ret.irs[0], Return) assert node_ret.irs[0].values[0] == f.get_local_variable_from_name("y") @@ -93,19 +93,19 @@ def test_nested_ifs_with_loop_legacy(slither_from_solidity_source) -> None: c: Contract = slither.get_contract_from_name("Contract")[0] f: Function = c.functions[0] node_if = f.nodes[2] - assert node_if.son_true.type == NodeType.RETURN - node_explicit = node_if.son_true + assert node_if.successor_true.type == NodeType.RETURN + node_explicit = node_if.successor_true assert isinstance(node_explicit.irs[0], Return) assert node_explicit.irs[0].values[0] == f.get_local_variable_from_name("a") node_end_if = f.nodes[16] assert node_end_if.type == NodeType.ENDIF - assert node_end_if.sons[0].type == NodeType.RETURN - node_implicit = node_end_if.sons[0] + assert node_end_if.successors[0].type == NodeType.RETURN + node_implicit = node_end_if.successors[0] assert isinstance(node_implicit.irs[0], Return) assert node_implicit.irs[0].values[0] == f.get_local_variable_from_name("x") node_throw = f.nodes[11] assert node_throw.type == NodeType.THROW - assert len(node_throw.sons) == 0 + assert len(node_throw.successors) == 0 def test_nested_ifs_with_loop_compact(slither_from_solidity_source) -> None: @@ -136,14 +136,14 @@ def test_nested_ifs_with_loop_compact(slither_from_solidity_source) -> None: c: Contract = slither.get_contract_from_name("Contract")[0] f: Function = c.functions[0] node_if = f.nodes[2] - assert node_if.son_true.type == NodeType.RETURN - node_explicit = node_if.son_true + assert node_if.successor_true.type == NodeType.RETURN + node_explicit = node_if.successor_true assert isinstance(node_explicit.irs[0], Return) assert node_explicit.irs[0].values[0] == f.get_local_variable_from_name("a") node_end_if = f.nodes[17] assert node_end_if.type == NodeType.ENDIF - assert node_end_if.sons[0].type == NodeType.RETURN - node_implicit = node_end_if.sons[0] + assert node_end_if.successors[0].type == NodeType.RETURN + node_implicit = node_end_if.successors[0] assert isinstance(node_implicit.irs[0], Return) assert node_implicit.irs[0].values[0] == f.get_local_variable_from_name("x") @@ -175,14 +175,14 @@ def test_assembly_switch_cases(slither_from_solidity_source, legacy): assert node.irs[0].values[0] == f.get_local_variable_from_name("x") else: node_end_if = f.nodes[5] - assert node_end_if.sons[0].type == NodeType.RETURN - node_implicit = node_end_if.sons[0] + assert node_end_if.successors[0].type == NodeType.RETURN + node_implicit = node_end_if.successors[0] assert isinstance(node_implicit.irs[0], Return) assert node_implicit.irs[0].values[0] == f.get_local_variable_from_name("x") # This part will fail until issue #1927 is fixed node_explicit = f.nodes[10] assert node_explicit.type == NodeType.RETURN - assert len(node_explicit.sons) == 0 + assert len(node_explicit.successors) == 0 @pytest.mark.parametrize("legacy", [True, False]) @@ -199,8 +199,8 @@ def test_issue_1846_ternary_in_ternary(slither_from_solidity_source, legacy): f = c.functions[0] node_end_if = f.nodes[3] assert node_end_if.type == NodeType.ENDIF - assert len(node_end_if.sons) == 1 - node_ret = node_end_if.sons[0] + assert len(node_end_if.successors) == 1 + node_ret = node_end_if.successors[0] assert node_ret.type == NodeType.RETURN assert isinstance(node_ret.irs[0], Return) assert node_ret.irs[0].values[0] == f.get_local_variable_from_name("y") diff --git a/tests/unit/slithir/test_ssa_generation.py b/tests/unit/slithir/test_ssa_generation.py index 1a709c2f78..464628e483 100644 --- a/tests/unit/slithir/test_ssa_generation.py +++ b/tests/unit/slithir/test_ssa_generation.py @@ -155,7 +155,7 @@ def ssa_phi_node_properties(f: Function) -> None: if isinstance(ssa, Phi): n = len(ssa.read) if not isinstance(ssa.lvalue, StateIRVariable): - assert len(node.fathers) == n + assert len(node.predecessors) == n # TODO (hbrodin): This should probably go into another file, not specific to SSA @@ -168,13 +168,13 @@ def dominance_properties(f: Function) -> None: def find_path(from_node: Node, to: Node) -> bool: visited = set() - worklist = list(from_node.sons) + worklist = list(from_node.successors) while worklist: first, *worklist = worklist if first == to: return True visited.add(first) - for successor in first.sons: + for successor in first.successors: if successor not in visited: worklist.append(successor) return False @@ -1098,8 +1098,8 @@ def test_issue_1846_ternary_in_if(slither_from_solidity_source): f = c.functions[0] node = f.nodes[1] assert node.type == NodeType.IF - assert node.son_true.type == NodeType.IF - assert node.son_false.type == NodeType.EXPRESSION + assert node.successor_true.type == NodeType.IF + assert node.successor_false.type == NodeType.EXPRESSION def test_issue_1846_ternary_in_ternary(slither_from_solidity_source): @@ -1115,8 +1115,8 @@ def test_issue_1846_ternary_in_ternary(slither_from_solidity_source): f = c.functions[0] node = f.nodes[1] assert node.type == NodeType.IF - assert node.son_true.type == NodeType.IF - assert node.son_false.type == NodeType.EXPRESSION + assert node.successor_true.type == NodeType.IF + assert node.successor_false.type == NodeType.EXPRESSION def test_issue_2016(slither_from_solidity_source): diff --git a/tests/unit/slithir/test_ternary_expressions.py b/tests/unit/slithir/test_ternary_expressions.py index e89e54ee97..181c156c55 100644 --- a/tests/unit/slithir/test_ternary_expressions.py +++ b/tests/unit/slithir/test_ternary_expressions.py @@ -24,7 +24,7 @@ def test_ternary_conversions(solc_binary_path) -> None: if node.type in [NodeType.IF, NodeType.IFLOOP]: # Iterate over true and false son - for inner_node in node.sons: + for inner_node in node.successors: # Count all variables declared expression = inner_node.expression if isinstance( @@ -60,11 +60,20 @@ def test_ternary_tuple(solc_binary_path) -> None: assert len(if_nodes) == 1 if_node = if_nodes[0] - assert isinstance(if_node.son_true.expression, AssignmentOperation) + assert isinstance(if_node.successor_true.expression, AssignmentOperation) assert ( - len([ir for ir in if_node.son_true.all_slithir_operations() if isinstance(ir, Unpack)]) == 1 + len( + [ir for ir in if_node.successor_true.all_slithir_operations() if isinstance(ir, Unpack)] + ) + == 1 ) assert ( - len([ir for ir in if_node.son_false.all_slithir_operations() if isinstance(ir, Unpack)]) + len( + [ + ir + for ir in if_node.successor_false.all_slithir_operations() + if isinstance(ir, Unpack) + ] + ) == 1 )