angr 9.2.112__py3-none-manylinux2014_aarch64.whl → 9.2.113__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 (31) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/cfg/cfg_base.py +3 -0
  3. angr/analyses/decompiler/condition_processor.py +9 -2
  4. angr/analyses/decompiler/optimization_passes/__init__.py +3 -1
  5. angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +367 -0
  6. angr/analyses/decompiler/optimization_passes/deadblock_remover.py +1 -1
  7. angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +99 -12
  8. angr/analyses/decompiler/optimization_passes/optimization_pass.py +79 -9
  9. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +21 -0
  10. angr/analyses/decompiler/optimization_passes/return_duplicator_low.py +111 -9
  11. angr/analyses/decompiler/redundant_label_remover.py +17 -0
  12. angr/analyses/decompiler/seq_cf_structure_counter.py +37 -0
  13. angr/analyses/decompiler/structured_codegen/c.py +4 -5
  14. angr/analyses/decompiler/structuring/phoenix.py +3 -3
  15. angr/analyses/reaching_definitions/rd_state.py +2 -0
  16. angr/analyses/reaching_definitions/reaching_definitions.py +7 -0
  17. angr/angrdb/serializers/loader.py +91 -7
  18. angr/calling_conventions.py +11 -9
  19. angr/knowledge_plugins/key_definitions/live_definitions.py +5 -0
  20. angr/knowledge_plugins/propagations/states.py +3 -2
  21. angr/procedures/stubs/ReturnUnconstrained.py +1 -2
  22. angr/procedures/stubs/syscall_stub.py +1 -2
  23. angr/sim_type.py +354 -136
  24. angr/state_plugins/debug_variables.py +2 -2
  25. angr/storage/memory_mixins/multi_value_merger_mixin.py +12 -2
  26. {angr-9.2.112.dist-info → angr-9.2.113.dist-info}/METADATA +6 -6
  27. {angr-9.2.112.dist-info → angr-9.2.113.dist-info}/RECORD +31 -29
  28. {angr-9.2.112.dist-info → angr-9.2.113.dist-info}/WHEEL +1 -1
  29. {angr-9.2.112.dist-info → angr-9.2.113.dist-info}/LICENSE +0 -0
  30. {angr-9.2.112.dist-info → angr-9.2.113.dist-info}/entry_points.txt +0 -0
  31. {angr-9.2.112.dist-info → angr-9.2.113.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  # pylint:disable=unused-argument
2
+ import logging
2
3
  from typing import TYPE_CHECKING
3
4
  from collections.abc import Generator
4
5
  from enum import Enum
@@ -11,10 +12,13 @@ from angr.analyses.decompiler.condition_processor import ConditionProcessor
11
12
  from angr.analyses.decompiler.goto_manager import GotoManager
12
13
  from angr.analyses.decompiler.structuring import RecursiveStructurer, PhoenixStructurer
13
14
  from angr.analyses.decompiler.utils import add_labels
15
+ from angr.analyses.decompiler.seq_cf_structure_counter import ControlFlowStructureCounter
14
16
 
15
17
  if TYPE_CHECKING:
16
18
  from angr.knowledge_plugins.functions import Function
17
19
 
20
+ _l = logging.getLogger(__name__)
21
+
18
22
 
19
23
  class MultipleBlocksException(Exception):
20
24
  """
@@ -274,6 +278,7 @@ class StructuringOptimizationPass(OptimizationPass):
274
278
  prevent_new_gotos=True,
275
279
  strictly_less_gotos=False,
276
280
  recover_structure_fails=True,
281
+ must_improve_rel_quality=True,
277
282
  max_opt_iters=1,
278
283
  simplify_ail=True,
279
284
  require_gotos=True,
@@ -286,10 +291,15 @@ class StructuringOptimizationPass(OptimizationPass):
286
291
  self._max_opt_iters = max_opt_iters
287
292
  self._simplify_ail = simplify_ail
288
293
  self._require_gotos = require_gotos
294
+ self._must_improve_rel_quality = must_improve_rel_quality
289
295
 
290
296
  self._goto_manager: GotoManager | None = None
291
297
  self._prev_graph: networkx.DiGraph | None = None
292
298
 
299
+ # relative quality metrics (excludes gotos)
300
+ self._initial_structure_counter = None
301
+ self._current_structure_counter = None
302
+
293
303
  def _analyze(self, cache=None) -> bool:
294
304
  raise NotImplementedError()
295
305
 
@@ -297,7 +307,7 @@ class StructuringOptimizationPass(OptimizationPass):
297
307
  """
298
308
  Wrapper for _analyze() that verifies the graph is structurable before and after the optimization.
299
309
  """
300
- if not self._graph_is_structurable(self._graph):
310
+ if not self._graph_is_structurable(self._graph, initial=True):
301
311
  return
302
312
 
303
313
  initial_gotos = self._goto_manager.gotos.copy()
@@ -340,6 +350,10 @@ class StructuringOptimizationPass(OptimizationPass):
340
350
  self.out_graph = None
341
351
  return
342
352
 
353
+ if self._must_improve_rel_quality and not self._improves_relative_quality():
354
+ self.out_graph = None
355
+ return
356
+
343
357
  def _fixed_point_analyze(self, cache=None):
344
358
  for _ in range(self._max_opt_iters):
345
359
  if self._require_gotos and not self._goto_manager.gotos:
@@ -359,7 +373,7 @@ class StructuringOptimizationPass(OptimizationPass):
359
373
  self.out_graph = self._prev_graph if self._recover_structure_fails else None
360
374
  break
361
375
 
362
- def _graph_is_structurable(self, graph, readd_labels=False) -> bool:
376
+ def _graph_is_structurable(self, graph, readd_labels=False, initial=False) -> bool:
363
377
  """
364
378
  Checks weather the input graph is structurable under the Phoenix schema-matching structuring algorithm.
365
379
  As a side effect, this will also update the region identifier and goto manager of this optimization pass.
@@ -380,18 +394,74 @@ class StructuringOptimizationPass(OptimizationPass):
380
394
  if self._ri is None:
381
395
  return False
382
396
 
383
- rs = self.project.analyses[RecursiveStructurer].prep(kb=self.kb)(
384
- self._ri.region,
385
- cond_proc=self._ri.cond_proc,
386
- func=self._func,
387
- structurer_cls=PhoenixStructurer,
388
- )
397
+ # we should try-catch structuring here because we can often pass completely invalid graphs
398
+ # that break the assumptions of the structuring algorithm
399
+ try:
400
+ rs = self.project.analyses[RecursiveStructurer].prep(kb=self.kb)(
401
+ self._ri.region,
402
+ cond_proc=self._ri.cond_proc,
403
+ func=self._func,
404
+ structurer_cls=PhoenixStructurer,
405
+ )
406
+ # pylint:disable=broad-except
407
+ except Exception:
408
+ _l.warning("Internal structuring failed for OptimizationPass on %s", self._func.name)
409
+ rs = None
410
+
389
411
  if not rs or not rs.result or not rs.result.nodes or rs.result_incomplete:
390
412
  return False
391
413
 
392
414
  rs = self.project.analyses.RegionSimplifier(self._func, rs.result, kb=self.kb, variable_kb=self._variable_kb)
393
- if not rs or rs.goto_manager is None:
415
+ if not rs or rs.goto_manager is None or rs.result is None:
394
416
  return False
395
417
 
418
+ self._analyze_simplified_region(rs.result, initial=initial)
396
419
  self._goto_manager = rs.goto_manager
397
420
  return True
421
+
422
+ # pylint:disable=no-self-use
423
+ def _analyze_simplified_region(self, region, initial=False):
424
+ """
425
+ Analyze the simplified regions after a successful structuring pass.
426
+ This should be overridden by the subclass if it needs to do anything with the simplified regions for making
427
+ optimizations decisions.
428
+ """
429
+ if region is None:
430
+ return
431
+
432
+ # record quality metrics
433
+ if self._must_improve_rel_quality:
434
+ if initial:
435
+ self._initial_structure_counter = ControlFlowStructureCounter(region)
436
+ else:
437
+ self._current_structure_counter = ControlFlowStructureCounter(region)
438
+
439
+ def _improves_relative_quality(self) -> bool:
440
+ """
441
+ Checks if the new structured output improves (or maintains) the relative quality of the control flow structures
442
+ present in the function.
443
+
444
+ For now, this only involves loops
445
+ """
446
+ if self._initial_structure_counter is None or self._current_structure_counter is None:
447
+ _l.warning("Relative quality check failed due to missing structure counters")
448
+ return True
449
+
450
+ prev_wloops = self._initial_structure_counter.while_loops
451
+ curr_wloops = self._current_structure_counter.while_loops
452
+ prev_dloops = self._initial_structure_counter.do_while_loops
453
+ curr_dloops = self._current_structure_counter.do_while_loops
454
+ prev_floops = self._initial_structure_counter.for_loops
455
+ curr_floops = self._current_structure_counter.for_loops
456
+ total_prev_loops = prev_wloops + prev_dloops + prev_floops
457
+ total_curr_loops = curr_wloops + curr_dloops + curr_floops
458
+
459
+ # Sometimes, if we mess up structuring you can easily tell because we traded "good" loops for "bad" loops.
460
+ # Generally, loops are ordered good -> bad as follows: for, while, do-while.
461
+ # Note: this check is only for _trading_, meaning the total number of loops must be the same.
462
+ #
463
+ # 1. We traded to remove a for-loop
464
+ if curr_floops < prev_floops and total_curr_loops == total_prev_loops:
465
+ return False
466
+
467
+ return True
@@ -38,6 +38,7 @@ class ReturnDuplicatorBase:
38
38
  self.node_idx = count(start=node_idx_start)
39
39
  self._max_calls_in_region = max_calls_in_regions
40
40
  self._minimize_copies_for_regions = minimize_copies_for_regions
41
+ self._supergraph = None
41
42
 
42
43
  # this should also be set by the optimization passes initer
43
44
  self._func = func
@@ -71,6 +72,8 @@ class ReturnDuplicatorBase:
71
72
  # for connected in_edges that form a region
72
73
  endnode_regions = self._copy_connected_edge_components(endnode_regions, graph)
73
74
 
75
+ # refresh the supergraph
76
+ self._supergraph = to_ail_supergraph(graph)
74
77
  for region_head, (in_edges, region) in endnode_regions.items():
75
78
  is_single_const_ret_region = self._is_simple_return_graph(region)
76
79
  for in_edge in in_edges:
@@ -150,6 +153,7 @@ class ReturnDuplicatorBase:
150
153
  else:
151
154
  node_copy = copy.deepcopy(node)
152
155
  node_copy.idx = next(self.node_idx)
156
+ self._fix_copied_node_labels(node_copy)
153
157
  copies[node] = node_copy
154
158
 
155
159
  # modify Jump.target_idx and ConditionalJump.{true,false}_target_idx accordingly
@@ -446,3 +450,20 @@ class ReturnDuplicatorBase:
446
450
  all_region_block_sets = {}
447
451
  _unpack_every_region(top_region, all_region_block_sets)
448
452
  return all_region_block_sets
453
+
454
+ @staticmethod
455
+ def _fix_copied_node_labels(block: Block):
456
+ for i in range(len(block.statements)): # pylint:disable=consider-using-enumerate
457
+ stmt = block.statements[i]
458
+ if isinstance(stmt, Label):
459
+ # fix the default name by suffixing it with the new block ID
460
+ new_name = stmt.name if stmt.name else f"Label_{stmt.ins_addr:x}"
461
+ if stmt.block_idx is not None:
462
+ suffix = f"__{stmt.block_idx}"
463
+ if new_name.endswith(suffix):
464
+ new_name = new_name[: -len(suffix)]
465
+ else:
466
+ new_name = stmt.name
467
+ new_name += f"__{block.idx}"
468
+
469
+ block.statements[i] = Label(stmt.idx, new_name, stmt.ins_addr, block_idx=block.idx, **stmt.tags)
@@ -4,7 +4,7 @@ import inspect
4
4
  import networkx
5
5
 
6
6
  from ailment import Block
7
- from ailment.statement import ConditionalJump
7
+ from ailment.statement import ConditionalJump, Label
8
8
 
9
9
  from .return_duplicator_base import ReturnDuplicatorBase
10
10
  from .optimization_pass import StructuringOptimizationPass
@@ -71,23 +71,29 @@ class ReturnDuplicatorLow(StructuringOptimizationPass, ReturnDuplicatorBase):
71
71
  return ReturnDuplicatorBase._check(self)
72
72
 
73
73
  def _should_duplicate_dst(self, src, dst, graph, dst_is_const_ret=False):
74
- return self._is_goto_edge(src, dst, graph=graph, check_for_ifstmts=True)
74
+ return self._is_goto_edge(src, dst, graph=graph)
75
75
 
76
76
  def _is_goto_edge(
77
77
  self,
78
78
  src: Block,
79
79
  dst: Block,
80
80
  graph: networkx.DiGraph = None,
81
- check_for_ifstmts=True,
82
81
  max_level_check=1,
83
82
  ):
84
83
  """
85
- TODO: correct how goto edge addressing works
84
+ TODO: Implement a more principled way of checking if an edge is a goto edge with Phoenix's structuring info
86
85
  This function only exists because a long-standing bug that sometimes reports the if-stmt addr
87
- above a goto edge as the goto src. Because of this, we need to check for predecessors above the goto and
88
- see if they are a goto. This needs to include Jump to deal with loops.
86
+ above a goto edge as the goto src.
89
87
  """
90
- if check_for_ifstmts and graph is not None:
88
+ # Do a simple and fast check first
89
+ is_simple_goto = self._goto_manager.is_goto_edge(src, dst)
90
+ if is_simple_goto:
91
+ return True
92
+
93
+ if graph is not None:
94
+ # Special case 1:
95
+ # We need to check for predecessors above the goto and see if they are a goto.
96
+ # This needs to include Jump to deal with loops.
91
97
  blocks = [src]
92
98
  level_blocks = [src]
93
99
  for _ in range(max_level_check):
@@ -109,8 +115,104 @@ class ReturnDuplicatorLow(StructuringOptimizationPass, ReturnDuplicatorBase):
109
115
 
110
116
  if self._goto_manager.is_goto_edge(block, dst):
111
117
  return True
112
- else:
113
- return self._goto_manager.is_goto_edge(src, dst)
118
+
119
+ # Special case 2: A "goto edge" that ReturnDuplicator wants to test might be an edge that Phoenix
120
+ # includes in its loop region (during the cyclic refinement). In fact, Phoenix tends to include as many
121
+ # nodes as possible into the loop region, and generate a goto edge (which ends up in the structured code)
122
+ # from `dst` to the loop successor.
123
+ # an example of this is captured by the test case `TestDecompiler.test_stty_recover_mode_ret_dup_region`.
124
+ # until someone (ideally @mahaloz) implements a more principled way of translating "goto statements" that
125
+ # Phoenix generates and "goto edges" that ReturnDuplicator tests, we rely on the following stopgap to
126
+ # handle this case.
127
+ node = dst
128
+ while True:
129
+ succs = list(graph.successors(node))
130
+ if len(succs) != 1:
131
+ break
132
+ succ = succs[0]
133
+ if succ is node:
134
+ # loop!
135
+ break
136
+ succ_preds = list(graph.predecessors(succ))
137
+ if len(succ_preds) != 1:
138
+ break
139
+ if self._goto_manager.is_goto_edge(node, succ):
140
+ return True
141
+ # keep testing the next edge
142
+ node = succ
143
+
144
+ # Special case 3: In Phoenix, regions full of only if-stmts can be collapsed and moved. This causes
145
+ # the goto manager to report gotos that are at the top of the region instead of ones in the middle of it.
146
+ # Because of this, we need to gather all the nodes above the original src and check if any of them
147
+ # go to the destination. Additionally, we need to do this on the supergraph to get rid of
148
+ # goto edges that are removed by Phoenix.
149
+ # This case is observed in the test case `TestDecompiler.test_tail_tail_bytes_ret_dup`.
150
+ if self._supergraph is None:
151
+ return False
152
+
153
+ super_to_og_nodes = {n: self._supergraph.nodes[n]["original_nodes"] for n in self._supergraph.nodes}
154
+ og_to_super_nodes = {og: super_n for super_n, ogs in super_to_og_nodes.items() for og in ogs}
155
+ super_src = og_to_super_nodes.get(src, None)
156
+ super_dst = og_to_super_nodes.get(dst, None)
157
+ if super_src is None or super_dst is None:
158
+ return False
159
+
160
+ # collect all nodes which have only an if-stmt in them that are ancestors of super_src
161
+ check_blks = {super_src}
162
+ level_blocks = {super_src}
163
+ for _ in range(10):
164
+ done = False
165
+ if_blks = set()
166
+ for lblock in level_blocks:
167
+ preds = list(self._supergraph.predecessors(lblock))
168
+ for pred in preds:
169
+ only_cond_jump = all(isinstance(s, (ConditionalJump, Label)) for s in pred.statements)
170
+ if only_cond_jump:
171
+ if_blks.add(pred)
172
+
173
+ done = len(if_blks) == 0
174
+
175
+ if done:
176
+ break
177
+
178
+ check_blks |= if_blks
179
+ level_blocks = if_blks
180
+
181
+ # convert all the found if-only super-blocks back into their original blocks
182
+ og_check_blocks = set()
183
+ for blk in check_blks:
184
+ og_check_blocks |= set(super_to_og_nodes[blk])
185
+
186
+ # check if any of the original blocks are gotos to the destination
187
+ goto_hits = 0
188
+ for block in og_check_blocks:
189
+ if self._goto_manager.is_goto_edge(block, dst):
190
+ goto_hits += 1
191
+
192
+ # Although it is good to find a goto in the if-only block region, having more than a single goto
193
+ # existing that goes to the same dst is a bad sign. This can be seen in the the following test:
194
+ # TestDecompiler.test_dd_iread_ret_dup_region
195
+ #
196
+ # It occurs when you have something like:
197
+ # ```
198
+ # if (a || c)
199
+ # goto target;
200
+ # target:
201
+ # return 0;
202
+ # ```
203
+ #
204
+ #
205
+ # This looks like an edge from (a, target) and (c, target) but it is actually a single edge.
206
+ # If you allow both to duplicate you get the following:
207
+ # ```
208
+ # if (a):
209
+ # return
210
+ # if (c):
211
+ # return
212
+ # ```
213
+ # This is not the desired behavior.
214
+ # So we need to check if there is only a single goto that goes to the destination.
215
+ return goto_hits == 1
114
216
 
115
217
  return False
116
218
 
@@ -30,6 +30,9 @@ class RedundantLabelRemover:
30
30
  self._walker0 = SequenceWalker(handlers=handlers0)
31
31
  self._walker0.walk(self.root)
32
32
 
33
+ # update jump targets
34
+ self._update_jump_targets()
35
+
33
36
  handlers1 = {
34
37
  ailment.Block: self._handle_Block,
35
38
  }
@@ -37,6 +40,20 @@ class RedundantLabelRemover:
37
40
  self._walker1.walk(self.root)
38
41
  self.result = self.root
39
42
 
43
+ def _update_jump_targets(self) -> None:
44
+ """
45
+ Update self._jump_targets after the first pass fills in self._new_jump_target.
46
+ """
47
+
48
+ if self._new_jump_target:
49
+ jump_targets = set()
50
+ for jt in self._jump_targets:
51
+ if jt in self._new_jump_target:
52
+ jump_targets.add(self._new_jump_target[jt])
53
+ else:
54
+ jump_targets.add(jt)
55
+ self._jump_targets = jump_targets
56
+
40
57
  #
41
58
  # Handlers
42
59
  #
@@ -0,0 +1,37 @@
1
+ from angr.analyses.decompiler.sequence_walker import SequenceWalker
2
+ from angr.analyses.decompiler.structuring.structurer_nodes import SwitchCaseNode, LoopNode
3
+
4
+
5
+ class ControlFlowStructureCounter(SequenceWalker):
6
+ """
7
+ Counts the number of different types of control flow structures found in a sequence of nodes.
8
+ This should be used after the sequence has been simplified.
9
+ """
10
+
11
+ def __init__(self, node):
12
+ handlers = {
13
+ LoopNode: self._handle_Loop,
14
+ }
15
+ super().__init__(handlers)
16
+
17
+ self.while_loops = 0
18
+ self.do_while_loops = 0
19
+ self.for_loops = 0
20
+
21
+ self.walk(node)
22
+
23
+ def _handle_Loop(self, node: LoopNode, **kwargs):
24
+ if node.sort == "while":
25
+ self.while_loops += 1
26
+ elif node.sort == "do-while":
27
+ self.do_while_loops += 1
28
+ elif node.sort == "for":
29
+ self.for_loops += 1
30
+
31
+ return super()._handle_Loop(node, **kwargs)
32
+
33
+ def _handle_Condition(self, node, parent=None, **kwargs):
34
+ return super()._handle_Condition(node, parent=parent, **kwargs)
35
+
36
+ def _handle_SwitchCase(self, node: SwitchCaseNode, parent=None, **kwargs):
37
+ return super()._handle_SwitchCase(node, parent=parent, **kwargs)
@@ -2769,9 +2769,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
2769
2769
  if offset == 0:
2770
2770
  data_type = renegotiate_type(data_type, base_type)
2771
2771
  if base_type == data_type or (
2772
- not isinstance(base_type, SimTypeBottom)
2773
- and not isinstance(data_type, SimTypeBottom)
2774
- and base_type.size < data_type.size
2772
+ base_type.size is not None and data_type.size is not None and base_type.size < data_type.size
2775
2773
  ):
2776
2774
  # case 1: we're done because we found it
2777
2775
  # case 2: we're done because we can never find it and we might as well stop early
@@ -2784,7 +2782,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
2784
2782
  return _force_type_cast(base_type, data_type, expr)
2785
2783
  return CUnaryOp("Dereference", expr, codegen=self)
2786
2784
 
2787
- if isinstance(base_type, SimTypeBottom):
2785
+ if base_type.size is None:
2788
2786
  stride = 1
2789
2787
  else:
2790
2788
  stride = base_type.size // self.project.arch.byte_width or 1
@@ -2968,7 +2966,7 @@ class CStructuredCodeGenerator(BaseStructuredCodeGenerator, Analysis):
2968
2966
  kernel_type = unpack_typeref(unpack_pointer(kernel.type))
2969
2967
  assert kernel_type
2970
2968
 
2971
- if isinstance(kernel_type, SimTypeBottom):
2969
+ if kernel_type.size is None:
2972
2970
  return bail_out()
2973
2971
  kernel_stride = kernel_type.size // self.project.arch.byte_width
2974
2972
 
@@ -3699,6 +3697,7 @@ class MakeTypecastsImplicit(CStructuredCodeWalker):
3699
3697
  and isinstance(intermediate_ty, (SimTypeChar, SimTypeInt, SimTypeNum))
3700
3698
  and isinstance(start_ty, (SimTypeChar, SimTypeInt, SimTypeNum))
3701
3699
  ):
3700
+ assert dst_ty.size and start_ty.size and intermediate_ty.size
3702
3701
  if dst_ty.size <= start_ty.size and dst_ty.size <= intermediate_ty.size:
3703
3702
  # this is a down- or neutral-cast with an intermediate step that doesn't matter
3704
3703
  result = child.expr
@@ -719,7 +719,7 @@ class PhoenixStructurer(StructurerBase):
719
719
  break_stmt = Jump(
720
720
  None,
721
721
  Const(None, None, successor.addr, self.project.arch.bits),
722
- None,
722
+ target_idx=successor.idx if isinstance(successor, Block) else None,
723
723
  ins_addr=last_src_stmt.ins_addr,
724
724
  )
725
725
  break_node = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
@@ -727,7 +727,7 @@ class PhoenixStructurer(StructurerBase):
727
727
  break_stmt = Jump(
728
728
  None,
729
729
  Const(None, None, successor.addr, self.project.arch.bits),
730
- None,
730
+ target_idx=successor.idx if isinstance(successor, Block) else None,
731
731
  ins_addr=last_src_stmt.ins_addr,
732
732
  )
733
733
  break_node_inner = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
@@ -744,7 +744,7 @@ class PhoenixStructurer(StructurerBase):
744
744
  break_stmt = Jump(
745
745
  None,
746
746
  Const(None, None, successor.addr, self.project.arch.bits),
747
- None,
747
+ target_idx=successor.idx if isinstance(successor, Block) else None,
748
748
  ins_addr=last_src_stmt.ins_addr,
749
749
  )
750
750
  break_node = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
@@ -93,6 +93,7 @@ class ReachingDefinitionsState:
93
93
  all_definitions: set[Definition] | None = None,
94
94
  initializer: Optional["RDAStateInitializer"] = None,
95
95
  element_limit: int = 5,
96
+ merge_into_tops: bool = True,
96
97
  ):
97
98
  # handy short-hands
98
99
  self.codeloc = codeloc
@@ -130,6 +131,7 @@ class ReachingDefinitionsState:
130
131
  track_tmps=self._track_tmps,
131
132
  canonical_size=canonical_size,
132
133
  element_limit=element_limit,
134
+ merge_into_tops=merge_into_tops,
133
135
  )
134
136
  if self.analysis is not None:
135
137
  self.live_definitions.project = self.analysis.project
@@ -76,6 +76,7 @@ class ReachingDefinitionsAnalysis(
76
76
  track_liveness: bool = True,
77
77
  func_addr: int | None = None,
78
78
  element_limit: int = 5,
79
+ merge_into_tops: bool = True,
79
80
  ):
80
81
  """
81
82
  :param subject: The subject of the analysis: a function, or a single basic block
@@ -110,6 +111,10 @@ class ReachingDefinitionsAnalysis(
110
111
  :param track_liveness: Whether to track liveness information. This can consume
111
112
  sizeable amounts of RAM on large functions. (e.g. ~15GB for a function
112
113
  with 4k nodes)
114
+ :param merge_into_tops: Merge known values into TOP if TOP is present.
115
+ If True: {TOP} V {0xabc} = {TOP}
116
+ If False: {TOP} V {0xabc} = {TOP, 0xabc}
117
+
113
118
 
114
119
  """
115
120
 
@@ -134,6 +139,7 @@ class ReachingDefinitionsAnalysis(
134
139
  self._use_callee_saved_regs_at_return = use_callee_saved_regs_at_return
135
140
  self._func_addr = func_addr
136
141
  self._element_limit = element_limit
142
+ self._merge_into_tops = merge_into_tops
137
143
 
138
144
  if dep_graph is None or dep_graph is False:
139
145
  self._dep_graph = None
@@ -473,6 +479,7 @@ class ReachingDefinitionsAnalysis(
473
479
  canonical_size=self._canonical_size,
474
480
  initializer=self._state_initializer,
475
481
  element_limit=self._element_limit,
482
+ merge_into_tops=self._merge_into_tops,
476
483
  )
477
484
 
478
485
  # pylint: disable=no-self-use,arguments-differ