Source code for angr.analyses.decompiler.structuring.structurer_nodes

# pylint:disable=missing-class-docstring
from typing import List, Tuple, Any, Optional, Union, OrderedDict as ODict

import claripy
import ailment


INDENT_DELTA = 2


[docs]class EmptyBlockNotice(Exception): pass
[docs]class MultiNode: __slots__ = ( "nodes", "addr", "idx", )
[docs] def __init__(self, nodes, addr=None, idx=None): # delayed import from ..graph_region import GraphRegion # pylint:disable=import-outside-toplevel self.nodes = [] for node in nodes: if type(node) is MultiNode: self.nodes += node.nodes elif type(node) is GraphRegion: self.nodes += node.nodes else: self.nodes.append(node) self.addr = addr if addr is not None else self.nodes[0].addr self.idx = idx if idx is not None else self.nodes[0].idx if isinstance(self.nodes[0], ailment.Block) else None
[docs] def copy(self): return MultiNode(self.nodes[::], addr=self.addr, idx=self.idx)
def __repr__(self): addrs = [] s = "" for node in self.nodes: if hasattr(node, "addr"): addrs.append(node.addr) s = f": {min(addrs):#x}-{max(addrs):#x}" return "<MultiNode %#x of %d nodes%s>" % (self.addr, len(self.nodes), s) def __hash__(self): # changing self.nodes does not change the hash, which enables in-place editing return hash((MultiNode, self.addr, self.idx)) def __eq__(self, other): return isinstance(other, MultiNode) and self.nodes == other.nodes
[docs] def dbg_repr(self, indent=0): s = "" for node in self.nodes: s += node.dbg_repr(indent=indent + INDENT_DELTA) s += "\n" return s
[docs]class BaseNode: __slots__ = ()
[docs] @staticmethod def test_empty_node(node): # pylint:disable=simplifiable-if-statement if type(node) is ailment.Block: if not node.statements: return True # not empty return False elif type(node) is CodeNode: return BaseNode.test_empty_node(node.node) # unsupported node type. probably not empty? return False
[docs] @staticmethod def test_empty_condition_node(cond_node): for node in [cond_node.true_node, cond_node.false_node]: if node is None: continue if type(node) is CodeNode and BaseNode.test_empty_node(node.node): continue if BaseNode.test_empty_node(node): continue return False return True
addr: Optional[int]
[docs] def dbg_repr(self, indent=0): return " " * indent + f"## dbg_repr not implemented for {type(self).__name__}"
[docs]class SequenceNode(BaseNode): __slots__ = ( "addr", "nodes", )
[docs] def __init__(self, addr: Optional[int], nodes=None): self.addr = addr self.nodes = nodes if nodes is not None else []
def __repr__(self): if self.addr is None: return "<SequenceNode, %d nodes>" % len(self.nodes) else: return "<SequenceNode %#x, %d nodes>" % (self.addr, len(self.nodes))
[docs] def add_node(self, node): self.nodes.append(node)
[docs] def insert_node(self, pos, node): self.nodes.insert(pos, node)
[docs] def remove_node(self, node): self.nodes.remove(node)
[docs] def node_position(self, node): return self.nodes.index(node)
[docs] def copy(self): return SequenceNode(self.addr, nodes=self.nodes[::])
[docs] def dbg_repr(self, indent=0): s = "" for node in self.nodes: s += node.dbg_repr(indent=indent + INDENT_DELTA) s += "\n" return s
[docs]class CodeNode(BaseNode): __slots__ = ( "node", "reaching_condition", )
[docs] def __init__(self, node, reaching_condition): self.node = node self.reaching_condition = reaching_condition
def __repr__(self): if self.addr is not None: if self.idx is not None: return f"<CodeNode {self.addr:#x}.{self.idx}>" return "<CodeNode %#x>" % self.addr else: return "<CodeNode %s>" % repr(self.node) @property def addr(self): if hasattr(self.node, "addr"): return self.node.addr else: return None @property def idx(self): if hasattr(self.node, "idx"): return self.node.idx return None
[docs] def dbg_repr(self, indent=0): indent_str = indent * " " s = "" if self.reaching_condition is not None and not claripy.is_true(self.reaching_condition): s += ( indent_str + "if (<block-missing>; %s)\n" + indent_str + "{\n" + indent_str + " %s\n" + indent_str + "}" ) % (self.reaching_condition, self.node) else: s += indent_str + str(self.node) return s
[docs] def copy(self): return CodeNode(self.node, self.reaching_condition)
[docs]class ConditionNode(BaseNode): __slots__ = ( "addr", "node", "reaching_condition", "condition", "true_node", "false_node", )
[docs] def __init__(self, addr, reaching_condition, condition, true_node, false_node=None): self.addr = addr self.reaching_condition = reaching_condition self.condition = condition self.true_node = true_node self.false_node = false_node
[docs] def dbg_repr(self, indent=0): indent_str = indent * " " s = ( indent_str + f"if (<block-missing>; {self.condition})\n{indent_str}{{\n" f"{self.true_node.dbg_repr(indent + INDENT_DELTA) if self.true_node is not None else ''}{indent_str}}}\n" ) if self.false_node is not None: s += f"{indent_str}else\n{indent_str}{{\n{self.false_node.dbg_repr(indent + INDENT_DELTA)}{indent_str}}}\n" return s
def __repr__(self): if self.addr is not None: return "<ConditionNode %#x>" % self.addr else: return f"<ConditionNode ({self.true_node!r}|{self.false_node!r})>"
[docs]class CascadingConditionNode(BaseNode): __slots__ = ( "addr", "condition_and_nodes", "else_node", )
[docs] def __init__(self, addr, condition_and_nodes: List[Tuple[Any, BaseNode]], else_node: BaseNode = None): self.addr = addr self.condition_and_nodes = condition_and_nodes self.else_node = else_node
[docs]class LoopNode(BaseNode): __slots__ = ( "sort", "condition", "sequence_node", "initializer", "iterator", "_addr", "_continue_addr", )
[docs] def __init__(self, sort, condition, sequence_node, addr=None, continue_addr=None, initializer=None, iterator=None): self.sort = sort self.condition = condition self.sequence_node = sequence_node self.initializer = initializer self.iterator = iterator self._addr = addr self._continue_addr = continue_addr
[docs] def copy(self): return LoopNode( self.sort, self.condition, self.sequence_node, addr=self._addr, initializer=self.initializer, iterator=self.iterator, )
@property def addr(self): if self._addr is None: return self.sequence_node.addr else: return self._addr @property def continue_addr(self): if self._continue_addr is None: return self.addr else: return self._continue_addr @continue_addr.setter def continue_addr(self, value): self._continue_addr = value def __repr__(self): return f"<LoopNode {self.sort}@{self.addr:#x}>"
[docs] def dbg_repr(self, indent=0): # initializer = self.initializer.dbg_repr() if self.initializer is not None else 'None' # iterator = self.iterator.dbg_repr() if self.iterator else 'None' addr = hex(self.addr) if isinstance(self.addr, int) else str(self.addr) continue_addr = hex(self.continue_addr) if isinstance(self.continue_addr, int) else str(self.continue_addr) indent_str = " " * indent return ( f"{indent_str}LoopNode(sort={self.sort}, initializer={self.initializer}, condition={self.condition}, " f"iterator={self.iterator}, addr={addr}, continue_addr={continue_addr}):\n" f"{self.sequence_node.dbg_repr(indent=indent + INDENT_DELTA)}" )
[docs]class BreakNode(BaseNode): __slots__ = ( "addr", "target", )
[docs] def __init__(self, addr, target): self.addr = addr self.target = target
[docs] def dbg_repr(self, indent=0): return " " * indent + "BreakNode"
[docs]class ContinueNode(BaseNode): __slots__ = ( "addr", "target", )
[docs] def __init__(self, addr, target): self.addr = addr self.target = target
[docs] def dbg_repr(self, indent=0): return " " * indent + "ContinueNode"
[docs]class ConditionalBreakNode(BreakNode): __slots__ = ("condition",)
[docs] def __init__(self, addr, condition, target): super().__init__(addr, target) self.condition = condition
def __repr__(self): return f"<ConditionalBreakNode {self.addr:#x} target:{self.target}>"
[docs] def dbg_repr(self, indent=0): return " " * indent + "ConditionalBreakNode(condition={self.condition})"
[docs]class SwitchCaseNode(BaseNode): __slots__ = ( "switch_expr", "cases", "default_node", "addr", )
[docs] def __init__(self, switch_expr, cases: ODict[Union[int, Tuple[int, ...]], SequenceNode], default_node, addr=None): self.switch_expr = switch_expr self.cases: ODict[Union[int, Tuple[int, ...]], SequenceNode] = cases self.default_node = default_node self.addr = addr
[docs]class IncompleteSwitchCaseNode(BaseNode): """ Describes an incomplete set of switch-case nodes. Usually an intermediate result. Should always be restructured into a SwitchCaseNode by the end of structuring. Only used in Phoenix structurer. """ __slots__ = ("addr", "head", "cases")
[docs] def __init__(self, addr, head, cases: List): self.addr = addr self.head = head self.cases: List = cases
# # The following classes are custom AIL statements (not nodes, unfortunately) #
[docs]class IncompleteSwitchCaseHeadStatement(ailment.statement.Statement): """ Describes a switch-case head. This is only created by LoweredSwitchSimplifier. """ __slots__ = ( "addr", "switch_variable", "case_addrs", )
[docs] def __init__(self, idx, switch_variable, case_addrs, **kwargs): super().__init__(idx, **kwargs) self.switch_variable = switch_variable # original cmp node addr, case value | "default", address of the case node self.case_addrs: List[Tuple[ailment.Block, Union[int, str], int, int]] = case_addrs
def __repr__(self): return f"SwitchCaseHead: switch {self.switch_variable} with {len(self.case_addrs)} cases" def __str__(self): return f"switch ({str(self.switch_variable)}): {len(self.case_addrs)} cases"