unidecompiler-simulator 0.1.1__tar.gz
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.
- unidecompiler_simulator-0.1.1/PKG-INFO +94 -0
- unidecompiler_simulator-0.1.1/README.md +81 -0
- unidecompiler_simulator-0.1.1/pyproject.toml +21 -0
- unidecompiler_simulator-0.1.1/setup.cfg +4 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator/__init__.py +53 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator/adapters.py +147 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator/engine.py +1393 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator/environment.py +109 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator/values.py +121 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator.egg-info/PKG-INFO +94 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator.egg-info/SOURCES.txt +12 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator.egg-info/dependency_links.txt +1 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator.egg-info/requires.txt +1 -0
- unidecompiler_simulator-0.1.1/src/unidecompiler_simulator.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: unidecompiler-simulator
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A decoupled generic IR simulator for unidecompiler
|
|
5
|
+
Author-email: Wker <1670133844@qq.com>
|
|
6
|
+
License-Expression: AGPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/Wker666/unidecompiler
|
|
8
|
+
Project-URL: Repository, https://github.com/Wker666/unidecompiler
|
|
9
|
+
Project-URL: Issues, https://github.com/Wker666/unidecompiler/issues
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: unidecompiler<0.2.0,>=0.1.1
|
|
13
|
+
|
|
14
|
+
# unidecompiler-simulator
|
|
15
|
+
|
|
16
|
+
`unidecompiler-simulator` is a library for bounded execution of
|
|
17
|
+
`unidecompiler` generic IR. It depends on the decompiler core but the core does
|
|
18
|
+
not depend on, import, or know about this package.
|
|
19
|
+
|
|
20
|
+
The simulator owns frames, control flow, calls, limits, exceptions, and the
|
|
21
|
+
execution trace. A frontend may expose an optional `simulation_adapter`
|
|
22
|
+
attribute for frontend-specific function lookup and runtime facts. The adapter
|
|
23
|
+
may answer individual operations, but it must not execute functions, interpret
|
|
24
|
+
instructions, recover control flow, or return executable callbacks.
|
|
25
|
+
|
|
26
|
+
Generic IR execution is available without a frontend:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from unidecompiler_simulator import SimulationEngine
|
|
30
|
+
|
|
31
|
+
result = SimulationEngine().simulate_function(module, function, args=(1, 2))
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Artifact execution gives an adapter the decoded frontend-owned payload only for
|
|
35
|
+
function lookup and runtime hooks:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
result = SimulationEngine().simulate_artifact(
|
|
39
|
+
data,
|
|
40
|
+
"sample.bytecode",
|
|
41
|
+
query={"class": "Example", "method": "run"},
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The `unidecompiler-cli` package hosts the command-line interface and accepts a
|
|
46
|
+
frontend-owned function query and JSON arguments:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
unidecompiler simulate sample.bytecode --function 'Example.run' --args '[1, 2]'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For the Lua bubble-sort fixture, compile the source before invoking the
|
|
53
|
+
simulator. The Lua frontend adapter supplies only Lua function lookup and
|
|
54
|
+
runtime value facts; the simulator still executes the lifted generic IR.
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
luac -o bubble_sort.luac simulator_projects/source/arithmetic.lua
|
|
58
|
+
unidecompiler simulate bubble_sort.luac --frontend lua --function bubble_sort --args '[[5, 1, 4, 2, 8]]'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The completed result contains `"values": [[1, 2, 4, 5, 8]]`.
|
|
62
|
+
|
|
63
|
+
Built-in frontend adapters accept the following frontend-owned function queries:
|
|
64
|
+
|
|
65
|
+
| Frontend | Query |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| Python `.pyc` | Unique function name, for example `arithmetic` |
|
|
68
|
+
| JVM `.class` | Unique method name or `Class.method`, for example `Sample.add` |
|
|
69
|
+
| .NET assembly | Unique method name or `Type.Method`, for example `Probe.Add` |
|
|
70
|
+
| WebAssembly | Function name or `$funcN`, for example `add` or `$func0` |
|
|
71
|
+
| Lua chunk | Unique function name, for example `bubble_sort` |
|
|
72
|
+
|
|
73
|
+
Ambiguous queries are rejected by the relevant frontend adapter. The simulator
|
|
74
|
+
does not choose among overloads or perform frontend-specific name recovery.
|
|
75
|
+
|
|
76
|
+
Execution is bounded and in-memory. Unknown operations, unsupported IR, and
|
|
77
|
+
unsafe runtime behavior stop with a structured result instead of guessing.
|
|
78
|
+
|
|
79
|
+
Applications may inject an `ExternalEnvironment` for unresolved named calls.
|
|
80
|
+
The environment receives only `ExternalCallRequest` data and returns an
|
|
81
|
+
`ExternalCallResult`; it never receives IR, frames, adapters, or execution
|
|
82
|
+
control. Python-file loading is intentionally owned by `unidecompiler-cli`,
|
|
83
|
+
not this library.
|
|
84
|
+
|
|
85
|
+
The simulator does not resolve `Global` expressions as function names. Any
|
|
86
|
+
frontend-specific function lookup or dynamic call target must be provided by
|
|
87
|
+
the optional adapter and must resolve back to a `FunctionIR` owned by the
|
|
88
|
+
current lifted module.
|
|
89
|
+
|
|
90
|
+
The simulator intentionally does not execute frontend bytecode, frontend
|
|
91
|
+
opcode tables, or core `Effect` objects directly. Core is responsible for
|
|
92
|
+
lifting VM-neutral effects into generic IR; this package executes that generic
|
|
93
|
+
IR. Keeping that boundary strict prevents a language-specific opcode switch
|
|
94
|
+
from leaking into the simulator and keeps every frontend replaceable.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# unidecompiler-simulator
|
|
2
|
+
|
|
3
|
+
`unidecompiler-simulator` is a library for bounded execution of
|
|
4
|
+
`unidecompiler` generic IR. It depends on the decompiler core but the core does
|
|
5
|
+
not depend on, import, or know about this package.
|
|
6
|
+
|
|
7
|
+
The simulator owns frames, control flow, calls, limits, exceptions, and the
|
|
8
|
+
execution trace. A frontend may expose an optional `simulation_adapter`
|
|
9
|
+
attribute for frontend-specific function lookup and runtime facts. The adapter
|
|
10
|
+
may answer individual operations, but it must not execute functions, interpret
|
|
11
|
+
instructions, recover control flow, or return executable callbacks.
|
|
12
|
+
|
|
13
|
+
Generic IR execution is available without a frontend:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from unidecompiler_simulator import SimulationEngine
|
|
17
|
+
|
|
18
|
+
result = SimulationEngine().simulate_function(module, function, args=(1, 2))
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Artifact execution gives an adapter the decoded frontend-owned payload only for
|
|
22
|
+
function lookup and runtime hooks:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
result = SimulationEngine().simulate_artifact(
|
|
26
|
+
data,
|
|
27
|
+
"sample.bytecode",
|
|
28
|
+
query={"class": "Example", "method": "run"},
|
|
29
|
+
)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `unidecompiler-cli` package hosts the command-line interface and accepts a
|
|
33
|
+
frontend-owned function query and JSON arguments:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
unidecompiler simulate sample.bytecode --function 'Example.run' --args '[1, 2]'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For the Lua bubble-sort fixture, compile the source before invoking the
|
|
40
|
+
simulator. The Lua frontend adapter supplies only Lua function lookup and
|
|
41
|
+
runtime value facts; the simulator still executes the lifted generic IR.
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
luac -o bubble_sort.luac simulator_projects/source/arithmetic.lua
|
|
45
|
+
unidecompiler simulate bubble_sort.luac --frontend lua --function bubble_sort --args '[[5, 1, 4, 2, 8]]'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The completed result contains `"values": [[1, 2, 4, 5, 8]]`.
|
|
49
|
+
|
|
50
|
+
Built-in frontend adapters accept the following frontend-owned function queries:
|
|
51
|
+
|
|
52
|
+
| Frontend | Query |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| Python `.pyc` | Unique function name, for example `arithmetic` |
|
|
55
|
+
| JVM `.class` | Unique method name or `Class.method`, for example `Sample.add` |
|
|
56
|
+
| .NET assembly | Unique method name or `Type.Method`, for example `Probe.Add` |
|
|
57
|
+
| WebAssembly | Function name or `$funcN`, for example `add` or `$func0` |
|
|
58
|
+
| Lua chunk | Unique function name, for example `bubble_sort` |
|
|
59
|
+
|
|
60
|
+
Ambiguous queries are rejected by the relevant frontend adapter. The simulator
|
|
61
|
+
does not choose among overloads or perform frontend-specific name recovery.
|
|
62
|
+
|
|
63
|
+
Execution is bounded and in-memory. Unknown operations, unsupported IR, and
|
|
64
|
+
unsafe runtime behavior stop with a structured result instead of guessing.
|
|
65
|
+
|
|
66
|
+
Applications may inject an `ExternalEnvironment` for unresolved named calls.
|
|
67
|
+
The environment receives only `ExternalCallRequest` data and returns an
|
|
68
|
+
`ExternalCallResult`; it never receives IR, frames, adapters, or execution
|
|
69
|
+
control. Python-file loading is intentionally owned by `unidecompiler-cli`,
|
|
70
|
+
not this library.
|
|
71
|
+
|
|
72
|
+
The simulator does not resolve `Global` expressions as function names. Any
|
|
73
|
+
frontend-specific function lookup or dynamic call target must be provided by
|
|
74
|
+
the optional adapter and must resolve back to a `FunctionIR` owned by the
|
|
75
|
+
current lifted module.
|
|
76
|
+
|
|
77
|
+
The simulator intentionally does not execute frontend bytecode, frontend
|
|
78
|
+
opcode tables, or core `Effect` objects directly. Core is responsible for
|
|
79
|
+
lifting VM-neutral effects into generic IR; this package executes that generic
|
|
80
|
+
IR. Keeping that boundary strict prevents a language-specific opcode switch
|
|
81
|
+
from leaking into the simulator and keeps every frontend replaceable.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "unidecompiler-simulator"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "A decoupled generic IR simulator for unidecompiler"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "AGPL-3.0-or-later"
|
|
11
|
+
authors = [{ name = "Wker", email = "1670133844@qq.com" }]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
dependencies = ["unidecompiler>=0.1.1,<0.2.0"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://github.com/Wker666/unidecompiler"
|
|
17
|
+
Repository = "https://github.com/Wker666/unidecompiler"
|
|
18
|
+
Issues = "https://github.com/Wker666/unidecompiler/issues"
|
|
19
|
+
|
|
20
|
+
[tool.setuptools.packages.find]
|
|
21
|
+
where = ["src"]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Decoupled, bounded execution of unidecompiler generic IR."""
|
|
2
|
+
|
|
3
|
+
from unidecompiler_simulator.adapters import (
|
|
4
|
+
CallRequest,
|
|
5
|
+
IntrinsicCall,
|
|
6
|
+
NotHandled,
|
|
7
|
+
ResolvedFunction,
|
|
8
|
+
SimulationTarget,
|
|
9
|
+
SimulationTargetCandidate,
|
|
10
|
+
SimulationAdapter,
|
|
11
|
+
)
|
|
12
|
+
from unidecompiler_simulator.engine import (
|
|
13
|
+
SimulationEngine,
|
|
14
|
+
SimulationCancellation,
|
|
15
|
+
SimulationEvent,
|
|
16
|
+
SimulationLimits,
|
|
17
|
+
SimulationResult,
|
|
18
|
+
SimulationStatus,
|
|
19
|
+
SimulationTargetListing,
|
|
20
|
+
)
|
|
21
|
+
from unidecompiler_simulator.environment import (
|
|
22
|
+
ExternalCallRequest,
|
|
23
|
+
ExternalCallResult,
|
|
24
|
+
ExternalCallStatus,
|
|
25
|
+
ExternalEnvironment,
|
|
26
|
+
ExternalFunction,
|
|
27
|
+
)
|
|
28
|
+
from unidecompiler_simulator.values import ObjectValue, SliceValue, TableValue
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"NotHandled",
|
|
32
|
+
"CallRequest",
|
|
33
|
+
"IntrinsicCall",
|
|
34
|
+
"ObjectValue",
|
|
35
|
+
"SliceValue",
|
|
36
|
+
"TableValue",
|
|
37
|
+
"ResolvedFunction",
|
|
38
|
+
"SimulationAdapter",
|
|
39
|
+
"SimulationTarget",
|
|
40
|
+
"SimulationTargetCandidate",
|
|
41
|
+
"SimulationEngine",
|
|
42
|
+
"SimulationCancellation",
|
|
43
|
+
"SimulationEvent",
|
|
44
|
+
"SimulationLimits",
|
|
45
|
+
"SimulationResult",
|
|
46
|
+
"SimulationStatus",
|
|
47
|
+
"SimulationTargetListing",
|
|
48
|
+
"ExternalCallRequest",
|
|
49
|
+
"ExternalCallResult",
|
|
50
|
+
"ExternalCallStatus",
|
|
51
|
+
"ExternalEnvironment",
|
|
52
|
+
"ExternalFunction",
|
|
53
|
+
]
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Protocol, runtime_checkable
|
|
5
|
+
|
|
6
|
+
from unidecompiler.core.ir import FunctionIR, ModuleIR
|
|
7
|
+
from unidecompiler.plugins import FrontendModule
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class _NotHandled:
|
|
11
|
+
def __repr__(self) -> str:
|
|
12
|
+
return "NotHandled"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
NotHandled = _NotHandled()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class CallRequest:
|
|
20
|
+
"""Data-only call request passed to an optional frontend adapter."""
|
|
21
|
+
|
|
22
|
+
callee: object
|
|
23
|
+
args: tuple[object, ...]
|
|
24
|
+
keywords: tuple[tuple[str, object], ...] = ()
|
|
25
|
+
context: object | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class SimulationTargetCandidate:
|
|
30
|
+
"""A frontend-owned lookup query suitable for presenting to a host."""
|
|
31
|
+
|
|
32
|
+
label: str
|
|
33
|
+
query: object
|
|
34
|
+
|
|
35
|
+
def __post_init__(self) -> None:
|
|
36
|
+
if not self.label:
|
|
37
|
+
raise ValueError("simulation target label must not be empty")
|
|
38
|
+
if callable(self.query):
|
|
39
|
+
raise TypeError("simulation target query cannot be executable")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class SimulationTarget:
|
|
44
|
+
"""A verified generic function target with an opaque frontend query."""
|
|
45
|
+
|
|
46
|
+
label: str
|
|
47
|
+
query: object
|
|
48
|
+
function_index: int
|
|
49
|
+
params: tuple[str, ...]
|
|
50
|
+
|
|
51
|
+
def __post_init__(self) -> None:
|
|
52
|
+
if not self.label:
|
|
53
|
+
raise ValueError("simulation target label must not be empty")
|
|
54
|
+
if self.function_index < 0:
|
|
55
|
+
raise ValueError("simulation target function_index must be non-negative")
|
|
56
|
+
if not all(isinstance(param, str) for param in self.params):
|
|
57
|
+
raise TypeError("simulation target parameters must be strings")
|
|
58
|
+
if callable(self.query):
|
|
59
|
+
raise TypeError("simulation target query cannot be executable")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class IntrinsicCall:
|
|
64
|
+
"""A data-only request for a simulator-owned pure runtime operation."""
|
|
65
|
+
|
|
66
|
+
name: str
|
|
67
|
+
bit_width: int | None = None
|
|
68
|
+
|
|
69
|
+
def __post_init__(self) -> None:
|
|
70
|
+
if not self.name:
|
|
71
|
+
raise ValueError("intrinsic name must not be empty")
|
|
72
|
+
if self.bit_width is not None and self.bit_width <= 0:
|
|
73
|
+
raise ValueError("intrinsic bit_width must be positive")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class ResolvedFunction:
|
|
78
|
+
"""A frontend-selected generic function plus an opaque identity token.
|
|
79
|
+
|
|
80
|
+
The simulator validates that ``function`` belongs to the lifted module and
|
|
81
|
+
owns all execution. ``context`` is only passed back to adapter hooks; it is
|
|
82
|
+
never called, interpreted, or used as control flow by the simulator.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
function: FunctionIR
|
|
86
|
+
context: object | None = None
|
|
87
|
+
identifier: str = ""
|
|
88
|
+
|
|
89
|
+
def __post_init__(self) -> None:
|
|
90
|
+
if callable(self.context):
|
|
91
|
+
raise TypeError("resolved function context cannot be executable")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@runtime_checkable
|
|
95
|
+
class SimulationAdapter(Protocol):
|
|
96
|
+
"""Optional frontend runtime facts, never a frontend execution engine.
|
|
97
|
+
|
|
98
|
+
Implementations may define any subset of the operation methods. Missing
|
|
99
|
+
methods are treated as ``NotHandled``. They must not expose execute/run/
|
|
100
|
+
step/eval methods or return executable callbacks.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
frontend_id: str
|
|
104
|
+
|
|
105
|
+
def resolve_function(
|
|
106
|
+
self,
|
|
107
|
+
query: object,
|
|
108
|
+
decoded_module: FrontendModule,
|
|
109
|
+
lifted_module: ModuleIR,
|
|
110
|
+
) -> ResolvedFunction | _NotHandled: ...
|
|
111
|
+
|
|
112
|
+
def list_simulation_targets(
|
|
113
|
+
self,
|
|
114
|
+
decoded_module: FrontendModule,
|
|
115
|
+
lifted_module: ModuleIR,
|
|
116
|
+
) -> tuple[SimulationTargetCandidate, ...] | _NotHandled: ...
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def adapter_for(plugin: object) -> SimulationAdapter | None:
|
|
120
|
+
adapter = getattr(plugin, "simulation_adapter", None)
|
|
121
|
+
if adapter is None:
|
|
122
|
+
return None
|
|
123
|
+
if isinstance(adapter, type):
|
|
124
|
+
adapter = adapter()
|
|
125
|
+
frontend_id = getattr(adapter, "frontend_id", None)
|
|
126
|
+
if not isinstance(frontend_id, str) or not frontend_id:
|
|
127
|
+
raise TypeError("simulation_adapter must declare a non-empty frontend_id")
|
|
128
|
+
if not callable(getattr(adapter, "resolve_function", None)):
|
|
129
|
+
raise TypeError("simulation_adapter must provide resolve_function")
|
|
130
|
+
forbidden = {"execute_function", "run", "step", "eval", "interpret", "next_instruction"}
|
|
131
|
+
exposed = forbidden.intersection(name for name in dir(adapter) if not name.startswith("__"))
|
|
132
|
+
if exposed:
|
|
133
|
+
names = ", ".join(sorted(exposed))
|
|
134
|
+
raise TypeError(f"simulation_adapter exposes forbidden execution methods: {names}")
|
|
135
|
+
return adapter # type: ignore[return-value]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def call_adapter(adapter: object | None, name: str, *args: Any) -> object:
|
|
139
|
+
if adapter is None:
|
|
140
|
+
return NotHandled
|
|
141
|
+
method = getattr(adapter, name, None)
|
|
142
|
+
if method is None:
|
|
143
|
+
return NotHandled
|
|
144
|
+
result = method(*args)
|
|
145
|
+
if callable(result) and not isinstance(result, ResolvedFunction):
|
|
146
|
+
raise TypeError(f"adapter operation {name!r} returned an executable callback")
|
|
147
|
+
return result
|