depyo 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +97 -0
- package/depyo.js +213 -0
- package/lib/BinaryReader.js +153 -0
- package/lib/OpCode.js +90 -0
- package/lib/OpCodes.js +940 -0
- package/lib/PycDecompiler.js +2031 -0
- package/lib/PycDisassembler.js +55 -0
- package/lib/PycReader.js +905 -0
- package/lib/PycResult.js +82 -0
- package/lib/PythonObject.js +242 -0
- package/lib/Unpickle.js +173 -0
- package/lib/ast/ast_node.js +3442 -0
- package/lib/bytecode/python_1_0.js +116 -0
- package/lib/bytecode/python_1_1.js +116 -0
- package/lib/bytecode/python_1_3.js +119 -0
- package/lib/bytecode/python_1_4.js +121 -0
- package/lib/bytecode/python_1_5.js +120 -0
- package/lib/bytecode/python_1_6.js +124 -0
- package/lib/bytecode/python_2_0.js +137 -0
- package/lib/bytecode/python_2_1.js +142 -0
- package/lib/bytecode/python_2_2.js +147 -0
- package/lib/bytecode/python_2_3.js +145 -0
- package/lib/bytecode/python_2_4.js +147 -0
- package/lib/bytecode/python_2_5.js +147 -0
- package/lib/bytecode/python_2_6.js +147 -0
- package/lib/bytecode/python_2_7.js +151 -0
- package/lib/bytecode/python_3_0.js +132 -0
- package/lib/bytecode/python_3_1.js +135 -0
- package/lib/bytecode/python_3_10.js +312 -0
- package/lib/bytecode/python_3_11.js +284 -0
- package/lib/bytecode/python_3_12.js +327 -0
- package/lib/bytecode/python_3_13.js +173 -0
- package/lib/bytecode/python_3_14.js +177 -0
- package/lib/bytecode/python_3_2.js +136 -0
- package/lib/bytecode/python_3_3.js +136 -0
- package/lib/bytecode/python_3_4.js +137 -0
- package/lib/bytecode/python_3_5.js +149 -0
- package/lib/bytecode/python_3_6.js +153 -0
- package/lib/bytecode/python_3_7.js +292 -0
- package/lib/bytecode/python_3_8.js +294 -0
- package/lib/bytecode/python_3_9.js +296 -0
- package/lib/code_reader.js +146 -0
- package/lib/handlers/binary_ops.js +174 -0
- package/lib/handlers/collections_update.js +239 -0
- package/lib/handlers/comparisons.js +95 -0
- package/lib/handlers/context_managers.js +250 -0
- package/lib/handlers/control_flow_jumps.js +954 -0
- package/lib/handlers/exceptions_blocks.js +952 -0
- package/lib/handlers/formatting.js +31 -0
- package/lib/handlers/function_calls.js +496 -0
- package/lib/handlers/function_class_build.js +330 -0
- package/lib/handlers/generators_async.js +172 -0
- package/lib/handlers/imports.js +53 -0
- package/lib/handlers/load_store_names.js +711 -0
- package/lib/handlers/loop_iterator.js +318 -0
- package/lib/handlers/misc_other.js +1201 -0
- package/lib/handlers/pattern_matching.js +226 -0
- package/lib/handlers/stack_ops.js +280 -0
- package/lib/handlers/subscript_slice.js +394 -0
- package/lib/handlers/unary_ops.js +91 -0
- package/lib/handlers/unpack.js +141 -0
- package/lib/stack_history.js +63 -0
- package/lib/zip_reader.js +217 -0
- package/package.json +35 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
const AST = require('../ast/ast_node');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper function to extract WITH statement pattern from bytecode
|
|
5
|
+
* Pattern (Python 2.6-3.1 with SETUP_FINALLY, Python 3.2+ with SETUP_WITH):
|
|
6
|
+
*
|
|
7
|
+
* LOAD_* (context manager)
|
|
8
|
+
* DUP_TOP
|
|
9
|
+
* LOAD_ATTR (__exit__)
|
|
10
|
+
* STORE_FAST (temp var)
|
|
11
|
+
* LOAD_ATTR (__enter__)
|
|
12
|
+
* CALL_FUNCTION 0
|
|
13
|
+
* STORE_FAST (temp var 2)
|
|
14
|
+
* SETUP_FINALLY/SETUP_WITH <- we are here
|
|
15
|
+
* LOAD_FAST (temp var 2)
|
|
16
|
+
* DELETE_FAST (temp var 2)
|
|
17
|
+
* [STORE_FAST (variable) | POP_TOP] <- variable name or no "as" clause
|
|
18
|
+
*/
|
|
19
|
+
function extractWithPattern() {
|
|
20
|
+
// Look ahead to confirm this is a WITH pattern
|
|
21
|
+
let nextInstr = this.code.Next;
|
|
22
|
+
if (!nextInstr) return null;
|
|
23
|
+
|
|
24
|
+
// After SETUP_FINALLY/SETUP_WITH, should have LOAD_FAST + DELETE_FAST
|
|
25
|
+
if (nextInstr.OpCodeID !== this.OpCodes.LOAD_FAST_A) return null;
|
|
26
|
+
|
|
27
|
+
let secondInstr = this.code.PeekInstructionAtOffset(nextInstr.Offset + 3);
|
|
28
|
+
if (!secondInstr || secondInstr.OpCodeID !== this.OpCodes.DELETE_FAST_A) return null;
|
|
29
|
+
|
|
30
|
+
// Third instruction determines if there's an "as" clause
|
|
31
|
+
let thirdInstr = this.code.PeekInstructionAtOffset(secondInstr.Offset + 3);
|
|
32
|
+
let asVariable = null;
|
|
33
|
+
|
|
34
|
+
if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.STORE_FAST_A) {
|
|
35
|
+
// Has "as variable"
|
|
36
|
+
asVariable = new AST.ASTName(thirdInstr.Name?.toString() || "###FIXME###");
|
|
37
|
+
} else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.POP_TOP) {
|
|
38
|
+
// No "as" clause
|
|
39
|
+
asVariable = null;
|
|
40
|
+
} else {
|
|
41
|
+
return null; // Not a WITH pattern
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Look backward for the context manager expression
|
|
45
|
+
let ctxMgrExpr = null;
|
|
46
|
+
|
|
47
|
+
// Walk back to find LOAD_* instruction (context manager)
|
|
48
|
+
// Scan by instruction index (not offset - instruction sizes vary!)
|
|
49
|
+
for (let idx = this.code.CurrentInstructionIndex - 1; idx >= 0; idx--) {
|
|
50
|
+
let instr = this.code.Instructions[idx];
|
|
51
|
+
if (!instr) continue;
|
|
52
|
+
|
|
53
|
+
if ([this.OpCodes.LOAD_FAST_A, this.OpCodes.LOAD_GLOBAL_A, this.OpCodes.LOAD_NAME_A,
|
|
54
|
+
this.OpCodes.LOAD_DEREF_A, this.OpCodes.CALL_FUNCTION, this.OpCodes.CALL_FUNCTION_A,
|
|
55
|
+
this.OpCodes.BINARY_SUBSCR, this.OpCodes.LOAD_ATTR_A].includes(instr.OpCodeID)) {
|
|
56
|
+
|
|
57
|
+
// Check if next instruction (by index) is DUP_TOP
|
|
58
|
+
let afterLoad = this.code.Instructions[idx + 1];
|
|
59
|
+
if (afterLoad && afterLoad.OpCodeID === this.OpCodes.DUP_TOP) {
|
|
60
|
+
// Found it! Create AST node for the context manager
|
|
61
|
+
if (instr.OpCodeID === this.OpCodes.LOAD_FAST_A ||
|
|
62
|
+
instr.OpCodeID === this.OpCodes.LOAD_GLOBAL_A ||
|
|
63
|
+
instr.OpCodeID === this.OpCodes.LOAD_NAME_A ||
|
|
64
|
+
instr.OpCodeID === this.OpCodes.LOAD_DEREF_A) {
|
|
65
|
+
ctxMgrExpr = new AST.ASTName(instr.Name?.toString() || "###FIXME###");
|
|
66
|
+
}else {
|
|
67
|
+
// For CALL_FUNCTION, LOAD_ATTR, etc., we can't easily reconstruct
|
|
68
|
+
// the expression without replaying the stack. Use placeholder for now.
|
|
69
|
+
ctxMgrExpr = new AST.ASTName("###FIXME###");
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
contextManager: ctxMgrExpr,
|
|
78
|
+
asVariable: asVariable
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function handleSetupWithA() {
|
|
83
|
+
let pattern = extractWithPattern.call(this);
|
|
84
|
+
|
|
85
|
+
// Find the WITH_CLEANUP instruction to determine the actual end of the WITH block
|
|
86
|
+
let withEnd = this.code.Current.JumpTarget;
|
|
87
|
+
let searchInstr = this.code.PeekInstructionAtOffset(withEnd);
|
|
88
|
+
for (let i = 0; i < 10 && searchInstr; i++) {
|
|
89
|
+
if (searchInstr.OpCodeID === this.OpCodes.WITH_CLEANUP ||
|
|
90
|
+
searchInstr.OpCodeID === this.OpCodes.WITH_CLEANUP_START ||
|
|
91
|
+
searchInstr.OpCodeID === this.OpCodes.WITH_CLEANUP_FINISH) {
|
|
92
|
+
withEnd = searchInstr.Offset;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
searchInstr = this.code.PeekInstructionAtOffset(searchInstr.Offset + 3);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let withBlock = new AST.ASTWithBlock(this.code.Current.Offset, withEnd);
|
|
99
|
+
|
|
100
|
+
if (pattern) {
|
|
101
|
+
withBlock.expr = pattern.contextManager;
|
|
102
|
+
withBlock.var = pattern.asVariable;
|
|
103
|
+
|
|
104
|
+
// Skip the pattern instructions (LOAD_FAST, DELETE_FAST, STORE_FAST/POP_TOP)
|
|
105
|
+
// These were already extracted into withBlock.expr and withBlock.var
|
|
106
|
+
this.code.GoNext(); // Skip LOAD_FAST
|
|
107
|
+
this.code.GoNext(); // Skip DELETE_FAST
|
|
108
|
+
this.code.GoNext(); // Skip STORE_FAST or POP_TOP
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.blocks.push(withBlock);
|
|
112
|
+
this.curBlock = this.blocks.top();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function handleWithCleanupStart() {
|
|
116
|
+
handleWithCleanup.call(this);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleWithCleanup() {
|
|
120
|
+
// Stack top should be a None. Ignore it.
|
|
121
|
+
let none = this.dataStack.pop();
|
|
122
|
+
|
|
123
|
+
if (global.g_cliArgs?.debug) {
|
|
124
|
+
console.log(`WITH_CLEANUP at offset ${this.code.Current.Offset}, curBlock=${this.curBlock.type_str}, end=${this.curBlock.end}, stackTop=${none?.constructor.name}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!(none instanceof AST.ASTNone)) {
|
|
128
|
+
if (global.g_cliArgs?.debug) {
|
|
129
|
+
console.error("Something TERRIBLE happened!\n");
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.With
|
|
135
|
+
&& this.curBlock.end == this.code.Current.Offset) {
|
|
136
|
+
let withBlock = this.curBlock;
|
|
137
|
+
this.blocks.pop(); // Remove WITH block from stack
|
|
138
|
+
this.curBlock = this.blocks.top(); // Get parent block
|
|
139
|
+
this.curBlock.append(withBlock);
|
|
140
|
+
|
|
141
|
+
if (global.g_cliArgs?.debug) {
|
|
142
|
+
console.log(`WITH block closed successfully, nodes count=${withBlock.nodes.length}, expr=${withBlock.expr?.name}, var=${withBlock.var?.name}`);
|
|
143
|
+
console.log(` Added to ${this.curBlock.type_str} block`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
if (global.g_cliArgs?.debug) {
|
|
148
|
+
console.error(`WITH_CLEANUP mismatch: curBlock.type=${this.curBlock.type_str}, curBlock.end=${this.curBlock.end}, currentOffset=${this.code.Current.Offset}`);
|
|
149
|
+
console.error(`Something TERRIBLE happened! No matching with block found for WITH_CLEANUP at ${this.code.Current.Offset}\n`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function handleWithCleanupFinish() {
|
|
155
|
+
/* Ignore this */
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Handler for BEFORE_WITH opcode (Python 3.11+)
|
|
160
|
+
*
|
|
161
|
+
* In Python 3.11+, context managers use BEFORE_WITH instead of SETUP_WITH.
|
|
162
|
+
* BEFORE_WITH:
|
|
163
|
+
* - Pops the context manager from the stack
|
|
164
|
+
* - Calls __enter__() on it
|
|
165
|
+
* - Pushes the result (which the next STORE_* will capture as the 'as' variable)
|
|
166
|
+
*
|
|
167
|
+
* The with block boundaries come from the exception table, not from jump targets.
|
|
168
|
+
*/
|
|
169
|
+
function handleBeforeWith() {
|
|
170
|
+
// Pop the context manager from the stack (result of CALL opcode)
|
|
171
|
+
let ctxMgr = this.dataStack.pop();
|
|
172
|
+
|
|
173
|
+
if (global.g_cliArgs?.debug) {
|
|
174
|
+
console.log(`[BEFORE_WITH] at offset ${this.code.Current.Offset}, ctxMgr=${ctxMgr?.constructor.name}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Find the with block end from exception table
|
|
178
|
+
// In Python 3.11+, the exception table entry for with statements has depth=1
|
|
179
|
+
// and covers the range from STORE_* (after BEFORE_WITH) to the end of the body
|
|
180
|
+
let withEnd = this.code.LastOffset;
|
|
181
|
+
const exceptionTable = this.object.ExceptionTable || [];
|
|
182
|
+
const currentOffset = this.code.Current.Offset;
|
|
183
|
+
|
|
184
|
+
// Find the exception table entry for this with statement
|
|
185
|
+
// It should start at the STORE_* instruction that follows BEFORE_WITH
|
|
186
|
+
let witchExcTableEntry = null;
|
|
187
|
+
for (const entry of exceptionTable) {
|
|
188
|
+
// Look for entry that starts right after BEFORE_WITH
|
|
189
|
+
// The entry.start is typically the STORE_* instruction offset
|
|
190
|
+
if (entry.start > currentOffset && entry.start <= currentOffset + 10) {
|
|
191
|
+
// Found the exception table entry for this with block
|
|
192
|
+
// The 'end' points to the first instruction AFTER the protected body
|
|
193
|
+
// We use this directly since the pre-handler WITH close logic will
|
|
194
|
+
// close the block when we REACH this offset (before processing)
|
|
195
|
+
withEnd = entry.end || withEnd;
|
|
196
|
+
witchExcTableEntry = entry;
|
|
197
|
+
if (global.g_cliArgs?.debug) {
|
|
198
|
+
console.log(`[BEFORE_WITH] Found exception table entry: start=${entry.start}, end=${entry.end}, withEnd=${withEnd}, target=${entry.target}`);
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Mark this exception table entry as belonging to a with statement
|
|
205
|
+
// This prevents it from being treated as a regular exception handler
|
|
206
|
+
if (witchExcTableEntry) {
|
|
207
|
+
witchExcTableEntry._isWithStatement = true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Create the WITH block (uninitialized - processStore will set expr and var)
|
|
211
|
+
let withBlock = new AST.ASTWithBlock(currentOffset, withEnd);
|
|
212
|
+
// Don't set expr here - let processStore do it when it handles the STORE_* instruction
|
|
213
|
+
// This is important because processStore checks for uninitialized WITH blocks
|
|
214
|
+
|
|
215
|
+
// Push the __enter__ result to the stack
|
|
216
|
+
// This will be consumed by the subsequent STORE_* instruction (for 'as' variable)
|
|
217
|
+
// The processStore function will pop this and set withBlock.expr and withBlock.var
|
|
218
|
+
this.dataStack.push(ctxMgr);
|
|
219
|
+
|
|
220
|
+
// Push the with block onto the block stack
|
|
221
|
+
this.blocks.push(withBlock);
|
|
222
|
+
this.curBlock = this.blocks.top();
|
|
223
|
+
|
|
224
|
+
if (global.g_cliArgs?.debug) {
|
|
225
|
+
console.log(`[BEFORE_WITH] Created WITH block (uninitialized), end=${withEnd}, ctxMgr=${ctxMgr?.codeFragment?.()}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function handleBeforeAsyncWith() {
|
|
230
|
+
let ctxmgr = this.dataStack.top();
|
|
231
|
+
let callNode = new AST.ASTCall(new AST.ASTName('await'), [new AST.ASTBinary(ctxmgr, new AST.ASTName('__aenter__'), AST.ASTBinary.BinOp.Attr)], []);
|
|
232
|
+
callNode.line = this.code.Current.LineNo;
|
|
233
|
+
this.dataStack.push(callNode);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function handleSetupAsyncWithA() {
|
|
237
|
+
let asyncWithBlock = new AST.ASTBlock(AST.ASTBlock.BlockType.AsyncWith, this.code.Current.Offset, this.code.Current.JumpTarget);
|
|
238
|
+
this.blocks.push(asyncWithBlock);
|
|
239
|
+
this.curBlock = this.blocks.top();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = {
|
|
243
|
+
handleBeforeWith,
|
|
244
|
+
handleBeforeAsyncWith,
|
|
245
|
+
handleSetupWithA,
|
|
246
|
+
handleWithCleanupStart,
|
|
247
|
+
handleWithCleanup,
|
|
248
|
+
handleWithCleanupFinish,
|
|
249
|
+
handleSetupAsyncWithA
|
|
250
|
+
};
|