angr 9.2.175__cp310-abi3-macosx_11_0_arm64.whl → 9.2.176__cp310-abi3-macosx_11_0_arm64.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 (42) hide show
  1. angr/__init__.py +1 -1
  2. angr/analyses/calling_convention/calling_convention.py +12 -0
  3. angr/analyses/complete_calling_conventions.py +39 -26
  4. angr/analyses/decompiler/ail_simplifier.py +13 -11
  5. angr/analyses/decompiler/ccall_rewriters/rewriter_base.py +5 -1
  6. angr/analyses/decompiler/clinic.py +54 -40
  7. angr/analyses/decompiler/optimization_passes/ite_region_converter.py +3 -3
  8. angr/analyses/decompiler/optimization_passes/lowered_switch_simplifier.py +2 -2
  9. angr/analyses/decompiler/peephole_optimizations/__init__.py +4 -4
  10. angr/analyses/decompiler/peephole_optimizations/{inlined_wstrcpy.py → inlined_wcscpy.py} +16 -8
  11. angr/analyses/decompiler/peephole_optimizations/{inlined_wstrcpy_consolidation.py → inlined_wcscpy_consolidation.py} +13 -13
  12. angr/analyses/decompiler/ssailification/rewriting_engine.py +14 -1
  13. angr/analyses/decompiler/structured_codegen/c.py +6 -5
  14. angr/analyses/decompiler/structuring/dream.py +2 -2
  15. angr/analyses/decompiler/structuring/phoenix.py +101 -23
  16. angr/analyses/stack_pointer_tracker.py +4 -3
  17. angr/analyses/typehoon/lifter.py +29 -18
  18. angr/analyses/typehoon/simple_solver.py +157 -50
  19. angr/analyses/typehoon/translator.py +34 -34
  20. angr/analyses/typehoon/typeconsts.py +33 -15
  21. angr/analyses/typehoon/typevars.py +9 -2
  22. angr/analyses/variable_recovery/engine_ail.py +4 -2
  23. angr/analyses/variable_recovery/engine_base.py +4 -1
  24. angr/analyses/variable_recovery/variable_recovery_fast.py +3 -1
  25. angr/engines/icicle.py +4 -4
  26. angr/engines/vex/claripy/ccall.py +3 -3
  27. angr/knowledge_plugins/functions/function.py +17 -0
  28. angr/procedures/posix/pthread.py +4 -4
  29. angr/procedures/stubs/format_parser.py +3 -3
  30. angr/rustylib.abi3.so +0 -0
  31. angr/sim_type.py +11 -6
  32. angr/simos/windows.py +1 -1
  33. angr/storage/memory_mixins/paged_memory/page_backer_mixins.py +1 -1
  34. angr/unicornlib.dylib +0 -0
  35. angr/utils/constants.py +1 -1
  36. angr/utils/strings.py +20 -0
  37. {angr-9.2.175.dist-info → angr-9.2.176.dist-info}/METADATA +5 -5
  38. {angr-9.2.175.dist-info → angr-9.2.176.dist-info}/RECORD +42 -41
  39. {angr-9.2.175.dist-info → angr-9.2.176.dist-info}/WHEEL +0 -0
  40. {angr-9.2.175.dist-info → angr-9.2.176.dist-info}/entry_points.txt +0 -0
  41. {angr-9.2.175.dist-info → angr-9.2.176.dist-info}/licenses/LICENSE +0 -0
  42. {angr-9.2.175.dist-info → angr-9.2.176.dist-info}/top_level.txt +0 -0
@@ -6,31 +6,31 @@ from angr.ailment.statement import Call, Store
6
6
 
7
7
  from angr.sim_type import SimTypePointer, SimTypeWideChar
8
8
  from .base import PeepholeOptimizationMultiStmtBase
9
- from .inlined_wstrcpy import InlinedWstrcpy
9
+ from .inlined_wcscpy import InlinedWcscpy
10
10
 
11
11
 
12
- class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
12
+ class InlinedWcscpyConsolidation(PeepholeOptimizationMultiStmtBase):
13
13
  """
14
- Consolidate multiple inlined wstrcpy/wstrncpy calls.
14
+ Consolidate multiple inlined wcscpy/wcsncpy calls.
15
15
  """
16
16
 
17
17
  __slots__ = ()
18
18
 
19
- NAME = "Consolidate multiple inlined wstrncpy calls"
19
+ NAME = "Consolidate multiple inlined wcsncpy calls"
20
20
  stmt_classes = ((Call, Call), (Call, Store))
21
21
 
22
22
  def optimize( # type:ignore
23
23
  self, stmts: list[Call], stmt_idx: int | None = None, block=None, **kwargs
24
24
  ): # pylint:disable=unused-argument
25
25
  last_stmt, stmt = stmts
26
- if InlinedWstrcpy.is_inlined_wstrncpy(last_stmt):
26
+ if InlinedWcscpy.is_inlined_wcsncpy(last_stmt):
27
27
  assert last_stmt.args is not None
28
28
  assert self.kb is not None
29
29
  s_last: bytes = self.kb.custom_strings[last_stmt.args[1].value]
30
30
  addr_last = last_stmt.args[0]
31
31
  new_str = None # will be set if consolidation should happen
32
32
 
33
- if isinstance(stmt, Call) and InlinedWstrcpy.is_inlined_wstrncpy(stmt):
33
+ if isinstance(stmt, Call) and InlinedWcscpy.is_inlined_wcsncpy(stmt):
34
34
  assert stmt.args is not None
35
35
  # consolidating two calls
36
36
  s_curr: bytes = self.kb.custom_strings[stmt.args[1].value]
@@ -50,7 +50,7 @@ class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
50
50
  # it's probably the terminating null byte
51
51
  r, s = True, b"\x00\x00"
52
52
  else:
53
- r, s = InlinedWstrcpy.is_integer_likely_a_wide_string(
53
+ r, s = InlinedWcscpy.is_integer_likely_a_wide_string(
54
54
  stmt.data.value, stmt.size, stmt.endness, min_length=1 # type:ignore
55
55
  )
56
56
  if r and s is not None:
@@ -60,7 +60,7 @@ class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
60
60
  assert self.project is not None
61
61
  wstr_type = SimTypePointer(SimTypeWideChar()).with_arch(self.project.arch)
62
62
  if new_str.endswith(b"\x00\x00"):
63
- call_name = "wstrcpy"
63
+ call_name = "wcsncpy"
64
64
  new_str_idx = self.kb.custom_strings.allocate(new_str[:-2])
65
65
  args = [
66
66
  last_stmt.args[0],
@@ -68,7 +68,7 @@ class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
68
68
  ]
69
69
  prototype = None
70
70
  else:
71
- call_name = "wstrncpy"
71
+ call_name = "wcsncpy"
72
72
  new_str_idx = self.kb.custom_strings.allocate(new_str)
73
73
  args = [
74
74
  last_stmt.args[0],
@@ -96,18 +96,18 @@ class InlinedWstrcpyConsolidation(PeepholeOptimizationMultiStmtBase):
96
96
  return StackBaseOffset(None, addr.bits, 0), addr.operand.stack_offset
97
97
  if isinstance(addr, BinaryOp):
98
98
  if addr.op == "Add" and isinstance(addr.operands[1], Const) and isinstance(addr.operands[1].value, int):
99
- base_0, offset_0 = InlinedWstrcpyConsolidation._parse_addr(addr.operands[0])
99
+ base_0, offset_0 = InlinedWcscpyConsolidation._parse_addr(addr.operands[0])
100
100
  return base_0, offset_0 + addr.operands[1].value
101
101
  if addr.op == "Sub" and isinstance(addr.operands[1], Const) and isinstance(addr.operands[1].value, int):
102
- base_0, offset_0 = InlinedWstrcpyConsolidation._parse_addr(addr.operands[0])
102
+ base_0, offset_0 = InlinedWcscpyConsolidation._parse_addr(addr.operands[0])
103
103
  return base_0, offset_0 - addr.operands[1].value
104
104
 
105
105
  return addr, 0
106
106
 
107
107
  @staticmethod
108
108
  def _get_delta(addr_0: Expression, addr_1: Expression) -> int | None:
109
- base_0, offset_0 = InlinedWstrcpyConsolidation._parse_addr(addr_0)
110
- base_1, offset_1 = InlinedWstrcpyConsolidation._parse_addr(addr_1)
109
+ base_0, offset_0 = InlinedWcscpyConsolidation._parse_addr(addr_0)
110
+ base_1, offset_1 = InlinedWcscpyConsolidation._parse_addr(addr_1)
111
111
  if base_0.likes(base_1):
112
112
  return offset_1 - offset_0
113
113
  return None
@@ -97,6 +97,19 @@ class SimEngineSSARewriting(
97
97
  self._current_vvar_id += 1
98
98
  return self._current_vvar_id
99
99
 
100
+ #
101
+ # Util functions
102
+ #
103
+
104
+ @staticmethod
105
+ def _is_head_controlled_loop_jump(block, jump_stmt: ConditionalJump) -> bool:
106
+ concrete_targets = []
107
+ if isinstance(jump_stmt.true_target, Const):
108
+ concrete_targets.append(jump_stmt.true_target.value)
109
+ if isinstance(jump_stmt.false_target, Const):
110
+ concrete_targets.append(jump_stmt.false_target.value)
111
+ return not all(block.addr <= t < block.addr + block.original_size for t in concrete_targets)
112
+
100
113
  #
101
114
  # Handlers
102
115
  #
@@ -303,7 +316,7 @@ class SimEngineSSARewriting(
303
316
  new_true_target = self._expr(stmt.true_target) if stmt.true_target is not None else None
304
317
  new_false_target = self._expr(stmt.false_target) if stmt.false_target is not None else None
305
318
 
306
- if self.stmt_idx != len(self.block.statements) - 1:
319
+ if self.stmt_idx != len(self.block.statements) - 1 and self._is_head_controlled_loop_jump(self.block, stmt):
307
320
  # the conditional jump is in the middle of the block (e.g., the block generated from lifting rep stosq).
308
321
  # we need to make a copy of the state and use the state of this point in its successor
309
322
  self.head_controlled_loop_outstate = self.state.copy()
@@ -42,6 +42,7 @@ from angr.utils.constants import is_alignment_mask
42
42
  from angr.utils.library import get_cpp_function_name
43
43
  from angr.utils.loader import is_in_readonly_segment, is_in_readonly_section
44
44
  from angr.utils.types import unpack_typeref, unpack_pointer_and_array, dereference_simtype_by_lib
45
+ from angr.utils.strings import decode_utf16_string
45
46
  from angr.analyses.decompiler.utils import structured_node_is_simple_return
46
47
  from angr.analyses.decompiler.notes.deobfuscated_strings import DeobfuscatedStringsNote
47
48
  from angr.errors import UnsupportedNodeTypeError, AngrRuntimeError
@@ -2269,9 +2270,9 @@ class CConstant(CExpression):
2269
2270
  elif isinstance(self._type, SimTypePointer) and isinstance(self._type.pts_to, SimTypeWideChar):
2270
2271
  refval = self.reference_values[self._type]
2271
2272
  v = (
2272
- refval.content.decode("utf_16_le")
2273
+ decode_utf16_string(refval.content)
2273
2274
  if isinstance(refval, MemoryData)
2274
- else refval.decode("utf_16_le")
2275
+ else decode_utf16_string(refval)
2275
2276
  ) # it's a string
2276
2277
  yield CConstant.str_to_c_str(v, prefix="L"), self
2277
2278
  return
@@ -4076,9 +4077,9 @@ class MakeTypecastsImplicit(CStructuredCodeWalker):
4076
4077
  class FieldReferenceCleanup(CStructuredCodeWalker):
4077
4078
  def handle_CTypeCast(self, obj):
4078
4079
  if isinstance(obj.dst_type, SimTypePointer) and not isinstance(obj.dst_type.pts_to, SimTypeBottom):
4079
- obj = obj.codegen._access_reference(obj.expr, obj.dst_type.pts_to)
4080
- if not isinstance(obj, CTypeCast):
4081
- return self.handle(obj)
4080
+ new_obj = obj.codegen._access_reference(obj.expr, obj.dst_type.pts_to)
4081
+ if not isinstance(new_obj, CTypeCast):
4082
+ return self.handle(new_obj)
4082
4083
  return super().handle_CTypeCast(obj)
4083
4084
 
4084
4085
 
@@ -548,7 +548,7 @@ class DreamStructurer(StructurerBase):
548
548
  cmp = switch_extract_cmp_bounds(last_stmt)
549
549
  if not cmp:
550
550
  return False
551
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
551
+ cmp_expr, cmp_lb, _cmp_ub = cmp # pylint:disable=unused-variable
552
552
 
553
553
  # the real indirect jump
554
554
  if len(addr2nodes[target]) != 1:
@@ -619,7 +619,7 @@ class DreamStructurer(StructurerBase):
619
619
  cmp = switch_extract_cmp_bounds(last_stmt)
620
620
  if not cmp:
621
621
  return False
622
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
622
+ cmp_expr, cmp_lb, _cmp_ub = cmp # pylint:disable=unused-variable
623
623
 
624
624
  jumptable_entries = jump_table.jumptable_entries
625
625
  assert jumptable_entries is not None
@@ -929,23 +929,55 @@ class PhoenixStructurer(StructurerBase):
929
929
  )
930
930
  break_node = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
931
931
  else:
932
- break_stmt = Jump(
933
- None,
934
- Const(None, None, successor.addr, self.project.arch.bits),
935
- target_idx=successor.idx if isinstance(successor, Block) else None,
936
- ins_addr=last_src_stmt.ins_addr,
937
- )
938
- break_node_inner = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
939
- fallthrough_node = next(iter(succ for succ in fullgraph.successors(src) if succ is not dst))
940
- fallthrough_stmt = Jump(
941
- None,
942
- Const(None, None, fallthrough_node.addr, self.project.arch.bits),
943
- target_idx=successor.idx if isinstance(successor, Block) else None,
944
- ins_addr=last_src_stmt.ins_addr,
945
- )
946
- break_node_inner_fallthrough = Block(
947
- last_src_stmt.ins_addr, None, statements=[fallthrough_stmt]
932
+ fallthrough_node = next(
933
+ iter(succ for succ in fullgraph.successors(src) if succ is not dst), None
948
934
  )
935
+ if fallthrough_node is not None:
936
+ # we create a conditional jump that will be converted to a conditional break later
937
+ break_stmt = Jump(
938
+ None,
939
+ Const(None, None, successor.addr, self.project.arch.bits),
940
+ target_idx=successor.idx if isinstance(successor, Block) else None,
941
+ ins_addr=last_src_stmt.ins_addr,
942
+ )
943
+ break_node_inner = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
944
+ fallthrough_stmt = Jump(
945
+ None,
946
+ Const(None, None, fallthrough_node.addr, self.project.arch.bits),
947
+ target_idx=successor.idx if isinstance(successor, Block) else None,
948
+ ins_addr=last_src_stmt.ins_addr,
949
+ )
950
+ break_node_inner_fallthrough = Block(
951
+ last_src_stmt.ins_addr, None, statements=[fallthrough_stmt]
952
+ )
953
+ else:
954
+ # the fallthrough node does not exist in the graph. we create a conditional jump that
955
+ # jumps to an address
956
+ if not isinstance(last_src_stmt, ConditionalJump):
957
+ raise TypeError(f"Unexpected last_src_stmt type {type(last_src_stmt)}")
958
+ other_target = (
959
+ last_src_stmt.true_target
960
+ if isinstance(last_src_stmt.false_target, Const)
961
+ and last_src_stmt.false_target.value == successor.addr
962
+ else last_src_stmt.false_target
963
+ )
964
+ assert other_target is not None
965
+ break_stmt = Jump(
966
+ None,
967
+ Const(None, None, successor.addr, self.project.arch.bits),
968
+ target_idx=successor.idx if isinstance(successor, Block) else None,
969
+ ins_addr=last_src_stmt.ins_addr,
970
+ )
971
+ break_node_inner = Block(last_src_stmt.ins_addr, None, statements=[break_stmt])
972
+ fallthrough_stmt = Jump(
973
+ None,
974
+ other_target,
975
+ target_idx=successor.idx if isinstance(successor, Block) else None,
976
+ ins_addr=last_src_stmt.ins_addr,
977
+ )
978
+ break_node_inner_fallthrough = Block(
979
+ last_src_stmt.ins_addr, None, statements=[fallthrough_stmt]
980
+ )
949
981
  break_node = ConditionNode(
950
982
  last_src_stmt.ins_addr,
951
983
  None,
@@ -1454,7 +1486,7 @@ class PhoenixStructurer(StructurerBase):
1454
1486
  )
1455
1487
  if not cmp:
1456
1488
  return False
1457
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
1489
+ cmp_expr, cmp_lb, _cmp_ub = cmp
1458
1490
 
1459
1491
  assert cond_node.addr is not None
1460
1492
  switch_head_addr = cond_node.addr
@@ -1477,7 +1509,7 @@ class PhoenixStructurer(StructurerBase):
1477
1509
  cmp = switch_extract_cmp_bounds(last_stmt)
1478
1510
  if not cmp:
1479
1511
  return False
1480
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
1512
+ cmp_expr, cmp_lb, _cmp_ub = cmp # pylint:disable=unused-variable
1481
1513
 
1482
1514
  switch_head_addr = last_stmt.ins_addr
1483
1515
 
@@ -1849,7 +1881,7 @@ class PhoenixStructurer(StructurerBase):
1849
1881
  cmp = switch_extract_cmp_bounds(last_stmt)
1850
1882
  if not cmp:
1851
1883
  return False
1852
- cmp_expr, cmp_lb, cmp_ub = cmp # pylint:disable=unused-variable
1884
+ cmp_expr, cmp_lb, _cmp_ub = cmp # pylint:disable=unused-variable
1853
1885
 
1854
1886
  if isinstance(last_stmt.false_target, Const):
1855
1887
  default_addr = last_stmt.false_target.value
@@ -2148,19 +2180,54 @@ class PhoenixStructurer(StructurerBase):
2148
2180
  jump_node = Block(out_src.addr, 0, statements=[jump_stmt])
2149
2181
  case_node.nodes.append(jump_node)
2150
2182
 
2151
- if out_edges_to_head: # noqa:SIM108
2183
+ # out_dst_succ is the successor within the current region
2184
+ # out_dst_succ_fullgraph is the successor outside the current region
2185
+ if out_edges_to_head:
2152
2186
  # add an edge from SwitchCaseNode to head so that a loop will be structured later
2153
2187
  out_dst_succ = head
2188
+ out_dst_succ_fullgraph = None
2154
2189
  else:
2155
2190
  # add an edge from SwitchCaseNode to its most immediate successor (if there is one)
2156
- out_dst_succ = other_out_edges[0][1] if other_out_edges else None
2191
+ # there might be an in-region successor and an out-of-region successor (especially due to the
2192
+ # introduction of self.dowhile_known_tail_nodes)!
2193
+ # example: 7995a0325b446c462bdb6ae10b692eee2ecadd8e888e9d7729befe4412007afb, function 1400EF820
2194
+ out_dst_succs = []
2195
+ out_dst_succs_fullgraph = []
2196
+ for _, o in other_out_edges:
2197
+ if o in graph:
2198
+ out_dst_succs.append(o)
2199
+ elif o in full_graph:
2200
+ out_dst_succs_fullgraph.append(o)
2201
+ out_dst_succ = sorted(out_dst_succs, key=lambda o: o.addr)[0] if out_dst_succs else None
2202
+ out_dst_succ_fullgraph = (
2203
+ sorted(out_dst_succs_fullgraph, key=lambda o: o.addr)[0] if out_dst_succs_fullgraph else None
2204
+ )
2205
+ if len(out_dst_succs) > 1:
2206
+ assert out_dst_succ is not None
2207
+ l.warning(
2208
+ "Multiple in-region successors detected for switch-case node at %#x. Picking %#x as the "
2209
+ "successor and dropping others.",
2210
+ scnode.addr,
2211
+ out_dst_succ.addr,
2212
+ )
2213
+ if len(out_dst_succs_fullgraph) > 1:
2214
+ assert out_dst_succ_fullgraph is not None
2215
+ l.warning(
2216
+ "Multiple out-of-region successors detected for switch-case node at %#x. Picking %#x as the "
2217
+ "successor and dropping others.",
2218
+ scnode.addr,
2219
+ out_dst_succ_fullgraph.addr,
2220
+ )
2157
2221
 
2158
2222
  if out_dst_succ is not None:
2159
- if out_dst_succ in graph:
2160
- graph.add_edge(scnode, out_dst_succ)
2223
+ graph.add_edge(scnode, out_dst_succ)
2161
2224
  full_graph.add_edge(scnode, out_dst_succ)
2162
2225
  if full_graph.has_edge(head, out_dst_succ):
2163
2226
  full_graph.remove_edge(head, out_dst_succ)
2227
+ if out_dst_succ_fullgraph is not None:
2228
+ full_graph.add_edge(scnode, out_dst_succ_fullgraph)
2229
+ if full_graph.has_edge(head, out_dst_succ_fullgraph):
2230
+ full_graph.remove_edge(head, out_dst_succ_fullgraph)
2164
2231
 
2165
2232
  # fix full_graph if needed: remove successors that are no longer needed
2166
2233
  for _out_src, out_dst in other_out_edges:
@@ -2925,6 +2992,17 @@ class PhoenixStructurer(StructurerBase):
2925
2992
  ordered_nodes.remove(postorder_head)
2926
2993
  acyclic_graph.remove_node(postorder_head)
2927
2994
  node_seq = {nn: (len(ordered_nodes) - idx) for (idx, nn) in enumerate(ordered_nodes)} # post-order
2995
+ if len(node_seq) < len(acyclic_graph):
2996
+ # some nodes are not reachable from head - add them to node_seq as well
2997
+ # but this is usually the result of incorrect structuring, so we may still fail at a later point
2998
+ l.warning("Adding %d unreachable nodes to node_seq", len(acyclic_graph) - len(node_seq))
2999
+ unreachable_nodes = sorted(
3000
+ (nn for nn in acyclic_graph if nn not in node_seq),
3001
+ key=lambda n: (n.addr, (-1 if n.idx is None else n.idx) if hasattr(n, "idx") else 0),
3002
+ )
3003
+ max_seq = max(node_seq.values(), default=0)
3004
+ for i, nn in enumerate(unreachable_nodes):
3005
+ node_seq[nn] = max_seq + i
2928
3006
 
2929
3007
  if all_edges_wo_dominance:
2930
3008
  all_edges_wo_dominance = self._order_virtualizable_edges(full_graph, all_edges_wo_dominance, node_seq)
@@ -787,9 +787,10 @@ class StackPointerTracker(Analysis, ForwardAnalysis):
787
787
  ):
788
788
  sp_adjusted = True
789
789
  sp_v = state.regs[self.project.arch.sp_offset]
790
- sp_v -= Constant(stmt.data.con.value)
791
- state.put(self.project.arch.sp_offset, sp_v, force=True) # sp -= OFFSET
792
- state.put(stmt.offset, Constant(0), force=True) # rax = 0
790
+ if sp_v is not None:
791
+ sp_v -= Constant(stmt.data.con.value)
792
+ state.put(self.project.arch.sp_offset, sp_v, force=True) # sp -= OFFSET
793
+ state.put(stmt.offset, Constant(0), force=True) # rax = 0
793
794
  break
794
795
 
795
796
  callee_cleanups = [
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+ import itertools
2
3
  from typing import TYPE_CHECKING
3
4
 
4
5
  from angr.sim_type import (
@@ -26,45 +27,53 @@ class TypeLifter:
26
27
  Lift SimTypes to type constants.
27
28
  """
28
29
 
29
- __slots__ = ("bits", "memo")
30
+ __slots__ = ("bits", "memo", "named_struct_id_counter", "struct_name_to_idx")
30
31
 
31
32
  def __init__(self, bits: int):
32
33
  if bits not in (32, 64):
33
34
  raise ValueError("TypeLifter only supports 32-bit or 64-bit pointers.")
34
35
  self.bits = bits
35
36
  self.memo = {}
37
+ self.named_struct_id_counter = itertools.count(133337)
38
+ self.struct_name_to_idx = {}
36
39
 
37
40
  def lift(self, ty: SimType):
38
41
  handler = _mapping.get(type(ty), None)
39
42
  if handler is None:
40
- return BottomType()
43
+ return BottomType(name=ty.label)
41
44
 
42
45
  return handler(self, ty)
43
46
 
44
- def _lift_SimTypeChar(self, ty): # pylint:disable=unused-argument,no-self-use
45
- return Int8()
47
+ def _lift_SimTypeChar(self, ty): # pylint:disable=no-self-use
48
+ return Int8(name=ty.label)
46
49
 
47
- def _lift_SimTypeShort(self, ty): # pylint:disable=unused-argument,no-self-use
48
- return Int16()
50
+ def _lift_SimTypeShort(self, ty): # pylint:disable=no-self-use
51
+ return Int16(name=ty.label)
49
52
 
50
- def _lift_SimTypeInt(self, ty): # pylint:disable=unused-argument,no-self-use
51
- return Int32()
53
+ def _lift_SimTypeInt(self, ty): # pylint:disable=no-self-use
54
+ return Int32(name=ty.label)
52
55
 
53
- def _lift_SimTypeLongLong(self, ty): # pylint:disable=unused-argument,no-self-use
54
- return Int64()
56
+ def _lift_SimTypeLongLong(self, ty): # pylint:disable=no-self-use
57
+ return Int64(name=ty.label)
55
58
 
56
59
  def _lift_SimTypePointer(self, ty: SimTypePointer):
57
60
  if self.bits == 32:
58
- return Pointer32(self.lift(ty.pts_to))
61
+ return Pointer32(self.lift(ty.pts_to), name=ty.label)
59
62
  if self.bits == 64:
60
- return Pointer64(self.lift(ty.pts_to))
63
+ return Pointer64(self.lift(ty.pts_to), name=ty.label)
61
64
  raise ValueError(f"Unsupported bits {self.bits}.")
62
65
 
63
66
  def _lift_SimStruct(self, ty: SimStruct) -> TypeConstant | BottomType:
64
67
  if ty in self.memo:
65
68
  return BottomType()
66
69
 
67
- obj = Struct(fields={}, name=ty.name)
70
+ struct_idx = {}
71
+ if ty.name:
72
+ if ty.name not in self.struct_name_to_idx:
73
+ self.struct_name_to_idx[ty.name] = next(self.named_struct_id_counter)
74
+ struct_idx["idx"] = self.struct_name_to_idx[ty.name]
75
+
76
+ obj = Struct(fields={}, name=ty.name, **struct_idx)
68
77
  self.memo[ty] = obj
69
78
  converted_fields = {}
70
79
  field_names = {}
@@ -76,6 +85,7 @@ class TypeLifter:
76
85
  field_names[ty_offsets[field_name]] = field_name
77
86
  obj.fields = converted_fields
78
87
  obj.field_names = field_names
88
+ del self.memo[ty]
79
89
  return obj
80
90
 
81
91
  def _lift_SimCppClass(self, ty: SimCppClass) -> TypeConstant | BottomType:
@@ -94,17 +104,18 @@ class TypeLifter:
94
104
  field_names[ty_offsets[field_name]] = field_name
95
105
  obj.fields = converted_fields
96
106
  obj.field_names = field_names
107
+ del self.memo[ty]
97
108
  return obj
98
109
 
99
110
  def _lift_SimTypeArray(self, ty: SimTypeArray) -> Array:
100
111
  elem_type = self.lift(ty.elem_type)
101
- return Array(elem_type, count=ty.length)
112
+ return Array(elem_type, count=ty.length, name=ty.label)
102
113
 
103
- def _lift_SimTypeFloat(self, ty: SimTypeFloat) -> Float32: # pylint:disable=unused-argument,no-self-use
104
- return Float32()
114
+ def _lift_SimTypeFloat(self, ty: SimTypeFloat) -> Float32: # pylint:disable=no-self-use
115
+ return Float32(name=ty.label)
105
116
 
106
- def _lift_SimTypeDouble(self, ty: SimTypeDouble) -> Float64: # pylint:disable=unused-argument,no-self-use
107
- return Float64()
117
+ def _lift_SimTypeDouble(self, ty: SimTypeDouble) -> Float64: # pylint:disable=no-self-use
118
+ return Float64(name=ty.label)
108
119
 
109
120
 
110
121
  _mapping = {