noctyra 0.1.0__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.
- noctyra-0.1.0/.claude/settings.local.json +9 -0
- noctyra-0.1.0/.github/dependabot.yml +11 -0
- noctyra-0.1.0/.github/workflows/ci.yml +35 -0
- noctyra-0.1.0/.gitignore +14 -0
- noctyra-0.1.0/.pre-commit-config.yaml +22 -0
- noctyra-0.1.0/.python-version +1 -0
- noctyra-0.1.0/CLAUDE.md +50 -0
- noctyra-0.1.0/LICENSE +21 -0
- noctyra-0.1.0/Makefile +21 -0
- noctyra-0.1.0/PKG-INFO +98 -0
- noctyra-0.1.0/README.md +69 -0
- noctyra-0.1.0/SECURITY.md +21 -0
- noctyra-0.1.0/assets/demo.gif +0 -0
- noctyra-0.1.0/classes/Context.py +75 -0
- noctyra-0.1.0/classes/Function.py +7 -0
- noctyra-0.1.0/classes/Variable.py +35 -0
- noctyra-0.1.0/classes/__init__.py +5 -0
- noctyra-0.1.0/main.py +100 -0
- noctyra-0.1.0/make.bat +21 -0
- noctyra-0.1.0/pyproject.toml +53 -0
- noctyra-0.1.0/tests/conftest.py +4 -0
- noctyra-0.1.0/tests/test_transformers.py +264 -0
- noctyra-0.1.0/transformers/Attributes.py +36 -0
- noctyra-0.1.0/transformers/BaseTransformer.py +46 -0
- noctyra-0.1.0/transformers/CompareSimplifier.py +55 -0
- noctyra-0.1.0/transformers/Constants.py +42 -0
- noctyra-0.1.0/transformers/DeadCodeEleminator.py +34 -0
- noctyra-0.1.0/transformers/Functions.py +33 -0
- noctyra-0.1.0/transformers/ListComp.py +99 -0
- noctyra-0.1.0/transformers/NameReplacer.py +19 -0
- noctyra-0.1.0/transformers/Pipeline.py +76 -0
- noctyra-0.1.0/transformers/__init__.py +19 -0
- noctyra-0.1.0/transformers/safe_eval.py +358 -0
- noctyra-0.1.0/utils/__init__.py +1 -0
- noctyra-0.1.0/utils/logger.py +14 -0
- noctyra-0.1.0/utils/shared.py +95 -0
- noctyra-0.1.0/uv.lock +721 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(timeout 30 time python main.py /home/lulu/Documents/noctyra_security_audit/poc_02_dos_bigint.py --output /home/lulu/Documents/noctyra_security_audit/outputs/poc_02_out.py)",
|
|
5
|
+
"Bash(echo \"EXIT:$?\")",
|
|
6
|
+
"Bash(timeout 60 bash -c \"time uv run python main.py /home/lulu/Documents/noctyra_security_audit/poc_01_dos_listcomp.py --output /home/lulu/Documents/noctyra_security_audit/outputs/poc_01_out.py\")"
|
|
7
|
+
]
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v6
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v7
|
|
17
|
+
with:
|
|
18
|
+
enable-cache: true
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
run: uv python install
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: uv sync --all-extras --dev
|
|
25
|
+
|
|
26
|
+
- name: Run Ruff (Linter)
|
|
27
|
+
run: uv run ruff check .
|
|
28
|
+
|
|
29
|
+
- name: Run Mypy (Type Check)
|
|
30
|
+
run: uv run mypy . --ignore-missing-imports
|
|
31
|
+
|
|
32
|
+
- name: Run Tests
|
|
33
|
+
run: |
|
|
34
|
+
export PYTHONPATH=$PYTHONPATH:.
|
|
35
|
+
uv run pytest tests/
|
noctyra-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-added-large-files
|
|
9
|
+
|
|
10
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
11
|
+
rev: v0.9.9
|
|
12
|
+
hooks:
|
|
13
|
+
- id: ruff
|
|
14
|
+
args: [ --fix ]
|
|
15
|
+
- id: ruff-format
|
|
16
|
+
|
|
17
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
18
|
+
rev: v1.15.0
|
|
19
|
+
hooks:
|
|
20
|
+
- id: mypy
|
|
21
|
+
additional_dependencies: [tokenize-rt==5.2.0, types-requests]
|
|
22
|
+
args: [--ignore-missing-imports]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
noctyra-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
Use `uv` for all package management:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv sync # Install basic dependencies
|
|
11
|
+
uv sync --all-extras --dev # Install all dependencies including dev tools
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
| Task | Command |
|
|
15
|
+
|------|---------|
|
|
16
|
+
| Run | `python main.py <input_file> [--output out.py] [--iterations N] [--debug]` |
|
|
17
|
+
| Test | `make test` → `uv run pytest tests/` |
|
|
18
|
+
| Lint | `make lint` → `uv run ruff check .` |
|
|
19
|
+
| Format | `make format` → `uv run black . && uv run ruff check . --fix` |
|
|
20
|
+
| Typecheck | `make typecheck` → `uv run mypy . --ignore-missing-imports` |
|
|
21
|
+
| Single test | `uv run pytest tests/test_transformers.py::TestName -v` |
|
|
22
|
+
|
|
23
|
+
## Architecture
|
|
24
|
+
|
|
25
|
+
Noctyra is an AST-based Python deobfuscation/transformation framework. It parses a Python source file, runs a pipeline of AST transformers iteratively until no changes occur (or a fixed iteration count), then formats and writes the result.
|
|
26
|
+
|
|
27
|
+
**Execution flow:**
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
main.py → ast.parse() → TransformerPipeline → black.format_str() → output file
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**TransformerPipeline** (`transformers/pipeline.py`) runs five transformers in sequence each iteration:
|
|
34
|
+
|
|
35
|
+
1. **ConstsTransformer** (`constants.py`) — constant folding, encoding/compression calls (base64, zlib, brotli, etc.)
|
|
36
|
+
2. **BasicFunctions** (`functions.py`) — `exec()` unrolling, evaluation of `ord`, `chr`, `int`, `str`, `bytes`, etc.
|
|
37
|
+
3. **BasicAttributes** (`attributes.py`) — string/bytes method resolution (`.decode`, `.join`, `.hex`, etc.) when all arguments are constant
|
|
38
|
+
4. **LCTransformer** (`listcomp.py`) — folds list comprehensions and generators into literal lists
|
|
39
|
+
5. **ConditionSimplifier** (`condsimplifier.py`) — simplifies comparisons and chained boolean expressions
|
|
40
|
+
|
|
41
|
+
**SafeEval** (`transformers/safe_eval.py`) is the core evaluation engine used by most transformers. It walks AST nodes to evaluate constant expressions safely, with hard resource limits (100,000 bytes max allocation, ±100,000 numeric range, whitelist of allowed operators/functions).
|
|
42
|
+
|
|
43
|
+
**SharedUtils** (`utils/shared.py`) holds maps reused across transformers: operator functions, safe built-ins, and encoding/compression handlers (including third-party: brotli, zstd, blosc).
|
|
44
|
+
|
|
45
|
+
## Key constraints
|
|
46
|
+
|
|
47
|
+
- **Python ≥ 3.14** required (uses newer AST APIs).
|
|
48
|
+
- SafeEval is intentionally restricted — never expand it without considering bypass risks. For untrusted input, run in an external sandbox.
|
|
49
|
+
- Transformers must be **idempotent**: applying them twice should produce the same result as once.
|
|
50
|
+
- Pre-commit hooks run ruff (lint + format) and mypy automatically on commit.
|
noctyra-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 0p4n1k <0p4n1k@mailfence.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
noctyra-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.PHONY: test lint format typecheck help
|
|
2
|
+
|
|
3
|
+
help:
|
|
4
|
+
@echo "Noctyra Development Commands"
|
|
5
|
+
@echo " test Run unit tests"
|
|
6
|
+
@echo " lint Check code style with ruff"
|
|
7
|
+
@echo " format Format code with black and ruff"
|
|
8
|
+
@echo " typecheck Run mypy type checking"
|
|
9
|
+
|
|
10
|
+
test:
|
|
11
|
+
uv run pytest tests/
|
|
12
|
+
|
|
13
|
+
lint:
|
|
14
|
+
uv run ruff check .
|
|
15
|
+
|
|
16
|
+
format:
|
|
17
|
+
uv run black .
|
|
18
|
+
uv run ruff check . --fix
|
|
19
|
+
|
|
20
|
+
typecheck:
|
|
21
|
+
uv run mypy . --ignore-missing-imports
|
noctyra-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: noctyra
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A generic static Python deobfuscator based on AST transformations
|
|
5
|
+
Project-URL: Homepage, https://github.com/0p4n1k/Noctyra
|
|
6
|
+
Project-URL: Repository, https://github.com/0p4n1k/Noctyra
|
|
7
|
+
Project-URL: Issues, https://github.com/0p4n1k/Noctyra/issues
|
|
8
|
+
Author-email: 0p4n1k <0p4n1k@mailfence.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ast,deobfuscator,python,reverse-engineering,static-analysis
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: black>=26.3.1
|
|
24
|
+
Requires-Dist: blosc>=1.11.4
|
|
25
|
+
Requires-Dist: brotli>=1.2.0
|
|
26
|
+
Requires-Dist: rich>=14.3.3
|
|
27
|
+
Requires-Dist: zstd>=1.5.7.3
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# Noctyra
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+

|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
Noctyra is an AST-based framework designed for code transformation and deobfuscation. It provides a modular pipeline to analyze and simplify Python source code by resolving complex expressions and logic.
|
|
38
|
+
|
|
39
|
+
## Overview
|
|
40
|
+
|
|
41
|
+
The project aims to provide a flexible environment for processing Python's Abstract Syntax Tree. By employing a sequence of pluggable transformers, Noctyra can:
|
|
42
|
+
|
|
43
|
+
- Simplify nested logic and conditions.
|
|
44
|
+
- Resolve static values and common encoding patterns.
|
|
45
|
+
- Unroll dynamic execution blocks into readable code.
|
|
46
|
+
- Optimize and clean up obfuscated constructs.
|
|
47
|
+
|
|
48
|
+
## Getting Started
|
|
49
|
+
|
|
50
|
+
### Prerequisites
|
|
51
|
+
|
|
52
|
+
Ensure you have `uv` installed for dependency management.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv sync
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Running the Pipeline
|
|
59
|
+
|
|
60
|
+
To process a target script, use the following command:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python main.py <input_file> [options]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Options:**
|
|
67
|
+
- `file`: Path to the input Python file (required).
|
|
68
|
+
- `--output`: File name for the transformed code (default: `out.py`).
|
|
69
|
+
- `--iterations`: Set a fixed number of transformation passes (default: `0` for auto-detect).
|
|
70
|
+
- `--max-iterations`: Limit the passes in auto mode (default: `100`).
|
|
71
|
+
- `--debug`: Enable verbose logging for debugging transformations. (look sick)
|
|
72
|
+
|
|
73
|
+
## Development
|
|
74
|
+
|
|
75
|
+
This project uses `uv` for dependency management and a `Makefile` for common tasks.
|
|
76
|
+
|
|
77
|
+
### Setup
|
|
78
|
+
```bash
|
|
79
|
+
uv sync --all-extras --dev
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Quality Control
|
|
83
|
+
Before submitting a PR or pushing changes, ensure all checks pass:
|
|
84
|
+
|
|
85
|
+
- **Run Tests**: `make test`
|
|
86
|
+
- **Lint & Format**: `make format`
|
|
87
|
+
- **Type Check**: `make typecheck`
|
|
88
|
+
|
|
89
|
+
*(Windows users: These commands work automatically via `make.bat`.)*
|
|
90
|
+
|
|
91
|
+
### CI/CD
|
|
92
|
+
Automated checks are performed on every push via GitHub Actions, including linting with Ruff, type checking with Mypy, and unit testing with Pytest.
|
|
93
|
+
|
|
94
|
+
## Security Notice
|
|
95
|
+
|
|
96
|
+
Noctyra includes an internal evaluation engine with basic resource limits. However, when dealing with untrusted code, it is highly recommended to run the pipeline within an isolated environment (such as a container or sandbox) to prevent potential side effects or resource exhaustion.
|
|
97
|
+
|
|
98
|
+
---
|
noctyra-0.1.0/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Noctyra
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
Noctyra is an AST-based framework designed for code transformation and deobfuscation. It provides a modular pipeline to analyze and simplify Python source code by resolving complex expressions and logic.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
The project aims to provide a flexible environment for processing Python's Abstract Syntax Tree. By employing a sequence of pluggable transformers, Noctyra can:
|
|
13
|
+
|
|
14
|
+
- Simplify nested logic and conditions.
|
|
15
|
+
- Resolve static values and common encoding patterns.
|
|
16
|
+
- Unroll dynamic execution blocks into readable code.
|
|
17
|
+
- Optimize and clean up obfuscated constructs.
|
|
18
|
+
|
|
19
|
+
## Getting Started
|
|
20
|
+
|
|
21
|
+
### Prerequisites
|
|
22
|
+
|
|
23
|
+
Ensure you have `uv` installed for dependency management.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv sync
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Running the Pipeline
|
|
30
|
+
|
|
31
|
+
To process a target script, use the following command:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
python main.py <input_file> [options]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Options:**
|
|
38
|
+
- `file`: Path to the input Python file (required).
|
|
39
|
+
- `--output`: File name for the transformed code (default: `out.py`).
|
|
40
|
+
- `--iterations`: Set a fixed number of transformation passes (default: `0` for auto-detect).
|
|
41
|
+
- `--max-iterations`: Limit the passes in auto mode (default: `100`).
|
|
42
|
+
- `--debug`: Enable verbose logging for debugging transformations. (look sick)
|
|
43
|
+
|
|
44
|
+
## Development
|
|
45
|
+
|
|
46
|
+
This project uses `uv` for dependency management and a `Makefile` for common tasks.
|
|
47
|
+
|
|
48
|
+
### Setup
|
|
49
|
+
```bash
|
|
50
|
+
uv sync --all-extras --dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Quality Control
|
|
54
|
+
Before submitting a PR or pushing changes, ensure all checks pass:
|
|
55
|
+
|
|
56
|
+
- **Run Tests**: `make test`
|
|
57
|
+
- **Lint & Format**: `make format`
|
|
58
|
+
- **Type Check**: `make typecheck`
|
|
59
|
+
|
|
60
|
+
*(Windows users: These commands work automatically via `make.bat`.)*
|
|
61
|
+
|
|
62
|
+
### CI/CD
|
|
63
|
+
Automated checks are performed on every push via GitHub Actions, including linting with Ruff, type checking with Mypy, and unit testing with Pytest.
|
|
64
|
+
|
|
65
|
+
## Security Notice
|
|
66
|
+
|
|
67
|
+
Noctyra includes an internal evaluation engine with basic resource limits. However, when dealing with untrusted code, it is highly recommended to run the pipeline within an isolated environment (such as a container or sandbox) to prevent potential side effects or resource exhaustion.
|
|
68
|
+
|
|
69
|
+
---
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
The following versions of Noctyra are currently supported with security updates:
|
|
6
|
+
|
|
7
|
+
| Version | Supported
|
|
8
|
+
| ------- | ------------------
|
|
9
|
+
| 0.1.x | ✅
|
|
10
|
+
|
|
11
|
+
## Reporting a Vulnerability
|
|
12
|
+
|
|
13
|
+
We take the security of Noctyra's `SafeEval` and AST transformation process seriously. If you find a way to achieve arbitrary code execution during the transformation phase (bypassing the evaluation guards), please report it responsibly.
|
|
14
|
+
|
|
15
|
+
**Do not open an issue on GitHub.** Please report vulnerabilities through the following method:
|
|
16
|
+
|
|
17
|
+
1. Send an email to [0p4n1k@mailfence.com]
|
|
18
|
+
2. Include a proof-of-concept (PoC) payload.
|
|
19
|
+
3. Describe the impact and the environment (Python version, OS).
|
|
20
|
+
|
|
21
|
+
We will respond within 48 hours and coordinate a fix.
|
|
Binary file
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from .Function import CustomFunction
|
|
2
|
+
from .Variable import Variable, supported_types
|
|
3
|
+
import ast
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Context:
|
|
7
|
+
def __init__(self, vars=None):
|
|
8
|
+
self.vars: list[Variable] = vars or []
|
|
9
|
+
|
|
10
|
+
def get(self, name: str):
|
|
11
|
+
for var in self.vars:
|
|
12
|
+
if var.name == name and var.has_value():
|
|
13
|
+
return var
|
|
14
|
+
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
def has(self, name: str):
|
|
18
|
+
return any(var.name == name for var in self.vars)
|
|
19
|
+
|
|
20
|
+
def set(self, name: str, value):
|
|
21
|
+
|
|
22
|
+
for var in self.vars:
|
|
23
|
+
if var.name == name:
|
|
24
|
+
var.set(value=value)
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
self.vars.append(Variable(name=name, value=value))
|
|
28
|
+
|
|
29
|
+
def copy_with(self, vars: list[Variable]):
|
|
30
|
+
new = Context(vars=self.vars.copy())
|
|
31
|
+
new.vars.extend(vars)
|
|
32
|
+
return new
|
|
33
|
+
|
|
34
|
+
def invalidate(self, name: str) -> None:
|
|
35
|
+
self.vars = [v for v in self.vars if v.name != name]
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def from_tree(tree: ast.AST) -> "Context":
|
|
39
|
+
ctx = Context()
|
|
40
|
+
|
|
41
|
+
for node in ast.iter_child_nodes(tree):
|
|
42
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
if isinstance(node, (ast.Assign, ast.AnnAssign, ast.NamedExpr)):
|
|
46
|
+
try:
|
|
47
|
+
if node.value is None:
|
|
48
|
+
continue
|
|
49
|
+
targets = (
|
|
50
|
+
node.targets if isinstance(node, ast.Assign) else [node.target]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if not all(isinstance(t, ast.Name) for t in targets):
|
|
54
|
+
continue
|
|
55
|
+
if isinstance(node.value, ast.Lambda):
|
|
56
|
+
value = CustomFunction(node.value.body, node.value.args)
|
|
57
|
+
|
|
58
|
+
else:
|
|
59
|
+
value = ast.literal_eval(node.value)
|
|
60
|
+
|
|
61
|
+
if isinstance(value, supported_types):
|
|
62
|
+
for target in targets:
|
|
63
|
+
if isinstance(target, ast.Name):
|
|
64
|
+
ctx.set(target.id, value)
|
|
65
|
+
|
|
66
|
+
except ValueError:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
elif isinstance(node, ast.AugAssign):
|
|
70
|
+
if not isinstance(node.target, ast.Name):
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
ctx.invalidate(node.target.id)
|
|
74
|
+
|
|
75
|
+
return ctx
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from .Function import CustomFunction
|
|
2
|
+
|
|
3
|
+
supported_types = int | float | str | bool | bytes | list | dict | set | CustomFunction
|
|
4
|
+
supported_iterator = str | bytes | dict | list | set
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Variable:
|
|
8
|
+
def __init__(self, name, value):
|
|
9
|
+
|
|
10
|
+
if not isinstance(name, str):
|
|
11
|
+
raise ValueError(f"Variable name must be a string. {name=}, {value=}")
|
|
12
|
+
|
|
13
|
+
if not isinstance(value, supported_types):
|
|
14
|
+
raise ValueError(f"Unsupported variable type. {name=}, {value=}")
|
|
15
|
+
|
|
16
|
+
self.name = name
|
|
17
|
+
self.value = value
|
|
18
|
+
|
|
19
|
+
def has_value(self):
|
|
20
|
+
return self.value is not None
|
|
21
|
+
|
|
22
|
+
def set(self, *, value):
|
|
23
|
+
|
|
24
|
+
if not isinstance(value, supported_types) and value is not None:
|
|
25
|
+
raise ValueError(
|
|
26
|
+
f"Unsupported variable type: {type(value).__name__}. {self.name=}, {value=}"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
self.value = value
|
|
30
|
+
|
|
31
|
+
def get(self) -> supported_types:
|
|
32
|
+
return self.value
|
|
33
|
+
|
|
34
|
+
def __repr__(self):
|
|
35
|
+
return f"Variable(name={self.name}, value={self.value})"
|
noctyra-0.1.0/main.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from transformers import (
|
|
2
|
+
ConstsTransformer,
|
|
3
|
+
BasicFunctions,
|
|
4
|
+
BasicAttributes,
|
|
5
|
+
LCTransformer,
|
|
6
|
+
ConditionSimplifier,
|
|
7
|
+
NameReplacer,
|
|
8
|
+
DeadCodeRemover,
|
|
9
|
+
)
|
|
10
|
+
from transformers import TransformerPipeline
|
|
11
|
+
from utils.logger import LOGGER
|
|
12
|
+
import argparse
|
|
13
|
+
import black
|
|
14
|
+
import ast
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
argparser = argparse.ArgumentParser(
|
|
19
|
+
description="Transform Python code using AST transformers."
|
|
20
|
+
)
|
|
21
|
+
argparser.add_argument("file", help="Path to the Python file to transform.")
|
|
22
|
+
argparser.add_argument(
|
|
23
|
+
"--iterations",
|
|
24
|
+
type=int,
|
|
25
|
+
default=0,
|
|
26
|
+
help="Number of iterations to apply the transformers. Default is 0 (auto).",
|
|
27
|
+
)
|
|
28
|
+
argparser.add_argument(
|
|
29
|
+
"--max-iterations",
|
|
30
|
+
type=int,
|
|
31
|
+
default=100,
|
|
32
|
+
help="Maximum iterations when using auto mode. Default is 100.",
|
|
33
|
+
)
|
|
34
|
+
argparser.add_argument(
|
|
35
|
+
"--max-depth",
|
|
36
|
+
type=int,
|
|
37
|
+
default=50,
|
|
38
|
+
help="Maximum depth when inspecting recursive function. Default is 50.",
|
|
39
|
+
)
|
|
40
|
+
argparser.add_argument(
|
|
41
|
+
"--max-allocation",
|
|
42
|
+
type=int,
|
|
43
|
+
default=100_000,
|
|
44
|
+
help="Maximum allocation to prevent DOS. Default is 100000.",
|
|
45
|
+
)
|
|
46
|
+
argparser.add_argument(
|
|
47
|
+
"--output",
|
|
48
|
+
type=str,
|
|
49
|
+
default="out.py",
|
|
50
|
+
help="Output file for the transformed code. Default is out.py.",
|
|
51
|
+
)
|
|
52
|
+
argparser.add_argument("--debug", action="store_true", help="Enable debug logging.")
|
|
53
|
+
argparser.add_argument(
|
|
54
|
+
"--stdout", action="store_true", help="Output transformed code to stdout."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
args = argparser.parse_args()
|
|
58
|
+
|
|
59
|
+
if args.debug:
|
|
60
|
+
LOGGER.setLevel("DEBUG")
|
|
61
|
+
|
|
62
|
+
with open(args.file, "r", encoding="utf-8") as f:
|
|
63
|
+
code = f.read()
|
|
64
|
+
|
|
65
|
+
tree = ast.parse(code)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
pipeline = TransformerPipeline(
|
|
69
|
+
transformers=[
|
|
70
|
+
ConstsTransformer,
|
|
71
|
+
BasicFunctions,
|
|
72
|
+
BasicAttributes,
|
|
73
|
+
LCTransformer,
|
|
74
|
+
ConditionSimplifier,
|
|
75
|
+
NameReplacer,
|
|
76
|
+
DeadCodeRemover,
|
|
77
|
+
],
|
|
78
|
+
iterations=args.iterations,
|
|
79
|
+
max_iterations=args.max_iterations,
|
|
80
|
+
max_depth=args.max_depth,
|
|
81
|
+
max_allocation=args.max_allocation,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
result = pipeline.visit(tree)
|
|
85
|
+
|
|
86
|
+
new_code = ast.unparse(result)
|
|
87
|
+
|
|
88
|
+
res = black.format_str(new_code, mode=black.Mode())
|
|
89
|
+
|
|
90
|
+
with open(args.output, "w", encoding="utf-8") as f:
|
|
91
|
+
f.write(res)
|
|
92
|
+
|
|
93
|
+
LOGGER.debug(f"Output written to {args.output}")
|
|
94
|
+
|
|
95
|
+
if args.stdout:
|
|
96
|
+
print(res)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
main()
|
noctyra-0.1.0/make.bat
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
if "%1"=="test" (
|
|
3
|
+
uv run pytest tests/
|
|
4
|
+
exit /b %ERRORLEVEL%
|
|
5
|
+
) else if "%1"=="lint" (
|
|
6
|
+
uv run ruff check .
|
|
7
|
+
exit /b %ERRORLEVEL%
|
|
8
|
+
) else if "%1"=="format" (
|
|
9
|
+
uv run ruff format .
|
|
10
|
+
uv run ruff check . --fix
|
|
11
|
+
exit /b %ERRORLEVEL%
|
|
12
|
+
) else if "%1"=="typecheck" (
|
|
13
|
+
uv run mypy . --ignore-missing-imports
|
|
14
|
+
exit /b %ERRORLEVEL%
|
|
15
|
+
) else (
|
|
16
|
+
echo Noctyra Development Commands (Windows)
|
|
17
|
+
echo make test - Run unit tests
|
|
18
|
+
echo make lint - Check code style with ruff
|
|
19
|
+
echo make format - Format code with ruff
|
|
20
|
+
echo make typecheck - Run mypy type checking
|
|
21
|
+
)
|