# pylint:disable=arguments-renamed,isinstance-second-argument-not-valid-type,missing-class-docstring
from typing import Optional, List, TYPE_CHECKING
try:
import claripy
except ImportError:
claripy = None
from .tagged_object import TaggedObject
from .utils import get_bits, stable_hash, is_none_or_likeable
if TYPE_CHECKING:
from .statement import Statement
[docs]class Expression(TaggedObject):
"""
The base class of all AIL expressions.
"""
__slots__ = ("depth",)
[docs] def __init__(self, idx, depth, **kwargs):
super().__init__(idx, **kwargs)
self.depth = depth
def __repr__(self):
raise NotImplementedError()
[docs] def has_atom(self, atom, identity=True):
if identity:
return self is atom
else:
return self.likes(atom)
def __eq__(self, other):
if self is other:
return True
return type(self) is type(other) and self.likes(other) and self.idx == other.idx
[docs] def likes(self, atom): # pylint:disable=unused-argument,no-self-use
raise NotImplementedError()
[docs] def replace(self, old_expr, new_expr):
if self is old_expr:
r = True
replaced = new_expr
elif not isinstance(self, Atom):
r, replaced = self.replace(old_expr, new_expr)
else:
r, replaced = False, self
return r, replaced
def __add__(self, other):
return BinaryOp(None, "Add", [self, other], False, **self.tags)
def __sub__(self, other):
return BinaryOp(None, "Sub", [self, other], False, **self.tags)
[docs]class Atom(Expression):
__slots__ = (
"variable",
"variable_offset",
)
[docs] def __init__(self, idx, variable, variable_offset=0, **kwargs):
super().__init__(idx, 0, **kwargs)
self.variable = variable
self.variable_offset = variable_offset
def __repr__(self):
return "Atom (%d)" % self.idx
[docs] def copy(self): # pylint:disable=no-self-use
return NotImplementedError()
[docs]class Const(Atom):
__slots__ = (
"value",
"bits",
)
[docs] def __init__(self, idx, variable, value, bits, **kwargs):
super().__init__(idx, variable, **kwargs)
self.value = value
self.bits = bits
@property
def size(self):
return self.bits // 8
def __repr__(self):
return str(self)
def __str__(self):
if isinstance(self.value, int):
return "%#x<%d>" % (self.value, self.bits)
elif isinstance(self.value, float):
return "%f<%d>" % (self.value, self.bits)
else:
return f"{self.value}<{self.bits}>"
[docs] def likes(self, other):
return type(self) is type(other) and self.value == other.value and self.bits == other.bits
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash((self.value, self.bits))
@property
def sign_bit(self):
return self.value >> (self.bits - 1)
[docs] def copy(self) -> "Const":
return Const(self.idx, self.variable, self.value, self.bits, **self.tags)
[docs]class Tmp(Atom):
__slots__ = (
"tmp_idx",
"bits",
)
[docs] def __init__(self, idx, variable, tmp_idx, bits, **kwargs):
super().__init__(idx, variable, **kwargs)
self.tmp_idx = tmp_idx
self.bits = bits
@property
def size(self):
return self.bits // 8
def __repr__(self):
return str(self)
def __str__(self):
return "t%d" % self.tmp_idx
[docs] def likes(self, other):
return type(self) is type(other) and self.tmp_idx == other.tmp_idx and self.bits == other.bits
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash(("tmp", self.tmp_idx, self.bits))
[docs] def copy(self) -> "Tmp":
return Tmp(self.idx, self.variable, self.tmp_idx, self.bits, **self.tags)
[docs]class Register(Atom):
__slots__ = (
"reg_offset",
"bits",
)
[docs] def __init__(self, idx, variable, reg_offset, bits, **kwargs):
super().__init__(idx, variable, **kwargs)
self.reg_offset = reg_offset
self.bits = bits
@property
def size(self):
return self.bits // 8
[docs] def likes(self, atom):
return type(self) is type(atom) and self.reg_offset == atom.reg_offset and self.bits == atom.bits
def __repr__(self):
return str(self)
def __str__(self):
if hasattr(self, "reg_name"):
return "%s<%d>" % (self.reg_name, self.bits // 8)
if self.variable is None:
return "reg_%d<%d>" % (self.reg_offset, self.bits // 8)
else:
return "%s" % str(self.variable.name)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash(("reg", self.reg_offset, self.bits, self.idx))
[docs] def copy(self) -> "Register":
return Register(self.idx, self.variable, self.reg_offset, self.bits, **self.tags)
[docs]class Op(Expression):
__slots__ = ("op",)
[docs] def __init__(self, idx, depth, op, **kwargs):
super().__init__(idx, depth, **kwargs)
self.op = op
@property
def verbose_op(self):
return self.op
[docs]class UnaryOp(Op):
__slots__ = (
"operand",
"bits",
"variable",
"variable_offset",
)
[docs] def __init__(self, idx, op, operand, variable=None, variable_offset=None, **kwargs):
super().__init__(idx, (operand.depth if isinstance(operand, Expression) else 0) + 1, op, **kwargs)
self.operand = operand
self.bits = operand.bits
self.variable = variable
self.variable_offset = variable_offset
def __str__(self):
return f"({self.op} {str(self.operand)})"
def __repr__(self):
return str(self)
[docs] def likes(self, other):
return (
type(other) is UnaryOp and self.op == other.op and self.bits == other.bits and self.operand == other.operand
)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash((self.op, self.operand, self.bits))
[docs] def replace(self, old_expr, new_expr):
if self.operand == old_expr:
r = True
replaced_operand = new_expr
else:
r, replaced_operand = self.operand.replace(old_expr, new_expr)
if r:
return True, UnaryOp(self.idx, self.op, replaced_operand, **self.tags)
else:
return False, self
@property
def operands(self):
return [self.operand]
@property
def size(self):
return self.bits // 8
[docs] def copy(self) -> "UnaryOp":
return UnaryOp(
self.idx, self.op, self.operand, variable=self.variable, variable_offset=self.variable_offset, **self.tags
)
[docs] def has_atom(self, atom, identity=True):
if super().has_atom(atom, identity=identity):
return True
return self.operand.has_atom(atom, identity=identity)
[docs]class Convert(UnaryOp):
TYPE_INT = 0
TYPE_FP = 1
__slots__ = (
"from_bits",
"to_bits",
"is_signed",
"from_type",
"to_type",
"rounding_mode",
)
[docs] def __init__(
self,
idx,
from_bits,
to_bits,
is_signed,
operand,
from_type=TYPE_INT,
to_type=TYPE_INT,
rounding_mode=None,
**kwargs,
):
super().__init__(idx, "Convert", operand, **kwargs)
self.from_bits = from_bits
self.to_bits = to_bits
# override the size
self.bits = to_bits
self.is_signed = is_signed
self.from_type = from_type
self.to_type = to_type
self.rounding_mode = rounding_mode
def __str__(self):
return "Conv(%d->%s%d, %s)" % (self.from_bits, "s" if self.is_signed else "", self.to_bits, self.operand)
def __repr__(self):
return str(self)
[docs] def likes(self, other):
return (
type(other) is Convert
and self.from_bits == other.from_bits
and self.to_bits == other.to_bits
and self.bits == other.bits
and self.is_signed == other.is_signed
and self.operand.likes(other.operand)
and self.from_type == other.from_type
and self.to_type == other.to_type
and self.rounding_mode == other.rounding_mode
)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash(
(
self.operand,
self.from_bits,
self.to_bits,
self.bits,
self.is_signed,
self.from_type,
self.to_type,
self.rounding_mode,
)
)
[docs] def replace(self, old_expr, new_expr):
if self.operand == old_expr:
r0 = True
replaced_operand = new_expr
else:
r0, replaced_operand = self.operand.replace(old_expr, new_expr)
if self.rounding_mode is not None:
if self.rounding_mode.likes(old_expr):
r1 = True
replaced_rm = new_expr
else:
r1, replaced_rm = self.rounding_mode.replace(old_expr, new_expr)
else:
r1 = False
replaced_rm = None
if r0 or r1:
return True, Convert(
self.idx,
self.from_bits,
self.to_bits,
self.is_signed,
replaced_operand if replaced_operand is not None else self.operand,
from_type=self.from_type,
to_type=self.to_type,
rounding_mode=replaced_rm if replaced_rm is not None else self.rounding_mode,
**self.tags,
)
else:
return False, self
[docs] def copy(self) -> "Convert":
return Convert(
self.idx,
self.from_bits,
self.to_bits,
self.is_signed,
self.operand,
from_type=self.from_type,
to_type=self.to_type,
rounding_mode=self.rounding_mode,
**self.tags,
)
[docs]class Reinterpret(UnaryOp):
__slots__ = (
"from_bits",
"from_type",
"to_bits",
"to_type",
)
[docs] def __init__(self, idx, from_bits: int, from_type: str, to_bits: int, to_type: str, operand, **kwargs):
super().__init__(idx, "Reinterpret", operand, **kwargs)
assert (from_type == "I" and to_type == "F") or (from_type == "F" and to_type == "I")
self.from_bits = from_bits
self.from_type = from_type
self.to_bits = to_bits
self.to_type = to_type
self.bits = self.to_bits
def __str__(self):
return f"Reinterpret({self.from_type}{self.from_bits}->{self.to_type}{self.to_bits}, {self.operand})"
def __repr__(self):
return str(self)
[docs] def likes(self, other):
return (
type(other) is Reinterpret
and self.from_bits == other.from_bits
and self.from_type == other.from_type
and self.to_bits == other.to_bits
and self.to_type == other.to_type
and self.operand == other.operand
)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash(
(
self.operand,
self.from_bits,
self.from_type,
self.to_bits,
self.to_type,
)
)
[docs] def replace(self, old_expr, new_expr):
if self.operand == old_expr:
r = True
replaced_operand = new_expr
else:
r, replaced_operand = self.operand.replace(old_expr, new_expr)
if r:
return True, Reinterpret(
self.idx, self.from_bits, self.from_type, self.to_bits, self.to_type, replaced_operand, **self.tags
)
else:
return False, self
[docs] def copy(self) -> "Reinterpret":
return Reinterpret(
self.idx, self.from_bits, self.from_type, self.to_bits, self.to_type, self.operand, **self.tags
)
[docs]class BinaryOp(Op):
__slots__ = (
"operands",
"bits",
"signed",
"variable",
"variable_offset",
"floating_point",
"rounding_mode",
"from_bits", # for divmod
"to_bits", # for divmod
)
OPSTR_MAP = {
"Add": "+",
"AddF": "+",
"AddV": "+",
"Sub": "-",
"SubF": "-",
"Mul": "*",
"MulF": "*",
"MulV": "*",
"Div": "/",
"DivF": "/",
"DivMod": "/m",
"Mod": "%",
"Xor": "^",
"And": "&",
"LogicalAnd": "&&",
"Or": "|",
"LogicalOr": "||",
"Shl": "<<",
"Shr": ">>",
"Sar": ">>a",
"CmpF": "CmpF",
"CmpEQ": "==",
"CmpNE": "!=",
"CmpLT": "<",
"CmpLE": "<=",
"CmpGT": ">",
"CmpGE": ">=",
"CmpLTs": "<s",
"CmpLEs": "<=s",
"CmpGTs": ">s",
"CmpGEs": ">=s",
"Concat": "CONCAT",
"Ror": "ROR",
"Rol": "ROL",
"Carry": "CARRY",
"SCarry": "SCARRY",
"SBorrow": "SBORROW",
}
COMPARISON_NEGATION = {
"CmpEQ": "CmpNE",
"CmpNE": "CmpEQ",
"CmpLT": "CmpGE",
"CmpGE": "CmpLT",
"CmpLE": "CmpGT",
"CmpGT": "CmpLE",
"CmpLTs": "CmpGEs",
"CmpGEs": "CmpLTs",
"CmpLEs": "CmpGTs",
"CmpGTs": "CmpLEs",
}
[docs] def __init__(
self,
idx,
op,
operands,
signed,
variable=None,
variable_offset=None,
bits=None,
floating_point=False,
rounding_mode=None,
from_bits=None,
to_bits=None,
**kwargs,
):
depth = (
max(
operands[0].depth if isinstance(operands[0], Expression) else 0,
operands[1].depth if isinstance(operands[1], Expression) else 0,
)
+ 1
)
# special handling of initialization with signed op names
if op and op.endswith("s"):
op = op[:-1]
signed = True
super().__init__(idx, depth, op, **kwargs)
assert len(operands) == 2
self.operands = operands
if bits is not None:
self.bits = bits
elif self.op == "CmpF":
self.bits = 32 # floating point comparison
elif self.op in {
"CmpEQ",
"CmpNE",
"CmpLT",
"CmpGE",
"CmpLE",
"CmpGT",
"ExpCmpNE",
}:
self.bits = 1
elif self.op in {"Carry", "SCarry", "SBorrow"}:
self.bits = 8
elif self.op == "Concat":
self.bits = get_bits(operands[0]) + get_bits(operands[1])
elif self.op == "Mull":
self.bits = get_bits(operands[0]) * 2 if not isinstance(operands[0], int) else get_bits(operands[1]) * 2
else:
self.bits = get_bits(operands[0]) if not isinstance(operands[0], int) else get_bits(operands[1])
self.signed = signed
self.variable = variable
self.variable_offset = variable_offset
self.floating_point = floating_point
self.rounding_mode = rounding_mode
self.from_bits = from_bits
self.to_bits = to_bits
# TODO: sanity check of operands' sizes for some ops
# assert self.bits == operands[1].bits
def __str__(self):
op_str = self.OPSTR_MAP.get(self.verbose_op, self.verbose_op)
return f"({str(self.operands[0])} {op_str} {str(self.operands[1])})"
def __repr__(self):
return f"{self.verbose_op}({self.operands[0]}, {self.operands[1]})"
[docs] def likes(self, other):
return (
type(other) is BinaryOp
and self.op == other.op
and self.bits == other.bits
and self.signed == other.signed
and is_none_or_likeable(self.operands, other.operands, is_list=True)
and self.floating_point == other.floating_point
and self.rounding_mode == other.rounding_mode
)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash(
(self.op, tuple(self.operands), self.bits, self.signed, self.floating_point, self.rounding_mode)
)
[docs] def has_atom(self, atom, identity=True):
if super().has_atom(atom, identity=identity):
return True
for op in self.operands:
if identity and op == atom:
return True
if not identity and isinstance(op, Expression) and op.likes(atom):
return True
if isinstance(op, Expression) and op.has_atom(atom, identity=identity):
return True
if self.rounding_mode is not None:
if identity and self.rounding_mode == atom:
return True
if not identity and isinstance(self.rounding_mode, Atom) and self.rounding_mode.likes(atom):
return True
if isinstance(self.rounding_mode, Atom) and self.rounding_mode.has_atom(atom, identity=identity):
return True
return False
[docs] def replace(self, old_expr, new_expr):
if self.operands[0] == old_expr:
r0 = True
replaced_operand_0 = new_expr
elif isinstance(self.operands[0], Expression):
r0, replaced_operand_0 = self.operands[0].replace(old_expr, new_expr)
else:
r0, replaced_operand_0 = False, None
if self.operands[1] == old_expr:
r1 = True
replaced_operand_1 = new_expr
elif isinstance(self.operands[1], Expression):
r1, replaced_operand_1 = self.operands[1].replace(old_expr, new_expr)
else:
r1, replaced_operand_1 = False, None
if self.rounding_mode is not None:
if self.rounding_mode == old_expr:
r2 = True
replaced_rm = new_expr
elif isinstance(self.rounding_mode, Expression):
r2, replaced_rm = self.rounding_mode.replace(old_expr, new_expr)
else:
r2, replaced_rm = False, None
if r0 or r1:
return True, BinaryOp(
self.idx,
self.op,
[replaced_operand_0, replaced_operand_1],
self.signed,
bits=self.bits,
floating_point=self.floating_point,
rounding_mode=self.rounding_mode,
from_bits=self.from_bits,
to_bits=self.to_bits,
**self.tags,
)
else:
return False, self
@property
def verbose_op(self):
op = self.op
if self.floating_point:
op += "F"
else:
if self.signed:
op += "s"
return op
@property
def size(self):
return self.bits // 8
[docs] def copy(self) -> "BinaryOp":
return BinaryOp(
self.idx,
self.op,
self.operands[::],
self.signed,
variable=self.variable,
variable_offset=self.variable_offset,
bits=self.bits,
floating_point=self.floating_point,
rounding_mode=self.rounding_mode,
from_bits=self.from_bits,
to_bits=self.to_bits,
**self.tags,
)
[docs]class TernaryOp(Op):
OPSTR_MAP = {}
__slots__ = (
"operands",
"bits",
)
[docs] def __init__(self, idx, op, operands, bits=None, **kwargs):
depth = (
max(
operands[0].depth if isinstance(operands[0], Expression) else 0,
operands[1].depth if isinstance(operands[1], Expression) else 0,
operands[2].depth if isinstance(operands[1], Expression) else 0,
)
+ 1
)
super().__init__(idx, depth, op, **kwargs)
assert len(operands) == 3
self.operands = operands
self.bits = bits
def __str__(self):
return f"{self.verbose_op}({self.operands[0]}, {self.operands[1]}, {self.operands[2]})"
def __repr__(self):
return f"{self.verbose_op}({self.operands[0]}, {self.operands[1]}, {self.operands[2]})"
[docs] def likes(self, other):
return (
type(other) is TernaryOp
and self.op == other.op
and self.bits == other.bits
and self.operands == other.operands
)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash((self.op, tuple(self.operands), self.bits))
[docs] def has_atom(self, atom, identity=True):
if super().has_atom(atom, identity=identity):
return True
for op in self.operands:
if identity and op == atom:
return True
if not identity and isinstance(op, Atom) and op.likes(atom):
return True
if isinstance(op, Atom) and op.has_atom(atom, identity=identity):
return True
return False
[docs] def replace(self, old_expr, new_expr):
if self.operands[0] == old_expr:
r0 = True
replaced_operand_0 = new_expr
elif isinstance(self.operands[0], Expression):
r0, replaced_operand_0 = self.operands[0].replace(old_expr, new_expr)
else:
r0, replaced_operand_0 = False, None
if self.operands[1] == old_expr:
r1 = True
replaced_operand_1 = new_expr
elif isinstance(self.operands[1], Expression):
r1, replaced_operand_1 = self.operands[1].replace(old_expr, new_expr)
else:
r1, replaced_operand_1 = False, None
if self.operands[2] == old_expr:
r2 = True
replaced_operand_2 = new_expr
elif isinstance(self.operands[2], Expression):
r2, replaced_operand_2 = self.operands[2].replace(old_expr, new_expr)
else:
r2, replaced_operand_2 = False, None
if r0 or r1 or r2:
return True, TernaryOp(
self.idx,
self.op,
[replaced_operand_0, replaced_operand_1, replaced_operand_2],
bits=self.bits,
**self.tags,
)
else:
return False, self
@property
def verbose_op(self):
return self.op
@property
def size(self):
return self.bits // 8
[docs] def copy(self) -> "TernaryOp":
return TernaryOp(self.idx, self.op, self.operands[::], bits=self.bits, **self.tags)
[docs]class Load(Expression):
__slots__ = (
"addr",
"size",
"endness",
"variable",
"variable_offset",
"guard",
"alt",
)
[docs] def __init__(self, idx, addr, size, endness, variable=None, variable_offset=None, guard=None, alt=None, **kwargs):
depth = max(addr.depth, size.depth if isinstance(size, Expression) else 0) + 1
super().__init__(idx, depth, **kwargs)
self.addr = addr
self.size = size
self.endness = endness
self.guard = guard
self.alt = alt
self.variable = variable
self.variable_offset = variable_offset
@property
def bits(self):
return self.size * 8
def __repr__(self):
return str(self)
def __str__(self):
return "Load(addr=%s, size=%d, endness=%s)" % (self.addr, self.size, self.endness)
[docs] def has_atom(self, atom, identity=True):
if super().has_atom(atom, identity=identity):
return True
if claripy is not None and isinstance(self.addr, (int, claripy.ast.Base)):
return False
return self.addr.has_atom(atom, identity=identity)
[docs] def replace(self, old_expr, new_expr):
if self.addr == old_expr:
r = True
replaced_addr = new_expr
else:
r, replaced_addr = self.addr.replace(old_expr, new_expr)
if r:
return True, Load(self.idx, replaced_addr, self.size, self.endness, **self.tags)
else:
return False, self
def _likes_addr(self, other_addr):
if hasattr(self.addr, "likes") and hasattr(other_addr, "likes"):
return self.addr.likes(other_addr)
return self.addr == other_addr
[docs] def likes(self, other):
return (
type(other) is Load
and self._likes_addr(other.addr)
and self.size == other.size
and self.endness == other.endness
and self.guard == other.guard
and self.alt == other.alt
)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash(("Load", self.addr, self.size, self.endness))
[docs] def copy(self) -> "Load":
return Load(
self.idx,
self.addr,
self.size,
self.endness,
variable=self.variable,
variable_offset=self.variable_offset,
guard=self.guard,
alt=self.alt,
**self.tags,
)
[docs]class ITE(Expression):
__slots__ = (
"cond",
"iffalse",
"iftrue",
"bits",
"variable",
"variable_offset",
)
[docs] def __init__(self, idx, cond, iffalse, iftrue, variable=None, variable_offset=None, **kwargs):
depth = (
max(
cond.depth if isinstance(cond, Expression) else 0,
iffalse.depth if isinstance(iffalse, Expression) else 0,
iftrue.depth if isinstance(iftrue, Expression) else 0,
)
+ 1
)
super().__init__(idx, depth, **kwargs)
self.cond = cond
self.iffalse = iffalse
self.iftrue = iftrue
self.bits = iftrue.bits
self.variable = variable
self.variable_offset = variable_offset
def __repr__(self):
return str(self)
def __str__(self):
return f"(({self.cond}) ? ({self.iftrue}) : ({self.iffalse}))"
[docs] def likes(self, atom):
return (
type(atom) is ITE
and self.cond == atom.cond
and self.iffalse == atom.iffalse
and self.iftrue == atom.iftrue
and self.bits == atom.bits
)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash((ITE, self.cond, self.iffalse, self.iftrue, self.bits))
[docs] def has_atom(self, atom, identity=True):
if super().has_atom(atom, identity=identity):
return True
return (
self.cond.has_atom(atom, identity=identity)
or self.iftrue.has_atom(atom, identity=identity)
or self.iffalse.has_atom(atom, identity=identity)
)
[docs] def replace(self, old_expr, new_expr):
if self.cond == old_expr:
cond_replaced = True
new_cond = new_expr
else:
cond_replaced, new_cond = self.cond.replace(old_expr, new_expr)
if self.iffalse == old_expr:
iffalse_replaced = True
new_iffalse = new_expr
else:
iffalse_replaced, new_iffalse = self.iffalse.replace(old_expr, new_expr)
if self.iftrue == old_expr:
iftrue_replaced = True
new_iftrue = new_expr
else:
iftrue_replaced, new_iftrue = self.iftrue.replace(old_expr, new_expr)
replaced = cond_replaced or iftrue_replaced or iffalse_replaced
if replaced:
return True, ITE(self.idx, new_cond, new_iffalse, new_iftrue, **self.tags)
else:
return False, self
@property
def size(self):
return self.bits // 8
[docs] def copy(self) -> "ITE":
return ITE(self.idx, self.cond, self.iffalse, self.iftrue, **self.tags)
[docs]class DirtyExpression(Expression):
__slots__ = (
"dirty_expr",
"bits",
)
[docs] def __init__(self, idx, dirty_expr, bits=None, **kwargs):
super().__init__(idx, 1, **kwargs)
self.dirty_expr = dirty_expr
self.bits = bits
[docs] def likes(self, other):
return type(other) is DirtyExpression and other.dirty_expr == self.dirty_expr
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash((DirtyExpression, self.dirty_expr))
def __repr__(self):
return "DirtyExpression (%s)" % type(self.dirty_expr)
def __str__(self):
return "[D] %s" % str(self.dirty_expr)
[docs] def copy(self) -> "DirtyExpression":
return DirtyExpression(self.idx, self.dirty_expr, bits=self.bits, **self.tags)
[docs] def replace(self, old_expr, new_expr):
if old_expr is self.dirty_expr:
return True, DirtyExpression(self.idx, new_expr, bits=self.bits, **self.tags)
if isinstance(self.dirty_expr, Expression):
replaced, new_dirty_expr = self.dirty_expr.replace(old_expr, new_expr)
else:
replaced = False
new_dirty_expr = None
if replaced:
return True, DirtyExpression(self.idx, new_dirty_expr, bits=self.bits, **self.tags)
else:
return False, self
@property
def size(self):
return self.bits // 8
[docs]class VEXCCallExpression(Expression):
__slots__ = (
"cee_name",
"operands",
"bits",
)
[docs] def __init__(self, idx, cee_name, operands, bits=None, **kwargs):
super().__init__(idx, max(operand.depth for operand in operands), **kwargs)
self.cee_name = cee_name
self.operands = operands
self.bits = bits
[docs] def likes(self, other):
return (
type(other) is VEXCCallExpression
and other.cee_name == self.cee_name
and len(self.operands) == len(other.operands)
and self.bits == other.bits
and all(op1.likes(op2) for op1, op2 in zip(other.operands, self.operands))
)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash((VEXCCallExpression, self.cee_name, self.bits, tuple(self.operands)))
def __repr__(self):
return f"VEXCCallExpression [{self.cee_name}]"
def __str__(self):
operands_str = ", ".join(repr(op) for op in self.operands)
return f"{self.cee_name}({operands_str})"
[docs] def copy(self) -> "VEXCCallExpression":
return VEXCCallExpression(self.idx, self.cee_name, self.operands, bits=self.bits, **self.tags)
[docs] def replace(self, old_expr, new_expr):
new_operands = []
replaced = False
for operand in self.operands:
if operand is old_expr:
new_operands.append(new_expr)
replaced = True
else:
operand_replaced, new_operand = operand.replace(old_expr, new_expr)
if operand_replaced:
new_operands.append(new_operand)
replaced = True
else:
new_operands.append(operand)
if replaced:
return True, VEXCCallExpression(self.idx, self.cee_name, tuple(new_operands), bits=self.bits, **self.tags)
else:
return False, self
@property
def size(self):
return self.bits // 8
[docs]class MultiStatementExpression(Expression):
"""
For representing comma-separated statements and expression in C.
"""
__slots__ = (
"stmts",
"expr",
)
[docs] def __init__(self, idx: Optional[int], stmts: List["Statement"], expr: Expression, **kwargs):
super().__init__(idx, expr.depth + 1, **kwargs)
self.stmts = stmts
self.expr = expr
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash((MultiStatementExpression,) + tuple(self.stmts) + (self.expr,))
[docs] def likes(self, other):
return type(self) is type(other) and self.stmts == other.stmts and self.expr == other.expr
def __repr__(self):
return f"MultiStatementExpression({self.stmts}, {self.expr})"
def __str__(self):
stmts_str = [str(stmt) for stmt in self.stmts]
expr_str = str(self.expr)
concatenated_str = ", ".join(stmts_str + [expr_str])
return f"({concatenated_str})"
@property
def bits(self):
return self.expr.bits
@property
def size(self):
return self.expr.size
[docs] def replace(self, old_expr, new_expr):
replaced = False
new_stmts = []
for stmt in self.stmts:
r, new_stmt = stmt.replace(old_expr, new_expr)
new_stmts.append(new_stmt if new_stmt is not None else stmt)
replaced |= r
if self.expr is old_expr:
replaced = True
new_expr_ = new_expr
else:
r, new_expr_ = self.expr.replace(old_expr, new_expr)
replaced |= r
if replaced:
return True, MultiStatementExpression(
self.idx, new_stmts, new_expr_ if new_expr_ is not None else self.expr, **self.tags
)
return False, self
[docs] def copy(self) -> "MultiStatementExpression":
return MultiStatementExpression(self.idx, self.stmts[::], self.expr, **self.tags)
#
# Special (Dummy) expressions
#
[docs]class BasePointerOffset(Expression):
__slots__ = (
"bits",
"base",
"offset",
"variable",
"variable_offset",
)
[docs] def __init__(self, idx, bits, base, offset, variable=None, variable_offset=None, **kwargs):
super().__init__(idx, (offset.depth if isinstance(offset, Expression) else 0) + 1, **kwargs)
self.bits = bits
self.base = base
self.offset = offset
self.variable = variable
self.variable_offset = variable_offset
@property
def size(self):
return self.bits // 8
def __repr__(self):
if self.offset is None:
return "BaseOffset(%s)" % self.base
if isinstance(self.offset, int):
return "BaseOffset(%s, %d)" % (self.base, self.offset)
return f"BaseOffset({self.base}, {self.offset})"
def __str__(self):
if self.offset is None:
return str(self.base)
if isinstance(self.offset, int):
return "%s%+d" % (self.base, self.offset)
return f"{self.base}+{self.offset}"
[docs] def likes(self, other):
return (
type(other) is type(self)
and self.bits == other.bits
and self.base == other.base
and self.offset == other.offset
)
__hash__ = TaggedObject.__hash__
def _hash_core(self):
return stable_hash((self.bits, self.base, self.offset))
[docs] def replace(self, old_expr, new_expr):
if isinstance(self.base, Expression):
base_replaced, new_base = self.base.replace(old_expr, new_expr)
else:
base_replaced, new_base = False, self.base
if isinstance(self.offset, Expression):
offset_replaced, new_offset = self.offset.replace(old_expr, new_expr)
else:
offset_replaced, new_offset = False, self.offset
if base_replaced or offset_replaced:
return True, BasePointerOffset(self.idx, self.bits, new_base, new_offset, **self.tags)
return False, self
[docs] def copy(self) -> "BasePointerOffset":
return BasePointerOffset(self.idx, self.bits, self.base, self.offset, **self.tags)
[docs]class StackBaseOffset(BasePointerOffset):
__slots__ = ()
[docs] def __init__(self, idx, bits, offset, **kwargs):
# stack base offset is always signed
if offset >= (1 << (bits - 1)):
offset -= 1 << bits
super().__init__(idx, bits, "stack_base", offset, **kwargs)
[docs] def copy(self) -> "StackBaseOffset":
return StackBaseOffset(self.idx, self.bits, self.offset, **self.tags)
[docs]def negate(expr: Expression) -> Expression:
if isinstance(expr, UnaryOp) and expr.op == "Not":
# unpack
return expr.operand
if isinstance(expr, BinaryOp) and expr.op in BinaryOp.COMPARISON_NEGATION:
return BinaryOp(
expr.idx,
BinaryOp.COMPARISON_NEGATION[expr.op],
expr.operands,
expr.signed,
bits=expr.bits,
floating_point=expr.floating_point,
rounding_mode=expr.rounding_mode,
**expr.tags,
)
return UnaryOp(None, "Not", expr, **expr.tags)