angr 9.2.123__py3-none-manylinux2014_aarch64.whl → 9.2.125__py3-none-manylinux2014_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of angr might be problematic. Click here for more details.

Files changed (103) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/__init__.py +9 -1
  3. angr/analyses/cfg/indirect_jump_resolvers/mips_elf_fast.py +11 -8
  4. angr/analyses/cfg/indirect_jump_resolvers/mips_elf_got.py +2 -2
  5. angr/analyses/codecave.py +77 -0
  6. angr/analyses/decompiler/ail_simplifier.py +16 -19
  7. angr/analyses/decompiler/callsite_maker.py +8 -7
  8. angr/analyses/decompiler/ccall_rewriters/amd64_ccalls.py +24 -2
  9. angr/analyses/decompiler/clinic.py +58 -2
  10. angr/analyses/decompiler/condition_processor.py +10 -3
  11. angr/analyses/decompiler/decompilation_cache.py +2 -0
  12. angr/analyses/decompiler/decompiler.py +54 -8
  13. angr/analyses/decompiler/dephication/graph_vvar_mapping.py +10 -2
  14. angr/analyses/decompiler/dephication/rewriting_engine.py +64 -1
  15. angr/analyses/decompiler/expression_narrower.py +5 -1
  16. angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
  17. angr/analyses/decompiler/optimization_passes/div_simplifier.py +4 -1
  18. angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +13 -0
  19. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +23 -4
  20. angr/analyses/decompiler/optimization_passes/optimization_pass.py +3 -1
  21. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +8 -5
  22. angr/analyses/decompiler/optimization_passes/return_duplicator_high.py +10 -5
  23. angr/analyses/decompiler/optimization_passes/return_duplicator_low.py +18 -7
  24. angr/analyses/decompiler/optimization_passes/switch_default_case_duplicator.py +6 -0
  25. angr/analyses/decompiler/optimization_passes/tag_slicer.py +41 -0
  26. angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +2 -0
  27. angr/analyses/decompiler/peephole_optimizations/const_mull_a_shift.py +2 -0
  28. angr/analyses/decompiler/peephole_optimizations/constant_derefs.py +2 -2
  29. angr/analyses/decompiler/peephole_optimizations/remove_cascading_conversions.py +8 -2
  30. angr/analyses/decompiler/region_identifier.py +36 -0
  31. angr/analyses/decompiler/region_simplifiers/loop.py +2 -8
  32. angr/analyses/decompiler/region_simplifiers/switch_cluster_simplifier.py +9 -3
  33. angr/analyses/decompiler/ssailification/rewriting.py +5 -2
  34. angr/analyses/decompiler/ssailification/rewriting_engine.py +151 -25
  35. angr/analyses/decompiler/ssailification/rewriting_state.py +1 -0
  36. angr/analyses/decompiler/ssailification/ssailification.py +17 -9
  37. angr/analyses/decompiler/ssailification/traversal.py +3 -1
  38. angr/analyses/decompiler/ssailification/traversal_engine.py +35 -8
  39. angr/analyses/decompiler/ssailification/traversal_state.py +1 -0
  40. angr/analyses/decompiler/structured_codegen/c.py +42 -4
  41. angr/analyses/decompiler/structuring/phoenix.py +3 -0
  42. angr/analyses/patchfinder.py +137 -0
  43. angr/analyses/pathfinder.py +282 -0
  44. angr/analyses/propagator/engine_ail.py +10 -3
  45. angr/analyses/reaching_definitions/engine_ail.py +10 -15
  46. angr/analyses/s_propagator.py +16 -9
  47. angr/analyses/s_reaching_definitions/s_rda_view.py +127 -63
  48. angr/analyses/smc.py +159 -0
  49. angr/analyses/variable_recovery/engine_ail.py +14 -0
  50. angr/analyses/variable_recovery/engine_base.py +11 -0
  51. angr/angrdb/models.py +1 -2
  52. angr/engines/light/engine.py +12 -0
  53. angr/engines/vex/heavy/heavy.py +2 -0
  54. angr/exploration_techniques/spiller_db.py +1 -2
  55. angr/knowledge_plugins/__init__.py +2 -0
  56. angr/knowledge_plugins/decompilation.py +45 -0
  57. angr/knowledge_plugins/functions/function.py +4 -0
  58. angr/knowledge_plugins/functions/function_manager.py +18 -9
  59. angr/knowledge_plugins/functions/function_parser.py +1 -1
  60. angr/knowledge_plugins/functions/soot_function.py +1 -0
  61. angr/knowledge_plugins/key_definitions/atoms.py +8 -0
  62. angr/misc/ux.py +2 -2
  63. angr/procedures/definitions/parse_win32json.py +2 -1
  64. angr/project.py +17 -1
  65. angr/state_plugins/history.py +6 -4
  66. angr/storage/memory_mixins/actions_mixin.py +7 -7
  67. angr/storage/memory_mixins/address_concretization_mixin.py +5 -5
  68. angr/storage/memory_mixins/bvv_conversion_mixin.py +1 -1
  69. angr/storage/memory_mixins/clouseau_mixin.py +3 -3
  70. angr/storage/memory_mixins/conditional_store_mixin.py +3 -3
  71. angr/storage/memory_mixins/default_filler_mixin.py +3 -3
  72. angr/storage/memory_mixins/memory_mixin.py +45 -34
  73. angr/storage/memory_mixins/paged_memory/page_backer_mixins.py +15 -14
  74. angr/storage/memory_mixins/paged_memory/paged_memory_mixin.py +27 -16
  75. angr/storage/memory_mixins/paged_memory/pages/cooperation.py +18 -9
  76. angr/storage/memory_mixins/paged_memory/pages/ispo_mixin.py +5 -5
  77. angr/storage/memory_mixins/paged_memory/pages/multi_values.py +89 -55
  78. angr/storage/memory_mixins/paged_memory/pages/mv_list_page.py +16 -25
  79. angr/storage/memory_mixins/paged_memory/pages/permissions_mixin.py +11 -9
  80. angr/storage/memory_mixins/paged_memory/pages/ultra_page.py +23 -7
  81. angr/storage/memory_mixins/paged_memory/privileged_mixin.py +1 -1
  82. angr/storage/memory_mixins/regioned_memory/region_meta_mixin.py +9 -7
  83. angr/storage/memory_mixins/regioned_memory/regioned_memory_mixin.py +9 -9
  84. angr/storage/memory_mixins/regioned_memory/static_find_mixin.py +1 -0
  85. angr/storage/memory_mixins/simple_interface_mixin.py +2 -2
  86. angr/storage/memory_mixins/simplification_mixin.py +2 -2
  87. angr/storage/memory_mixins/size_resolution_mixin.py +1 -1
  88. angr/storage/memory_mixins/slotted_memory.py +3 -3
  89. angr/storage/memory_mixins/smart_find_mixin.py +1 -0
  90. angr/storage/memory_mixins/underconstrained_mixin.py +5 -5
  91. angr/storage/memory_mixins/unwrapper_mixin.py +4 -4
  92. angr/storage/memory_object.py +4 -3
  93. angr/utils/bits.py +4 -0
  94. angr/utils/constants.py +1 -1
  95. angr/utils/graph.py +15 -0
  96. angr/utils/tagged_interval_map.py +112 -0
  97. angr/vaults.py +2 -2
  98. {angr-9.2.123.dist-info → angr-9.2.125.dist-info}/METADATA +6 -6
  99. {angr-9.2.123.dist-info → angr-9.2.125.dist-info}/RECORD +103 -96
  100. {angr-9.2.123.dist-info → angr-9.2.125.dist-info}/WHEEL +1 -1
  101. {angr-9.2.123.dist-info → angr-9.2.125.dist-info}/LICENSE +0 -0
  102. {angr-9.2.123.dist-info → angr-9.2.125.dist-info}/entry_points.txt +0 -0
  103. {angr-9.2.123.dist-info → angr-9.2.125.dist-info}/top_level.txt +0 -0
@@ -7,8 +7,6 @@ import logging
7
7
  import claripy
8
8
  import cle
9
9
 
10
- from .paged_memory_mixin import PagedMemoryMixin
11
-
12
10
  l = logging.getLogger(__name__)
13
11
 
14
12
  BackerType = Union[bytes, bytearray, list[int]]
@@ -29,6 +27,9 @@ class NotMemoryview:
29
27
  memoryview(self.obj)[self.offset : self.offset + self.size][k] = v
30
28
 
31
29
 
30
+ from .paged_memory_mixin import PagedMemoryMixin
31
+
32
+
32
33
  class ClemoryBackerMixin(PagedMemoryMixin):
33
34
  def __init__(self, cle_memory_backer: None | cle.Loader | cle.Clemory = None, **kwargs):
34
35
  super().__init__(**kwargs)
@@ -49,9 +50,9 @@ class ClemoryBackerMixin(PagedMemoryMixin):
49
50
  o._cle_loader = self._cle_loader
50
51
  return o
51
52
 
52
- def _initialize_page(self, pageno, force_default=False, **kwargs):
53
+ def _initialize_page(self, pageno, permissions=None, *, force_default=False, **kwargs):
53
54
  if self._clemory_backer is None or force_default:
54
- return super()._initialize_page(pageno, **kwargs)
55
+ return super()._initialize_page(pageno, permissions=permissions, **kwargs)
55
56
 
56
57
  addr = pageno * self.page_size
57
58
 
@@ -59,10 +60,10 @@ class ClemoryBackerMixin(PagedMemoryMixin):
59
60
  backer_iter: BackerIterType = self._clemory_backer.backers(addr)
60
61
  backer_start, backer = next(backer_iter)
61
62
  except StopIteration:
62
- return super()._initialize_page(pageno, **kwargs)
63
+ return super()._initialize_page(pageno, permissions=permissions, **kwargs)
63
64
 
64
65
  if backer_start >= addr + self.page_size:
65
- return super()._initialize_page(pageno, **kwargs)
66
+ return super()._initialize_page(pageno, permissions=permissions, **kwargs)
66
67
 
67
68
  # Load data from backere
68
69
  data = self._data_from_backer(addr, backer, backer_start, backer_iter)
@@ -184,9 +185,9 @@ class ClemoryBackerMixin(PagedMemoryMixin):
184
185
 
185
186
 
186
187
  class ConcreteBackerMixin(ClemoryBackerMixin):
187
- def _initialize_page(self, pageno, force_default=False, **kwargs):
188
+ def _initialize_page(self, pageno, permissions=None, *, force_default=False, **kwargs):
188
189
  if self._clemory_backer is None or force_default:
189
- return super()._initialize_page(pageno, **kwargs)
190
+ return super()._initialize_page(pageno, permissions=permissions, **kwargs)
190
191
 
191
192
  addr = pageno * self.page_size
192
193
 
@@ -194,10 +195,10 @@ class ConcreteBackerMixin(ClemoryBackerMixin):
194
195
  backer_iter = self._clemory_backer.backers(addr)
195
196
  backer_start, backer = next(backer_iter)
196
197
  except StopIteration:
197
- return super()._initialize_page(pageno, **kwargs)
198
+ return super()._initialize_page(pageno, permissions=permissions, **kwargs)
198
199
 
199
200
  if backer_start >= addr + self.page_size:
200
- return super()._initialize_page(pageno, **kwargs)
201
+ return super()._initialize_page(pageno, permissions=permissions, **kwargs)
201
202
 
202
203
  if self.state.project.concrete_target:
203
204
  l.debug("Fetching data from concrete target")
@@ -207,7 +208,7 @@ class ConcreteBackerMixin(ClemoryBackerMixin):
207
208
  )
208
209
  else:
209
210
  # the concrete backer only is here to support concrete loading, defer back to the CleMemoryBacker
210
- return super()._initialize_page(pageno, **kwargs)
211
+ return super()._initialize_page(pageno, permissions=permissions, **kwargs)
211
212
 
212
213
  permissions = self._cle_permissions_lookup(addr)
213
214
 
@@ -237,11 +238,11 @@ class DictBackerMixin(PagedMemoryMixin):
237
238
  o._dict_memory_backer = self._dict_memory_backer
238
239
  return o
239
240
 
240
- def _initialize_page(self, pageno: int, force_default=False, **kwargs):
241
+ def _initialize_page(self, pageno: int, permissions=None, *, force_default=False, **kwargs):
241
242
  page_addr = pageno * self.page_size
242
243
 
243
244
  if self._dict_memory_backer is None or force_default:
244
- return super()._initialize_page(pageno, **kwargs)
245
+ return super()._initialize_page(pageno, permissions=permissions, **kwargs)
245
246
 
246
247
  new_page = None
247
248
 
@@ -261,6 +262,6 @@ class DictBackerMixin(PagedMemoryMixin):
261
262
  )
262
263
 
263
264
  if new_page is None:
264
- return super()._initialize_page(pageno, **kwargs)
265
+ return super()._initialize_page(pageno, permissions=permissions, **kwargs)
265
266
 
266
267
  return new_page
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
  import cffi
3
- from typing import Any
3
+ from typing import Any, Generic, Literal, overload
4
4
  from collections.abc import Iterable
5
5
  import logging
6
6
  from collections import defaultdict
7
7
 
8
8
  import claripy
9
9
 
10
+ from angr.state_plugins.sim_action_object import SimActionObject
10
11
  from angr.storage.memory_mixins.memory_mixin import MemoryMixin
11
12
  from angr.storage.memory_mixins.paged_memory.pages import PageType, ListPage, UltraPage, MVListPage
12
13
  from angr.errors import SimMemoryError
@@ -17,14 +18,17 @@ ffi = cffi.FFI()
17
18
  l = logging.getLogger(__name__)
18
19
 
19
20
 
20
- class PagedMemoryMixin(MemoryMixin):
21
+ class PagedMemoryMixin(
22
+ Generic[PageType],
23
+ MemoryMixin[int | claripy.ast.BV | SimActionObject, claripy.ast.BV, int | claripy.ast.BV | SimActionObject],
24
+ ):
21
25
  """
22
26
  A bottom-level storage mechanism. Dispatches reads to individual pages, the type of which is the PAGE_TYPE class
23
27
  variable.
24
28
  """
25
29
 
26
30
  SUPPORTS_CONCRETE_LOAD = True
27
- PAGE_TYPE: type[PageType] = None # must be provided in subclass
31
+ PAGE_TYPE: type[PageType] # must be provided in subclass
28
32
 
29
33
  def __init__(self, page_size=0x1000, default_permissions=3, permissions_map=None, page_kwargs=None, **kwargs):
30
34
  super().__init__(**kwargs)
@@ -84,7 +88,7 @@ class PagedMemoryMixin(MemoryMixin):
84
88
  kwargs["allow_default"] = True
85
89
  return PagedMemoryMixin._initialize_page(self, pageno, permissions=permissions, **kwargs)
86
90
 
87
- def _initialize_page(self, pageno: int, permissions=None, allow_default=True, **kwargs) -> PageType:
91
+ def _initialize_page(self, pageno: int, permissions=None, *, allow_default=True, **kwargs) -> PageType:
88
92
  if not allow_default:
89
93
  raise SimMemoryError("I have been instructed not to create a default page")
90
94
 
@@ -110,7 +114,7 @@ class PagedMemoryMixin(MemoryMixin):
110
114
  def _divide_addr(self, addr: int) -> tuple[int, int]:
111
115
  return divmod(addr, self.page_size)
112
116
 
113
- def load(self, addr: int, size: int | None = None, endness=None, **kwargs):
117
+ def load(self, addr: int, size: int | None = None, *, endness=None, **kwargs):
114
118
  if endness is None:
115
119
  endness = self.endness
116
120
 
@@ -164,7 +168,7 @@ class PagedMemoryMixin(MemoryMixin):
164
168
  l.debug("%s.load(%#x, %d, %s) = %s", self.id, addr, size, endness, out)
165
169
  return out
166
170
 
167
- def store(self, addr: int, data, size: int | None = None, endness=None, **kwargs):
171
+ def store(self, addr: int, data, size: int | None = None, *, endness=None, **kwargs):
168
172
  if endness is None:
169
173
  endness = self.endness
170
174
 
@@ -253,7 +257,7 @@ class PagedMemoryMixin(MemoryMixin):
253
257
  pageno = (pageno + 1) % max_pageno
254
258
  pageoff = 0
255
259
 
256
- def merge(self, others: Iterable[PagedMemoryMixin], merge_conditions, common_ancestor=None) -> bool:
260
+ def merge(self, others, merge_conditions, common_ancestor=None):
257
261
  changed_pages_and_offsets: dict[int, set[int] | None] = {}
258
262
  for o in others:
259
263
  for changed_page, changed_offsets in self.changed_pages(o).items():
@@ -315,7 +319,7 @@ class PagedMemoryMixin(MemoryMixin):
315
319
  return True
316
320
 
317
321
  def permissions(self, addr, permissions=None, **kwargs):
318
- if type(addr) is not int:
322
+ if not isinstance(addr, int):
319
323
  raise TypeError("addr must be an int in paged memory")
320
324
  pageno, _ = self._divide_addr(addr)
321
325
  try:
@@ -323,15 +327,15 @@ class PagedMemoryMixin(MemoryMixin):
323
327
  except SimMemoryError as e:
324
328
  raise SimMemoryError(f"{addr:#x} is not mapped") from e
325
329
 
326
- if type(permissions) is int:
330
+ if isinstance(permissions, int):
327
331
  permissions = claripy.BVV(permissions, 3)
328
332
 
329
- result = page.permissions
333
+ result = page.permission_bits
330
334
  if permissions is not None:
331
- page.permissions = permissions
335
+ page.permission_bits = permissions
332
336
  return result
333
337
 
334
- def map_region(self, addr, length, permissions, init_zero=False, **kwargs):
338
+ def map_region(self, addr, length, permissions, *, init_zero=False, **kwargs):
335
339
  if type(addr) is not int:
336
340
  raise TypeError("addr must be an int in paged memory")
337
341
  pageno, pageoff = self._divide_addr(addr)
@@ -399,6 +403,11 @@ class PagedMemoryMixin(MemoryMixin):
399
403
  else:
400
404
  return True
401
405
 
406
+ @overload
407
+ def _load_to_memoryview(self, addr, size, with_bitmap: Literal[True]) -> tuple[memoryview, memoryview]: ...
408
+ @overload
409
+ def _load_to_memoryview(self, addr, size, with_bitmap: Literal[False]) -> memoryview: ...
410
+
402
411
  def _load_to_memoryview(self, addr, size, with_bitmap):
403
412
  result = self.load(addr, size, endness="Iend_BE")
404
413
  if result.op == "BVV":
@@ -469,7 +478,7 @@ class PagedMemoryMixin(MemoryMixin):
469
478
  return memoryview(bytes(size)), memoryview(b"\x01" * size)
470
479
  return memoryview(b"")
471
480
 
472
- def concrete_load(self, addr, size, writing=False, with_bitmap=False, **kwargs):
481
+ def concrete_load(self, addr, size, writing=False, *, with_bitmap: bool = False, **kwargs):
473
482
  pageno, offset = self._divide_addr(addr)
474
483
  subsize = min(size, self.page_size - offset)
475
484
  try:
@@ -481,9 +490,11 @@ class PagedMemoryMixin(MemoryMixin):
481
490
 
482
491
  if not page.SUPPORTS_CONCRETE_LOAD:
483
492
  # the page does not support concrete_load
484
- return self._load_to_memoryview(addr, size, with_bitmap)
493
+ if with_bitmap:
494
+ return self._load_to_memoryview(addr, size, True)
495
+ return self._load_to_memoryview(addr, size, False)
485
496
 
486
- data, bitmap = page.concrete_load(offset, subsize, **kwargs)
497
+ data, bitmap = page.concrete_load(offset, subsize, with_bitmap=True, **kwargs)
487
498
  if with_bitmap:
488
499
  return data, bitmap
489
500
 
@@ -513,7 +524,7 @@ class PagedMemoryMixin(MemoryMixin):
513
524
  except (SimMemoryError, AttributeError):
514
525
  break
515
526
  else:
516
- newdata, bitmap = concrete_load(offset, subsize, **kwargs)
527
+ newdata, bitmap = concrete_load(offset, subsize, with_bitmap=True, **kwargs)
517
528
  for i, byte in enumerate(bitmap):
518
529
  if byte != 0:
519
530
  break
@@ -1,26 +1,32 @@
1
1
  from __future__ import annotations
2
- from typing import Any
2
+ from abc import abstractmethod
3
+ from typing import Generic, TypeVar
4
+ from collections.abc import Generator
3
5
 
4
6
  import claripy
5
7
 
6
8
  from angr.storage.memory_object import SimMemoryObject, SimLabeledMemoryObject
7
9
  from .multi_values import MultiValues
8
10
 
11
+ T = TypeVar("T")
9
12
 
10
- class CooperationBase:
13
+
14
+ class CooperationBase(Generic[T]):
11
15
  """
12
16
  Any given subclass of this class which is not a subclass of MemoryMixin should have the property that any subclass
13
17
  it which *is* a subclass of MemoryMixin should all work with the same datatypes
14
18
  """
15
19
 
16
20
  @classmethod
17
- def _compose_objects(cls, objects, size, endness, **kwargs):
21
+ @abstractmethod
22
+ def _compose_objects(cls, objects: list[list[tuple[int, T]]], size: str, endness: str, **kwargs) -> T:
18
23
  """
19
24
  Provide this a list of the result of several load calls, and it will compose them into a single result.
20
25
  """
21
26
 
22
27
  @classmethod
23
- def _decompose_objects(cls, addr, data, endness, **kwargs) -> tuple[Any, int, int]:
28
+ @abstractmethod
29
+ def _decompose_objects(cls, addr, data, endness, **kwargs) -> Generator[tuple[T, int, int], int]:
24
30
  """
25
31
  A bidirectional generator. No idea if this is overengineered. Usage is that you send it a size to use
26
32
  and it yields a tuple of three elements: the object to store for the next n bytes, the base address of the
@@ -28,13 +34,14 @@ class CooperationBase:
28
34
  """
29
35
 
30
36
  @classmethod
31
- def _zero_objects(cls, addr, size, **kwargs):
37
+ @abstractmethod
38
+ def _zero_objects(cls, addr, size, **kwargs) -> Generator[tuple[T, int, int], int]:
32
39
  """
33
40
  Like decompose objects, but with a size to zero-fill instead of explicit data
34
41
  """
35
42
 
36
43
  @classmethod
37
- def _force_store_cooperation(cls, addr, data, size, endness, **kwargs):
44
+ def _force_store_cooperation(cls, addr: int, data: T, size: int, endness: str, **kwargs):
38
45
  if data is not None:
39
46
  sub_gen = cls._decompose_objects(addr, data, endness, **kwargs)
40
47
  else:
@@ -50,7 +57,7 @@ class CooperationBase:
50
57
  return cls._compose_objects([results], size, endness, **kwargs)
51
58
 
52
59
 
53
- class MemoryObjectMixin(CooperationBase):
60
+ class MemoryObjectMixin(CooperationBase[SimMemoryObject]):
54
61
  """
55
62
  Uses SimMemoryObjects in region storage.
56
63
  With this, load will return a list of tuple (address, MO) and store will take a MO.
@@ -123,11 +130,12 @@ class MemoryObjectMixin(CooperationBase):
123
130
  else:
124
131
  memory_object = SimLabeledMemoryObject(data, cur_addr, endness, byte_width=byte_width, label=label)
125
132
 
133
+ next_elem_size_left = 0
134
+ next_elem_index = 0
126
135
  if data.symbolic and data.op == "Concat":
127
136
  next_elem_size_left = data.args[0].size() // 8
128
- next_elem_index = 0
129
137
 
130
- size = yield
138
+ size = yield NotImplemented, NotImplemented, NotImplemented
131
139
  max_size = kwargs.get("max_size", size)
132
140
  while True:
133
141
  if data.symbolic and data.op == "Concat" and data.size() > max_size:
@@ -235,6 +243,7 @@ class MemoryObjectSetMixin(CooperationBase):
235
243
  mv.add_value(start_offset, prev_value)
236
244
  prev_value = ...
237
245
 
246
+ assert next(mv.values(), None) is not None, "MultiValues may not be empty"
238
247
  return mv
239
248
 
240
249
  @classmethod
@@ -14,7 +14,7 @@ class ISPOMixin(MemoryMixin):
14
14
  def set_state(self, state):
15
15
  raise Exception("Cannot set state on this stateless object")
16
16
 
17
- def _default_value(self, *args, memory=None, **kwargs):
17
+ def _default_value(self, *args, memory, **kwargs):
18
18
  try:
19
19
  func = memory._default_value
20
20
  except AttributeError as e:
@@ -22,7 +22,7 @@ class ISPOMixin(MemoryMixin):
22
22
  else:
23
23
  return func(*args, **kwargs)
24
24
 
25
- def _add_constraints(self, *args, memory=None, **kwargs):
25
+ def _add_constraints(self, *args, memory, **kwargs):
26
26
  try:
27
27
  func = memory._add_constraints
28
28
  except AttributeError as e:
@@ -30,7 +30,7 @@ class ISPOMixin(MemoryMixin):
30
30
  else:
31
31
  return func(*args, **kwargs)
32
32
 
33
- def _merge_values(self, *args, memory=None, **kwargs):
33
+ def _merge_values(self, *args, memory, **kwargs):
34
34
  try:
35
35
  func = memory._merge_values
36
36
  except AttributeError as ex:
@@ -38,7 +38,7 @@ class ISPOMixin(MemoryMixin):
38
38
  else:
39
39
  return func(*args, **kwargs)
40
40
 
41
- def _merge_labels(self, *args, memory=None, **kwargs):
41
+ def _merge_labels(self, *args, memory, **kwargs):
42
42
  try:
43
43
  func = memory._merge_labels
44
44
  except AttributeError as ex:
@@ -46,7 +46,7 @@ class ISPOMixin(MemoryMixin):
46
46
  else:
47
47
  return func(*args, **kwargs)
48
48
 
49
- def _update_mappings(self, *args, memory=None, **kwargs):
49
+ def _update_mappings(self, *args, memory, **kwargs):
50
50
  try:
51
51
  func = memory._update_mappings
52
52
  except AttributeError as ex:
@@ -1,13 +1,17 @@
1
1
  from __future__ import annotations
2
2
  from collections.abc import Iterator
3
+ from typing import Any, Generic, TypeGuard, TypeVar, cast
3
4
  import archinfo
4
5
 
5
6
  import claripy
6
7
 
8
+ from angr.errors import AngrTypeError
7
9
  from angr.storage.memory_object import bv_slice
8
10
 
11
+ MVType = TypeVar("MVType", bound=claripy.ast.BV | claripy.ast.FP)
9
12
 
10
- class MultiValues:
13
+
14
+ class MultiValues(Generic[MVType]):
11
15
  """
12
16
  Represents a byte vector where each byte can have one or multiple values.
13
17
 
@@ -20,27 +24,31 @@ class MultiValues:
20
24
  "_single_value",
21
25
  )
22
26
 
23
- _single_value: claripy.ast.Bits | None
24
- _values: dict[int, set[claripy.ast.Bits]] | None
27
+ _single_value: MVType | None
28
+ _values: dict[int, set[MVType]] | None
25
29
 
26
30
  def __init__(
27
31
  self,
28
- v: claripy.ast.Bits | MultiValues | None | dict[int, set[claripy.ast.Bits]] = None,
29
- offset_to_values=None,
32
+ v: MVType | MultiValues[MVType] | None | dict[int, set[MVType]] = None,
33
+ offset_to_values: dict[int, set[MVType]] | None = None,
30
34
  ):
31
35
  if v is not None and offset_to_values is not None:
32
36
  raise TypeError("You cannot specify v and offset_to_values at the same time!")
33
37
 
34
- self._single_value = (
35
- None
36
- if v is None
37
- else v if isinstance(v, claripy.ast.Bits) else v._single_value if isinstance(v, MultiValues) else None
38
- )
39
- self._values = (
40
- offset_to_values
41
- if offset_to_values is not None
42
- else v if isinstance(v, dict) else v._values if isinstance(v, MultiValues) else None
43
- )
38
+ if v is None:
39
+ self._single_value = None
40
+ self._values = offset_to_values
41
+ elif isinstance(v, (claripy.ast.BV, claripy.ast.FP)):
42
+ self._single_value = v
43
+ self._values = None
44
+ elif isinstance(v, MultiValues):
45
+ self._single_value = v._single_value
46
+ self._values = v._values
47
+ elif isinstance(v, dict):
48
+ self._single_value = None
49
+ self._values = v
50
+ else:
51
+ raise TypeError(type(v))
44
52
 
45
53
  if self._single_value is None and self._values is None:
46
54
  self._values = {}
@@ -56,7 +64,7 @@ class MultiValues:
56
64
  if not isinstance(vs, set):
57
65
  raise TypeError("Each value in offset_to_values must be a set!")
58
66
 
59
- def add_value(self, offset: int, value: claripy.ast.Bits) -> None:
67
+ def add_value(self, offset: int, value: MVType) -> None:
60
68
  if len(value) == 0:
61
69
  return
62
70
  if self._single_value is not None:
@@ -80,32 +88,40 @@ class MultiValues:
80
88
 
81
89
  succ_offset = self._adjacent_offset(offset, before=False)
82
90
  if succ_offset is not None:
83
- value_end = offset + value.length // 8
91
+ value_end = offset + value.size() // 8
84
92
  if value_end > succ_offset:
93
+ if isinstance(value, claripy.ast.FP):
94
+ raise AngrTypeError("Unsupported case. How do we handle floating point values overlapping?")
85
95
  # value is too long. we need to break value into two
86
96
  mid_value_size = succ_offset - offset
87
- remaining_value = value[value.length - mid_value_size * 8 - 1 : 0]
97
+ remaining_value = cast(MVType, value[value.size() - mid_value_size * 8 - 1 : 0])
88
98
  # update value
89
- value = value[value.length - 1 : value.length - mid_value_size * 8]
99
+ value = cast(MVType, value[value.size() - 1 : value.size() - mid_value_size * 8])
90
100
  self.add_value(succ_offset, remaining_value)
91
101
 
92
102
  if self._values[offset]:
93
- curr_value_size = next(iter(self._values[offset])).length // 8
94
- if curr_value_size > value.length // 8:
103
+ curr_value_size = next(iter(self._values[offset])).size() // 8
104
+ if curr_value_size > value.size() // 8:
95
105
  # we need to break existing values
96
106
  new_curr_values = set()
97
107
  remaining_values = set()
98
108
  for v in self._values[offset]:
99
- new_curr_values.add(v[v.length - 1 : v.length - value.length])
100
- remaining_values.add(v[v.length - value.length - 1 : 0])
109
+ if isinstance(v, claripy.ast.FP):
110
+ raise AngrTypeError("Unsupported case. How do we handle floating point values overlapping?")
111
+
112
+ new_curr_values.add(v[v.size() - 1 : v.size() - value.size()])
113
+ remaining_values.add(v[v.size() - value.size() - 1 : 0])
101
114
  self._values[offset] = new_curr_values
102
115
  for v in remaining_values:
103
- self.add_value(offset + value.length // 8, v)
104
- elif curr_value_size < value.length // 8:
116
+ self.add_value(offset + value.size() // 8, v)
117
+ elif curr_value_size < value.size() // 8:
118
+ if isinstance(value, claripy.ast.FP):
119
+ raise AngrTypeError("Unsupported case. How do we handle floating point values overlapping?")
120
+
105
121
  # value is too long. we need to break value into two
106
- remaining_value = value[value.length - curr_value_size * 8 - 1 : 0]
122
+ remaining_value = cast(MVType, value[value.size() - curr_value_size * 8 - 1 : 0])
107
123
  # update value
108
- value = value[value.length - 1 : value.length - curr_value_size * 8]
124
+ value = cast(MVType, value[value.size() - 1 : value.size() - curr_value_size * 8])
109
125
  self.add_value(offset + curr_value_size, remaining_value)
110
126
 
111
127
  self._values[offset].add(value)
@@ -113,26 +129,28 @@ class MultiValues:
113
129
  pre_offset = self._adjacent_offset(offset, before=True)
114
130
  if pre_offset is not None:
115
131
  pre_values = self._values[pre_offset]
116
- pre_values_size = next(iter(pre_values)).length // 8
132
+ pre_values_size = next(iter(pre_values)).size() // 8
117
133
  if pre_offset + pre_values_size > offset:
118
134
  # we need to break the preceding values
119
135
  new_pre_value_size = offset - pre_offset
120
136
  new_pre_values = set()
121
137
  remaining_values = set()
122
138
  for v in pre_values:
123
- new_pre_values.add(v[v.length - 1 : v.length - new_pre_value_size * 8])
124
- remaining_values.add(v[v.length - new_pre_value_size * 8 - 1 : 0])
139
+ if isinstance(v, claripy.ast.FP):
140
+ raise AngrTypeError("Unsupported case. How do we handle floating point values overlapping?")
141
+ new_pre_values.add(v[v.size() - 1 : v.size() - new_pre_value_size * 8])
142
+ remaining_values.add(v[v.size() - new_pre_value_size * 8 - 1 : 0])
125
143
  self._values[pre_offset] = new_pre_values
126
144
  for v in remaining_values:
127
145
  self.add_value(offset, v)
128
146
 
129
- def one_value(self, strip_annotations: bool = False) -> claripy.ast.Bits | None:
147
+ def one_value(self, strip_annotations: bool = False) -> MVType | None:
130
148
  if self._single_value is not None:
131
149
  return self._single_value
132
150
 
133
151
  assert self._values is not None
134
152
 
135
- if len(self._values) == 1 and len(self._values[0]) == 1:
153
+ if len(self._values) == 1 and 0 in self._values and len(self._values[0]) == 1:
136
154
  return next(iter(self._values[0]))
137
155
  if strip_annotations:
138
156
  all_values_wo_annotations = {
@@ -144,7 +162,7 @@ class MultiValues:
144
162
 
145
163
  def __len__(self) -> int:
146
164
  if self._single_value is not None:
147
- return self._single_value.length
165
+ return self._single_value.size()
148
166
 
149
167
  assert self._values is not None
150
168
 
@@ -155,7 +173,7 @@ class MultiValues:
155
173
  max_len = max(x.size() for x in self._values[max_offset])
156
174
  return max_offset * 8 + max_len # FIXME: we are assuming byte_width of 8
157
175
 
158
- def merge(self, mv: MultiValues) -> MultiValues:
176
+ def merge(self, mv: MultiValues[MVType]) -> MultiValues[MVType]:
159
177
  new_values = {k: set(v) for k, v in self.items()}
160
178
  for off, vs in mv.items():
161
179
  if off not in new_values:
@@ -187,47 +205,46 @@ class MultiValues:
187
205
  def __contains__(self, offset: int) -> bool:
188
206
  if self._single_value is not None:
189
207
  return offset == 0
190
- return False if not self._values else offset in self._values
208
+ assert self._values is not None
209
+ return offset in self._values
191
210
 
192
- def __getitem__(self, offset: int) -> set[claripy.ast.Bits]:
211
+ def __getitem__(self, offset: int) -> set[MVType]:
193
212
  if self._single_value is not None:
194
213
  if offset == 0:
195
214
  return {self._single_value}
196
- raise KeyError
197
- if not self._values:
198
- raise KeyError
215
+ raise KeyError(offset)
216
+ assert self._values is not None
199
217
  return self._values[offset]
200
218
 
201
219
  def keys(self) -> set[int]:
202
220
  if self._single_value is not None:
203
221
  return {0}
204
- return set() if not self._values else set(self._values.keys())
222
+ assert self._values is not None
223
+ return set(self._values.keys())
205
224
 
206
- def values(self) -> Iterator[set[claripy.ast.Bits]]:
225
+ def values(self) -> Iterator[set[MVType]]:
207
226
  if self._single_value is not None:
208
227
  yield {self._single_value}
209
228
  else:
210
- if self._values is None:
211
- return
229
+ assert self._values is not None
212
230
  yield from self._values.values()
213
231
 
214
- def items(self) -> Iterator[tuple[int, set[claripy.ast.Bits]]]:
232
+ def items(self) -> Iterator[tuple[int, set[MVType]]]:
215
233
  if self._single_value is not None:
216
234
  yield 0, {self._single_value}
217
235
  else:
218
- if self._values is None:
219
- yield 0, set()
220
- else:
221
- yield from self._values.items()
236
+ assert self._values is not None
237
+ yield from self._values.items()
222
238
 
223
239
  def count(self) -> int:
224
240
  if self._single_value is not None:
225
241
  return 1
226
- if self._values is None:
227
- return 0
242
+ assert self._values is not None
228
243
  return len(self._values)
229
244
 
230
- def extract(self, offset: int, length: int, endness: str) -> MultiValues:
245
+ def extract(
246
+ self: MultiValues[claripy.ast.BV], offset: int, length: int, endness: str
247
+ ) -> MultiValues[claripy.ast.BV]:
231
248
  end = offset + length
232
249
  result = MultiValues(claripy.BVV(b""))
233
250
  for obj_offset, values in self.items():
@@ -240,16 +257,19 @@ class MultiValues:
240
257
 
241
258
  slice_start = max(0, offset - obj_offset)
242
259
  slice_end = min(obj_length, end - obj_offset)
243
- sliced = bv_slice(value, slice_start, slice_end - slice_start, endness == archinfo.Endness.LE, 8)
260
+ bv_value: claripy.ast.BV = claripy.fpToIEEEBV(value) if isinstance(value, claripy.ast.FP) else value
261
+ sliced = bv_slice(bv_value, slice_start, slice_end - slice_start, endness == archinfo.Endness.LE, 8)
244
262
  if len(sliced):
245
263
  result.add_value(max(0, obj_offset - offset), sliced)
246
264
 
247
265
  return result
248
266
 
249
- def concat(self, other: MultiValues | claripy.ast.Bits | bytes) -> MultiValues:
250
- if isinstance(other, bytes):
267
+ def concat(
268
+ self: MultiValues[claripy.ast.BV], other: MultiValues[claripy.ast.BV] | claripy.ast.BV | bytes
269
+ ) -> MultiValues[claripy.ast.BV]:
270
+ if isinstance(other, (bytes, bytearray, memoryview)):
251
271
  other = claripy.BVV(other)
252
- if isinstance(other, claripy.ast.Bits):
272
+ if isinstance(other, claripy.ast.BV):
253
273
  other = MultiValues(other)
254
274
  offset = len(self) // 8
255
275
  result = MultiValues(self)
@@ -283,3 +303,17 @@ class MultiValues:
283
303
  # we missed it...
284
304
  return None
285
305
  return None
306
+
307
+
308
+ def mv_is_bv(mv: MultiValues[Any]) -> TypeGuard[MultiValues[claripy.ast.BV]]:
309
+ if mv._single_value is not None:
310
+ return isinstance(mv._single_value, claripy.ast.BV)
311
+ assert mv._values is not None
312
+ return all(isinstance(value, claripy.ast.BV) for valueset in mv._values.values() for value in valueset)
313
+
314
+
315
+ def mv_is_fp(mv: MultiValues[Any]) -> TypeGuard[MultiValues[claripy.ast.FP]]:
316
+ if mv._single_value is not None:
317
+ return isinstance(mv._single_value, claripy.ast.FP)
318
+ assert mv._values is not None
319
+ return all(isinstance(value, claripy.ast.FP) for valueset in mv._values.values() for value in valueset)