pyflashkit 1.0.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.
- flashkit/__init__.py +54 -0
- flashkit/abc/__init__.py +79 -0
- flashkit/abc/builder.py +847 -0
- flashkit/abc/constants.py +198 -0
- flashkit/abc/disasm.py +364 -0
- flashkit/abc/parser.py +434 -0
- flashkit/abc/types.py +275 -0
- flashkit/abc/writer.py +230 -0
- flashkit/analysis/__init__.py +28 -0
- flashkit/analysis/call_graph.py +317 -0
- flashkit/analysis/inheritance.py +267 -0
- flashkit/analysis/references.py +371 -0
- flashkit/analysis/strings.py +299 -0
- flashkit/cli/__init__.py +75 -0
- flashkit/cli/_util.py +52 -0
- flashkit/cli/build.py +36 -0
- flashkit/cli/callees.py +30 -0
- flashkit/cli/callers.py +30 -0
- flashkit/cli/class_cmd.py +83 -0
- flashkit/cli/classes.py +71 -0
- flashkit/cli/disasm.py +77 -0
- flashkit/cli/extract.py +36 -0
- flashkit/cli/info.py +41 -0
- flashkit/cli/packages.py +30 -0
- flashkit/cli/refs.py +31 -0
- flashkit/cli/strings.py +58 -0
- flashkit/cli/tags.py +32 -0
- flashkit/cli/tree.py +52 -0
- flashkit/errors.py +33 -0
- flashkit/info/__init__.py +31 -0
- flashkit/info/class_info.py +176 -0
- flashkit/info/member_info.py +275 -0
- flashkit/info/package_info.py +60 -0
- flashkit/search/__init__.py +16 -0
- flashkit/search/search.py +456 -0
- flashkit/swf/__init__.py +66 -0
- flashkit/swf/builder.py +283 -0
- flashkit/swf/parser.py +164 -0
- flashkit/swf/tags.py +120 -0
- flashkit/workspace/__init__.py +20 -0
- flashkit/workspace/resource.py +189 -0
- flashkit/workspace/workspace.py +232 -0
- pyflashkit-1.0.0.dist-info/METADATA +281 -0
- pyflashkit-1.0.0.dist-info/RECORD +48 -0
- pyflashkit-1.0.0.dist-info/WHEEL +5 -0
- pyflashkit-1.0.0.dist-info/entry_points.txt +2 -0
- pyflashkit-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyflashkit-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Inheritance graph for ABC classes.
|
|
3
|
+
|
|
4
|
+
Builds a directed graph of class inheritance and interface implementation
|
|
5
|
+
from resolved ClassInfo data. Supports ancestor/descendant queries,
|
|
6
|
+
interface implementor lookup, and subclass checks.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from flashkit.workspace import Workspace
|
|
11
|
+
from flashkit.analysis.inheritance import InheritanceGraph
|
|
12
|
+
|
|
13
|
+
ws = Workspace()
|
|
14
|
+
ws.load_swf("application.swf")
|
|
15
|
+
graph = InheritanceGraph.from_classes(ws.classes)
|
|
16
|
+
|
|
17
|
+
parent = graph.get_parent("MySprite")
|
|
18
|
+
children = graph.get_children("BaseEntity")
|
|
19
|
+
implementors = graph.get_implementors("IDisposable")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from collections import defaultdict
|
|
26
|
+
|
|
27
|
+
from ..info.class_info import ClassInfo
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class InheritanceGraph:
|
|
32
|
+
"""Directed graph of class inheritance and interface relationships.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
classes: Map of qualified name → ClassInfo.
|
|
36
|
+
parent_map: Map of class name → superclass name.
|
|
37
|
+
children_map: Map of class name → set of direct subclass names.
|
|
38
|
+
interface_map: Map of class name → set of interface names it implements.
|
|
39
|
+
implementors_map: Map of interface name → set of class names implementing it.
|
|
40
|
+
"""
|
|
41
|
+
classes: dict[str, ClassInfo] = field(default_factory=dict)
|
|
42
|
+
parent_map: dict[str, str] = field(default_factory=dict)
|
|
43
|
+
children_map: dict[str, set[str]] = field(default_factory=lambda: defaultdict(set))
|
|
44
|
+
interface_map: dict[str, set[str]] = field(default_factory=lambda: defaultdict(set))
|
|
45
|
+
implementors_map: dict[str, set[str]] = field(default_factory=lambda: defaultdict(set))
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_classes(cls, classes: list[ClassInfo]) -> InheritanceGraph:
|
|
49
|
+
"""Build an InheritanceGraph from a list of ClassInfo objects.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
classes: All resolved classes (typically from Workspace.classes).
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Populated InheritanceGraph.
|
|
56
|
+
"""
|
|
57
|
+
graph = cls()
|
|
58
|
+
|
|
59
|
+
for ci in classes:
|
|
60
|
+
key = ci.qualified_name
|
|
61
|
+
graph.classes[key] = ci
|
|
62
|
+
|
|
63
|
+
# Parent → child edge
|
|
64
|
+
if ci.super_name and ci.super_name != "*":
|
|
65
|
+
super_qualified = (
|
|
66
|
+
f"{ci.super_package}.{ci.super_name}"
|
|
67
|
+
if ci.super_package else ci.super_name
|
|
68
|
+
)
|
|
69
|
+
graph.parent_map[key] = super_qualified
|
|
70
|
+
graph.children_map[super_qualified].add(key)
|
|
71
|
+
|
|
72
|
+
# Interface edges
|
|
73
|
+
for iface in ci.interfaces:
|
|
74
|
+
graph.interface_map[key].add(iface)
|
|
75
|
+
graph.implementors_map[iface].add(key)
|
|
76
|
+
|
|
77
|
+
return graph
|
|
78
|
+
|
|
79
|
+
def get_parent(self, name: str) -> str | None:
|
|
80
|
+
"""Get the direct superclass of a class.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
name: Class name (simple or qualified).
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Superclass qualified name, or None if not found / root class.
|
|
87
|
+
"""
|
|
88
|
+
key = self._resolve_name(name)
|
|
89
|
+
return self.parent_map.get(key) if key else None
|
|
90
|
+
|
|
91
|
+
def get_children(self, name: str) -> list[str]:
|
|
92
|
+
"""Get direct subclasses of a class.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
name: Class name (simple or qualified).
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List of direct subclass qualified names.
|
|
99
|
+
"""
|
|
100
|
+
key = self._resolve_name(name)
|
|
101
|
+
if key is None:
|
|
102
|
+
return []
|
|
103
|
+
return sorted(self.children_map.get(key, set()))
|
|
104
|
+
|
|
105
|
+
def get_all_parents(self, name: str) -> list[str]:
|
|
106
|
+
"""Get the full ancestor chain (class → superclass → ... → root).
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
name: Class name (simple or qualified).
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
List of ancestor qualified names, from immediate parent to root.
|
|
113
|
+
"""
|
|
114
|
+
key = self._resolve_name(name)
|
|
115
|
+
if key is None:
|
|
116
|
+
return []
|
|
117
|
+
result: list[str] = []
|
|
118
|
+
visited: set[str] = set()
|
|
119
|
+
current = key
|
|
120
|
+
while current in self.parent_map:
|
|
121
|
+
parent = self.parent_map[current]
|
|
122
|
+
if parent in visited:
|
|
123
|
+
break # circular inheritance guard
|
|
124
|
+
visited.add(parent)
|
|
125
|
+
result.append(parent)
|
|
126
|
+
current = parent
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
def get_all_children(self, name: str) -> list[str]:
|
|
130
|
+
"""Get all descendants (transitive closure of subclasses).
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
name: Class name (simple or qualified).
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of all descendant qualified names (breadth-first order).
|
|
137
|
+
"""
|
|
138
|
+
key = self._resolve_name(name)
|
|
139
|
+
if key is None:
|
|
140
|
+
return []
|
|
141
|
+
result: list[str] = []
|
|
142
|
+
visited: set[str] = set()
|
|
143
|
+
queue = [key]
|
|
144
|
+
while queue:
|
|
145
|
+
current = queue.pop(0)
|
|
146
|
+
for child in sorted(self.children_map.get(current, set())):
|
|
147
|
+
if child not in visited:
|
|
148
|
+
visited.add(child)
|
|
149
|
+
result.append(child)
|
|
150
|
+
queue.append(child)
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
def get_implementors(self, interface_name: str) -> list[str]:
|
|
154
|
+
"""Get all classes that directly implement an interface.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
interface_name: Interface name (simple or qualified).
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of implementing class qualified names.
|
|
161
|
+
"""
|
|
162
|
+
# Try exact match first, then simple name scan
|
|
163
|
+
if interface_name in self.implementors_map:
|
|
164
|
+
return sorted(self.implementors_map[interface_name])
|
|
165
|
+
# Try matching simple name in the implementors keys
|
|
166
|
+
for key, impls in self.implementors_map.items():
|
|
167
|
+
if key == interface_name or key.endswith(f".{interface_name}"):
|
|
168
|
+
return sorted(impls)
|
|
169
|
+
return []
|
|
170
|
+
|
|
171
|
+
def get_interfaces(self, name: str) -> list[str]:
|
|
172
|
+
"""Get all interfaces directly implemented by a class.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
name: Class name (simple or qualified).
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of interface name strings.
|
|
179
|
+
"""
|
|
180
|
+
key = self._resolve_name(name)
|
|
181
|
+
if key is None:
|
|
182
|
+
return []
|
|
183
|
+
return sorted(self.interface_map.get(key, set()))
|
|
184
|
+
|
|
185
|
+
def get_siblings(self, name: str) -> list[str]:
|
|
186
|
+
"""Get classes sharing the same direct parent (excluding self).
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
name: Class name (simple or qualified).
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
List of sibling class qualified names.
|
|
193
|
+
"""
|
|
194
|
+
key = self._resolve_name(name)
|
|
195
|
+
if key is None:
|
|
196
|
+
return []
|
|
197
|
+
parent = self.parent_map.get(key)
|
|
198
|
+
if parent is None:
|
|
199
|
+
return []
|
|
200
|
+
return sorted(c for c in self.children_map.get(parent, set()) if c != key)
|
|
201
|
+
|
|
202
|
+
def is_subclass(self, child: str, parent: str) -> bool:
|
|
203
|
+
"""Check if *child* is a (transitive) subclass of *parent*.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
child: Potential subclass name.
|
|
207
|
+
parent: Potential ancestor name.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
True if child inherits from parent (directly or transitively).
|
|
211
|
+
"""
|
|
212
|
+
child_key = self._resolve_name(child)
|
|
213
|
+
parent_key = self._resolve_name(parent)
|
|
214
|
+
if child_key is None or parent_key is None:
|
|
215
|
+
return False
|
|
216
|
+
ancestors = self.get_all_parents(child_key)
|
|
217
|
+
return parent_key in ancestors
|
|
218
|
+
|
|
219
|
+
def get_roots(self) -> list[str]:
|
|
220
|
+
"""Get all root classes (no superclass in the graph).
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
List of root class qualified names.
|
|
224
|
+
"""
|
|
225
|
+
roots: list[str] = []
|
|
226
|
+
for name in self.classes:
|
|
227
|
+
parent = self.parent_map.get(name)
|
|
228
|
+
if parent is None or parent not in self.classes:
|
|
229
|
+
roots.append(name)
|
|
230
|
+
return sorted(roots)
|
|
231
|
+
|
|
232
|
+
def get_depth(self, name: str) -> int:
|
|
233
|
+
"""Get the inheritance depth of a class (0 for roots).
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
name: Class name (simple or qualified).
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Number of ancestors in the graph, or -1 if not found.
|
|
240
|
+
"""
|
|
241
|
+
key = self._resolve_name(name)
|
|
242
|
+
if key is None:
|
|
243
|
+
return -1
|
|
244
|
+
return len(self.get_all_parents(key))
|
|
245
|
+
|
|
246
|
+
def _resolve_name(self, name: str) -> str | None:
|
|
247
|
+
"""Resolve a simple or qualified name to a name known in the graph.
|
|
248
|
+
|
|
249
|
+
Checks loaded classes first, then parent/children map keys
|
|
250
|
+
(for external classes like Object that aren't loaded but appear
|
|
251
|
+
as superclasses).
|
|
252
|
+
"""
|
|
253
|
+
# Exact match in loaded classes
|
|
254
|
+
if name in self.classes:
|
|
255
|
+
return name
|
|
256
|
+
# Exact match in external references (parent/child keys)
|
|
257
|
+
if name in self.children_map or name in self.parent_map:
|
|
258
|
+
return name
|
|
259
|
+
# Try simple name match in loaded classes
|
|
260
|
+
for qname, ci in self.classes.items():
|
|
261
|
+
if ci.name == name:
|
|
262
|
+
return qname
|
|
263
|
+
# Try simple name match in children_map keys (external classes)
|
|
264
|
+
for key in self.children_map:
|
|
265
|
+
if key.endswith(f".{name}") or key == name:
|
|
266
|
+
return key
|
|
267
|
+
return None
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cross-reference index for ABC elements.
|
|
3
|
+
|
|
4
|
+
Builds indexes that answer "where is X used?" questions by scanning
|
|
5
|
+
class traits (field types, method signatures) and method body opcodes.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
from flashkit.workspace import Workspace
|
|
10
|
+
from flashkit.analysis.references import ReferenceIndex
|
|
11
|
+
|
|
12
|
+
ws = Workspace()
|
|
13
|
+
ws.load_swf("application.swf")
|
|
14
|
+
refs = ReferenceIndex.from_workspace(ws)
|
|
15
|
+
|
|
16
|
+
users = refs.field_type_users("int")
|
|
17
|
+
creators = refs.instantiators("Point")
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from collections import defaultdict
|
|
24
|
+
|
|
25
|
+
from ..abc.types import AbcFile
|
|
26
|
+
from ..abc.disasm import decode_instructions
|
|
27
|
+
from ..abc.constants import (
|
|
28
|
+
OP_pushstring, OP_constructprop, OP_callproperty, OP_callpropvoid,
|
|
29
|
+
OP_getlex, OP_coerce, OP_newclass,
|
|
30
|
+
)
|
|
31
|
+
from ..info.member_info import resolve_multiname, build_method_body_map
|
|
32
|
+
from ..info.class_info import ClassInfo
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Reference:
|
|
37
|
+
"""A single cross-reference entry.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
source_class: Qualified name of the class containing this reference.
|
|
41
|
+
source_member: Member name (method or field) where this reference occurs.
|
|
42
|
+
target: The referenced name (type name, class name, or string).
|
|
43
|
+
ref_kind: Category: ``"field_type"``, ``"param_type"``, ``"return_type"``,
|
|
44
|
+
``"instantiation"``, ``"call"``, ``"string_use"``, ``"coerce"``,
|
|
45
|
+
``"class_ref"``.
|
|
46
|
+
method_index: Method index if this reference is from a method body, else -1.
|
|
47
|
+
offset: Bytecode offset if from a method body, else -1.
|
|
48
|
+
"""
|
|
49
|
+
source_class: str
|
|
50
|
+
source_member: str
|
|
51
|
+
target: str
|
|
52
|
+
ref_kind: str
|
|
53
|
+
method_index: int = -1
|
|
54
|
+
offset: int = -1
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class ReferenceIndex:
|
|
59
|
+
"""Cross-reference index over all classes and method bodies.
|
|
60
|
+
|
|
61
|
+
Provides efficient lookup for "where is X used?" queries.
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
refs: All reference entries.
|
|
65
|
+
by_target: Map of target name → list of references to it.
|
|
66
|
+
by_source: Map of source class name → list of references from it.
|
|
67
|
+
"""
|
|
68
|
+
refs: list[Reference] = field(default_factory=list)
|
|
69
|
+
by_target: dict[str, list[Reference]] = field(
|
|
70
|
+
default_factory=lambda: defaultdict(list))
|
|
71
|
+
by_source: dict[str, list[Reference]] = field(
|
|
72
|
+
default_factory=lambda: defaultdict(list))
|
|
73
|
+
|
|
74
|
+
def _add(self, ref: Reference) -> None:
|
|
75
|
+
"""Add a reference to all indexes."""
|
|
76
|
+
self.refs.append(ref)
|
|
77
|
+
self.by_target[ref.target].append(ref)
|
|
78
|
+
self.by_source[ref.source_class].append(ref)
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_workspace(cls, workspace: object) -> ReferenceIndex:
|
|
82
|
+
"""Build a ReferenceIndex from a Workspace.
|
|
83
|
+
|
|
84
|
+
Scans all class traits and method bodies.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
workspace: A Workspace instance.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Populated ReferenceIndex.
|
|
91
|
+
"""
|
|
92
|
+
from ..workspace.workspace import Workspace
|
|
93
|
+
ws: Workspace = workspace # type: ignore[assignment]
|
|
94
|
+
|
|
95
|
+
index = cls()
|
|
96
|
+
|
|
97
|
+
for ci in ws.classes:
|
|
98
|
+
index._index_class_traits(ci)
|
|
99
|
+
|
|
100
|
+
for abc in ws.abc_blocks:
|
|
101
|
+
index._index_method_bodies(abc, ws.classes)
|
|
102
|
+
|
|
103
|
+
return index
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def from_classes_and_abc(cls, classes: list[ClassInfo],
|
|
107
|
+
abc_blocks: list[AbcFile]) -> ReferenceIndex:
|
|
108
|
+
"""Build a ReferenceIndex from class and ABC lists directly.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
classes: All resolved ClassInfo objects.
|
|
112
|
+
abc_blocks: All AbcFile objects.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Populated ReferenceIndex.
|
|
116
|
+
"""
|
|
117
|
+
index = cls()
|
|
118
|
+
for ci in classes:
|
|
119
|
+
index._index_class_traits(ci)
|
|
120
|
+
for abc in abc_blocks:
|
|
121
|
+
index._index_method_bodies(abc, classes)
|
|
122
|
+
return index
|
|
123
|
+
|
|
124
|
+
def _index_class_traits(self, ci: ClassInfo) -> None:
|
|
125
|
+
"""Index field types, method param types, and return types from a class."""
|
|
126
|
+
qname = ci.qualified_name
|
|
127
|
+
|
|
128
|
+
# Field types (instance + static)
|
|
129
|
+
for f in ci.all_fields:
|
|
130
|
+
if f.type_name and f.type_name != "*":
|
|
131
|
+
self._add(Reference(
|
|
132
|
+
source_class=qname,
|
|
133
|
+
source_member=f.name,
|
|
134
|
+
target=f.type_name,
|
|
135
|
+
ref_kind="field_type",
|
|
136
|
+
))
|
|
137
|
+
|
|
138
|
+
# Method signatures (instance + static)
|
|
139
|
+
for m in ci.all_methods:
|
|
140
|
+
# Return type
|
|
141
|
+
if m.return_type and m.return_type != "*":
|
|
142
|
+
self._add(Reference(
|
|
143
|
+
source_class=qname,
|
|
144
|
+
source_member=m.name,
|
|
145
|
+
target=m.return_type,
|
|
146
|
+
ref_kind="return_type",
|
|
147
|
+
method_index=m.method_index,
|
|
148
|
+
))
|
|
149
|
+
# Parameter types
|
|
150
|
+
for pt in m.param_types:
|
|
151
|
+
if pt and pt != "*":
|
|
152
|
+
self._add(Reference(
|
|
153
|
+
source_class=qname,
|
|
154
|
+
source_member=m.name,
|
|
155
|
+
target=pt,
|
|
156
|
+
ref_kind="param_type",
|
|
157
|
+
method_index=m.method_index,
|
|
158
|
+
))
|
|
159
|
+
|
|
160
|
+
# Superclass reference
|
|
161
|
+
if ci.super_name and ci.super_name != "*" and ci.super_name != "Object":
|
|
162
|
+
super_qualified = (
|
|
163
|
+
f"{ci.super_package}.{ci.super_name}"
|
|
164
|
+
if ci.super_package else ci.super_name
|
|
165
|
+
)
|
|
166
|
+
self._add(Reference(
|
|
167
|
+
source_class=qname,
|
|
168
|
+
source_member="<extends>",
|
|
169
|
+
target=super_qualified,
|
|
170
|
+
ref_kind="extends",
|
|
171
|
+
))
|
|
172
|
+
|
|
173
|
+
# Interface references
|
|
174
|
+
for iface in ci.interfaces:
|
|
175
|
+
self._add(Reference(
|
|
176
|
+
source_class=qname,
|
|
177
|
+
source_member="<implements>",
|
|
178
|
+
target=iface,
|
|
179
|
+
ref_kind="implements",
|
|
180
|
+
))
|
|
181
|
+
|
|
182
|
+
def _index_method_bodies(self, abc: AbcFile,
|
|
183
|
+
classes: list[ClassInfo]) -> None:
|
|
184
|
+
"""Index references from method body opcodes."""
|
|
185
|
+
method_name_map = _build_method_owner_map(abc, classes)
|
|
186
|
+
|
|
187
|
+
for body in abc.method_bodies:
|
|
188
|
+
owner_class = method_name_map.get(body.method, "")
|
|
189
|
+
method_name = f"method_{body.method}"
|
|
190
|
+
|
|
191
|
+
# Find the method name from classes
|
|
192
|
+
for ci in classes:
|
|
193
|
+
for m in ci.all_methods:
|
|
194
|
+
if m.method_index == body.method:
|
|
195
|
+
method_name = m.name
|
|
196
|
+
if not owner_class:
|
|
197
|
+
owner_class = ci.qualified_name
|
|
198
|
+
break
|
|
199
|
+
if ci.constructor_index == body.method:
|
|
200
|
+
method_name = "<init>"
|
|
201
|
+
if not owner_class:
|
|
202
|
+
owner_class = ci.qualified_name
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
instructions = decode_instructions(body.code)
|
|
206
|
+
except Exception:
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
for instr in instructions:
|
|
210
|
+
if not instr.operands:
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
mn_index = instr.operands[0]
|
|
214
|
+
|
|
215
|
+
if instr.opcode == OP_constructprop:
|
|
216
|
+
target = resolve_multiname(abc, mn_index)
|
|
217
|
+
if target != "*" and not target.startswith("multiname["):
|
|
218
|
+
self._add(Reference(
|
|
219
|
+
source_class=owner_class,
|
|
220
|
+
source_member=method_name,
|
|
221
|
+
target=target,
|
|
222
|
+
ref_kind="instantiation",
|
|
223
|
+
method_index=body.method,
|
|
224
|
+
offset=instr.offset,
|
|
225
|
+
))
|
|
226
|
+
|
|
227
|
+
elif instr.opcode in (OP_callproperty, OP_callpropvoid):
|
|
228
|
+
target = resolve_multiname(abc, mn_index)
|
|
229
|
+
if target != "*" and not target.startswith("multiname["):
|
|
230
|
+
self._add(Reference(
|
|
231
|
+
source_class=owner_class,
|
|
232
|
+
source_member=method_name,
|
|
233
|
+
target=target,
|
|
234
|
+
ref_kind="call",
|
|
235
|
+
method_index=body.method,
|
|
236
|
+
offset=instr.offset,
|
|
237
|
+
))
|
|
238
|
+
|
|
239
|
+
elif instr.opcode == OP_getlex:
|
|
240
|
+
target = resolve_multiname(abc, mn_index)
|
|
241
|
+
if target != "*" and not target.startswith("multiname["):
|
|
242
|
+
self._add(Reference(
|
|
243
|
+
source_class=owner_class,
|
|
244
|
+
source_member=method_name,
|
|
245
|
+
target=target,
|
|
246
|
+
ref_kind="class_ref",
|
|
247
|
+
method_index=body.method,
|
|
248
|
+
offset=instr.offset,
|
|
249
|
+
))
|
|
250
|
+
|
|
251
|
+
elif instr.opcode == OP_coerce:
|
|
252
|
+
target = resolve_multiname(abc, mn_index)
|
|
253
|
+
if target != "*" and not target.startswith("multiname["):
|
|
254
|
+
self._add(Reference(
|
|
255
|
+
source_class=owner_class,
|
|
256
|
+
source_member=method_name,
|
|
257
|
+
target=target,
|
|
258
|
+
ref_kind="coerce",
|
|
259
|
+
method_index=body.method,
|
|
260
|
+
offset=instr.offset,
|
|
261
|
+
))
|
|
262
|
+
|
|
263
|
+
elif instr.opcode == OP_pushstring:
|
|
264
|
+
str_index = instr.operands[0]
|
|
265
|
+
if 0 < str_index < len(abc.string_pool):
|
|
266
|
+
self._add(Reference(
|
|
267
|
+
source_class=owner_class,
|
|
268
|
+
source_member=method_name,
|
|
269
|
+
target=abc.string_pool[str_index],
|
|
270
|
+
ref_kind="string_use",
|
|
271
|
+
method_index=body.method,
|
|
272
|
+
offset=instr.offset,
|
|
273
|
+
))
|
|
274
|
+
|
|
275
|
+
def field_type_users(self, type_name: str) -> list[Reference]:
|
|
276
|
+
"""Find all fields of a given type.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
type_name: The type name to search for.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
List of references where a field has this type.
|
|
283
|
+
"""
|
|
284
|
+
return [r for r in self.by_target.get(type_name, [])
|
|
285
|
+
if r.ref_kind == "field_type"]
|
|
286
|
+
|
|
287
|
+
def method_param_users(self, type_name: str) -> list[Reference]:
|
|
288
|
+
"""Find all methods that take a parameter of a given type.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
type_name: The type name to search for.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
List of references where a method parameter has this type.
|
|
295
|
+
"""
|
|
296
|
+
return [r for r in self.by_target.get(type_name, [])
|
|
297
|
+
if r.ref_kind == "param_type"]
|
|
298
|
+
|
|
299
|
+
def method_return_users(self, type_name: str) -> list[Reference]:
|
|
300
|
+
"""Find all methods that return a given type.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
type_name: The type name to search for.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List of references where a method returns this type.
|
|
307
|
+
"""
|
|
308
|
+
return [r for r in self.by_target.get(type_name, [])
|
|
309
|
+
if r.ref_kind == "return_type"]
|
|
310
|
+
|
|
311
|
+
def instantiators(self, class_name: str) -> list[Reference]:
|
|
312
|
+
"""Find all places that construct instances of a class.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
class_name: The class being instantiated.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
List of instantiation references.
|
|
319
|
+
"""
|
|
320
|
+
return [r for r in self.by_target.get(class_name, [])
|
|
321
|
+
if r.ref_kind == "instantiation"]
|
|
322
|
+
|
|
323
|
+
def string_users(self, string: str) -> list[Reference]:
|
|
324
|
+
"""Find all places that push a specific string constant.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
string: The exact string value.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
List of string usage references.
|
|
331
|
+
"""
|
|
332
|
+
return [r for r in self.by_target.get(string, [])
|
|
333
|
+
if r.ref_kind == "string_use"]
|
|
334
|
+
|
|
335
|
+
def references_from(self, class_name: str) -> list[Reference]:
|
|
336
|
+
"""Get all outgoing references from a class.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
class_name: The source class qualified name.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
All references originating from this class.
|
|
343
|
+
"""
|
|
344
|
+
return self.by_source.get(class_name, [])
|
|
345
|
+
|
|
346
|
+
def references_to(self, target: str) -> list[Reference]:
|
|
347
|
+
"""Get all incoming references to a target.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
target: The target name (type, class, method, or string).
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
All references pointing to this target.
|
|
354
|
+
"""
|
|
355
|
+
return self.by_target.get(target, [])
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def total_refs(self) -> int:
|
|
359
|
+
return len(self.refs)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _build_method_owner_map(abc: AbcFile,
|
|
363
|
+
classes: list[ClassInfo]) -> dict[int, str]:
|
|
364
|
+
"""Map method_index → owning class qualified name."""
|
|
365
|
+
owner: dict[int, str] = {}
|
|
366
|
+
for ci in classes:
|
|
367
|
+
owner[ci.constructor_index] = ci.qualified_name
|
|
368
|
+
owner[ci.static_init_index] = ci.qualified_name
|
|
369
|
+
for m in ci.all_methods:
|
|
370
|
+
owner[m.method_index] = ci.qualified_name
|
|
371
|
+
return owner
|