Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pyvex/stmt.py: 66%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import logging
4from collections.abc import Iterator
5from typing import TYPE_CHECKING
7from . import expr
8from .const import IRConst
9from .enums import IRCallee, IRRegArray, VEXObject, get_enum_from_int, get_int_from_enum
10from .errors import PyVEXError
11from .expr import Const, Get, IRExpr
12from .native import ffi, pvc
14if TYPE_CHECKING:
15 from .block import IRTypeEnv
17log = logging.getLogger("pyvex.stmt")
20class IRStmt(VEXObject):
21 """
22 IR statements in VEX represents operations with side-effects.
23 """
25 tag: str
26 tag_int = 0 # set automatically at bottom of file
28 __slots__ = []
30 def pp(self):
31 print(str(self))
33 @property
34 def child_expressions(self) -> Iterator[IRExpr]:
35 for k in self.__slots__:
36 v = getattr(self, k)
37 if isinstance(v, IRExpr):
38 # return itself
39 yield v
40 # return all the child expressions
41 yield from v.child_expressions
43 # ???
44 @property
45 def expressions(self):
46 return self.child_expressions
48 @property
49 def constants(self):
50 return sum((e.constants for e in self.expressions), [])
52 @staticmethod
53 def _from_c(c_stmt):
54 if c_stmt[0] == ffi.NULL:
55 return None
57 try:
58 stmt_class = enum_to_stmt_class(c_stmt.tag)
59 except KeyError:
60 raise PyVEXError("Unknown/unsupported IRStmtTag %s.\n" % get_enum_from_int(c_stmt.tag))
61 return stmt_class._from_c(c_stmt)
63 def typecheck(self, tyenv: IRTypeEnv) -> bool: # pylint: disable=unused-argument,no-self-use
64 return True
66 def replace_expression(self, replacements):
67 """
68 Replace child expressions in-place.
70 :param Dict[IRExpr, IRExpr] replacements: A mapping from expression-to-find to expression-to-replace-with
71 :return: None
72 """
74 for k in self.__slots__:
75 v = getattr(self, k)
76 if isinstance(v, IRExpr) and v in replacements:
77 setattr(self, k, replacements.get(v))
78 elif isinstance(v, IRExpr):
79 v.replace_expression(replacements)
80 elif type(v) is tuple:
81 # Rebuild the tuple
82 _lst = []
83 replaced = False
84 for expr_ in v:
85 if isinstance(expr_, IRExpr) and expr_ in replacements:
86 _lst.append(replacements.get(expr_))
87 replaced = True
88 else:
89 _lst.append(expr_)
90 if replaced:
91 setattr(self, k, tuple(_lst))
93 def __str__(self):
94 return self.pp_str(None, None, None)
96 def pp_str(self, reg_name=None, arch=None, tyenv=None) -> str:
97 raise NotImplementedError()
100class NoOp(IRStmt):
101 """
102 A no-operation statement. It is usually the result of an IR optimization.
103 """
105 __slots__ = []
107 tag = "Ist_NoOp"
109 def pp_str(self, reg_name=None, arch=None, tyenv=None):
110 return "IR-NoOp"
112 @staticmethod
113 def _from_c(c_stmt):
114 return NoOp()
117class IMark(IRStmt):
118 """
119 An instruction mark. It marks the start of the statements that represent a single machine instruction (the end of
120 those statements is marked by the next IMark or the end of the IRSB). Contains the address and length of the
121 instruction.
122 """
124 __slots__ = ["addr", "len", "delta"]
126 tag = "Ist_IMark"
128 def __init__(self, addr: int, length: int, delta: int):
129 self.addr = addr
130 self.len = length
131 self.delta = delta
133 def pp_str(self, reg_name=None, arch=None, tyenv=None):
134 return "------ IMark(0x%x, %d, %d) ------" % (self.addr, self.len, self.delta)
136 @staticmethod
137 def _from_c(c_stmt):
138 return IMark(c_stmt.Ist.IMark.addr, c_stmt.Ist.IMark.len, c_stmt.Ist.IMark.delta)
141class AbiHint(IRStmt):
142 """
143 An ABI hint, provides specific information about this platform's ABI.
144 """
146 __slots__ = ["base", "len", "nia"]
148 tag = "Ist_AbiHint"
150 def __init__(self, base, length, nia):
151 self.base = base
152 self.len = length
153 self.nia = nia
155 def pp_str(self, reg_name=None, arch=None, tyenv=None):
156 return "====== AbiHint(0x%s, %d, %s) ======" % (self.base, self.len, self.nia)
158 @staticmethod
159 def _from_c(c_stmt):
160 return AbiHint(
161 IRExpr._from_c(c_stmt.Ist.AbiHint.base), c_stmt.Ist.AbiHint.len, IRExpr._from_c(c_stmt.Ist.AbiHint.nia)
162 )
165class Put(IRStmt):
166 """
167 Write to a guest register, at a fixed offset in the guest state.
168 """
170 __slots__ = ["data", "offset"]
172 tag = "Ist_Put"
174 def __init__(self, data: IRExpr, offset: int):
175 self.data = data
176 self.offset = offset
178 ## TODO: Check if result_size and arch are available before looking of arch register name
179 def pp_str(self, reg_name=None, arch=None, tyenv=None):
180 if arch is not None and tyenv is not None:
181 reg_name = arch.translate_register_name(self.offset, self.data.result_size(tyenv) // 8)
183 if reg_name is not None:
184 return f"PUT({reg_name}) = {self.data}"
185 else:
186 return f"PUT(offset={self.offset}) = {self.data}"
188 @staticmethod
189 def _from_c(c_stmt):
190 return Put(IRExpr._from_c(c_stmt.Ist.Put.data), c_stmt.Ist.Put.offset)
192 def typecheck(self, tyenv):
193 return self.data.typecheck(tyenv)
196class PutI(IRStmt):
197 """
198 Write to a guest register, at a non-fixed offset in the guest state.
199 """
201 __slots__ = ["descr", "ix", "data", "bias"]
203 tag = "Ist_PutI"
205 def __init__(self, descr, ix, data, bias):
206 self.descr = descr
207 self.ix = ix
208 self.data = data
209 self.bias = bias
211 def pp_str(self, reg_name=None, arch=None, tyenv=None):
212 return "PutI(%s)[%s,%d] = %s" % (self.descr, self.ix, self.bias, self.data)
214 @staticmethod
215 def _from_c(c_stmt):
216 return PutI(
217 IRRegArray._from_c(c_stmt.Ist.PutI.details.descr),
218 IRExpr._from_c(c_stmt.Ist.PutI.details.ix),
219 IRExpr._from_c(c_stmt.Ist.PutI.details.data),
220 c_stmt.Ist.PutI.details.bias,
221 )
223 def typecheck(self, tyenv):
224 dataty = self.data.typecheck(tyenv)
225 if dataty is None:
226 return False
227 if dataty != self.descr.elemTy:
228 log.debug("Expression doesn't match RegArray type")
229 return False
230 return True
233class WrTmp(IRStmt):
234 """
235 Assign a value to a temporary. Note that SSA rules require each tmp is only assigned to once. IR sanity checking
236 will reject any block containing a temporary which is not assigned to exactly once.
237 """
239 __slots__ = ["data", "tmp"]
241 tag = "Ist_WrTmp"
243 def __init__(self, tmp, data: IRExpr):
244 self.tmp = tmp
245 self.data = data
247 def pp_str(self, reg_name=None, arch=None, tyenv=None):
248 # Support for named register in string representation of expr.Get
250 if arch is not None and tyenv is not None and isinstance(self.data, Get):
251 reg_name = arch.translate_register_name(self.data.offset, self.data.result_size(tyenv) // 8)
253 if reg_name is not None and isinstance(self.data, expr.Get):
254 return "t%d = %s" % (self.tmp, self.data.pp_str_with_name(reg_name))
255 else:
256 return "t%d = %s" % (self.tmp, self.data)
258 @staticmethod
259 def _from_c(c_stmt):
260 return WrTmp(c_stmt.Ist.WrTmp.tmp, IRExpr._from_c(c_stmt.Ist.WrTmp.data))
262 def typecheck(self, tyenv):
263 dataty = self.data.typecheck(tyenv)
264 if dataty is None:
265 return False
266 if dataty != tyenv.lookup(self.tmp):
267 log.debug("Expression doesn't match tmp type")
268 return False
269 return True
272class Store(IRStmt):
273 """
274 Write a value to memory..
275 """
277 __slots__ = ["addr", "data", "end"]
279 tag = "Ist_Store"
281 def __init__(self, addr: IRExpr, data: IRExpr, end: str):
282 self.addr = addr
283 self.data = data
284 self.end = end
286 @property
287 def endness(self):
288 return self.end
290 def pp_str(self, reg_name=None, arch=None, tyenv=None):
291 return f"ST{self.endness[-2:].lower()}({self.addr}) = {self.data}"
293 @staticmethod
294 def _from_c(c_stmt):
295 return Store(
296 IRExpr._from_c(c_stmt.Ist.Store.addr),
297 IRExpr._from_c(c_stmt.Ist.Store.data),
298 get_enum_from_int(c_stmt.Ist.Store.end),
299 )
301 def typecheck(self, tyenv):
302 dataty = self.data.typecheck(tyenv)
303 if dataty is None:
304 return False
305 addrty = self.addr.typecheck(tyenv)
306 if addrty is None:
307 return False
308 if addrty != tyenv.wordty:
309 log.debug("addr must be full word for arch")
310 return False
311 if self.end not in ("Iend_LE", "Iend_BE"):
312 log.debug("invalid endness enum")
313 return False
314 return True
317class CAS(IRStmt):
318 """
319 an atomic compare-and-swap operation.
320 """
322 __slots__ = ["addr", "dataLo", "dataHi", "expdLo", "expdHi", "oldLo", "oldHi", "end"]
324 tag = "Ist_CAS"
326 def __init__(self, addr, dataLo, dataHi, expdLo, expdHi, oldLo, oldHi, end):
327 self.addr = addr
328 self.dataLo = dataLo
329 self.dataHi = dataHi
330 self.expdLo = expdLo
331 self.expdHi = expdHi
332 self.oldLo = oldLo
333 self.oldHi = oldHi
334 self.end = end
336 @property
337 def endness(self):
338 return self.end
340 def pp_str(self, reg_name=None, arch=None, tyenv=None):
341 return "t({},{}) = CAS{}({} :: ({},{})->({},{}))".format(
342 self.oldLo, self.oldHi, self.end[-2:].lower(), self.addr, self.expdLo, self.expdHi, self.dataLo, self.dataHi
343 )
345 @staticmethod
346 def _from_c(c_stmt):
347 return CAS(
348 IRExpr._from_c(c_stmt.Ist.CAS.details.addr),
349 IRExpr._from_c(c_stmt.Ist.CAS.details.dataLo),
350 IRExpr._from_c(c_stmt.Ist.CAS.details.dataHi),
351 IRExpr._from_c(c_stmt.Ist.CAS.details.expdLo),
352 IRExpr._from_c(c_stmt.Ist.CAS.details.expdHi),
353 c_stmt.Ist.CAS.details.oldLo,
354 c_stmt.Ist.CAS.details.oldHi,
355 get_enum_from_int(c_stmt.Ist.CAS.details.end),
356 )
358 def typecheck(self, tyenv):
359 addrty = self.addr.typecheck(tyenv)
360 if addrty is None:
361 return False
362 if addrty != tyenv.wordty:
363 log.debug("addr must be full word for arch")
364 return False
365 if self.end not in ("Iend_LE", "Iend_BE"):
366 log.debug("invalid endness enum")
367 return False
369 if self.oldHi == 0xFFFFFFFF:
370 # single-element case
371 if self.expdHi is not None or self.dataHi is not None:
372 log.debug("expdHi and dataHi must be None")
373 return False
374 expdLoTy = self.expdLo.typecheck(tyenv)
375 dataLoTy = self.dataLo.typecheck(tyenv)
376 if expdLoTy is None or dataLoTy is None:
377 return False
378 if tyenv.lookup(self.oldLo) != expdLoTy or expdLoTy != dataLoTy:
379 log.debug("oldLo, expdL, dataLo must all have the same type")
380 return False
381 else:
382 # double-element case
383 expdLoTy = self.expdLo.typecheck(tyenv)
384 dataLoTy = self.dataLo.typecheck(tyenv)
385 expdHiTy = self.expdHi.typecheck(tyenv)
386 dataHiTy = self.dataHi.typecheck(tyenv)
387 if expdLoTy is None or dataLoTy is None or expdHiTy is None or dataHiTy is None:
388 return False
389 if (
390 tyenv.lookup(self.oldLo) != expdLoTy
391 or expdLoTy != dataLoTy
392 or tyenv.lookup(self.oldHi) != expdHiTy
393 or expdHiTy != dataHiTy
394 or expdLoTy != expdHiTy
395 ):
396 log.debug("oldLo, expdLo, dataLo, oldHi, expdHi, dataHi must all have the same type")
397 return False
399 return True
402class LLSC(IRStmt):
403 """
404 Either Load-Linked or Store-Conditional, depending on STOREDATA. If STOREDATA is NULL then this is a Load-Linked,
405 else it is a Store-Conditional.
406 """
408 __slots__ = ["addr", "storedata", "result", "end"]
410 tag = "Ist_LLSC"
412 def __init__(self, addr: IRExpr, storedata: IRExpr, result: int, end: str):
413 self.addr = addr
414 self.storedata = storedata
415 self.result = result
416 self.end = end
418 @property
419 def endness(self):
420 return self.end
422 def pp_str(self, reg_name=None, arch=None, tyenv=None):
423 if self.storedata is None:
424 return "t%d = LD%s-Linked(%s)" % (self.result, self.end[-2:].lower(), self.addr)
425 else:
426 return "t%d = ( ST%s-Cond(%s) = %s )" % (self.result, self.end[-2:].lower(), self.addr, self.storedata)
428 @staticmethod
429 def _from_c(c_stmt):
430 return LLSC(
431 IRExpr._from_c(c_stmt.Ist.LLSC.addr),
432 IRExpr._from_c(c_stmt.Ist.LLSC.storedata),
433 c_stmt.Ist.LLSC.result,
434 get_enum_from_int(c_stmt.Ist.LLSC.end),
435 )
437 def typecheck(self, tyenv):
438 addrty = self.addr.typecheck(tyenv)
439 if addrty is None:
440 return False
441 if addrty != tyenv.wordty:
442 log.debug("addr must be full word for arch")
443 return False
444 if self.end not in ("Iend_LE", "Iend_BE"):
445 log.debug("invalid endness enum")
446 return False
448 if self.storedata is not None:
449 # load-linked
450 storety = self.storedata.typecheck(tyenv)
451 if storety is None:
452 return False
454 if tyenv.lookup(self.result) != "Ity_I1":
455 log.debug("result tmp must be Ity_I1")
456 return False
458 return True
461class MBE(IRStmt):
462 __slots__ = ["event"]
464 tag = "Ist_MBE"
466 def __init__(self, event):
467 self.event = event
469 def pp_str(self, reg_name=None, arch=None, tyenv=None):
470 return "MBusEvent-" + self.event
472 @staticmethod
473 def _from_c(c_stmt):
474 return MBE(get_enum_from_int(c_stmt.Ist.MBE.event))
477class Dirty(IRStmt):
478 __slots__ = ["cee", "guard", "args", "tmp", "mFx", "mAddr", "mSize", "nFxState"]
480 tag = "Ist_Dirty"
482 def __init__(self, cee, guard, args, tmp, mFx, mAddr, mSize, nFxState):
483 self.cee = cee
484 self.guard = guard
485 self.args = tuple(args)
486 self.tmp = tmp
487 self.mFx = mFx
488 self.mAddr = mAddr
489 self.mSize = mSize
490 self.nFxState = nFxState
492 def pp_str(self, reg_name=None, arch=None, tyenv=None):
493 return "t{} = DIRTY {} {} ::: {}({})".format(
494 self.tmp, self.guard, "TODO(effects)", self.cee, ",".join(str(a) for a in self.args)
495 )
497 @property
498 def child_expressions(self):
499 expressions = sum((a.child_expressions for a in self.args), [])
500 expressions.extend(self.args)
501 expressions.append(self.guard)
502 expressions.extend(self.guard.child_expressions)
503 return expressions
505 @staticmethod
506 def _from_c(c_stmt):
507 args = []
508 for i in range(20):
509 a = c_stmt.Ist.Dirty.details.args[i]
510 if a == ffi.NULL:
511 break
513 args.append(IRExpr._from_c(a))
515 return Dirty(
516 IRCallee._from_c(c_stmt.Ist.Dirty.details.cee),
517 IRExpr._from_c(c_stmt.Ist.Dirty.details.guard),
518 tuple(args),
519 c_stmt.Ist.Dirty.details.tmp,
520 get_enum_from_int(c_stmt.Ist.Dirty.details.mFx),
521 IRExpr._from_c(c_stmt.Ist.Dirty.details.mAddr),
522 c_stmt.Ist.Dirty.details.mSize,
523 c_stmt.Ist.Dirty.details.nFxState,
524 )
527class Exit(IRStmt):
528 """
529 A conditional exit from the middle of an IRSB.
530 """
532 __slots__ = ["guard", "dst", "offsIP", "jk"]
534 tag = "Ist_Exit"
536 def __init__(self, guard: IRExpr, dst: IRConst, jk: str, offsIP: int):
537 self.guard = guard
538 self.dst = dst
539 self.offsIP = offsIP
540 self.jk = jk
542 @property
543 def jumpkind(self):
544 return self.jk
546 def pp_str(self, reg_name=None, arch=None, tyenv=None):
547 if arch is not None and tyenv is not None:
548 reg_name = arch.translate_register_name(self.offsIP, arch.bits // 8)
550 if reg_name is None:
551 return "if (%s) { PUT(offset=%d) = %#x; %s }" % (self.guard, self.offsIP, self.dst.value, self.jumpkind)
552 else:
553 return f"if ({self.guard}) {{ PUT({reg_name}) = {self.dst.value:#x}; {self.jumpkind} }}"
555 @property
556 def child_expressions(self):
557 return [self.guard] + self.guard.child_expressions + [Const(self.dst)]
559 @staticmethod
560 def _from_c(c_stmt):
561 return Exit(
562 IRExpr._from_c(c_stmt.Ist.Exit.guard),
563 IRConst._from_c(c_stmt.Ist.Exit.dst),
564 get_enum_from_int(c_stmt.Ist.Exit.jk),
565 c_stmt.Ist.Exit.offsIP,
566 )
568 def typecheck(self, tyenv):
569 if not self.jk.startswith("Ijk_"):
570 log.debug("Jumpkind is not a jumpkind enum")
571 return False
572 guardty = self.guard.typecheck(tyenv)
573 if guardty is None:
574 return False
575 if guardty != "Ity_I1":
576 log.debug("guard must be Ity_I1")
577 return False
578 return True
581class LoadG(IRStmt):
582 """
583 A guarded load.
584 """
586 __slots__ = ["addr", "alt", "guard", "dst", "cvt", "end", "cvt_types"]
588 tag = "Ist_LoadG"
590 def __init__(self, end: str, cvt: str, dst: int, addr: IRExpr, alt: IRExpr, guard: IRExpr):
591 self.addr = addr
592 self.alt = alt
593 self.guard = guard
594 self.dst = dst
595 self.cvt = cvt
596 self.end = end
598 type_in = ffi.new("IRType *") # TODO separate this from the pyvex C implementation
599 type_out = ffi.new("IRType *")
600 pvc.typeOfIRLoadGOp(get_int_from_enum(self.cvt), type_out, type_in)
601 type_in = ffi.cast("int *", type_in)[0]
602 type_out = ffi.cast("int *", type_out)[0]
603 self.cvt_types = (get_enum_from_int(type_in), get_enum_from_int(type_out))
605 @property
606 def endness(self):
607 return self.end
609 def pp_str(self, reg_name=None, arch=None, tyenv=None):
610 return "t%d = if (%s) %s(LD%s(%s)) else %s" % (
611 self.dst,
612 self.guard,
613 self.cvt,
614 self.end[-2:].lower(),
615 self.addr,
616 self.alt,
617 )
619 @staticmethod
620 def _from_c(c_stmt):
621 return LoadG(
622 get_enum_from_int(c_stmt.Ist.LoadG.details.end),
623 get_enum_from_int(c_stmt.Ist.LoadG.details.cvt),
624 c_stmt.Ist.LoadG.details.dst,
625 IRExpr._from_c(c_stmt.Ist.LoadG.details.addr),
626 IRExpr._from_c(c_stmt.Ist.LoadG.details.alt),
627 IRExpr._from_c(c_stmt.Ist.LoadG.details.guard),
628 )
630 def typecheck(self, tyenv):
631 addrty = self.addr.typecheck(tyenv)
632 if addrty is None:
633 return False
634 if addrty != tyenv.wordty:
635 log.debug("addr must be full word for arch")
636 return False
637 if self.end not in ("Iend_LE", "Iend_BE"):
638 log.debug("invalid endness enum")
639 return False
641 dstty = tyenv.lookup(self.dst)
642 guardty = self.guard.typecheck(tyenv)
643 altty = self.alt.typecheck(tyenv)
645 if guardty is None or altty is None:
646 return False
647 if dstty != "Ity_I32" or altty != "Ity_I32":
648 log.debug("dst and alt must be Ity_I32")
649 return False
650 if guardty != "Ity_I1":
651 log.debug("guard must be Ity_I1")
652 return False
653 if not self.cvt.startswith("ILGop_"):
654 log.debug("Invalid cvt enum")
655 return False
656 return True
659class StoreG(IRStmt):
660 """
661 A guarded store.
662 """
664 __slots__ = ["addr", "data", "guard", "end"]
666 tag = "Ist_StoreG"
668 def __init__(self, end, addr, data, guard):
669 self.addr = addr
670 self.data = data
671 self.guard = guard
672 self.end = end
674 @property
675 def endness(self):
676 return self.end
678 def pp_str(self, reg_name=None, arch=None, tyenv=None):
679 return f"if ({self.guard}) ST{self.end[-2:].lower()}({self.addr}) = {self.data}"
681 @staticmethod
682 def _from_c(c_stmt):
683 return StoreG(
684 get_enum_from_int(c_stmt.Ist.StoreG.details.end),
685 IRExpr._from_c(c_stmt.Ist.StoreG.details.addr),
686 IRExpr._from_c(c_stmt.Ist.StoreG.details.data),
687 IRExpr._from_c(c_stmt.Ist.StoreG.details.guard),
688 )
690 def typecheck(self, tyenv):
691 addrty = self.addr.typecheck(tyenv)
692 if addrty is None:
693 return False
694 if addrty != tyenv.wordty:
695 log.debug("addr must be full word for arch")
696 return False
697 if self.end not in ("Iend_LE", "Iend_BE"):
698 log.debug("invalid endness enum")
699 return False
701 guardty = self.guard.typecheck(tyenv)
702 dataty = self.data.typecheck(tyenv)
704 if guardty is None or dataty is None:
705 return False
706 if guardty != "Ity_I1":
707 log.debug("guard must be Ity_I1")
708 return False
709 return True
712_globals = globals().copy()
713#
714# Mapping from tag strings/enums to IRStmt classes
715#
716tag_to_stmt_mapping = {}
717enum_to_stmt_mapping = {}
718tag_count = 0
719cls = None
720for cls in _globals.values():
721 if type(cls) is type and issubclass(cls, IRStmt) and cls is not IRStmt:
722 tag_to_stmt_mapping[cls.tag] = cls
723 enum_to_stmt_mapping[get_int_from_enum(cls.tag)] = cls
724 cls.tag_int = tag_count
725 tag_count += 1
726del cls
729def tag_to_stmt_class(tag):
730 try:
731 return tag_to_stmt_mapping[tag]
732 except KeyError:
733 raise KeyError("No statement class for tag %s." % tag)
736def enum_to_stmt_class(tag_enum):
737 try:
738 return enum_to_stmt_mapping[tag_enum]
739 except KeyError:
740 raise KeyError("No statement class for tag %s." % get_enum_from_int(tag_enum))