angr 9.2.123__py3-none-manylinux2014_aarch64.whl → 9.2.124__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 (83) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/cfg/indirect_jump_resolvers/mips_elf_fast.py +11 -8
  3. angr/analyses/cfg/indirect_jump_resolvers/mips_elf_got.py +2 -2
  4. angr/analyses/decompiler/ail_simplifier.py +16 -19
  5. angr/analyses/decompiler/callsite_maker.py +8 -7
  6. angr/analyses/decompiler/ccall_rewriters/amd64_ccalls.py +24 -2
  7. angr/analyses/decompiler/clinic.py +27 -1
  8. angr/analyses/decompiler/condition_processor.py +10 -3
  9. angr/analyses/decompiler/decompilation_cache.py +2 -0
  10. angr/analyses/decompiler/decompiler.py +50 -8
  11. angr/analyses/decompiler/dephication/graph_vvar_mapping.py +10 -2
  12. angr/analyses/decompiler/dephication/rewriting_engine.py +64 -1
  13. angr/analyses/decompiler/expression_narrower.py +5 -1
  14. angr/analyses/decompiler/optimization_passes/div_simplifier.py +4 -1
  15. angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +7 -0
  16. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +23 -4
  17. angr/analyses/decompiler/optimization_passes/optimization_pass.py +3 -1
  18. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +8 -5
  19. angr/analyses/decompiler/optimization_passes/return_duplicator_high.py +10 -5
  20. angr/analyses/decompiler/optimization_passes/return_duplicator_low.py +18 -7
  21. angr/analyses/decompiler/optimization_passes/switch_default_case_duplicator.py +6 -0
  22. angr/analyses/decompiler/optimization_passes/win_stack_canary_simplifier.py +2 -0
  23. angr/analyses/decompiler/peephole_optimizations/const_mull_a_shift.py +2 -0
  24. angr/analyses/decompiler/peephole_optimizations/remove_cascading_conversions.py +8 -2
  25. angr/analyses/decompiler/region_identifier.py +36 -0
  26. angr/analyses/decompiler/region_simplifiers/loop.py +2 -8
  27. angr/analyses/decompiler/region_simplifiers/switch_cluster_simplifier.py +9 -3
  28. angr/analyses/decompiler/ssailification/rewriting.py +5 -2
  29. angr/analyses/decompiler/ssailification/rewriting_engine.py +151 -25
  30. angr/analyses/decompiler/ssailification/rewriting_state.py +1 -0
  31. angr/analyses/decompiler/ssailification/ssailification.py +17 -9
  32. angr/analyses/decompiler/ssailification/traversal.py +3 -1
  33. angr/analyses/decompiler/ssailification/traversal_engine.py +35 -8
  34. angr/analyses/decompiler/ssailification/traversal_state.py +1 -0
  35. angr/analyses/decompiler/structured_codegen/c.py +42 -4
  36. angr/analyses/decompiler/structuring/phoenix.py +3 -0
  37. angr/analyses/propagator/engine_ail.py +10 -3
  38. angr/analyses/reaching_definitions/engine_ail.py +10 -15
  39. angr/analyses/s_propagator.py +16 -9
  40. angr/analyses/s_reaching_definitions/s_rda_view.py +127 -63
  41. angr/analyses/variable_recovery/engine_ail.py +14 -0
  42. angr/analyses/variable_recovery/engine_base.py +11 -0
  43. angr/engines/light/engine.py +12 -0
  44. angr/knowledge_plugins/__init__.py +2 -0
  45. angr/knowledge_plugins/decompilation.py +45 -0
  46. angr/knowledge_plugins/key_definitions/atoms.py +8 -0
  47. angr/procedures/definitions/parse_win32json.py +2 -1
  48. angr/storage/memory_mixins/actions_mixin.py +7 -7
  49. angr/storage/memory_mixins/address_concretization_mixin.py +5 -5
  50. angr/storage/memory_mixins/bvv_conversion_mixin.py +1 -1
  51. angr/storage/memory_mixins/clouseau_mixin.py +3 -3
  52. angr/storage/memory_mixins/conditional_store_mixin.py +3 -3
  53. angr/storage/memory_mixins/default_filler_mixin.py +3 -3
  54. angr/storage/memory_mixins/memory_mixin.py +45 -34
  55. angr/storage/memory_mixins/paged_memory/page_backer_mixins.py +15 -14
  56. angr/storage/memory_mixins/paged_memory/paged_memory_mixin.py +27 -16
  57. angr/storage/memory_mixins/paged_memory/pages/cooperation.py +18 -9
  58. angr/storage/memory_mixins/paged_memory/pages/ispo_mixin.py +5 -5
  59. angr/storage/memory_mixins/paged_memory/pages/multi_values.py +89 -55
  60. angr/storage/memory_mixins/paged_memory/pages/mv_list_page.py +16 -25
  61. angr/storage/memory_mixins/paged_memory/pages/permissions_mixin.py +11 -9
  62. angr/storage/memory_mixins/paged_memory/pages/ultra_page.py +23 -7
  63. angr/storage/memory_mixins/paged_memory/privileged_mixin.py +1 -1
  64. angr/storage/memory_mixins/regioned_memory/region_meta_mixin.py +9 -7
  65. angr/storage/memory_mixins/regioned_memory/regioned_memory_mixin.py +9 -9
  66. angr/storage/memory_mixins/regioned_memory/static_find_mixin.py +1 -0
  67. angr/storage/memory_mixins/simple_interface_mixin.py +2 -2
  68. angr/storage/memory_mixins/simplification_mixin.py +2 -2
  69. angr/storage/memory_mixins/size_resolution_mixin.py +1 -1
  70. angr/storage/memory_mixins/slotted_memory.py +3 -3
  71. angr/storage/memory_mixins/smart_find_mixin.py +1 -0
  72. angr/storage/memory_mixins/underconstrained_mixin.py +5 -5
  73. angr/storage/memory_mixins/unwrapper_mixin.py +4 -4
  74. angr/storage/memory_object.py +4 -3
  75. angr/utils/constants.py +1 -1
  76. angr/utils/graph.py +15 -0
  77. angr/vaults.py +2 -2
  78. {angr-9.2.123.dist-info → angr-9.2.124.dist-info}/METADATA +6 -6
  79. {angr-9.2.123.dist-info → angr-9.2.124.dist-info}/RECORD +83 -82
  80. {angr-9.2.123.dist-info → angr-9.2.124.dist-info}/WHEEL +1 -1
  81. {angr-9.2.123.dist-info → angr-9.2.124.dist-info}/LICENSE +0 -0
  82. {angr-9.2.123.dist-info → angr-9.2.124.dist-info}/entry_points.txt +0 -0
  83. {angr-9.2.123.dist-info → angr-9.2.124.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ from ailment.expression import (
13
13
  BinaryOp,
14
14
  VirtualVariable,
15
15
  Phi,
16
+ UnaryOp,
16
17
  VirtualVariableCategory,
17
18
  )
18
19
  from ailment.statement import ConditionalJump, Jump, Assignment
@@ -238,6 +239,12 @@ class InlinedStringTransformationAILEngine(SimEngineLightAILMixin):
238
239
  return self.state.vvar_load(vvar)
239
240
  return None
240
241
 
242
+ def _handle_Neg(self, expr: UnaryOp):
243
+ v = self._expr(expr.operand)
244
+ if isinstance(v, claripy.ast.Bits):
245
+ return ~v
246
+ return None
247
+
241
248
  def _handle_Convert(self, expr: Convert):
242
249
  v = self._expr(expr.operand)
243
250
  if isinstance(v, claripy.ast.Bits):
@@ -202,6 +202,7 @@ class ITERegionConverter(OptimizationPass):
202
202
  true_stmt_src = true_stmt.src if isinstance(true_stmt, Assignment) else true_stmt
203
203
  true_stmt_dst = true_stmt.dst if isinstance(true_stmt, Assignment) else true_stmt.ret_expr
204
204
  false_stmt_src = false_stmt.src if isinstance(false_stmt, Assignment) else false_stmt
205
+ false_stmt_dst = false_stmt.dst if isinstance(false_stmt, Assignment) else false_stmt.ret_expr
205
206
 
206
207
  addr_obj = true_stmt_src if "ins_addr" in true_stmt_src.tags else true_stmt
207
208
  ternary_expr = ITE(
@@ -209,9 +210,7 @@ class ITERegionConverter(OptimizationPass):
209
210
  conditional_jump.condition,
210
211
  false_stmt_src,
211
212
  true_stmt_src,
212
- ins_addr=addr_obj.ins_addr,
213
- vex_block_addr=addr_obj.vex_block_addr,
214
- vex_stmt_idx=addr_obj.vex_stmt_idx,
213
+ **addr_obj.tags,
215
214
  )
216
215
  dst = VirtualVariable(
217
216
  true_stmt_dst.idx,
@@ -261,12 +260,32 @@ class ITERegionConverter(OptimizationPass):
261
260
  if not is_phi_assignment(stmt):
262
261
  stmts.append(stmt)
263
262
  continue
263
+
264
+ # is this the statement that we are looking for?
265
+ found_true_src_vvar, found_false_src_vvar = False, False
266
+ for src, vvar in stmt.src.src_and_vvars:
267
+ if vvar is not None:
268
+ if vvar.varid == true_stmt_dst.varid:
269
+ found_true_src_vvar = True
270
+ elif vvar.varid == false_stmt_dst.varid:
271
+ found_false_src_vvar = True
272
+ # we should only update the vvars of this phi statement if we found both true and false source vvars
273
+ update_vars = found_true_src_vvar and found_false_src_vvar
274
+
264
275
  new_src_and_vvars = []
276
+ original_vvars = []
265
277
  for src, vvar in stmt.src.src_and_vvars:
266
278
  if src not in region_tail_pred_srcs:
267
279
  new_src_and_vvars.append((src, vvar))
280
+ else:
281
+ original_vvars.append(vvar)
268
282
  new_vvar = new_assignment.dst.copy()
269
- new_src_and_vvars.append(((region_head.addr, region_head.idx), new_vvar))
283
+ if update_vars:
284
+ new_src_and_vvars.append(((region_head.addr, region_head.idx), new_vvar))
285
+ else:
286
+ new_src_and_vvars.append(
287
+ ((region_head.addr, region_head.idx), original_vvars[0] if original_vvars else None)
288
+ )
270
289
 
271
290
  new_phi = Phi(
272
291
  stmt.src.idx,
@@ -1,7 +1,7 @@
1
1
  # pylint:disable=unused-argument
2
2
  from __future__ import annotations
3
3
  import logging
4
- from typing import TYPE_CHECKING
4
+ from typing import Any, TYPE_CHECKING
5
5
  from collections.abc import Generator
6
6
  from enum import Enum
7
7
 
@@ -117,6 +117,7 @@ class OptimizationPass(BaseOptimizationPass):
117
117
  reaching_definitions=None,
118
118
  vvar_id_start=None,
119
119
  entry_node_addr=None,
120
+ scratch: dict[str, Any] | None = None,
120
121
  **kwargs,
121
122
  ):
122
123
  super().__init__(func)
@@ -127,6 +128,7 @@ class OptimizationPass(BaseOptimizationPass):
127
128
  self._variable_kb = variable_kb
128
129
  self._ri = region_identifier
129
130
  self._rd = reaching_definitions
131
+ self._scratch = scratch if scratch is not None else {}
130
132
  self._new_block_addrs = set()
131
133
  self.vvar_id_start = vvar_id_start
132
134
  self.entry_node_addr: tuple[int, int | None] = (
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
  from typing import Any
3
- from itertools import count
4
3
  import copy
5
4
  import logging
6
5
 
@@ -78,23 +77,27 @@ class ReturnDuplicatorBase:
78
77
  def __init__(
79
78
  self,
80
79
  func,
81
- node_idx_start: int = 0,
82
80
  max_calls_in_regions: int = 2,
83
81
  minimize_copies_for_regions: bool = True,
84
82
  ri: RegionIdentifier | None = None,
85
83
  vvar_id_start: int | None = None,
86
- **kwargs,
84
+ scratch: dict[str, Any] | None = None,
87
85
  ):
88
- self.node_idx = count(start=node_idx_start)
89
86
  self._max_calls_in_region = max_calls_in_regions
90
87
  self._minimize_copies_for_regions = minimize_copies_for_regions
91
88
  self._supergraph = None
92
89
 
93
90
  # this should also be set by the optimization passes initer
91
+ self.scratch = scratch if scratch is not None else {}
94
92
  self._func = func
95
93
  self._ri: RegionIdentifier | None = ri
96
94
  self.vvar_id_start = vvar_id_start
97
95
 
96
+ def next_node_idx(self) -> int:
97
+ node_idx = self.scratch.get("returndup_node_idx", 0) + 1
98
+ self.scratch["returndup_node_idx"] = node_idx
99
+ return node_idx
100
+
98
101
  #
99
102
  # must implement these methods
100
103
  #
@@ -208,7 +211,7 @@ class ReturnDuplicatorBase:
208
211
  else:
209
212
  node_copy = copy.deepcopy(node)
210
213
  node_copy = self._use_fresh_virtual_variables(node_copy, vvar_mapping)
211
- node_copy.idx = next(self.node_idx)
214
+ node_copy.idx = self.next_node_idx()
212
215
  self._fix_copied_node_labels(node_copy)
213
216
  copies[node] = node_copy
214
217
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import logging
3
+ from typing import Any
3
4
 
4
5
  import networkx
5
6
 
@@ -26,22 +27,26 @@ class ReturnDuplicatorHigh(OptimizationPass, ReturnDuplicatorBase):
26
27
  def __init__(
27
28
  self,
28
29
  func,
29
- # internal parameters that should be used by Clinic
30
- node_idx_start: int = 0,
31
30
  # settings
32
31
  max_calls_in_regions: int = 2,
33
32
  minimize_copies_for_regions: bool = True,
33
+ region_identifier=None,
34
+ vvar_id_start: int | None = None,
35
+ scratch: dict[str, Any] | None = None,
34
36
  **kwargs,
35
37
  ):
38
+ OptimizationPass.__init__(
39
+ self, func, vvar_id_start=vvar_id_start, scratch=scratch, region_identifier=region_identifier, **kwargs
40
+ )
36
41
  ReturnDuplicatorBase.__init__(
37
42
  self,
38
43
  func,
39
- node_idx_start=node_idx_start,
40
44
  max_calls_in_regions=max_calls_in_regions,
41
45
  minimize_copies_for_regions=minimize_copies_for_regions,
42
- **kwargs,
46
+ ri=region_identifier,
47
+ vvar_id_start=vvar_id_start,
48
+ scratch=scratch,
43
49
  )
44
- OptimizationPass.__init__(self, func, **kwargs)
45
50
  # since we run before the RegionIdentification pass in the decompiler, we need to collect it early here
46
51
  self._ri = self._recover_regions(self._graph)
47
52
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
  import logging
3
3
  import inspect
4
+ from typing import Any
4
5
 
5
6
  import networkx
6
7
 
@@ -46,25 +47,35 @@ class ReturnDuplicatorLow(StructuringOptimizationPass, ReturnDuplicatorBase):
46
47
  def __init__(
47
48
  self,
48
49
  func,
49
- # internal parameters that should be used by Clinic
50
- node_idx_start: int = 0,
51
50
  # settings
52
51
  max_opt_iters: int = 4,
53
52
  max_calls_in_regions: int = 2,
54
53
  prevent_new_gotos: bool = True,
55
54
  minimize_copies_for_regions: bool = True,
55
+ region_identifier=None,
56
+ vvar_id_start: int | None = None,
57
+ scratch: dict[str, Any] | None = None,
56
58
  **kwargs,
57
59
  ):
60
+ StructuringOptimizationPass.__init__(
61
+ self,
62
+ func,
63
+ max_opt_iters=max_opt_iters,
64
+ prevent_new_gotos=prevent_new_gotos,
65
+ require_gotos=True,
66
+ vvar_id_start=vvar_id_start,
67
+ scratch=scratch,
68
+ region_identifier=region_identifier,
69
+ **kwargs,
70
+ )
58
71
  ReturnDuplicatorBase.__init__(
59
72
  self,
60
73
  func,
61
- node_idx_start=node_idx_start,
62
74
  max_calls_in_regions=max_calls_in_regions,
63
75
  minimize_copies_for_regions=minimize_copies_for_regions,
64
- **kwargs,
65
- )
66
- StructuringOptimizationPass.__init__(
67
- self, func, max_opt_iters=max_opt_iters, prevent_new_gotos=prevent_new_gotos, require_gotos=True, **kwargs
76
+ ri=region_identifier,
77
+ vvar_id_start=vvar_id_start,
78
+ scratch=scratch,
68
79
  )
69
80
  self.analyze()
70
81
 
@@ -75,8 +75,12 @@ class SwitchDefaultCaseDuplicator(OptimizationPass):
75
75
  default_case_node_addrs = cache["default_case_node_addrs"]
76
76
 
77
77
  out_graph = None
78
+ duplicated_default_addrs: set[int] = set()
78
79
 
79
80
  for switch_head_addr, jump_node_addr, default_addr in default_case_node_addrs:
81
+ if default_addr in duplicated_default_addrs:
82
+ continue
83
+
80
84
  default_case_node = self._func.get_node(default_addr)
81
85
  unexpected_pred_addrs = {
82
86
  pred.addr
@@ -92,6 +96,8 @@ class SwitchDefaultCaseDuplicator(OptimizationPass):
92
96
  for jump_node in jump_nodes:
93
97
  jump_node_descedents |= networkx.descendants(self._graph, jump_node)
94
98
 
99
+ duplicated_default_addrs.add(default_addr)
100
+
95
101
  # duplicate default_case_node for each unexpected predecessor
96
102
  for unexpected_pred_addr in unexpected_pred_addrs:
97
103
  for unexpected_pred in self._get_blocks(unexpected_pred_addr):
@@ -100,6 +100,8 @@ class WinStackCanarySimplifier(OptimizationPass):
100
100
  for pred_addr in pred_addr_to_endpoint_addrs:
101
101
  # the predecessor should call _security_check_cookie
102
102
  endpoint_preds = list(self._get_blocks(pred_addr))
103
+ if not endpoint_preds:
104
+ continue
103
105
  if self._find_stmt_calling_security_check_cookie(endpoint_preds[0]) is None:
104
106
  _l.debug("The predecessor does not call _security_check_cookie().")
105
107
  continue
@@ -182,6 +182,8 @@ class ConstMullAShift(PeepholeOptimizationExprBase):
182
182
 
183
183
  @staticmethod
184
184
  def _check_divisor(a, b, ndigits=6):
185
+ if b == 0:
186
+ return None
185
187
  divisor_1 = 1 + (a // b)
186
188
  divisor_2 = int(round(a / float(b), ndigits))
187
189
  return divisor_1 if divisor_1 == divisor_2 else None
@@ -11,9 +11,15 @@ class RemoveCascadingConversions(PeepholeOptimizationExprBase):
11
11
  expr_classes = (Convert,)
12
12
 
13
13
  def optimize(self, expr: Convert, **kwargs):
14
- if isinstance(expr.operand, Convert):
14
+ if (
15
+ expr.from_type == Convert.TYPE_INT
16
+ and expr.to_type == Convert.TYPE_INT
17
+ and isinstance(expr.operand, Convert)
18
+ and expr.operand.from_type == Convert.TYPE_INT
19
+ and expr.operand.to_type == Convert.TYPE_INT
20
+ ):
15
21
  inner = expr.operand
16
- if inner.from_bits == expr.to_bits:
22
+ if inner.from_bits == expr.to_bits and inner.from_type == expr.to_type:
17
23
  if inner.from_bits < inner.to_bits:
18
24
  # extension -> truncation
19
25
  return inner.operand
@@ -163,6 +163,22 @@ class RegionIdentifier(Analysis):
163
163
  raise AngrRuntimeError("Cannot find the start node from the graph!") from ex
164
164
  raise AngrRuntimeError("Cannot find the start node from the graph!")
165
165
 
166
+ def _get_entry_node(self, graph: networkx.DiGraph):
167
+ if self.entry_node_addr is None:
168
+ return None
169
+ return next(
170
+ (
171
+ n
172
+ for n in graph.nodes()
173
+ if (
174
+ (n.addr, n.idx) == self.entry_node_addr
175
+ if isinstance(n, Block)
176
+ else n.addr == self.entry_node_addr[0]
177
+ )
178
+ ),
179
+ None,
180
+ )
181
+
166
182
  def _test_reducibility(self):
167
183
  # make a copy of the graph
168
184
  graph = networkx.DiGraph(self._graph)
@@ -188,8 +204,19 @@ class RegionIdentifier(Analysis):
188
204
  return len(graph.nodes) == 1
189
205
 
190
206
  def _make_supergraph(self, graph: networkx.DiGraph):
207
+
208
+ entry_node = None
209
+ if self.entry_node_addr is not None:
210
+ entry_node = next(iter(nn for nn in graph if nn.addr == self.entry_node_addr[0]), None)
211
+
191
212
  while True:
192
213
  for src, dst, data in graph.edges(data=True):
214
+ if entry_node is not None and dst is entry_node:
215
+ # the entry node must be kept instead of merged with its predecessor (which can happen in real
216
+ # binaries! e.g., 444a401b900eb825f216e95111dcb6ef94b01a81fc7b88a48599867db8c50365, function
217
+ # 0x1802BEA28, block 0x1802BEA05 and 0x1802BEA28)
218
+ continue
219
+
193
220
  type_ = data.get("type", None)
194
221
  if type_ == "fake_return":
195
222
  if len(list(graph.successors(src))) == 1 and len(list(graph.predecessors(dst))) == 1:
@@ -452,6 +479,8 @@ class RegionIdentifier(Analysis):
452
479
  #
453
480
 
454
481
  def _make_cyclic_region(self, head, graph: networkx.DiGraph):
482
+ original_entry = self._get_entry_node(graph)
483
+
455
484
  l.debug("Found cyclic region at %#08x", head.addr)
456
485
  initial_loop_nodes = self._find_initial_loop_nodes(graph, head)
457
486
  l.debug("Initial loop nodes %s", self._dbg_block_list(initial_loop_nodes))
@@ -505,6 +534,13 @@ class RegionIdentifier(Analysis):
505
534
  # multi-successor region. refinement is required
506
535
  self._refine_loop_successors(region, graph)
507
536
 
537
+ # if the head node is in the graph and it's not the head of the graph, we will need to update the head node
538
+ # address.
539
+ if original_entry is not None and original_entry in region.graph and region.head is not original_entry:
540
+ self.entry_node_addr = (head.addr, None)
541
+ # FIXME: the identified region will probably be incorrect. we may need to add a jump block that jumps to
542
+ # original_entry.
543
+
508
544
  return region
509
545
 
510
546
  def _refine_loop_successors(self, region, graph: networkx.DiGraph):
@@ -16,6 +16,7 @@ from angr.analyses.decompiler.structuring.structurer_nodes import (
16
16
  CascadingConditionNode,
17
17
  )
18
18
  from angr.analyses.decompiler.utils import is_statement_terminating, has_nonlabel_nonphi_statements
19
+ from angr.utils.ail import is_phi_assignment
19
20
 
20
21
 
21
22
  class LoopSimplifier(SequenceWalker):
@@ -104,14 +105,7 @@ class LoopSimplifier(SequenceWalker):
104
105
  )
105
106
  and (
106
107
  all(has_nonlabel_nonphi_statements(block) for block in self.continue_preludes[node])
107
- and all(
108
- not self._control_transferring_statement(block.statements[-1])
109
- for block in self.continue_preludes[node]
110
- )
111
- and all(
112
- block.statements[-1] == self.continue_preludes[node][0].statements[-1]
113
- for block in self.continue_preludes[node]
114
- )
108
+ and all(not is_phi_assignment(block.statements[-1]) for block in self.continue_preludes[node])
115
109
  )
116
110
  ):
117
111
  node.sort = "for"
@@ -284,6 +284,10 @@ def simplify_switch_clusters(
284
284
 
285
285
  for variable in var2switches:
286
286
  switch_regions = var2switches[variable]
287
+ if len(switch_regions) <= 1:
288
+ # nothing to simplify or merge if there is only one switch region
289
+ continue
290
+
287
291
  cond_regions = list(var2condnodes[variable])
288
292
 
289
293
  if not cond_regions:
@@ -449,10 +453,10 @@ def simplify_switch_clusters(
449
453
  # build the SwitchCase node and replace old nodes in the parent node
450
454
  cases_dict = OrderedDict(cases)
451
455
  new_switchcase = SwitchCaseNode(
452
- switch_regions_default_nodes[0].node.switch_expr,
456
+ switch_regions[0].node.switch_expr,
453
457
  cases_dict,
454
458
  default_node,
455
- addr=switch_regions_default_nodes[0].node.addr,
459
+ addr=switch_regions[0].node.addr,
456
460
  )
457
461
 
458
462
  # what are we trying to replace?
@@ -525,13 +529,15 @@ def simplify_lowered_switches_core(
525
529
 
526
530
  if outermost_node is None:
527
531
  return False
532
+ if not isinstance(outermost_node, ConditionNode):
533
+ return False
528
534
  if isinstance(outermost_node.condition, UnaryOp) and outermost_node.condition.op == "Not":
529
535
  # attempt to flip any simple negated comparison for normalized operations
530
536
  outermost_node.condition = negate(outermost_node.condition.operand)
531
537
 
532
538
  caseno_to_node = {}
533
539
  default_node_candidates: list[tuple[BaseNode, BaseNode]] = [] # parent to default node candidate
534
- stack: list[(ConditionNode, int, int)] = [(outermost_node, 0, 0xFFFF_FFFF_FFFF_FFFF)]
540
+ stack: list[tuple[BaseNode, int, int]] = [(outermost_node, 0, 0xFFFF_FFFF_FFFF_FFFF)]
535
541
  while stack:
536
542
  node, min_, max_ = stack.pop(0)
537
543
  if node not in node_to_condnode:
@@ -9,7 +9,7 @@ import networkx
9
9
  import ailment
10
10
  from ailment import Block
11
11
  from ailment.expression import Expression, Phi, VirtualVariable, VirtualVariableCategory
12
- from ailment.statement import Assignment, Label
12
+ from ailment.statement import Statement, Assignment, Label
13
13
 
14
14
  from angr.code_location import CodeLocation
15
15
  from angr.analyses import ForwardAnalysis
@@ -38,6 +38,7 @@ class RewritingAnalysis(ForwardAnalysis[RewritingState, NodeType, object, object
38
38
  udef_to_phiid: dict[tuple, set[int]],
39
39
  phiid_to_loc: dict[int, tuple[int, int | None]],
40
40
  stackvar_locs: dict[int, int],
41
+ rewrite_tmps: bool,
41
42
  ail_manager,
42
43
  vvar_id_start: int = 0,
43
44
  ):
@@ -52,6 +53,7 @@ class RewritingAnalysis(ForwardAnalysis[RewritingState, NodeType, object, object
52
53
  self._udef_to_phiid = udef_to_phiid
53
54
  self._phiid_to_loc = phiid_to_loc
54
55
  self._stackvar_locs = stackvar_locs
56
+ self._rewrite_tmps = rewrite_tmps
55
57
  self._ail_manager = ail_manager
56
58
  self._engine_ail = SimEngineSSARewriting(
57
59
  self.project.arch,
@@ -61,6 +63,7 @@ class RewritingAnalysis(ForwardAnalysis[RewritingState, NodeType, object, object
61
63
  udef_to_phiid=self._udef_to_phiid,
62
64
  phiid_to_loc=self._phiid_to_loc,
63
65
  stackvar_locs=self._stackvar_locs,
66
+ rewrite_tmps=self._rewrite_tmps,
64
67
  ail_manager=ail_manager,
65
68
  vvar_id_start=vvar_id_start,
66
69
  )
@@ -71,7 +74,7 @@ class RewritingAnalysis(ForwardAnalysis[RewritingState, NodeType, object, object
71
74
 
72
75
  self._analyze()
73
76
 
74
- self.def_to_vvid: dict[Expression, int] = self._engine_ail.def_to_vvid
77
+ self.def_to_vvid: dict[tuple[int, int | None, int, Expression | Statement], int] = self._engine_ail.def_to_vvid
75
78
  self.out_graph = self._make_new_graph(ail_graph)
76
79
 
77
80
  @property