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
flashkit/cli/disasm.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""``flashkit disasm`` — disassemble method bytecode."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from ._util import load, bold
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
11
|
+
p = sub.add_parser("disasm", help="Disassemble method bytecode")
|
|
12
|
+
p.add_argument("file", help="SWF or SWZ file")
|
|
13
|
+
p.add_argument("--class", dest="class_name",
|
|
14
|
+
help="Class to disassemble")
|
|
15
|
+
p.add_argument("--method-index", type=int,
|
|
16
|
+
help="Method index to disassemble")
|
|
17
|
+
p.set_defaults(func=run)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run(args: argparse.Namespace) -> None:
|
|
21
|
+
ws = load(args.file)
|
|
22
|
+
|
|
23
|
+
from ..abc.disasm import decode_instructions
|
|
24
|
+
|
|
25
|
+
if args.method_index is not None:
|
|
26
|
+
for abc in ws.abc_blocks:
|
|
27
|
+
for mb in abc.method_bodies:
|
|
28
|
+
if mb.method == args.method_index:
|
|
29
|
+
print(bold(f"Method {mb.method}") +
|
|
30
|
+
f" (max_stack={mb.max_stack}, "
|
|
31
|
+
f"locals={mb.local_count}, "
|
|
32
|
+
f"code={len(mb.code)} bytes)")
|
|
33
|
+
for instr in decode_instructions(mb.code):
|
|
34
|
+
ops = ", ".join(str(o) for o in instr.operands)
|
|
35
|
+
print(f" 0x{instr.offset:04X} "
|
|
36
|
+
f"{instr.mnemonic:<24s} {ops}")
|
|
37
|
+
return
|
|
38
|
+
print(f"Method index {args.method_index} not found.")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
if args.class_name:
|
|
42
|
+
cls = ws.get_class(args.class_name)
|
|
43
|
+
if cls is None:
|
|
44
|
+
matches = ws.find_classes(name=args.class_name)
|
|
45
|
+
if len(matches) == 1:
|
|
46
|
+
cls = matches[0]
|
|
47
|
+
else:
|
|
48
|
+
print(f"Class '{args.class_name}' not found.")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
for abc in ws.abc_blocks:
|
|
52
|
+
method_indices = set()
|
|
53
|
+
for m in cls.all_methods:
|
|
54
|
+
method_indices.add(m.method_index)
|
|
55
|
+
method_indices.add(cls.constructor_index)
|
|
56
|
+
|
|
57
|
+
for mb in abc.method_bodies:
|
|
58
|
+
if mb.method in method_indices:
|
|
59
|
+
mname = f"method_{mb.method}"
|
|
60
|
+
if mb.method == cls.constructor_index:
|
|
61
|
+
mname = f"{cls.name}()"
|
|
62
|
+
else:
|
|
63
|
+
for m in cls.all_methods:
|
|
64
|
+
if m.method_index == mb.method:
|
|
65
|
+
mname = m.name
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
print(bold(f"{cls.name}.{mname}") +
|
|
69
|
+
f" ({len(mb.code)} bytes)")
|
|
70
|
+
for instr in decode_instructions(mb.code):
|
|
71
|
+
ops = ", ".join(str(o) for o in instr.operands)
|
|
72
|
+
print(f" 0x{instr.offset:04X} "
|
|
73
|
+
f"{instr.mnemonic:<24s} {ops}")
|
|
74
|
+
print()
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
print("Specify --class or --method-index.")
|
flashkit/cli/extract.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""``flashkit extract`` — extract ABC bytecode from a SWF."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ._util import load
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
12
|
+
p = sub.add_parser("extract", help="Extract ABC blocks from SWF")
|
|
13
|
+
p.add_argument("file", help="SWF file")
|
|
14
|
+
p.add_argument("-o", "--output", help="Output directory")
|
|
15
|
+
p.set_defaults(func=run)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run(args: argparse.Namespace) -> None:
|
|
19
|
+
ws = load(args.file)
|
|
20
|
+
res = ws.resources[0]
|
|
21
|
+
out_dir = Path(args.output) if args.output else Path(".")
|
|
22
|
+
|
|
23
|
+
if not res.abc_blocks:
|
|
24
|
+
print("No ABC blocks found.")
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
from ..abc.writer import serialize_abc
|
|
28
|
+
|
|
29
|
+
for i, abc in enumerate(res.abc_blocks):
|
|
30
|
+
raw = serialize_abc(abc)
|
|
31
|
+
name = f"abc_{i}.abc"
|
|
32
|
+
dest = out_dir / name
|
|
33
|
+
dest.write_bytes(raw)
|
|
34
|
+
print(f" Wrote {dest} ({len(raw)} bytes)")
|
|
35
|
+
|
|
36
|
+
print(f"\nExtracted {len(res.abc_blocks)} ABC block(s)")
|
flashkit/cli/info.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""``flashkit info`` — show high-level file summary."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from ._util import load, bold
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
11
|
+
p = sub.add_parser("info", help="Show file summary")
|
|
12
|
+
p.add_argument("file", help="SWF or SWZ file")
|
|
13
|
+
p.set_defaults(func=run)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run(args: argparse.Namespace) -> None:
|
|
17
|
+
ws = load(args.file)
|
|
18
|
+
res = ws.resources[0]
|
|
19
|
+
|
|
20
|
+
print(bold(f"File: {res.path}"))
|
|
21
|
+
print(f" Format: {res.kind.upper()}")
|
|
22
|
+
if res.swf_version is not None:
|
|
23
|
+
print(f" SWF version: {res.swf_version}")
|
|
24
|
+
if res.swf_tags is not None:
|
|
25
|
+
print(f" Tags: {len(res.swf_tags)}")
|
|
26
|
+
print(f" ABC blocks: {len(res.abc_blocks)}")
|
|
27
|
+
print(f" Classes: {res.class_count}")
|
|
28
|
+
print(f" Methods: {res.method_count}")
|
|
29
|
+
print(f" Strings: {res.string_count}")
|
|
30
|
+
|
|
31
|
+
if res.abc_blocks:
|
|
32
|
+
abc = res.abc_blocks[0]
|
|
33
|
+
print(f" Namespaces: {len(abc.namespace_pool)}")
|
|
34
|
+
print(f" Multinames: {len(abc.multiname_pool)}")
|
|
35
|
+
interfaces = sum(1 for c in ws.classes if c.is_interface)
|
|
36
|
+
if interfaces:
|
|
37
|
+
print(f" Interfaces: {interfaces}")
|
|
38
|
+
|
|
39
|
+
pkgs = ws.packages
|
|
40
|
+
if pkgs:
|
|
41
|
+
print(f" Packages: {len(pkgs)}")
|
flashkit/cli/packages.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""``flashkit packages`` — list packages and their class counts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from ._util import load, bold, dim
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
11
|
+
p = sub.add_parser("packages", help="List packages")
|
|
12
|
+
p.add_argument("file", help="SWF or SWZ file")
|
|
13
|
+
p.set_defaults(func=run)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run(args: argparse.Namespace) -> None:
|
|
17
|
+
ws = load(args.file)
|
|
18
|
+
pkgs = ws.packages
|
|
19
|
+
|
|
20
|
+
if not pkgs:
|
|
21
|
+
print("No packages found.")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
print(bold(f"{'Package':<50} {'Classes':>8}"))
|
|
25
|
+
print("-" * 60)
|
|
26
|
+
for p in sorted(pkgs, key=lambda p: p.name):
|
|
27
|
+
name = p.name or dim("(default)")
|
|
28
|
+
print(f"{name:<50} {len(p.classes):>8}")
|
|
29
|
+
|
|
30
|
+
print(f"\n{len(pkgs)} package(s)")
|
flashkit/cli/refs.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""``flashkit refs`` — find cross-references to a name."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from ._util import load, bold, dim
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
11
|
+
p = sub.add_parser("refs", help="Find cross-references to a name")
|
|
12
|
+
p.add_argument("file", help="SWF or SWZ file")
|
|
13
|
+
p.add_argument("name", help="Target name")
|
|
14
|
+
p.set_defaults(func=run)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run(args: argparse.Namespace) -> None:
|
|
18
|
+
ws = load(args.file)
|
|
19
|
+
|
|
20
|
+
from ..analysis.references import ReferenceIndex
|
|
21
|
+
refs = ReferenceIndex.from_workspace(ws)
|
|
22
|
+
results = refs.references_to(args.name)
|
|
23
|
+
|
|
24
|
+
if not results:
|
|
25
|
+
print(f"No references to '{args.name}'.")
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
print(bold(f"References to '{args.name}'") + f" ({len(results)} refs)")
|
|
29
|
+
for r in results:
|
|
30
|
+
loc = f"{r.source_class}.{r.source_member}" if r.source_member else r.source_class
|
|
31
|
+
print(f" {loc} {dim(r.ref_kind)}")
|
flashkit/cli/strings.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""``flashkit strings`` — list or search string constants."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from ._util import load, bold, dim, green
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
11
|
+
p = sub.add_parser("strings", help="List or search strings")
|
|
12
|
+
p.add_argument("file", help="SWF or SWZ file")
|
|
13
|
+
p.add_argument("-s", "--search", help="Search term")
|
|
14
|
+
p.add_argument("-r", "--regex", action="store_true",
|
|
15
|
+
help="Treat search term as regex")
|
|
16
|
+
p.add_argument("-c", "--classify", action="store_true",
|
|
17
|
+
help="Classify strings (URLs, debug markers)")
|
|
18
|
+
p.add_argument("-v", "--verbose", action="store_true",
|
|
19
|
+
help="Show usage locations")
|
|
20
|
+
p.set_defaults(func=run)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run(args: argparse.Namespace) -> None:
|
|
24
|
+
ws = load(args.file)
|
|
25
|
+
|
|
26
|
+
from ..analysis.strings import StringIndex
|
|
27
|
+
idx = StringIndex.from_workspace(ws)
|
|
28
|
+
|
|
29
|
+
if args.search:
|
|
30
|
+
matches = idx.search(args.search, regex=args.regex)
|
|
31
|
+
if not matches:
|
|
32
|
+
print("No matching strings.")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
for s in matches:
|
|
36
|
+
print(f" {green(repr(s))}")
|
|
37
|
+
if args.verbose:
|
|
38
|
+
for u in idx.by_string.get(s, []):
|
|
39
|
+
print(f" used in {dim(u.class_name)}.{u.method_name}")
|
|
40
|
+
print(f"\n{len(matches)} unique string(s)")
|
|
41
|
+
else:
|
|
42
|
+
all_strings = sorted(idx.pool_strings)
|
|
43
|
+
if args.classify:
|
|
44
|
+
urls = idx.url_strings()
|
|
45
|
+
files = idx.debug_markers()
|
|
46
|
+
print(bold("URLs:"))
|
|
47
|
+
for s in urls:
|
|
48
|
+
print(f" {s}")
|
|
49
|
+
print(f"\n{bold('Debug markers:')}")
|
|
50
|
+
for s in files:
|
|
51
|
+
print(f" {s}")
|
|
52
|
+
print(f"\n{len(urls)} URL(s), {len(files)} debug marker(s), "
|
|
53
|
+
f"{len(all_strings)} total strings")
|
|
54
|
+
else:
|
|
55
|
+
for s in all_strings:
|
|
56
|
+
if s:
|
|
57
|
+
print(f" {s}")
|
|
58
|
+
print(f"\n{len(all_strings)} string(s)")
|
flashkit/cli/tags.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""``flashkit tags`` — list all SWF tags."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from ._util import load, bold
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
11
|
+
p = sub.add_parser("tags", help="List SWF tags")
|
|
12
|
+
p.add_argument("file", help="SWF file")
|
|
13
|
+
p.set_defaults(func=run)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run(args: argparse.Namespace) -> None:
|
|
17
|
+
ws = load(args.file)
|
|
18
|
+
res = ws.resources[0]
|
|
19
|
+
|
|
20
|
+
if res.swf_tags is None:
|
|
21
|
+
print("No SWF tags (file is SWZ format).")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
print(bold(f"{'#':<6} {'Type':<8} {'Name':<35} {'Size':>10}"))
|
|
25
|
+
print("-" * 62)
|
|
26
|
+
for i, tag in enumerate(res.swf_tags):
|
|
27
|
+
type_str = str(tag.tag_type)
|
|
28
|
+
name = tag.type_name
|
|
29
|
+
size = len(tag.payload)
|
|
30
|
+
print(f"{i:<6} {type_str:<8} {name:<35} {size:>10}")
|
|
31
|
+
|
|
32
|
+
print(f"\n{len(res.swf_tags)} tags total")
|
flashkit/cli/tree.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""``flashkit tree`` — show inheritance tree for a class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from ._util import load, bold, green
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(sub: argparse._SubParsersAction) -> None:
|
|
11
|
+
p = sub.add_parser("tree", help="Show inheritance tree")
|
|
12
|
+
p.add_argument("file", help="SWF or SWZ file")
|
|
13
|
+
p.add_argument("name", help="Class name")
|
|
14
|
+
p.add_argument("-a", "--ancestors", action="store_true",
|
|
15
|
+
help="Show ancestors instead of descendants")
|
|
16
|
+
p.set_defaults(func=run)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run(args: argparse.Namespace) -> None:
|
|
20
|
+
ws = load(args.file)
|
|
21
|
+
|
|
22
|
+
from ..analysis.inheritance import InheritanceGraph
|
|
23
|
+
graph = InheritanceGraph.from_classes(ws.classes)
|
|
24
|
+
|
|
25
|
+
name = args.name
|
|
26
|
+
|
|
27
|
+
if args.ancestors:
|
|
28
|
+
chain = graph.get_all_parents(name)
|
|
29
|
+
if not chain:
|
|
30
|
+
print(f"No ancestors for '{name}' (root or not found).")
|
|
31
|
+
return
|
|
32
|
+
print(bold(f"Ancestors of {name}:"))
|
|
33
|
+
for i, c in enumerate(chain):
|
|
34
|
+
print(f" {' ' * i}{c}")
|
|
35
|
+
print(f" {' ' * len(chain)}{green(name)}")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
children = graph.get_all_children(name)
|
|
39
|
+
direct = graph.get_children(name)
|
|
40
|
+
|
|
41
|
+
if not children and not direct:
|
|
42
|
+
print(f"No subclasses of '{name}'.")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
def _print_tree(n: str, depth: int = 0) -> None:
|
|
46
|
+
prefix = " " * depth
|
|
47
|
+
print(f"{prefix}{green(n) if depth == 0 else n}")
|
|
48
|
+
for child in sorted(graph.get_children(n)):
|
|
49
|
+
_print_tree(child, depth + 1)
|
|
50
|
+
|
|
51
|
+
_print_tree(name)
|
|
52
|
+
print(f"\n{len(children)} descendant(s)")
|
flashkit/errors.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
flashkit error hierarchy.
|
|
3
|
+
|
|
4
|
+
All flashkit-specific exceptions inherit from ``FlashkitError`` so
|
|
5
|
+
consumers can ``except FlashkitError`` to catch any library error.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FlashkitError(Exception):
|
|
10
|
+
"""Base exception for all flashkit errors."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ParseError(FlashkitError):
|
|
14
|
+
"""Raised when binary data cannot be parsed.
|
|
15
|
+
|
|
16
|
+
Covers both SWF container parsing and ABC bytecode parsing.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SWFParseError(ParseError):
|
|
21
|
+
"""Raised when SWF data is invalid or corrupted."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ABCParseError(ParseError):
|
|
25
|
+
"""Raised when ABC bytecode is invalid or corrupted."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SerializeError(FlashkitError):
|
|
29
|
+
"""Raised when an AbcFile cannot be serialized back to bytes."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ResourceError(FlashkitError):
|
|
33
|
+
"""Raised when a resource file cannot be loaded."""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rich resolved model for ABC class, field, and method information.
|
|
3
|
+
|
|
4
|
+
This package resolves raw ABC constant pool indices into human-readable
|
|
5
|
+
names, types, and signatures.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .class_info import ClassInfo, build_class_info, build_all_classes
|
|
9
|
+
from .member_info import (
|
|
10
|
+
FieldInfo,
|
|
11
|
+
MethodInfoResolved,
|
|
12
|
+
resolve_multiname,
|
|
13
|
+
resolve_multiname_full,
|
|
14
|
+
resolve_traits,
|
|
15
|
+
build_method_body_map,
|
|
16
|
+
)
|
|
17
|
+
from .package_info import PackageInfo, group_by_package
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"ClassInfo",
|
|
21
|
+
"build_class_info",
|
|
22
|
+
"build_all_classes",
|
|
23
|
+
"FieldInfo",
|
|
24
|
+
"MethodInfoResolved",
|
|
25
|
+
"resolve_multiname",
|
|
26
|
+
"resolve_multiname_full",
|
|
27
|
+
"resolve_traits",
|
|
28
|
+
"build_method_body_map",
|
|
29
|
+
"PackageInfo",
|
|
30
|
+
"group_by_package",
|
|
31
|
+
]
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Resolved class information.
|
|
3
|
+
|
|
4
|
+
``ClassInfo`` wraps an ABC InstanceInfo + ClassInfo pair with all names
|
|
5
|
+
resolved from the constant pool. Consumers get direct string access to
|
|
6
|
+
class names, superclass names, interface names, and fully resolved
|
|
7
|
+
field/method lists.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
|
|
14
|
+
from ..abc.types import AbcFile
|
|
15
|
+
from ..abc.constants import INSTANCE_Interface
|
|
16
|
+
from .member_info import (
|
|
17
|
+
FieldInfo, MethodInfoResolved,
|
|
18
|
+
resolve_multiname, resolve_multiname_full, resolve_traits,
|
|
19
|
+
build_method_body_map,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ClassInfo:
|
|
25
|
+
"""A fully resolved class definition.
|
|
26
|
+
|
|
27
|
+
All names are resolved from the ABC constant pool into strings.
|
|
28
|
+
Fields and methods are parsed from raw trait data into structured
|
|
29
|
+
``FieldInfo`` and ``MethodInfoResolved`` objects.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
name: Class name string.
|
|
33
|
+
package: Package/namespace string (empty for default package).
|
|
34
|
+
qualified_name: Full ``package.name`` string.
|
|
35
|
+
super_name: Superclass name (``"Object"`` for root, ``"*"`` if none).
|
|
36
|
+
super_package: Superclass package string.
|
|
37
|
+
interfaces: List of interface name strings.
|
|
38
|
+
is_interface: Whether this class is an interface.
|
|
39
|
+
is_sealed: Whether this class is sealed (no dynamic properties).
|
|
40
|
+
is_final: Whether this class is final (cannot be subclassed).
|
|
41
|
+
fields: Instance fields (FieldInfo list).
|
|
42
|
+
methods: Instance methods, getters, setters (MethodInfoResolved list).
|
|
43
|
+
static_fields: Static fields (FieldInfo list).
|
|
44
|
+
static_methods: Static methods (MethodInfoResolved list).
|
|
45
|
+
constructor_index: Method index for the instance initializer.
|
|
46
|
+
static_init_index: Method index for the static initializer.
|
|
47
|
+
instance_index: Index in AbcFile.instances (and AbcFile.classes).
|
|
48
|
+
multiname_index: Original multiname index for the class name.
|
|
49
|
+
super_multiname_index: Original multiname index for the superclass.
|
|
50
|
+
interface_multiname_indices: Original multiname indices for interfaces.
|
|
51
|
+
"""
|
|
52
|
+
name: str = ""
|
|
53
|
+
package: str = ""
|
|
54
|
+
qualified_name: str = ""
|
|
55
|
+
super_name: str = "*"
|
|
56
|
+
super_package: str = ""
|
|
57
|
+
interfaces: list[str] = field(default_factory=list)
|
|
58
|
+
is_interface: bool = False
|
|
59
|
+
is_sealed: bool = False
|
|
60
|
+
is_final: bool = False
|
|
61
|
+
fields: list[FieldInfo] = field(default_factory=list)
|
|
62
|
+
methods: list[MethodInfoResolved] = field(default_factory=list)
|
|
63
|
+
static_fields: list[FieldInfo] = field(default_factory=list)
|
|
64
|
+
static_methods: list[MethodInfoResolved] = field(default_factory=list)
|
|
65
|
+
constructor_index: int = 0
|
|
66
|
+
static_init_index: int = 0
|
|
67
|
+
instance_index: int = 0
|
|
68
|
+
multiname_index: int = 0
|
|
69
|
+
super_multiname_index: int = 0
|
|
70
|
+
interface_multiname_indices: list[int] = field(default_factory=list)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def all_fields(self) -> list[FieldInfo]:
|
|
74
|
+
"""All fields (instance + static)."""
|
|
75
|
+
return self.fields + self.static_fields
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def all_methods(self) -> list[MethodInfoResolved]:
|
|
79
|
+
"""All methods (instance + static)."""
|
|
80
|
+
return self.methods + self.static_methods
|
|
81
|
+
|
|
82
|
+
def get_field(self, name: str) -> FieldInfo | None:
|
|
83
|
+
"""Find a field by name (searches instance then static)."""
|
|
84
|
+
for f in self.fields:
|
|
85
|
+
if f.name == name:
|
|
86
|
+
return f
|
|
87
|
+
for f in self.static_fields:
|
|
88
|
+
if f.name == name:
|
|
89
|
+
return f
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
def get_method(self, name: str) -> MethodInfoResolved | None:
|
|
93
|
+
"""Find a method by name (searches instance then static)."""
|
|
94
|
+
for m in self.methods:
|
|
95
|
+
if m.name == name:
|
|
96
|
+
return m
|
|
97
|
+
for m in self.static_methods:
|
|
98
|
+
if m.name == name:
|
|
99
|
+
return m
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def build_class_info(abc: AbcFile, index: int,
|
|
104
|
+
method_body_map: dict[int, int] | None = None) -> ClassInfo:
|
|
105
|
+
"""Build a ClassInfo from an AbcFile instance/class pair.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
abc: The AbcFile containing the class.
|
|
109
|
+
index: Index into abc.instances and abc.classes.
|
|
110
|
+
method_body_map: Optional pre-built method→body index map.
|
|
111
|
+
If None, one will be built automatically.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Fully resolved ClassInfo.
|
|
115
|
+
"""
|
|
116
|
+
if method_body_map is None:
|
|
117
|
+
method_body_map = build_method_body_map(abc)
|
|
118
|
+
|
|
119
|
+
inst = abc.instances[index]
|
|
120
|
+
cls = abc.classes[index]
|
|
121
|
+
|
|
122
|
+
# Resolve class name
|
|
123
|
+
package, name = resolve_multiname_full(abc, inst.name)
|
|
124
|
+
qualified = f"{package}.{name}" if package else name
|
|
125
|
+
|
|
126
|
+
# Resolve superclass
|
|
127
|
+
super_package, super_name = resolve_multiname_full(abc, inst.super_name)
|
|
128
|
+
|
|
129
|
+
# Resolve interfaces
|
|
130
|
+
iface_names = [resolve_multiname(abc, i) for i in inst.interfaces]
|
|
131
|
+
|
|
132
|
+
# Resolve instance traits (fields + methods)
|
|
133
|
+
inst_fields, inst_methods = resolve_traits(
|
|
134
|
+
abc, inst.traits, is_static=False, method_body_map=method_body_map)
|
|
135
|
+
|
|
136
|
+
# Resolve static traits (static fields + static methods)
|
|
137
|
+
static_fields, static_methods = resolve_traits(
|
|
138
|
+
abc, cls.traits, is_static=True, method_body_map=method_body_map)
|
|
139
|
+
|
|
140
|
+
return ClassInfo(
|
|
141
|
+
name=name,
|
|
142
|
+
package=package,
|
|
143
|
+
qualified_name=qualified,
|
|
144
|
+
super_name=super_name,
|
|
145
|
+
super_package=super_package,
|
|
146
|
+
interfaces=iface_names,
|
|
147
|
+
is_interface=bool(inst.flags & INSTANCE_Interface),
|
|
148
|
+
is_sealed=bool(inst.flags & 0x01),
|
|
149
|
+
is_final=bool(inst.flags & 0x02),
|
|
150
|
+
fields=inst_fields,
|
|
151
|
+
methods=inst_methods,
|
|
152
|
+
static_fields=static_fields,
|
|
153
|
+
static_methods=static_methods,
|
|
154
|
+
constructor_index=inst.iinit,
|
|
155
|
+
static_init_index=cls.cinit,
|
|
156
|
+
instance_index=index,
|
|
157
|
+
multiname_index=inst.name,
|
|
158
|
+
super_multiname_index=inst.super_name,
|
|
159
|
+
interface_multiname_indices=list(inst.interfaces),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def build_all_classes(abc: AbcFile) -> list[ClassInfo]:
|
|
164
|
+
"""Build ClassInfo objects for all classes in an AbcFile.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
abc: The AbcFile to process.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List of ClassInfo, one per class, in the same order as abc.instances.
|
|
171
|
+
"""
|
|
172
|
+
method_body_map = build_method_body_map(abc)
|
|
173
|
+
return [
|
|
174
|
+
build_class_info(abc, i, method_body_map)
|
|
175
|
+
for i in range(len(abc.instances))
|
|
176
|
+
]
|