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", ) 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]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
@property def addr(self) -> int: raise NotImplementedError()
[docs]class SequenceNode(BaseNode): __slots__ = ( "addr", "nodes", ) 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", ) 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", ) 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 = "" s += ( indent_str + "if (<block-missing>; %s)\n" + indent_str + "{\n" + indent_str + "%s\n" + indent_str + "}\n" ) % ( self.condition, self.true_node.dbg_repr(indent + 2), ) if self.false_node is not None: s += ( indent_str + "else\n" + indent_str + "{\n" + indent_str + "%s\n" + indent_str + "}" ) % self.false_node.dbg_repr(indent + 2) 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", ) 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", ) 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]class BreakNode(BaseNode): __slots__ = ( "addr", "target", ) def __init__(self, addr, target): self.addr = addr self.target = target
[docs]class ContinueNode(BaseNode): __slots__ = ( "addr", "target", ) def __init__(self, addr, target): self.addr = addr self.target = target
[docs]class ConditionalBreakNode(BreakNode): __slots__ = ("condition",) 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]class SwitchCaseNode(BaseNode): __slots__ = ( "switch_expr", "cases", "default_node", "addr", ) 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") 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", ) 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"