ipeddle 0.1.0__py3-none-any.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.
- ipeddle/__init__.py +3 -0
- ipeddle/__main__.py +3 -0
- ipeddle/build.py +309 -0
- ipeddle/cli.py +219 -0
- ipeddle/config.py +95 -0
- ipeddle/doctor.py +173 -0
- ipeddle/errors.py +11 -0
- ipeddle/fsutil.py +131 -0
- ipeddle/inspect.py +329 -0
- ipeddle/macho.py +281 -0
- ipeddle/mangling.py +136 -0
- ipeddle/package.py +309 -0
- ipeddle/patcher.py +247 -0
- ipeddle/paths.py +76 -0
- ipeddle/proc.py +58 -0
- ipeddle/runtime.py +73 -0
- ipeddle/scaffold.py +83 -0
- ipeddle/sdk.py +130 -0
- ipeddle/setup.py +309 -0
- ipeddle/swiftsdk.py +143 -0
- ipeddle/tbd.py +308 -0
- ipeddle/toolchain.py +194 -0
- ipeddle/ui.py +56 -0
- ipeddle/xip.py +213 -0
- ipeddle-0.1.0.dist-info/METADATA +272 -0
- ipeddle-0.1.0.dist-info/RECORD +30 -0
- ipeddle-0.1.0.dist-info/WHEEL +4 -0
- ipeddle-0.1.0.dist-info/entry_points.txt +2 -0
- ipeddle-0.1.0.dist-info/licenses/LICENSE +201 -0
- ipeddle-0.1.0.dist-info/licenses/NOTICE +27 -0
ipeddle/macho.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""A small, dependency-free Mach-O reader (enough for ``ipeddle inspect``)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import struct
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
MH_MAGIC_64 = 0xFEEDFACF
|
|
9
|
+
FAT_MAGIC = 0xCAFEBABE
|
|
10
|
+
FAT_MAGIC_64 = 0xCAFEBABF
|
|
11
|
+
|
|
12
|
+
CPU_TYPE_ARM64 = 0x0100000C
|
|
13
|
+
CPU_TYPE_X86_64 = 0x01000007
|
|
14
|
+
CPU_SUBTYPE_ARM64E = 2
|
|
15
|
+
|
|
16
|
+
LC_REQ_DYLD = 0x80000000
|
|
17
|
+
LC_SEGMENT_64 = 0x19
|
|
18
|
+
LC_LOAD_DYLIB = 0xC
|
|
19
|
+
LC_LOAD_WEAK_DYLIB = 0x18 | LC_REQ_DYLD
|
|
20
|
+
LC_REEXPORT_DYLIB = 0x1F | LC_REQ_DYLD
|
|
21
|
+
LC_LAZY_LOAD_DYLIB = 0x20
|
|
22
|
+
LC_LOAD_UPWARD_DYLIB = 0x23 | LC_REQ_DYLD
|
|
23
|
+
LC_ID_DYLIB = 0xD
|
|
24
|
+
LC_RPATH = 0x1C | LC_REQ_DYLD
|
|
25
|
+
LC_ENCRYPTION_INFO_64 = 0x2C
|
|
26
|
+
LC_VERSION_MIN_IPHONEOS = 0x25
|
|
27
|
+
LC_BUILD_VERSION = 0x32
|
|
28
|
+
LC_DYLD_INFO = 0x22
|
|
29
|
+
LC_DYLD_INFO_ONLY = 0x22 | LC_REQ_DYLD
|
|
30
|
+
LC_DYLD_CHAINED_FIXUPS = 0x34 | LC_REQ_DYLD
|
|
31
|
+
LC_CODE_SIGNATURE = 0x1D
|
|
32
|
+
LC_MAIN = 0x28 | LC_REQ_DYLD
|
|
33
|
+
|
|
34
|
+
DYLIB_CMDS = {LC_LOAD_DYLIB: "load", LC_LOAD_WEAK_DYLIB: "weak", LC_REEXPORT_DYLIB: "reexport",
|
|
35
|
+
LC_LAZY_LOAD_DYLIB: "lazy", LC_LOAD_UPWARD_DYLIB: "upward"}
|
|
36
|
+
|
|
37
|
+
PLATFORMS = {1: "macOS", 2: "iOS", 3: "tvOS", 4: "watchOS", 5: "bridgeOS", 6: "Mac Catalyst",
|
|
38
|
+
7: "iOS Simulator", 8: "tvOS Simulator", 9: "watchOS Simulator", 10: "DriverKit",
|
|
39
|
+
11: "visionOS", 12: "visionOS Simulator"}
|
|
40
|
+
|
|
41
|
+
BIND_SPECIAL_SELF = 0
|
|
42
|
+
BIND_SPECIAL_MAIN = -1
|
|
43
|
+
BIND_SPECIAL_FLAT = -2
|
|
44
|
+
BIND_SPECIAL_WEAK = -3
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MachOError(Exception):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class Dylib:
|
|
53
|
+
path: str
|
|
54
|
+
kind: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class Import:
|
|
59
|
+
name: str
|
|
60
|
+
ordinal: int # 1-based library ordinal, or a BIND_SPECIAL_* value
|
|
61
|
+
weak: bool = False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class Slice:
|
|
66
|
+
cputype: int
|
|
67
|
+
cpusubtype: int
|
|
68
|
+
filetype: int
|
|
69
|
+
platform: str | None = None
|
|
70
|
+
minos: str | None = None
|
|
71
|
+
sdk: str | None = None
|
|
72
|
+
dylibs: list[Dylib] = field(default_factory=list)
|
|
73
|
+
rpaths: list[str] = field(default_factory=list)
|
|
74
|
+
imports: list[Import] = field(default_factory=list)
|
|
75
|
+
encrypted: bool = False
|
|
76
|
+
signed: bool = False
|
|
77
|
+
has_main: bool = False
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def arch(self) -> str:
|
|
81
|
+
if self.cputype == CPU_TYPE_ARM64:
|
|
82
|
+
return "arm64e" if (self.cpusubtype & 0xFF) == CPU_SUBTYPE_ARM64E else "arm64"
|
|
83
|
+
if self.cputype == CPU_TYPE_X86_64:
|
|
84
|
+
return "x86_64"
|
|
85
|
+
return hex(self.cputype)
|
|
86
|
+
|
|
87
|
+
def library_for(self, imp: Import) -> str:
|
|
88
|
+
if imp.ordinal == BIND_SPECIAL_FLAT:
|
|
89
|
+
return "<flat lookup>"
|
|
90
|
+
if imp.ordinal == BIND_SPECIAL_SELF:
|
|
91
|
+
return "<self>"
|
|
92
|
+
if imp.ordinal == BIND_SPECIAL_MAIN:
|
|
93
|
+
return "<main executable>"
|
|
94
|
+
if imp.ordinal == BIND_SPECIAL_WEAK:
|
|
95
|
+
return "<weak lookup>"
|
|
96
|
+
if 1 <= imp.ordinal <= len(self.dylibs):
|
|
97
|
+
return self.dylibs[imp.ordinal - 1].path
|
|
98
|
+
return f"<bad ordinal {imp.ordinal}>"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _ver(v: int) -> str:
|
|
102
|
+
major, minor, patch = v >> 16, (v >> 8) & 0xFF, v & 0xFF
|
|
103
|
+
return f"{major}.{minor}" + (f".{patch}" if patch else "")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _cstr(buf: bytes, off: int) -> str:
|
|
107
|
+
end = buf.find(b"\0", off)
|
|
108
|
+
return buf[off:end if end >= 0 else len(buf)].decode("utf-8", "replace")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _uleb(buf: bytes, i: int) -> tuple[int, int]:
|
|
112
|
+
result = shift = 0
|
|
113
|
+
while True:
|
|
114
|
+
b = buf[i]
|
|
115
|
+
i += 1
|
|
116
|
+
result |= (b & 0x7F) << shift
|
|
117
|
+
shift += 7
|
|
118
|
+
if not b & 0x80:
|
|
119
|
+
return result, i
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _sleb(buf: bytes, i: int) -> tuple[int, int]:
|
|
123
|
+
result = shift = 0
|
|
124
|
+
while True:
|
|
125
|
+
b = buf[i]
|
|
126
|
+
i += 1
|
|
127
|
+
result |= (b & 0x7F) << shift
|
|
128
|
+
shift += 7
|
|
129
|
+
if not b & 0x80:
|
|
130
|
+
if b & 0x40:
|
|
131
|
+
result -= 1 << shift
|
|
132
|
+
return result, i
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def parse_slice(data: bytes, base: int = 0) -> Slice:
|
|
136
|
+
magic, cputype, cpusub, filetype, ncmds, sizeofcmds, _flags, _res = struct.unpack_from("<IiiIIIII", data, base)
|
|
137
|
+
if magic != MH_MAGIC_64:
|
|
138
|
+
raise MachOError("not a 64-bit little-endian Mach-O")
|
|
139
|
+
sl = Slice(cputype & 0xFFFFFFFF, cpusub & 0xFFFFFFFF, filetype)
|
|
140
|
+
off = base + 32
|
|
141
|
+
end = off + sizeofcmds
|
|
142
|
+
chained = None
|
|
143
|
+
dyld_info = None
|
|
144
|
+
for _ in range(ncmds):
|
|
145
|
+
if off + 8 > end:
|
|
146
|
+
raise MachOError("load commands overrun their declared size")
|
|
147
|
+
cmd, cmdsize = struct.unpack_from("<II", data, off)
|
|
148
|
+
if cmdsize < 8:
|
|
149
|
+
raise MachOError("corrupt load command")
|
|
150
|
+
if cmd in DYLIB_CMDS:
|
|
151
|
+
name_off = struct.unpack_from("<I", data, off + 8)[0]
|
|
152
|
+
sl.dylibs.append(Dylib(_cstr(data, off + name_off), DYLIB_CMDS[cmd]))
|
|
153
|
+
elif cmd == LC_RPATH:
|
|
154
|
+
sl.rpaths.append(_cstr(data, off + struct.unpack_from("<I", data, off + 8)[0]))
|
|
155
|
+
elif cmd == LC_BUILD_VERSION:
|
|
156
|
+
plat, minos, sdk = struct.unpack_from("<III", data, off + 8)
|
|
157
|
+
sl.platform, sl.minos, sl.sdk = PLATFORMS.get(plat, str(plat)), _ver(minos), _ver(sdk)
|
|
158
|
+
elif cmd == LC_VERSION_MIN_IPHONEOS:
|
|
159
|
+
v, sdk = struct.unpack_from("<II", data, off + 8)
|
|
160
|
+
sl.platform, sl.minos, sl.sdk = "iOS", _ver(v), _ver(sdk)
|
|
161
|
+
elif cmd == LC_ENCRYPTION_INFO_64:
|
|
162
|
+
sl.encrypted = struct.unpack_from("<I", data, off + 16)[0] != 0
|
|
163
|
+
elif cmd == LC_CODE_SIGNATURE:
|
|
164
|
+
sl.signed = True
|
|
165
|
+
elif cmd == LC_MAIN:
|
|
166
|
+
sl.has_main = True
|
|
167
|
+
elif cmd == LC_DYLD_CHAINED_FIXUPS:
|
|
168
|
+
chained = struct.unpack_from("<II", data, off + 8)
|
|
169
|
+
elif cmd in (LC_DYLD_INFO, LC_DYLD_INFO_ONLY):
|
|
170
|
+
dyld_info = struct.unpack_from("<10I", data, off + 8)
|
|
171
|
+
off += cmdsize
|
|
172
|
+
if chained:
|
|
173
|
+
sl.imports = _chained_imports(data, base + chained[0], chained[1])
|
|
174
|
+
elif dyld_info:
|
|
175
|
+
imports = {}
|
|
176
|
+
for o, s in ((dyld_info[2], dyld_info[3]), (dyld_info[4], dyld_info[5]), (dyld_info[6], dyld_info[7])):
|
|
177
|
+
if s:
|
|
178
|
+
for imp in _bind_opcodes(data, base + o, s):
|
|
179
|
+
imports.setdefault((imp.name, imp.ordinal), imp)
|
|
180
|
+
sl.imports = list(imports.values())
|
|
181
|
+
return sl
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _chained_imports(data: bytes, off: int, size: int) -> list[Import]:
|
|
185
|
+
(_ver, _starts, imports_off, symbols_off, count, fmt, sym_fmt) = struct.unpack_from("<7I", data, off)
|
|
186
|
+
if sym_fmt != 0:
|
|
187
|
+
raise MachOError("compressed chained-fixup symbol tables are not supported")
|
|
188
|
+
out = []
|
|
189
|
+
p = off + imports_off
|
|
190
|
+
syms = off + symbols_off
|
|
191
|
+
for _ in range(count):
|
|
192
|
+
if fmt == 1: # DYLD_CHAINED_IMPORT
|
|
193
|
+
v = struct.unpack_from("<I", data, p)[0]
|
|
194
|
+
p += 4
|
|
195
|
+
lib, weak, name_off = v & 0xFF, (v >> 8) & 1, v >> 9
|
|
196
|
+
if lib > 0xF0:
|
|
197
|
+
lib -= 0x100
|
|
198
|
+
elif fmt == 2: # DYLD_CHAINED_IMPORT_ADDEND
|
|
199
|
+
v = struct.unpack_from("<I", data, p)[0]
|
|
200
|
+
p += 8
|
|
201
|
+
lib, weak, name_off = v & 0xFF, (v >> 8) & 1, v >> 9
|
|
202
|
+
if lib > 0xF0:
|
|
203
|
+
lib -= 0x100
|
|
204
|
+
elif fmt == 3: # DYLD_CHAINED_IMPORT_ADDEND64
|
|
205
|
+
v = struct.unpack_from("<Q", data, p)[0]
|
|
206
|
+
p += 16
|
|
207
|
+
lib, weak, name_off = v & 0xFFFF, (v >> 16) & 1, v >> 32
|
|
208
|
+
if lib > 0xFFF0:
|
|
209
|
+
lib -= 0x10000
|
|
210
|
+
else:
|
|
211
|
+
raise MachOError(f"unknown chained import format {fmt}")
|
|
212
|
+
out.append(Import(_cstr(data, syms + name_off), lib, bool(weak)))
|
|
213
|
+
return out
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _bind_opcodes(data: bytes, off: int, size: int) -> list[Import]:
|
|
217
|
+
out = []
|
|
218
|
+
i, end = off, off + size
|
|
219
|
+
ordinal, name, weak = 0, "", False
|
|
220
|
+
while i < end:
|
|
221
|
+
b = data[i]
|
|
222
|
+
i += 1
|
|
223
|
+
op, imm = b & 0xF0, b & 0x0F
|
|
224
|
+
if op == 0x00: # DONE (lazy streams continue)
|
|
225
|
+
continue
|
|
226
|
+
elif op == 0x10: # SET_DYLIB_ORDINAL_IMM
|
|
227
|
+
ordinal = imm
|
|
228
|
+
elif op == 0x20: # SET_DYLIB_ORDINAL_ULEB
|
|
229
|
+
ordinal, i = _uleb(data, i)
|
|
230
|
+
elif op == 0x30: # SET_DYLIB_SPECIAL_IMM
|
|
231
|
+
ordinal = (imm | 0xF0) - 0x100 if imm else 0
|
|
232
|
+
elif op == 0x40: # SET_SYMBOL_TRAILING_FLAGS_IMM
|
|
233
|
+
j = data.index(b"\0", i)
|
|
234
|
+
name = data[i:j].decode("utf-8", "replace")
|
|
235
|
+
weak = bool(imm & 0x1)
|
|
236
|
+
i = j + 1
|
|
237
|
+
elif op == 0x50: # SET_TYPE_IMM
|
|
238
|
+
pass
|
|
239
|
+
elif op == 0x60: # SET_ADDEND_SLEB
|
|
240
|
+
_, i = _sleb(data, i)
|
|
241
|
+
elif op == 0x70: # SET_SEGMENT_AND_OFFSET_ULEB
|
|
242
|
+
_, i = _uleb(data, i)
|
|
243
|
+
elif op == 0x80: # ADD_ADDR_ULEB
|
|
244
|
+
_, i = _uleb(data, i)
|
|
245
|
+
elif op in (0x90, 0xB0): # DO_BIND, DO_BIND_ADD_ADDR_IMM_SCALED
|
|
246
|
+
out.append(Import(name, ordinal, weak))
|
|
247
|
+
elif op == 0xA0: # DO_BIND_ADD_ADDR_ULEB
|
|
248
|
+
out.append(Import(name, ordinal, weak))
|
|
249
|
+
_, i = _uleb(data, i)
|
|
250
|
+
elif op == 0xC0: # DO_BIND_ULEB_TIMES_SKIPPING_ULEB
|
|
251
|
+
out.append(Import(name, ordinal, weak))
|
|
252
|
+
_, i = _uleb(data, i)
|
|
253
|
+
_, i = _uleb(data, i)
|
|
254
|
+
else:
|
|
255
|
+
break
|
|
256
|
+
return out
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def parse(data: bytes) -> list[Slice]:
|
|
260
|
+
if len(data) < 32:
|
|
261
|
+
raise MachOError("file too small to be Mach-O")
|
|
262
|
+
magic_be = struct.unpack_from(">I", data, 0)[0]
|
|
263
|
+
if magic_be in (FAT_MAGIC, FAT_MAGIC_64):
|
|
264
|
+
n = struct.unpack_from(">I", data, 4)[0]
|
|
265
|
+
slices = []
|
|
266
|
+
for k in range(n):
|
|
267
|
+
if magic_be == FAT_MAGIC:
|
|
268
|
+
_ct, _cs, offset, _size, _align = struct.unpack_from(">IIIII", data, 8 + k * 20)
|
|
269
|
+
else:
|
|
270
|
+
_ct, _cs, offset, _size, _align, _r = struct.unpack_from(">IIQQII", data, 8 + k * 32)
|
|
271
|
+
slices.append(parse_slice(data, offset))
|
|
272
|
+
return slices
|
|
273
|
+
return [parse_slice(data, 0)]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def is_macho(data: bytes) -> bool:
|
|
277
|
+
if len(data) < 4:
|
|
278
|
+
return False
|
|
279
|
+
le = struct.unpack_from("<I", data, 0)[0]
|
|
280
|
+
be = struct.unpack_from(">I", data, 0)[0]
|
|
281
|
+
return le == MH_MAGIC_64 or be in (FAT_MAGIC, FAT_MAGIC_64)
|
ipeddle/mangling.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Tiny helpers over Swift mangled names.
|
|
2
|
+
|
|
3
|
+
We never need a full demangler: ipeddle only has to answer "which module owns
|
|
4
|
+
this symbol?" so it can decide which library a symbol must be bound to.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
_PREFIXES = ("_$s", "$s", "_$S", "$S")
|
|
12
|
+
|
|
13
|
+
# Kind letters that may follow a nominal type in a mangled context.
|
|
14
|
+
_KIND_CHARS = set("VCOPaN")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_swift_symbol(name: str) -> bool:
|
|
18
|
+
return name.startswith(_PREFIXES)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _strip_prefix(name: str) -> str | None:
|
|
22
|
+
for p in _PREFIXES:
|
|
23
|
+
if name.startswith(p):
|
|
24
|
+
return name[len(p):]
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _read_ident(s: str, i: int) -> tuple[str, int] | None:
|
|
29
|
+
"""Read a ``<decimal length><identifier>`` token at ``s[i:]``.
|
|
30
|
+
|
|
31
|
+
Word-substituted identifiers (introduced by ``0``) are not decoded and
|
|
32
|
+
return ``None``.
|
|
33
|
+
"""
|
|
34
|
+
m = re.match(r"[1-9][0-9]*", s[i:])
|
|
35
|
+
if not m:
|
|
36
|
+
return None
|
|
37
|
+
n = int(m.group(0))
|
|
38
|
+
start = i + len(m.group(0))
|
|
39
|
+
ident = s[start:start + n]
|
|
40
|
+
if len(ident) != n or not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", ident):
|
|
41
|
+
return None
|
|
42
|
+
return ident, start + n
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def literal_identifiers(name: str) -> list[str]:
|
|
46
|
+
"""All literal ``<len><ident>`` tokens, in order (best effort)."""
|
|
47
|
+
s = _strip_prefix(name)
|
|
48
|
+
if s is None:
|
|
49
|
+
return []
|
|
50
|
+
out = []
|
|
51
|
+
i = 0
|
|
52
|
+
while i < len(s):
|
|
53
|
+
if s[i].isdigit() and s[i] != "0" and (i == 0 or not s[i - 1].isdigit()):
|
|
54
|
+
tok = _read_ident(s, i)
|
|
55
|
+
if tok:
|
|
56
|
+
out.append(tok[0])
|
|
57
|
+
i = tok[1]
|
|
58
|
+
continue
|
|
59
|
+
i += 1
|
|
60
|
+
return out
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def owner_module(name: str, known: set[str] | frozenset[str] | None = None) -> str | None:
|
|
64
|
+
"""Like :func:`_owner_module`; with ``known`` (the module names that exist
|
|
65
|
+
in the SDK), an answer that is not a module falls back to the first
|
|
66
|
+
module named literally in the mangling. That covers witness tables of
|
|
67
|
+
standard types (``$sSi7SwiftUI...WP``) and associated type descriptors
|
|
68
|
+
(``$s4Body7SwiftUI4ViewPTl``)."""
|
|
69
|
+
guess = _owner_module(name)
|
|
70
|
+
if known is None or guess in known:
|
|
71
|
+
return guess
|
|
72
|
+
for ident in literal_identifiers(name):
|
|
73
|
+
if ident in known:
|
|
74
|
+
return ident
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _owner_module(name: str) -> str | None:
|
|
79
|
+
"""Return the module that *defines* the entity a Swift symbol refers to.
|
|
80
|
+
|
|
81
|
+
Rules (enough for linking decisions):
|
|
82
|
+
* ``$s<len>Module...`` -> Module
|
|
83
|
+
* ``$s<context><len>ModuleE...`` (extension) -> Module of the extension
|
|
84
|
+
* ``$sSo<len>Type<kind><len>ModuleE...`` -> Module (extension on an
|
|
85
|
+
imported Objective-C/C type)
|
|
86
|
+
* ``$sS<x><len>ModuleE...`` -> Module (extension on a
|
|
87
|
+
standard library type, e.g. ``SS`` = String)
|
|
88
|
+
Anything else returns ``None``.
|
|
89
|
+
"""
|
|
90
|
+
s = _strip_prefix(name)
|
|
91
|
+
if s is None or not s:
|
|
92
|
+
return None
|
|
93
|
+
i = 0
|
|
94
|
+
first: str | None = None
|
|
95
|
+
if s.startswith(("So", "SC")):
|
|
96
|
+
tok = _read_ident(s, 2)
|
|
97
|
+
if tok is None:
|
|
98
|
+
return None
|
|
99
|
+
i = tok[1]
|
|
100
|
+
while i < len(s) and s[i] in _KIND_CHARS:
|
|
101
|
+
i += 1
|
|
102
|
+
elif len(s) >= 2 and s[0] == "S" and s[1].isalpha():
|
|
103
|
+
i = 2
|
|
104
|
+
else:
|
|
105
|
+
tok = _read_ident(s, 0)
|
|
106
|
+
if tok is None:
|
|
107
|
+
return None
|
|
108
|
+
first, i = tok
|
|
109
|
+
# Walk the remaining context chain looking for an extension marker.
|
|
110
|
+
steps = 0
|
|
111
|
+
while i < len(s) and steps < 8:
|
|
112
|
+
steps += 1
|
|
113
|
+
tok = _read_ident(s, i)
|
|
114
|
+
if tok is None:
|
|
115
|
+
break
|
|
116
|
+
ident, j = tok
|
|
117
|
+
if j < len(s) and s[j] == "E":
|
|
118
|
+
return ident
|
|
119
|
+
if first is None:
|
|
120
|
+
# Context started with a substitution and the next identifier is
|
|
121
|
+
# not an extension module: we cannot tell the owner reliably.
|
|
122
|
+
return None
|
|
123
|
+
i = j
|
|
124
|
+
while i < len(s) and s[i] in _KIND_CHARS:
|
|
125
|
+
i += 1
|
|
126
|
+
return first
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def mentions_module(name: str, module: str) -> bool:
|
|
130
|
+
"""True if the literal ``<len><module>`` token occurs in the mangling."""
|
|
131
|
+
s = _strip_prefix(name) or name
|
|
132
|
+
tok = f"{len(module)}{module}"
|
|
133
|
+
for m in re.finditer(re.escape(tok), s):
|
|
134
|
+
if m.start() == 0 or not s[m.start() - 1].isdigit():
|
|
135
|
+
return True
|
|
136
|
+
return False
|