Metadata-Version: 2.5
Name: bpatch
Version: 0.1.0
Summary: A library for runtime Python bytecode patching.
Project-URL: Homepage, https://git.ruject.fun/RuJect/bpatch
Project-URL: Repository, https://git.ruject.fun/RuJect/bpatch.git
Project-URL: Issues, https://git.ruject.fun/RuJect/bpatch/issues
Author-email: rus07tam <rus07tam+contact@ruject.fun>
License: Unlicense
License-File: LICENSE
Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# bpatch

A library for runtime Python bytecode patching.

## Usage

Before patches take effect, you must call `manager.apply_all()` to apply the registered patches.

### Prefix and Postfix (InjectPatcher)

The `prefix` and `postfix` decorators provide a high-level way to inject code before or after a target function's execution.

#### Prefix

A prefix hook runs before the target function. It must return a `FlowType` indicating whether to continue execution or return early.

```python
from bpatch.patch import Flow, FlowType, manager, prefix

def add(x: int, y: int) -> int:
    return x + y

@prefix(add)
def before_add(x: int, y: int) -> FlowType[int]:
    if x < 0:
        return (Flow.RETURN, -1)
    return (Flow.CONTINUE,)

manager.apply_all()

assert add(5, 7) == 12
assert add(-5, 7) == -1
```

#### Postfix

A postfix hook runs after the target function and can modify its return value.

```python
from bpatch.patch import manager, postfix

def multiply(x: int, y: int) -> int:
    return x * y

@postfix(multiply)
def after_multiply(result: int) -> int:
    return result * 2

manager.apply_all()

assert multiply(5, 5) == 50
```

### Transform (BytecodePatcher)

The `transform` decorator and `BytecodePatcher` allow for fine-grained manipulation of bytecode instructions. You can use matchers to replace, insert, or remove instructions.

```python
from bpatch.dis import Bytecode, Instruction, Opcode
from bpatch.patch import BytecodePatcher, manager, transform

def target_func() -> int:
    x = 42
    return x

def find_return(_ctx: Bytecode, inst: Instruction) -> bool:
    return inst.opcode == Opcode.RETURN_VALUE

nop = Instruction(Opcode.NOP, 0)
patcher = BytecodePatcher().replace(find_return, nop)

@transform(target_func, patcher=patcher)
def _patch(_p: BytecodePatcher) -> None:
    pass

manager.apply_all()
```

### Inline Labels

You can place `inline_label` inside a function to mark specific points in the bytecode, making it easier to target with `BytecodePatcher` using strings instead of custom finder functions.

```python
from bpatch.dis import Instruction, Opcode
from bpatch.patch import BytecodePatcher, inline_label, manager, transform

def labeled_func() -> int:
    inline_label("my_label")
    return 1

nop = Instruction(Opcode.NOP, 0)
patcher = BytecodePatcher().replace("my_label", nop)

@transform(labeled_func, patcher=patcher)
def _patch(_p: BytecodePatcher) -> None:
    pass

manager.apply_all()
```

### Executor

`Executor` allows you to trace and step through the bytecode execution of a function. The underlying implementation automatically selects the optimal backend for your Python version (`MonitoringExecutor` for 3.12+ or `TraceExecutor` for older versions).

You can use the executor to manually step through execution or yield instructions as they are executed.

```python
from bpatch.dis import Bytecode
from bpatch.executor import Executor

def simple_func() -> int:
    x = 100
    return x

bc = Bytecode.from_callable(simple_func)

# Method 1: Iterating over instructions as they execute
for inst in bc.exec():
    print(inst.opcode)

# Method 2: Manual stepping
exec_instance = Executor(bc)
while exec_instance.step():
    if exec_instance.current_instruction:
        print(exec_instance.current_instruction.opcode)
```
