# pylint:disable=missing-class-docstring
from typing import List, Tuple, Any, Optional, Union, OrderedDict as ODict
import claripy
import ailment
import ailment.utils
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", "_case_addrs_str")
[docs] def __init__(self, idx, switch_variable, case_addrs, **kwargs):
super().__init__(idx, **kwargs)
self.switch_variable = switch_variable
# original cmp node, case value | "default", address of the case node, idx of the case node,
# address of the next cmp node
self.case_addrs: List[Tuple[ailment.Block, Union[int, str], int, Optional[int], int]] = case_addrs
# a string representation of the addresses of all cases, used for hashing
self._case_addrs_str = str(sorted([c[0].addr for c in self.case_addrs if c[0] is not None]))
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"
__hash__ = ailment.statement.TaggedObject.__hash__
def _hash_core(self):
return ailment.utils.stable_hash(
(IncompleteSwitchCaseHeadStatement, self.idx, self.switch_variable, self._case_addrs_str)
)