angr 9.2.138__py3-none-manylinux2014_x86_64.whl → 9.2.140__py3-none-manylinux2014_x86_64.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 (100) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +48 -21
  3. angr/analyses/calling_convention/fact_collector.py +59 -12
  4. angr/analyses/calling_convention/utils.py +2 -2
  5. angr/analyses/cfg/cfg_base.py +13 -0
  6. angr/analyses/cfg/cfg_fast.py +23 -4
  7. angr/analyses/decompiler/ail_simplifier.py +79 -53
  8. angr/analyses/decompiler/block_simplifier.py +0 -2
  9. angr/analyses/decompiler/callsite_maker.py +80 -14
  10. angr/analyses/decompiler/clinic.py +99 -80
  11. angr/analyses/decompiler/condition_processor.py +2 -2
  12. angr/analyses/decompiler/decompiler.py +19 -7
  13. angr/analyses/decompiler/dephication/rewriting_engine.py +16 -7
  14. angr/analyses/decompiler/expression_narrower.py +1 -1
  15. angr/analyses/decompiler/optimization_passes/__init__.py +3 -0
  16. angr/analyses/decompiler/optimization_passes/condition_constprop.py +149 -0
  17. angr/analyses/decompiler/optimization_passes/const_prop_reverter.py +8 -7
  18. angr/analyses/decompiler/optimization_passes/deadblock_remover.py +12 -3
  19. angr/analyses/decompiler/optimization_passes/inlined_string_transformation_simplifier.py +1 -1
  20. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +21 -13
  21. angr/analyses/decompiler/optimization_passes/optimization_pass.py +21 -12
  22. angr/analyses/decompiler/optimization_passes/return_duplicator_base.py +17 -9
  23. angr/analyses/decompiler/optimization_passes/return_duplicator_high.py +7 -10
  24. angr/analyses/decompiler/peephole_optimizations/eager_eval.py +12 -1
  25. angr/analyses/decompiler/peephole_optimizations/remove_redundant_conversions.py +61 -25
  26. angr/analyses/decompiler/peephole_optimizations/remove_redundant_shifts.py +50 -1
  27. angr/analyses/decompiler/presets/fast.py +2 -0
  28. angr/analyses/decompiler/presets/full.py +2 -0
  29. angr/analyses/decompiler/region_simplifiers/expr_folding.py +259 -108
  30. angr/analyses/decompiler/region_simplifiers/region_simplifier.py +28 -9
  31. angr/analyses/decompiler/ssailification/rewriting_engine.py +20 -2
  32. angr/analyses/decompiler/ssailification/traversal_engine.py +4 -3
  33. angr/analyses/decompiler/structured_codegen/c.py +10 -3
  34. angr/analyses/decompiler/structuring/dream.py +28 -19
  35. angr/analyses/decompiler/structuring/phoenix.py +253 -89
  36. angr/analyses/decompiler/structuring/recursive_structurer.py +1 -0
  37. angr/analyses/decompiler/structuring/structurer_base.py +121 -46
  38. angr/analyses/decompiler/structuring/structurer_nodes.py +6 -1
  39. angr/analyses/decompiler/utils.py +60 -1
  40. angr/analyses/deobfuscator/api_obf_finder.py +13 -5
  41. angr/analyses/deobfuscator/api_obf_type2_finder.py +166 -0
  42. angr/analyses/deobfuscator/string_obf_finder.py +105 -18
  43. angr/analyses/forward_analysis/forward_analysis.py +1 -1
  44. angr/analyses/propagator/top_checker_mixin.py +6 -6
  45. angr/analyses/reaching_definitions/__init__.py +2 -1
  46. angr/analyses/reaching_definitions/dep_graph.py +1 -12
  47. angr/analyses/reaching_definitions/engine_vex.py +36 -31
  48. angr/analyses/reaching_definitions/function_handler.py +15 -2
  49. angr/analyses/reaching_definitions/rd_state.py +1 -37
  50. angr/analyses/reaching_definitions/reaching_definitions.py +13 -24
  51. angr/analyses/s_propagator.py +129 -87
  52. angr/analyses/s_reaching_definitions/s_rda_model.py +7 -1
  53. angr/analyses/s_reaching_definitions/s_rda_view.py +2 -2
  54. angr/analyses/s_reaching_definitions/s_reaching_definitions.py +3 -1
  55. angr/analyses/stack_pointer_tracker.py +36 -22
  56. angr/analyses/typehoon/simple_solver.py +45 -7
  57. angr/analyses/typehoon/typeconsts.py +18 -5
  58. angr/analyses/variable_recovery/engine_ail.py +1 -1
  59. angr/analyses/variable_recovery/engine_base.py +62 -67
  60. angr/analyses/variable_recovery/engine_vex.py +1 -1
  61. angr/analyses/variable_recovery/irsb_scanner.py +2 -2
  62. angr/block.py +69 -107
  63. angr/callable.py +14 -7
  64. angr/calling_conventions.py +81 -10
  65. angr/distributed/__init__.py +1 -1
  66. angr/engines/__init__.py +7 -8
  67. angr/engines/engine.py +3 -138
  68. angr/engines/failure.py +2 -2
  69. angr/engines/hook.py +2 -2
  70. angr/engines/light/engine.py +5 -10
  71. angr/engines/pcode/emulate.py +2 -2
  72. angr/engines/pcode/engine.py +2 -14
  73. angr/engines/pcode/lifter.py +2 -2
  74. angr/engines/procedure.py +2 -2
  75. angr/engines/soot/engine.py +2 -2
  76. angr/engines/soot/statements/switch.py +1 -1
  77. angr/engines/successors.py +123 -17
  78. angr/engines/syscall.py +2 -2
  79. angr/engines/unicorn.py +3 -3
  80. angr/engines/vex/heavy/heavy.py +3 -15
  81. angr/engines/vex/lifter.py +2 -2
  82. angr/engines/vex/light/light.py +2 -2
  83. angr/factory.py +4 -19
  84. angr/knowledge_plugins/cfg/cfg_model.py +3 -2
  85. angr/knowledge_plugins/key_definitions/atoms.py +8 -4
  86. angr/knowledge_plugins/key_definitions/live_definitions.py +41 -103
  87. angr/knowledge_plugins/labels.py +2 -2
  88. angr/knowledge_plugins/obfuscations.py +1 -0
  89. angr/knowledge_plugins/xrefs/xref_manager.py +4 -0
  90. angr/sim_type.py +19 -17
  91. angr/state_plugins/plugin.py +19 -4
  92. angr/storage/memory_mixins/memory_mixin.py +1 -1
  93. angr/storage/memory_mixins/paged_memory/pages/multi_values.py +10 -5
  94. angr/utils/ssa/__init__.py +119 -4
  95. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/METADATA +6 -6
  96. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/RECORD +100 -98
  97. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/LICENSE +0 -0
  98. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/WHEEL +0 -0
  99. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/entry_points.txt +0 -0
  100. {angr-9.2.138.dist-info → angr-9.2.140.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,7 @@ from ailment.expression import Const, UnaryOp, MultiStatementExpression
15
15
 
16
16
  from angr.utils.graph import GraphUtils
17
17
  from angr.utils.ail import is_phi_assignment
18
- from angr.knowledge_plugins.cfg import IndirectJumpType
18
+ from angr.knowledge_plugins.cfg import IndirectJump, IndirectJumpType
19
19
  from angr.utils.constants import SWITCH_MISSING_DEFAULT_NODE_ADDR
20
20
  from angr.utils.graph import dominates, to_acyclic_graph, dfs_back_edges
21
21
  from angr.analyses.decompiler.sequence_walker import SequenceWalker
@@ -28,6 +28,7 @@ from angr.analyses.decompiler.utils import (
28
28
  has_nonlabel_nonphi_statements,
29
29
  first_nonlabel_nonphi_statement,
30
30
  switch_extract_bitwiseand_jumptable_info,
31
+ switch_extract_switch_expr_from_jump_target,
31
32
  )
32
33
  from angr.analyses.decompiler.counters.call_counter import AILCallCounter
33
34
  from .structurer_nodes import (
@@ -241,6 +242,7 @@ class PhoenixStructurer(StructurerBase):
241
242
  def _match_cyclic_schemas(self, node, head, graph, full_graph) -> bool:
242
243
  matched, loop_node, successor_node = self._match_cyclic_while(node, head, graph, full_graph)
243
244
  if matched:
245
+ assert loop_node is not None and successor_node is not None
244
246
  # traverse this node and rewrite all conditional jumps that go outside the loop to breaks
245
247
  self._rewrite_conditional_jumps_to_breaks(loop_node.sequence_node, [successor_node.addr])
246
248
  # traverse this node and rewrite all jumps that go to the beginning of the loop to continue
@@ -249,6 +251,7 @@ class PhoenixStructurer(StructurerBase):
249
251
 
250
252
  matched, loop_node, successor_node = self._match_cyclic_dowhile(node, head, graph, full_graph)
251
253
  if matched:
254
+ assert loop_node is not None and successor_node is not None
252
255
  # traverse this node and rewrite all conditional jumps that go outside the loop to breaks
253
256
  self._rewrite_conditional_jumps_to_breaks(loop_node.sequence_node, [successor_node.addr])
254
257
  # traverse this node and rewrite all jumps that go to the beginning of the loop to continue
@@ -260,6 +263,7 @@ class PhoenixStructurer(StructurerBase):
260
263
  node, head, graph, full_graph
261
264
  )
262
265
  if matched:
266
+ assert loop_node is not None and successor_node is not None
263
267
  # traverse this node and rewrite all conditional jumps that go outside the loop to breaks
264
268
  self._rewrite_conditional_jumps_to_breaks(loop_node.sequence_node, [successor_node.addr])
265
269
  # traverse this node and rewrite all jumps that go to the beginning of the loop to continue
@@ -268,6 +272,7 @@ class PhoenixStructurer(StructurerBase):
268
272
 
269
273
  matched, loop_node = self._match_cyclic_natural_loop(node, head, graph, full_graph)
270
274
  if matched:
275
+ assert loop_node is not None
271
276
  if self._region.successors is not None and len(self._region.successors) == 1:
272
277
  # traverse this node and rewrite all conditional jumps that go outside the loop to breaks
273
278
  self._rewrite_conditional_jumps_to_breaks(
@@ -318,7 +323,7 @@ class PhoenixStructurer(StructurerBase):
318
323
  # otherwise it's a do-while loop
319
324
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, head_block, left)
320
325
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, head_block, right)
321
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
326
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
322
327
  # c = !c
323
328
  if head_block_idx == 0:
324
329
  self._remove_first_statement_if_jump(head_block)
@@ -346,7 +351,7 @@ class PhoenixStructurer(StructurerBase):
346
351
  if head_block is not None:
347
352
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, head_block, left)
348
353
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, head_block, right)
349
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
354
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
350
355
  # c = !c
351
356
  if PhoenixStructurer._is_single_statement_block(node):
352
357
  # the single-statement-block check is to ensure we don't execute any code before the
@@ -366,6 +371,7 @@ class PhoenixStructurer(StructurerBase):
366
371
  return True, loop_node, right
367
372
  # we generate a while-true loop instead
368
373
  last_stmt = self._remove_last_statement_if_jump(head_block)
374
+ assert last_stmt is not None
369
375
  cond_jump = Jump(
370
376
  None,
371
377
  Const(None, None, right.addr, self.project.arch.bits),
@@ -394,7 +400,7 @@ class PhoenixStructurer(StructurerBase):
394
400
  if head_block is not None:
395
401
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, head_block, left)
396
402
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, head_block, right)
397
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
403
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
398
404
  # c = !c
399
405
  self._remove_last_statement_if_jump(head_block)
400
406
  cond_break = ConditionalBreakNode(node.addr, edge_cond_right, right.addr)
@@ -426,6 +432,8 @@ class PhoenixStructurer(StructurerBase):
426
432
  return False, None, None
427
433
 
428
434
  loop_cond = None
435
+ successor_node = None
436
+ succ_node_is_true_node = None
429
437
  if (
430
438
  isinstance(node, SequenceNode)
431
439
  and node.nodes
@@ -436,21 +444,27 @@ class PhoenixStructurer(StructurerBase):
436
444
  and node.nodes[-1].true_node is not None
437
445
  and node.nodes[-1].false_node is not None
438
446
  ):
439
- successor_node = node.nodes[-1].true_node
440
- # test if the successor_node returns or not
441
- # FIXME: It might be too strict
442
- try:
443
- last_stmt = self.cond_proc.get_last_statement(successor_node)
444
- except EmptyBlockNotice:
445
- last_stmt = None
446
- if last_stmt is not None and isinstance(last_stmt, Return):
447
+ # try both true node and false node; pick the first node with only Returns as last statements as the
448
+ # successor.
449
+ if self._cyclic_while_with_single_successor_must_return(node.nodes[-1].true_node):
450
+ succ_node_is_true_node = True
451
+ successor_node = node.nodes[-1].true_node
447
452
  loop_cond = claripy.Not(node.nodes[-1].condition)
453
+ elif self._cyclic_while_with_single_successor_must_return(node.nodes[-1].false_node):
454
+ succ_node_is_true_node = False
455
+ successor_node = node.nodes[-1].false_node
456
+ loop_cond = node.nodes[-1].condition
457
+ else:
458
+ loop_cond = None
448
459
 
449
460
  if loop_cond is None:
450
461
  return False, None, None
451
462
 
452
463
  node_copy = node.copy()
453
- node_copy.nodes[-1] = node_copy.nodes[-1].false_node # replace the last node with the false node
464
+ # replace the last node with the intended successor node
465
+ node_copy.nodes[-1] = (
466
+ node_copy.nodes[-1].false_node if succ_node_is_true_node else node_copy.nodes[-1].true_node
467
+ )
454
468
  # check if there is a cycle that starts with node and ends with node
455
469
  next_node = node
456
470
  seq_node = SequenceNode(node.addr, nodes=[node_copy])
@@ -487,6 +501,15 @@ class PhoenixStructurer(StructurerBase):
487
501
 
488
502
  return True, loop_node, successor_node
489
503
 
504
+ def _cyclic_while_with_single_successor_must_return(self, successor_node: SequenceNode) -> bool:
505
+ try:
506
+ last_stmts = self.cond_proc.get_last_statements(successor_node)
507
+ except EmptyBlockNotice:
508
+ return False
509
+ if not last_stmts:
510
+ return False
511
+ return all(isinstance(stmt, Return) for stmt in last_stmts)
512
+
490
513
  def _match_cyclic_dowhile(self, node, head, graph, full_graph) -> tuple[bool, LoopNode | None, BaseNode | None]:
491
514
  preds = list(full_graph.predecessors(node))
492
515
  succs = list(full_graph.successors(node))
@@ -504,7 +527,7 @@ class PhoenixStructurer(StructurerBase):
504
527
  if succ_block is not None:
505
528
  edge_cond_succhead = self.cond_proc.recover_edge_condition(full_graph, succ_block, node)
506
529
  edge_cond_succout = self.cond_proc.recover_edge_condition(full_graph, succ_block, out_node)
507
- if claripy.is_true(claripy.Not(edge_cond_succhead) == edge_cond_succout):
530
+ if claripy.is_true(claripy.Not(edge_cond_succhead) == edge_cond_succout): # type: ignore
508
531
  # c = !c
509
532
  self._remove_last_statement_if_jump(succ)
510
533
  drop_succ = False
@@ -543,7 +566,7 @@ class PhoenixStructurer(StructurerBase):
543
566
  # possible candidate
544
567
  edge_cond_head = self.cond_proc.recover_edge_condition(full_graph, node, node)
545
568
  edge_cond_head_succ = self.cond_proc.recover_edge_condition(full_graph, node, succ)
546
- if claripy.is_true(claripy.Not(edge_cond_head) == edge_cond_head_succ):
569
+ if claripy.is_true(claripy.Not(edge_cond_head) == edge_cond_head_succ): # type: ignore
547
570
  # c = !c
548
571
  self._remove_last_statement_if_jump(node)
549
572
  seq_node = SequenceNode(node.addr, nodes=[node]) if not isinstance(node, SequenceNode) else node
@@ -617,9 +640,11 @@ class PhoenixStructurer(StructurerBase):
617
640
 
618
641
  def _refine_cyclic_core(self, loop_head) -> bool:
619
642
  graph: networkx.DiGraph = self._region.graph
620
- fullgraph: networkx.DiGraph = self._region.graph_with_successors
621
- if fullgraph is None:
622
- fullgraph = networkx.DiGraph(self._region.graph)
643
+ fullgraph: networkx.DiGraph = (
644
+ self._region.graph_with_successors
645
+ if self._region.graph_with_successors is not None
646
+ else networkx.DiGraph(self._region.graph)
647
+ )
623
648
 
624
649
  # check if there is an out-going edge from the loop head
625
650
  head_succs = list(fullgraph.successors(loop_head))
@@ -638,6 +663,7 @@ class PhoenixStructurer(StructurerBase):
638
663
  # for now, we handle the most common case: both successors exist in the graph of the parent region, and
639
664
  # one successor has a path to the other successor
640
665
  if is_while and is_dowhile and self._parent_region is not None:
666
+ assert result_while is not None and result_dowhile is not None
641
667
  succ_while = result_while[-1]
642
668
  succ_dowhile = result_dowhile[-1]
643
669
  if succ_while in self._parent_region.graph and succ_dowhile in self._parent_region.graph:
@@ -651,9 +677,11 @@ class PhoenixStructurer(StructurerBase):
651
677
  is_while = False
652
678
 
653
679
  if is_while:
680
+ assert result_while is not None
654
681
  loop_type = "while"
655
682
  continue_edges, outgoing_edges, continue_node, successor = result_while
656
683
  elif is_dowhile:
684
+ assert result_dowhile is not None
657
685
  loop_type = "do-while"
658
686
  continue_edges, outgoing_edges, continue_node, successor = result_dowhile
659
687
 
@@ -736,7 +764,9 @@ class PhoenixStructurer(StructurerBase):
736
764
  # unwanted results, e.g., inserting a break (that's intended to break out of the loop) inside a
737
765
  # switch-case that is nested within a loop.
738
766
  last_src_stmt = self.cond_proc.get_last_statement(src_block)
767
+ assert last_src_stmt is not None
739
768
  break_cond = self.cond_proc.recover_edge_condition(fullgraph, src_block, dst)
769
+ assert successor.addr is not None
740
770
  if claripy.is_true(break_cond):
741
771
  break_stmt = Jump(
742
772
  None,
@@ -761,7 +791,9 @@ class PhoenixStructurer(StructurerBase):
761
791
  )
762
792
  new_node = SequenceNode(src_block.addr, nodes=[src_block, break_node])
763
793
  if has_continue:
764
- if self.is_a_jump_target(last_src_stmt, continue_node.addr):
794
+ if continue_node.addr is not None and self.is_a_jump_target(
795
+ last_src_stmt, continue_node.addr
796
+ ):
765
797
  # instead of a conditional break node, we should insert a condition node instead
766
798
  break_stmt = Jump(
767
799
  None,
@@ -817,7 +849,7 @@ class PhoenixStructurer(StructurerBase):
817
849
  if fullgraph.in_degree[dst] == 0:
818
850
  # drop this node
819
851
  fullgraph.remove_node(dst)
820
- if dst in self._region.successors:
852
+ if self._region.successors and dst in self._region.successors:
821
853
  self._region.successors.remove(dst)
822
854
 
823
855
  if len(continue_edges) > 1:
@@ -839,7 +871,7 @@ class PhoenixStructurer(StructurerBase):
839
871
  if cont_block is None:
840
872
  # cont_block is not found. but it's ok. one possibility is that src is a jump table head with one
841
873
  # case being the loop head. in such cases, we can just remove the edge.
842
- if src.addr not in self.kb.cfgs["CFGFast"].jump_tables:
874
+ if src.addr not in self.jump_tables:
843
875
  l.debug(
844
876
  "_refine_cyclic_core: Cannot find the block going to loop head for edge %r -> %r. "
845
877
  "Remove the edge anyway.",
@@ -1040,25 +1072,29 @@ class PhoenixStructurer(StructurerBase):
1040
1072
  # switch cases
1041
1073
 
1042
1074
  def _match_acyclic_switch_cases(self, graph: networkx.DiGraph, full_graph: networkx.DiGraph, node) -> bool:
1043
- if isinstance(node, (SwitchCaseNode, IncompleteSwitchCaseNode)):
1075
+ if isinstance(node, SwitchCaseNode):
1076
+ return False
1077
+
1078
+ r = self._match_acyclic_switch_cases_address_loaded_from_memory_no_default_node(node, graph, full_graph)
1079
+ if r:
1080
+ return r
1081
+
1082
+ if isinstance(node, IncompleteSwitchCaseNode):
1044
1083
  return False
1045
1084
 
1046
1085
  r = self._match_acyclic_switch_cases_incomplete_switch_head(node, graph, full_graph)
1047
1086
  if r:
1048
1087
  return r
1049
- jump_tables = self.kb.cfgs["CFGFast"].jump_tables
1050
- r = self._match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(
1051
- node, graph, full_graph, jump_tables
1052
- )
1088
+ r = self._match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(node, graph, full_graph)
1053
1089
  if r:
1054
1090
  return r
1055
- r = self._match_acyclic_switch_cases_address_loaded_from_memory(node, graph, full_graph, jump_tables)
1091
+ r = self._match_acyclic_switch_cases_address_loaded_from_memory(node, graph, full_graph)
1056
1092
  if r:
1057
1093
  return r
1058
- r = self._match_acyclic_switch_cases_address_computed(node, graph, full_graph, jump_tables)
1094
+ r = self._match_acyclic_switch_cases_address_computed(node, graph, full_graph)
1059
1095
  if r:
1060
1096
  return r
1061
- return self._match_acyclic_incomplete_switch_cases(node, graph, full_graph, jump_tables)
1097
+ return self._match_acyclic_incomplete_switch_cases(node, graph, full_graph)
1062
1098
 
1063
1099
  def _match_acyclic_switch_cases_incomplete_switch_head(self, node, graph, full_graph) -> bool:
1064
1100
  try:
@@ -1074,7 +1110,7 @@ class PhoenixStructurer(StructurerBase):
1074
1110
 
1075
1111
  # make a fake jumptable
1076
1112
  node_default_addr = None
1077
- case_entries: dict[int, tuple[int, int | None]] = {}
1113
+ case_entries: dict[int, int | tuple[int, int | None]] = {}
1078
1114
  for _, case_value, case_target_addr, case_target_idx, _ in last_stmt.case_addrs:
1079
1115
  if isinstance(case_value, str):
1080
1116
  if case_value == "default":
@@ -1127,28 +1163,33 @@ class PhoenixStructurer(StructurerBase):
1127
1163
  if self._region.graph_with_successors is not None:
1128
1164
  self._region.graph_with_successors.remove_node(o)
1129
1165
 
1130
- self._switch_handle_gotos(cases, node_default, None)
1166
+ switch_end_addr = self._switch_find_switch_end_addr(cases, node_default, {nn.addr for nn in self._region.graph})
1167
+ if switch_end_addr is not None:
1168
+ self._switch_handle_gotos(cases, node_default, switch_end_addr)
1131
1169
  return True
1132
1170
 
1133
- def _match_acyclic_switch_cases_address_loaded_from_memory(self, node, graph, full_graph, jump_tables) -> bool:
1171
+ def _match_acyclic_switch_cases_address_loaded_from_memory(self, node, graph, full_graph) -> bool:
1134
1172
  try:
1135
1173
  last_stmt = self.cond_proc.get_last_statement(node)
1136
1174
  except EmptyBlockNotice:
1137
1175
  return False
1138
1176
 
1177
+ if last_stmt is None:
1178
+ return False
1179
+
1139
1180
  successor_addrs = extract_jump_targets(last_stmt)
1140
1181
  if len(successor_addrs) != 2:
1141
1182
  return False
1142
1183
 
1143
1184
  for t in successor_addrs:
1144
- if t in jump_tables:
1185
+ if t in self.jump_tables:
1145
1186
  # this is a candidate!
1146
1187
  target = t
1147
1188
  break
1148
1189
  else:
1149
1190
  return False
1150
1191
 
1151
- jump_table = jump_tables[target]
1192
+ jump_table = self.jump_tables[target]
1152
1193
  if jump_table.type != IndirectJumpType.Jumptable_AddressLoadedFromMemory:
1153
1194
  return False
1154
1195
 
@@ -1168,6 +1209,7 @@ class PhoenixStructurer(StructurerBase):
1168
1209
  return False
1169
1210
 
1170
1211
  # populate whitelist_edges
1212
+ assert jump_table.jumptable_entries is not None
1171
1213
  for case_node_addr in jump_table.jumptable_entries:
1172
1214
  self.whitelist_edges.add((node_a.addr, case_node_addr))
1173
1215
  self.whitelist_edges.add((node.addr, node_b_addr))
@@ -1193,6 +1235,12 @@ class PhoenixStructurer(StructurerBase):
1193
1235
  if not r:
1194
1236
  return False
1195
1237
 
1238
+ node_default = self._switch_find_default_node(graph, node, node_b_addr)
1239
+ if node_default is not None:
1240
+ # ensure we have successfully structured node_default
1241
+ if full_graph.out_degree(node_default) > 1:
1242
+ return False
1243
+
1196
1244
  # un-structure IncompleteSwitchCaseNode
1197
1245
  if isinstance(node_a, SequenceNode) and node_a.nodes and isinstance(node_a.nodes[0], IncompleteSwitchCaseNode):
1198
1246
  _, new_seq_node = self._unpack_sequencenode_head(graph, node_a)
@@ -1222,7 +1270,9 @@ class PhoenixStructurer(StructurerBase):
1222
1270
  switch_end_addr = node_b_addr
1223
1271
  else:
1224
1272
  # we don't know what the end address of this switch-case structure is. let's figure it out
1225
- switch_end_addr = None
1273
+ switch_end_addr = self._switch_find_switch_end_addr(
1274
+ cases, node_default, {nn.addr for nn in self._region.graph}
1275
+ )
1226
1276
  to_remove.add(node_default)
1227
1277
 
1228
1278
  to_remove.add(node_a) # add node_a
@@ -1243,14 +1293,104 @@ class PhoenixStructurer(StructurerBase):
1243
1293
 
1244
1294
  # fully structured into a switch-case. remove node from switch_case_known_heads
1245
1295
  self.switch_case_known_heads.remove(node)
1246
- self._switch_handle_gotos(cases, node_default, switch_end_addr)
1296
+ if switch_end_addr is not None:
1297
+ self._switch_handle_gotos(cases, node_default, switch_end_addr)
1247
1298
 
1248
1299
  return True
1249
1300
 
1250
- def _match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(
1251
- self, node, graph, full_graph, jump_tables
1252
- ) -> bool:
1253
- if node.addr not in jump_tables:
1301
+ def _match_acyclic_switch_cases_address_loaded_from_memory_no_default_node(self, node, graph, full_graph) -> bool:
1302
+ # sanity checks
1303
+ if not isinstance(node, IncompleteSwitchCaseNode):
1304
+ return False
1305
+ if node.addr not in self.jump_tables:
1306
+ return False
1307
+ # ensure _match_acyclic_switch_cases_address_load_from_memory cannot structure its predecessor (and this node)
1308
+ preds = list(graph.predecessors(node))
1309
+ if len(preds) != 1:
1310
+ return False
1311
+ pred = preds[0]
1312
+ if full_graph.out_degree[pred] != 1:
1313
+ return False
1314
+ jump_table: IndirectJump = self.jump_tables[node.addr]
1315
+ if jump_table.type != IndirectJumpType.Jumptable_AddressLoadedFromMemory:
1316
+ return False
1317
+
1318
+ # extract the comparison expression, lower-, and upper-bounds from the last statement
1319
+ last_stmt = self.cond_proc.get_last_statement(node.head)
1320
+ if not isinstance(last_stmt, Jump):
1321
+ return False
1322
+ cmp_expr = switch_extract_switch_expr_from_jump_target(last_stmt.target)
1323
+ if cmp_expr is None:
1324
+ return False
1325
+ cmp_lb = 0
1326
+
1327
+ # populate whitelist_edges
1328
+ assert jump_table.jumptable_entries is not None
1329
+
1330
+ # sanity check: case nodes are successors to node_a. all case nodes must have at most common one successor
1331
+ node_pred = None
1332
+ if graph.in_degree[node] == 1:
1333
+ node_pred = next(iter(graph.predecessors(node)))
1334
+
1335
+ case_nodes = list(graph.successors(node))
1336
+
1337
+ # case 1: the common successor happens to be directly reachable from node_a (usually as a result of compiler
1338
+ # optimization)
1339
+ # example: touch_touch_no_switch.o:main
1340
+ r = self.switch_case_entry_node_has_common_successor_case_1(graph, jump_table, case_nodes, node_pred)
1341
+
1342
+ # case 2: the common successor is not directly reachable from node_a. this is a more common case.
1343
+ if not r:
1344
+ r |= self.switch_case_entry_node_has_common_successor_case_2(graph, jump_table, case_nodes, node_pred)
1345
+
1346
+ if not r:
1347
+ return False
1348
+
1349
+ # un-structure IncompleteSwitchCaseNode
1350
+ if isinstance(node, IncompleteSwitchCaseNode):
1351
+ r = self._unpack_incompleteswitchcasenode(graph, node)
1352
+ if not r:
1353
+ return False
1354
+ self._unpack_incompleteswitchcasenode(full_graph, node) # this shall not fail
1355
+ # update node
1356
+ node = next(iter(nn for nn in graph.nodes if nn.addr == jump_table.addr))
1357
+
1358
+ case_and_entry_addrs = self._find_case_and_entry_addrs(node, graph, cmp_lb, jump_table)
1359
+
1360
+ cases, _, to_remove = self._switch_build_cases(
1361
+ case_and_entry_addrs,
1362
+ node,
1363
+ node,
1364
+ None,
1365
+ graph,
1366
+ full_graph,
1367
+ )
1368
+
1369
+ # we don't know what the end address of this switch-case structure is. let's figure it out
1370
+ switch_end_addr = self._switch_find_switch_end_addr(cases, None, {nn.addr for nn in self._region.graph})
1371
+ r = self._make_switch_cases_core(
1372
+ node,
1373
+ cmp_expr,
1374
+ cases,
1375
+ None,
1376
+ None,
1377
+ last_stmt.ins_addr,
1378
+ to_remove,
1379
+ graph,
1380
+ full_graph,
1381
+ node_a=None,
1382
+ )
1383
+ if not r:
1384
+ return False
1385
+
1386
+ # fully structured into a switch-case. remove node from switch_case_known_heads
1387
+ if switch_end_addr is not None:
1388
+ self._switch_handle_gotos(cases, None, switch_end_addr)
1389
+
1390
+ return True
1391
+
1392
+ def _match_acyclic_switch_cases_address_loaded_from_memory_no_ob_check(self, node, graph, full_graph) -> bool:
1393
+ if node.addr not in self.jump_tables:
1254
1394
  return False
1255
1395
 
1256
1396
  try:
@@ -1260,7 +1400,7 @@ class PhoenixStructurer(StructurerBase):
1260
1400
  if not (isinstance(last_stmt, Jump) and not isinstance(last_stmt.target, Const)):
1261
1401
  return False
1262
1402
 
1263
- jump_table = jump_tables[node.addr]
1403
+ jump_table = self.jump_tables[node.addr]
1264
1404
  if jump_table.type != IndirectJumpType.Jumptable_AddressLoadedFromMemory:
1265
1405
  return False
1266
1406
 
@@ -1272,6 +1412,7 @@ class PhoenixStructurer(StructurerBase):
1272
1412
  case_count = cmp_ub - cmp_lb + 1
1273
1413
 
1274
1414
  # ensure we have the same number of cases
1415
+ assert jump_table.jumptable_entries is not None
1275
1416
  if case_count != len(jump_table.jumptable_entries):
1276
1417
  return False
1277
1418
 
@@ -1311,7 +1452,7 @@ class PhoenixStructurer(StructurerBase):
1311
1452
  )
1312
1453
 
1313
1454
  assert node_default is None
1314
- switch_end_addr = None
1455
+ switch_end_addr = self._switch_find_switch_end_addr(cases, node_default, {nn.addr for nn in self._region.graph})
1315
1456
 
1316
1457
  r = self._make_switch_cases_core(
1317
1458
  node,
@@ -1330,14 +1471,15 @@ class PhoenixStructurer(StructurerBase):
1330
1471
 
1331
1472
  # fully structured into a switch-case. remove node from switch_case_known_heads
1332
1473
  self.switch_case_known_heads.remove(node)
1333
- self._switch_handle_gotos(cases, node_default, switch_end_addr)
1474
+ if switch_end_addr is not None:
1475
+ self._switch_handle_gotos(cases, node_default, switch_end_addr)
1334
1476
 
1335
1477
  return True
1336
1478
 
1337
- def _match_acyclic_switch_cases_address_computed(self, node, graph, full_graph, jump_tables) -> bool:
1338
- if node.addr not in jump_tables:
1479
+ def _match_acyclic_switch_cases_address_computed(self, node, graph, full_graph) -> bool:
1480
+ if node.addr not in self.jump_tables:
1339
1481
  return False
1340
- jump_table = jump_tables[node.addr]
1482
+ jump_table = self.jump_tables[node.addr]
1341
1483
  if jump_table.type != IndirectJumpType.Jumptable_AddressComputed:
1342
1484
  return False
1343
1485
 
@@ -1364,9 +1506,16 @@ class PhoenixStructurer(StructurerBase):
1364
1506
 
1365
1507
  if isinstance(last_stmt.false_target, Const):
1366
1508
  default_addr = last_stmt.false_target.value
1509
+ assert isinstance(default_addr, int)
1367
1510
  else:
1368
1511
  return False
1369
1512
 
1513
+ node_default = self._switch_find_default_node(graph, node, default_addr)
1514
+ if node_default is not None:
1515
+ # ensure we have successfully structured node_default
1516
+ if full_graph.out_degree(node_default) > 1:
1517
+ return False
1518
+
1370
1519
  case_and_entry_addrs = self._find_case_and_entry_addrs(node, graph, cmp_lb, jump_table)
1371
1520
 
1372
1521
  cases, node_default, to_remove = self._switch_build_cases(
@@ -1386,10 +1535,10 @@ class PhoenixStructurer(StructurerBase):
1386
1535
  )
1387
1536
 
1388
1537
  def _match_acyclic_incomplete_switch_cases(
1389
- self, node, graph: networkx.DiGraph, full_graph: networkx.DiGraph, jump_tables: dict
1538
+ self, node, graph: networkx.DiGraph, full_graph: networkx.DiGraph
1390
1539
  ) -> bool:
1391
1540
  # sanity checks
1392
- if node.addr not in jump_tables:
1541
+ if node.addr not in self.jump_tables:
1393
1542
  return False
1394
1543
  if isinstance(node, IncompleteSwitchCaseNode):
1395
1544
  return False
@@ -1398,9 +1547,11 @@ class PhoenixStructurer(StructurerBase):
1398
1547
 
1399
1548
  successors = list(graph.successors(node))
1400
1549
 
1550
+ jump_table = self.jump_tables[node.addr]
1551
+ assert jump_table.jumptable_entries is not None
1401
1552
  if (
1402
1553
  successors
1403
- and {succ.addr for succ in successors} == set(jump_tables[node.addr].jumptable_entries)
1554
+ and {succ.addr for succ in successors} == set(jump_table.jumptable_entries)
1404
1555
  and all(graph.in_degree[succ] == 1 for succ in successors)
1405
1556
  ):
1406
1557
  out_nodes = set()
@@ -1431,19 +1582,15 @@ class PhoenixStructurer(StructurerBase):
1431
1582
  graph,
1432
1583
  full_graph,
1433
1584
  ) -> tuple[OrderedDict, Any, set[Any]]:
1434
- cases: OrderedDict[int | tuple[int], SequenceNode] = OrderedDict()
1585
+ cases: OrderedDict[int | tuple[int, ...], SequenceNode] = OrderedDict()
1435
1586
  to_remove = set()
1436
1587
 
1437
- # it is possible that the default node gets duplicated by other analyses and creates a default node (addr.a)
1438
- # and a case node (addr.b). The addr.a node is a successor to the head node while the addr.b node is a
1439
- # successor to node_a
1440
1588
  default_node_candidates = (
1441
1589
  [nn for nn in graph.nodes if nn.addr == node_b_addr] if node_b_addr is not None else []
1442
1590
  )
1443
- node_default: BaseNode | None = next(
1444
- iter(nn for nn in default_node_candidates if graph.has_edge(head_node, nn)), None
1591
+ node_default = (
1592
+ self._switch_find_default_node(graph, head_node, node_b_addr) if node_b_addr is not None else None
1445
1593
  )
1446
-
1447
1594
  if node_default is not None and not isinstance(node_default, SequenceNode):
1448
1595
  # make the default node a SequenceNode so that we can insert Break and Continue nodes into it later
1449
1596
  new_node = SequenceNode(node_default.addr, nodes=[node_default])
@@ -1634,6 +1781,7 @@ class PhoenixStructurer(StructurerBase):
1634
1781
  for _out_src, out_dst in out_edges[1:]:
1635
1782
  if out_dst in full_graph and out_dst not in graph and full_graph.in_degree[out_dst] == 0:
1636
1783
  full_graph.remove_node(out_dst)
1784
+ assert self._region.successors is not None
1637
1785
  if out_dst in self._region.successors:
1638
1786
  self._region.successors.remove(out_dst)
1639
1787
 
@@ -1666,8 +1814,7 @@ class PhoenixStructurer(StructurerBase):
1666
1814
  return case_and_entry_addrs
1667
1815
 
1668
1816
  def _is_node_unstructured_switch_case_head(self, node) -> bool:
1669
- jump_tables = self.kb.cfgs["CFGFast"].jump_tables
1670
- if node.addr in jump_tables:
1817
+ if node.addr in self.jump_tables:
1671
1818
  # maybe it has been structured?
1672
1819
  try:
1673
1820
  last_stmts = self.cond_proc.get_last_statements(node)
@@ -1700,6 +1847,7 @@ class PhoenixStructurer(StructurerBase):
1700
1847
  and not self._is_switch_cases_address_loaded_from_memory_head_or_jumpnode(full_graph, end_node)
1701
1848
  and not self._is_switch_cases_address_loaded_from_memory_head_or_jumpnode(full_graph, start_node)
1702
1849
  and end_node not in self.dowhile_known_tail_nodes
1850
+ and not isinstance(end_node, IncompleteSwitchCaseNode)
1703
1851
  ):
1704
1852
  # merge two blocks
1705
1853
  new_seq = self._merge_nodes(start_node, end_node)
@@ -1719,6 +1867,8 @@ class PhoenixStructurer(StructurerBase):
1719
1867
  succs = list(full_graph.successors(start_node))
1720
1868
  if len(succs) == 2:
1721
1869
  left, right = succs
1870
+ if left.addr > right.addr:
1871
+ left, right = right, left
1722
1872
  if self._is_switch_cases_address_loaded_from_memory_head_or_jumpnode(
1723
1873
  full_graph, left
1724
1874
  ) or self._is_switch_cases_address_loaded_from_memory_head_or_jumpnode(full_graph, right):
@@ -1747,7 +1897,7 @@ class PhoenixStructurer(StructurerBase):
1747
1897
  ):
1748
1898
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1749
1899
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
1750
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
1900
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
1751
1901
  # c = !c
1752
1902
  last_if_jump = self._remove_last_statement_if_jump(start_node)
1753
1903
  new_cond_node = ConditionNode(
@@ -1789,7 +1939,7 @@ class PhoenixStructurer(StructurerBase):
1789
1939
  ) and not self._is_node_unstructured_switch_case_head(right):
1790
1940
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1791
1941
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
1792
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
1942
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
1793
1943
  # c = !c
1794
1944
  last_if_jump = self._remove_last_statement_if_jump(start_node)
1795
1945
  new_cond_node = ConditionNode(
@@ -1822,7 +1972,7 @@ class PhoenixStructurer(StructurerBase):
1822
1972
  ):
1823
1973
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1824
1974
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
1825
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
1975
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
1826
1976
  # c = !c
1827
1977
  last_if_jump = self._remove_last_statement_if_jump(start_node)
1828
1978
  new_cond_node = ConditionNode(
@@ -1857,7 +2007,7 @@ class PhoenixStructurer(StructurerBase):
1857
2007
  ):
1858
2008
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
1859
2009
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
1860
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
2010
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
1861
2011
  # c = !c
1862
2012
  try:
1863
2013
  last_stmt = self.cond_proc.get_last_statement(start_node)
@@ -1876,7 +2026,7 @@ class PhoenixStructurer(StructurerBase):
1876
2026
  self._remove_last_statement_if_jump(start_node)
1877
2027
  # add a goto node at the end
1878
2028
  new_jump_node = Block(
1879
- new_cond_node.addr,
2029
+ new_cond_node.addr if new_cond_node.addr is not None else 0x7EFF_FFFF,
1880
2030
  0,
1881
2031
  statements=[
1882
2032
  Jump(
@@ -2162,7 +2312,7 @@ class PhoenixStructurer(StructurerBase):
2162
2312
  ):
2163
2313
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2164
2314
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
2165
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
2315
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
2166
2316
  # c0 = !c0
2167
2317
  left_succs = list(full_graph.successors(left))
2168
2318
  if len(left_succs) == 2 and right in left_succs:
@@ -2171,7 +2321,9 @@ class PhoenixStructurer(StructurerBase):
2171
2321
  # there must be an edge between right and other_succ
2172
2322
  edge_cond_left_right = self.cond_proc.recover_edge_condition(full_graph, left, right)
2173
2323
  edge_cond_left_other = self.cond_proc.recover_edge_condition(full_graph, left, other_succ)
2174
- if claripy.is_true(claripy.Not(edge_cond_left_right) == edge_cond_left_other):
2324
+ if claripy.is_true(
2325
+ claripy.Not(edge_cond_left_right) == edge_cond_left_other # type: ignore
2326
+ ):
2175
2327
  # c1 = !c1
2176
2328
  return left, edge_cond_left, right, edge_cond_left_right, other_succ
2177
2329
  return None
@@ -2211,7 +2363,7 @@ class PhoenixStructurer(StructurerBase):
2211
2363
  ):
2212
2364
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2213
2365
  edge_cond_right = self.cond_proc.recover_edge_condition(full_graph, start_node, right)
2214
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right):
2366
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_right): # type: ignore
2215
2367
  # c0 = !c0
2216
2368
  right_succs = list(full_graph.successors(right))
2217
2369
  left_succs = list(full_graph.successors(left))
@@ -2220,7 +2372,9 @@ class PhoenixStructurer(StructurerBase):
2220
2372
  if len([succ for succ in left_succs if succ is not else_node]) == 1:
2221
2373
  edge_cond_right_left = self.cond_proc.recover_edge_condition(full_graph, right, left)
2222
2374
  edge_cond_right_else = self.cond_proc.recover_edge_condition(full_graph, right, else_node)
2223
- if claripy.is_true(claripy.Not(edge_cond_right_left) == edge_cond_right_else):
2375
+ if claripy.is_true(
2376
+ claripy.Not(edge_cond_right_left) == edge_cond_right_else # type: ignore
2377
+ ):
2224
2378
  # c1 = !c1
2225
2379
  return left, edge_cond_left, right, edge_cond_right_left, else_node
2226
2380
  return None
@@ -2254,7 +2408,7 @@ class PhoenixStructurer(StructurerBase):
2254
2408
  ):
2255
2409
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2256
2410
  edge_cond_successor = self.cond_proc.recover_edge_condition(full_graph, start_node, successor)
2257
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_successor):
2411
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_successor): # type: ignore
2258
2412
  # c0 = !c0
2259
2413
  left_succs = list(full_graph.successors(left))
2260
2414
  if len(left_succs) == 2 and successor in left_succs:
@@ -2265,7 +2419,9 @@ class PhoenixStructurer(StructurerBase):
2265
2419
  edge_cond_left_successor = self.cond_proc.recover_edge_condition(
2266
2420
  full_graph, left, successor
2267
2421
  )
2268
- if claripy.is_true(claripy.Not(edge_cond_left_right) == edge_cond_left_successor):
2422
+ if claripy.is_true(
2423
+ claripy.Not(edge_cond_left_right) == edge_cond_left_successor # type: ignore
2424
+ ):
2269
2425
  # c1 = !c1
2270
2426
  return left, edge_cond_left, successor, edge_cond_left_successor, right
2271
2427
  return None
@@ -2304,19 +2460,19 @@ class PhoenixStructurer(StructurerBase):
2304
2460
  ):
2305
2461
  edge_cond_left = self.cond_proc.recover_edge_condition(full_graph, start_node, left)
2306
2462
  edge_cond_else = self.cond_proc.recover_edge_condition(full_graph, start_node, else_node)
2307
- if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_else):
2463
+ if claripy.is_true(claripy.Not(edge_cond_left) == edge_cond_else): # type: ignore
2308
2464
  # c0 = !c0
2309
2465
  left_succs = list(full_graph.successors(left))
2310
2466
  if len(left_succs) == 2 and else_node in left_succs:
2311
2467
  right = next(iter(succ for succ in left_succs if succ is not else_node))
2312
2468
  edge_cond_left_right = self.cond_proc.recover_edge_condition(full_graph, left, right)
2313
2469
  edge_cond_left_else = self.cond_proc.recover_edge_condition(full_graph, left, else_node)
2314
- if claripy.is_true(claripy.Not(edge_cond_left_right) == edge_cond_left_else):
2470
+ if claripy.is_true(claripy.Not(edge_cond_left_right) == edge_cond_left_else): # type: ignore
2315
2471
  # c1 = !c1
2316
2472
  return left, edge_cond_left, right, edge_cond_left_right, else_node
2317
2473
  return None
2318
2474
 
2319
- def _last_resort_refinement(self, head, graph: networkx.DiGraph, full_graph: networkx.DiGraph | None) -> bool:
2475
+ def _last_resort_refinement(self, head, graph: networkx.DiGraph, full_graph: networkx.DiGraph) -> bool:
2320
2476
  if self._improve_algorithm:
2321
2477
  while self._edge_virtualization_hints:
2322
2478
  src, dst = self._edge_virtualization_hints.pop(0)
@@ -2396,6 +2552,7 @@ class PhoenixStructurer(StructurerBase):
2396
2552
  goto1_target = None
2397
2553
 
2398
2554
  if goto0_condition is not None:
2555
+ assert goto0_target is not None and goto1_target is not None
2399
2556
  goto0 = Block(
2400
2557
  last_stmt.ins_addr,
2401
2558
  0,
@@ -2461,11 +2618,11 @@ class PhoenixStructurer(StructurerBase):
2461
2618
 
2462
2619
  @staticmethod
2463
2620
  def _find_node_going_to_dst(
2464
- node: SequenceNode,
2621
+ node: BaseNode,
2465
2622
  dst: Block | BaseNode,
2466
2623
  last=True,
2467
2624
  condjump_only=False,
2468
- ) -> tuple[int | None, BaseNode | None, Block | None]:
2625
+ ) -> tuple[int | None, BaseNode | None, Block | MultiNode | BreakNode | None]:
2469
2626
  """
2470
2627
 
2471
2628
  :param node:
@@ -2477,6 +2634,14 @@ class PhoenixStructurer(StructurerBase):
2477
2634
  dst_addr = dst.addr
2478
2635
  dst_idx = dst.idx if isinstance(dst, Block) else ...
2479
2636
 
2637
+ class _Holder:
2638
+ """
2639
+ Holds parent_and_block and is accessible from within the handlers.
2640
+ """
2641
+
2642
+ parent_and_block: list[tuple[int, Any, Block | MultiNode | BreakNode]] = []
2643
+ block_id: int = -1
2644
+
2480
2645
  def _check(last_stmt):
2481
2646
  return (
2482
2647
  (
@@ -2512,34 +2677,34 @@ class PhoenixStructurer(StructurerBase):
2512
2677
  first_stmt = first_nonlabel_nonphi_statement(block)
2513
2678
  if first_stmt is not None:
2514
2679
  # this block has content. increment the block ID counter
2515
- walker.block_id += 1
2680
+ _Holder.block_id += 1
2516
2681
 
2517
2682
  if _check(first_stmt):
2518
- walker.parent_and_block.append((walker.block_id, parent, block))
2683
+ _Holder.parent_and_block.append((_Holder.block_id, parent, block))
2519
2684
  elif len(block.statements) > 1:
2520
2685
  last_stmt = block.statements[-1]
2521
2686
  if _check(last_stmt) or (
2522
2687
  not isinstance(last_stmt, (Jump, ConditionalJump))
2523
2688
  and block.addr + block.original_size == dst_addr
2524
2689
  ):
2525
- walker.parent_and_block.append((walker.block_id, parent, block))
2690
+ _Holder.parent_and_block.append((_Holder.block_id, parent, block))
2526
2691
 
2527
2692
  def _handle_MultiNode(block: MultiNode, parent=None, **kwargs): # pylint:disable=unused-argument
2528
2693
  if block.nodes and isinstance(block.nodes[-1], Block) and block.nodes[-1].statements:
2529
2694
  first_stmt = first_nonlabel_nonphi_statement(block)
2530
2695
  if first_stmt is not None:
2531
2696
  # this block has content. increment the block ID counter
2532
- walker.block_id += 1
2697
+ _Holder.block_id += 1
2533
2698
  if _check(block.nodes[-1].statements[-1]):
2534
- walker.parent_and_block.append((walker.block_id, parent, block))
2699
+ _Holder.parent_and_block.append((_Holder.block_id, parent, block))
2535
2700
 
2536
2701
  def _handle_BreakNode(break_node: BreakNode, parent=None, **kwargs): # pylint:disable=unused-argument
2537
- walker.block_id += 1
2702
+ _Holder.block_id += 1
2538
2703
  if break_node.target == dst_addr or (
2539
2704
  isinstance(break_node.target, Const) and break_node.target.value == dst_addr
2540
2705
  ):
2541
2706
  # FIXME: idx is ignored
2542
- walker.parent_and_block.append((walker.block_id, parent, break_node))
2707
+ _Holder.parent_and_block.append((_Holder.block_id, parent, break_node))
2543
2708
 
2544
2709
  walker = SequenceWalker(
2545
2710
  handlers={
@@ -2550,14 +2715,13 @@ class PhoenixStructurer(StructurerBase):
2550
2715
  update_seqnode_in_place=False,
2551
2716
  force_forward_scan=True,
2552
2717
  )
2553
- walker.parent_and_block: list[tuple[int, Any, Block | MultiNode]] = []
2554
- walker.block_id = -1
2718
+ _Holder.block_id = -1
2555
2719
  walker.walk(node)
2556
- if not walker.parent_and_block:
2720
+ if not _Holder.parent_and_block:
2557
2721
  return None, None, None
2558
2722
  if last:
2559
- return walker.parent_and_block[-1]
2560
- return walker.parent_and_block[0]
2723
+ return _Holder.parent_and_block[-1]
2724
+ return _Holder.parent_and_block[0]
2561
2725
 
2562
2726
  @staticmethod
2563
2727
  def _unpack_sequencenode_head(graph: networkx.DiGraph, seq: SequenceNode, new_seq=None):
@@ -2626,7 +2790,7 @@ class PhoenixStructurer(StructurerBase):
2626
2790
  return True
2627
2791
  return all(not isinstance(stmt, (ConditionalJump, Jump)) for stmt in stmts[:-1])
2628
2792
 
2629
- def _to_statement_list(node: Block | MultiNode | SequenceNode) -> list[Statement]:
2793
+ def _to_statement_list(node: Block | MultiNode | SequenceNode | BaseNode) -> list[Statement]:
2630
2794
  if isinstance(node, Block):
2631
2795
  return node.statements
2632
2796
  if isinstance(node, MultiNode):
@@ -2681,7 +2845,7 @@ class PhoenixStructurer(StructurerBase):
2681
2845
  graph.remove_edge(src, succ)
2682
2846
 
2683
2847
  @staticmethod
2684
- def _remove_first_statement_if_jump(node: BaseNode | Block) -> Jump | ConditionalJump | None:
2848
+ def _remove_first_statement_if_jump(node: BaseNode | Block | MultiNode) -> Jump | ConditionalJump | None:
2685
2849
  if isinstance(node, Block):
2686
2850
  if node.statements:
2687
2851
  idx = 0
@@ -2729,7 +2893,7 @@ class PhoenixStructurer(StructurerBase):
2729
2893
  src, dst = edge_
2730
2894
  dst_in_degree = graph.in_degree[dst]
2731
2895
  src_out_degree = graph.out_degree[src]
2732
- return -node_seq.get(dst), dst_in_degree, src_out_degree, -src.addr, -dst.addr
2896
+ return -node_seq.get(dst), dst_in_degree, src_out_degree, -src.addr, -dst.addr # type: ignore
2733
2897
 
2734
2898
  return sorted(edges, key=_sort_edge, reverse=True)
2735
2899