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,952 @@
|
|
|
1
|
+
const AST = require('../ast/ast_node');
|
|
2
|
+
|
|
3
|
+
function handleSetupExceptA() {
|
|
4
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Container && this.curBlock.line > -1 && this.curBlock.line == this.code.Current.LineNo) {
|
|
5
|
+
this.curBlock.except = this.code.Current.JumpTarget;
|
|
6
|
+
} else if (this.code.Prev?.OpCodeID == this.OpCodes.SETUP_FINALLY_A && this.code.Prev.LineNo > -1 && this.code.Prev.LineNo != this.code.Current.LineNo) {
|
|
7
|
+
let nextBlock = new AST.ASTBlock(AST.ASTBlock.BlockType.Try, this.code.Prev.Offset, this.code.Prev.JumpTarget, true);
|
|
8
|
+
this.blocks.push(nextBlock);
|
|
9
|
+
nextBlock = new AST.ASTContainerBlock(this.code.Current.Offset, 0, this.code.Current.JumpTarget);
|
|
10
|
+
this.blocks.push(nextBlock);
|
|
11
|
+
} else {
|
|
12
|
+
let nextBlock = new AST.ASTContainerBlock(this.code.Current.Offset, 0, this.code.Current.JumpTarget);
|
|
13
|
+
this.blocks.push(nextBlock);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let tryBlock = new AST.ASTBlock(AST.ASTBlock.BlockType.Try, this.code.Current.Offset, this.code.Current.JumpTarget, true);
|
|
17
|
+
this.blocks.push(tryBlock);
|
|
18
|
+
this.curBlock = this.blocks.top();
|
|
19
|
+
|
|
20
|
+
this.need_try = false;
|
|
21
|
+
|
|
22
|
+
// Register exception handler offset
|
|
23
|
+
if (this.exceptionHandlerOffsets) {
|
|
24
|
+
this.exceptionHandlerOffsets.add(this.code.Current.JumpTarget);
|
|
25
|
+
|
|
26
|
+
if (global.g_cliArgs?.debug) {
|
|
27
|
+
console.log(`[SETUP_EXCEPT] Registered exception handler at offset ${this.code.Current.JumpTarget}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleSetupFinallyA() {
|
|
33
|
+
// In Python 2.6-3.1, SETUP_FINALLY is used for WITH statements
|
|
34
|
+
// Try to detect WITH pattern and create ASTWithBlock instead of Container
|
|
35
|
+
let withPattern = extractWithPatternForFinally.call(this);
|
|
36
|
+
|
|
37
|
+
if (withPattern) {
|
|
38
|
+
// This is a WITH statement, create ASTWithBlock
|
|
39
|
+
if (global.g_cliArgs?.debug) {
|
|
40
|
+
console.log(`Detected WITH pattern at offset ${this.code.Current.Offset}: expr=${withPattern.contextManager?.name}, var=${withPattern.asVariable?.name}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Find the WITH_CLEANUP instruction to determine the actual end of the WITH block
|
|
44
|
+
let withEnd = this.code.Current.JumpTarget;
|
|
45
|
+
let searchInstr = this.code.PeekInstructionAtOffset(withEnd);
|
|
46
|
+
for (let i = 0; i < 10 && searchInstr; i++) {
|
|
47
|
+
if (searchInstr.OpCodeID === this.OpCodes.WITH_CLEANUP ||
|
|
48
|
+
searchInstr.OpCodeID === this.OpCodes.WITH_CLEANUP_START ||
|
|
49
|
+
searchInstr.OpCodeID === this.OpCodes.WITH_CLEANUP_FINISH) {
|
|
50
|
+
withEnd = searchInstr.Offset;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
searchInstr = this.code.PeekInstructionAtOffset(searchInstr.Offset + 3);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let withBlock = new AST.ASTWithBlock(this.code.Current.Offset, withEnd);
|
|
57
|
+
withBlock.line = this.code.Current.LineNo;
|
|
58
|
+
|
|
59
|
+
if (withPattern.contextManager) {
|
|
60
|
+
withBlock.expr = withPattern.contextManager;
|
|
61
|
+
withBlock.var = withPattern.asVariable;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.blocks.push(withBlock);
|
|
65
|
+
this.curBlock = this.blocks.top();
|
|
66
|
+
|
|
67
|
+
// Skip the pattern instructions (LOAD_FAST, DELETE_FAST, STORE_FAST/POP_TOP/UNPACK_SEQUENCE+STORE_FAST...)
|
|
68
|
+
// These were already extracted into withBlock.expr and withBlock.var
|
|
69
|
+
for (let i = 0; i < withPattern.skipCount; i++) {
|
|
70
|
+
this.code.GoNext();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (global.g_cliArgs?.debug) {
|
|
74
|
+
console.log(`After skip (${withPattern.skipCount} instructions): Current=${this.code.Current.Offset}:${this.code.Current.InstructionName}, Next=${this.code.Next?.Offset}:${this.code.Next?.InstructionName}`);
|
|
75
|
+
console.log(`WITH block created: start=${withBlock.start}, end=${withBlock.end}, expr=${withBlock.expr?.name}, var=${withBlock.var?.name}`);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
// Normal SETUP_FINALLY for try/finally blocks (or try/except in Python 3.8+)
|
|
79
|
+
let nextBlock = new AST.ASTContainerBlock(this.code.Current.Offset, this.code.Current.JumpTarget);
|
|
80
|
+
nextBlock.line = this.code.Current.LineNo;
|
|
81
|
+
this.blocks.push(nextBlock);
|
|
82
|
+
this.curBlock = this.blocks.top();
|
|
83
|
+
|
|
84
|
+
this.need_try = true;
|
|
85
|
+
|
|
86
|
+
// Register exception handler offset for Python 3.8+ try-except
|
|
87
|
+
// Target offset may be exception handler (starts with DUP_TOP + LOAD_NAME + COMPARE_OP)
|
|
88
|
+
if (this.exceptionHandlerOffsets) {
|
|
89
|
+
this.exceptionHandlerOffsets.add(this.code.Current.JumpTarget);
|
|
90
|
+
|
|
91
|
+
if (global.g_cliArgs?.debug) {
|
|
92
|
+
console.log(`[SETUP_FINALLY] offset=${this.code.Current.Offset}, arg=${this.code.Current.Argument}, JumpTarget=${this.code.Current.JumpTarget}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleSetupCleanupA() {
|
|
99
|
+
// Python 3.13+ SETUP_CLEANUP behaves like SETUP_FINALLY for decompilation.
|
|
100
|
+
handleSetupFinallyA.call(this);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Helper to extract WITH pattern from SETUP_FINALLY (Python 2.6-3.1)
|
|
105
|
+
* Same pattern as extractWithPattern but callable from exceptions_blocks.js
|
|
106
|
+
*/
|
|
107
|
+
function extractWithPatternForFinally() {
|
|
108
|
+
// Python 2.6-2.7 pattern: LOAD_FAST → DELETE_FAST → STORE_FAST/POP_TOP
|
|
109
|
+
// Python 3.0+ pattern: Different (LOAD_FAST → LOAD_FAST → CALL_FUNCTION)
|
|
110
|
+
// Strategy: Check backward scan first - if DUP_TOP found, likely WITH statement
|
|
111
|
+
// Then try to extract variable name from forward scan if possible
|
|
112
|
+
|
|
113
|
+
// Look ahead to confirm this is a WITH pattern
|
|
114
|
+
let nextInstr = this.code.Next;
|
|
115
|
+
if (!nextInstr) {
|
|
116
|
+
if (global.g_cliArgs?.debug) {
|
|
117
|
+
console.log(`WITH pattern check failed: no next instruction`);
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Python 2.7 pattern check
|
|
123
|
+
if (nextInstr.OpCodeID === this.OpCodes.LOAD_FAST_A) {
|
|
124
|
+
let secondInstr = this.code.PeekInstructionAtOffset(nextInstr.Offset + 3);
|
|
125
|
+
|
|
126
|
+
// Python 2.7: LOAD_FAST → DELETE_FAST → STORE_FAST/POP_TOP/UNPACK_SEQUENCE
|
|
127
|
+
if (secondInstr && secondInstr.OpCodeID === this.OpCodes.DELETE_FAST_A) {
|
|
128
|
+
// Third instruction determines if there's an "as" clause
|
|
129
|
+
let thirdInstr = this.code.PeekInstructionAtOffset(secondInstr.Offset + 3);
|
|
130
|
+
let asVariable = null;
|
|
131
|
+
let skipCount = 3; // Default: LOAD_FAST + DELETE_FAST + (STORE_FAST|POP_TOP)
|
|
132
|
+
|
|
133
|
+
if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.STORE_FAST_A) {
|
|
134
|
+
// Has "as variable"
|
|
135
|
+
asVariable = new AST.ASTName(thirdInstr.Name?.toString() || "###FIXME###");
|
|
136
|
+
} else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.POP_TOP) {
|
|
137
|
+
// No "as" clause
|
|
138
|
+
asVariable = null;
|
|
139
|
+
} else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.UNPACK_SEQUENCE_A) {
|
|
140
|
+
// Tuple unpacking: with expr as (x, y):
|
|
141
|
+
// Pattern: LOAD_FAST, DELETE_FAST, UNPACK_SEQUENCE n, STORE_FAST..., STORE_FAST...
|
|
142
|
+
// Use placeholder for tuple unpacking (reconstructing tuple is complex)
|
|
143
|
+
asVariable = new AST.ASTName("###FIXME###");
|
|
144
|
+
// Skip LOAD_FAST + DELETE_FAST + UNPACK_SEQUENCE + n STORE_FAST instructions
|
|
145
|
+
skipCount = 3 + (thirdInstr.Argument || 2);
|
|
146
|
+
} else {
|
|
147
|
+
if (global.g_cliArgs?.debug) {
|
|
148
|
+
console.log(`WITH pattern (Python 2.7) check failed: third=${thirdInstr?.InstructionName}`);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Python 2.7 pattern matched - proceed with backward scan
|
|
154
|
+
let ctxMgrExpr = extractContextManager.call(this);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
contextManager: ctxMgrExpr,
|
|
158
|
+
asVariable: asVariable,
|
|
159
|
+
skipCount: skipCount
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Python 3.0+ pattern: Different forward sequence
|
|
165
|
+
// Try backward scan - if DUP_TOP found, assume WITH statement
|
|
166
|
+
if (global.g_cliArgs?.debug) {
|
|
167
|
+
console.log(`Trying Python 3.0+ WITH pattern detection at offset ${this.code.Current.Offset}`);
|
|
168
|
+
console.log(` Next 5 instructions:`);
|
|
169
|
+
for (let i = 0; i < 5; i++) {
|
|
170
|
+
let instr = this.code.PeekInstructionAtOffset(this.code.Next.Offset + i * 3);
|
|
171
|
+
if (instr) {
|
|
172
|
+
console.log(` [+${i}] ${instr.Offset}: ${instr.InstructionName} arg=${instr.Argument} name=${instr.Name}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let ctxMgrExpr = extractContextManager.call(this);
|
|
178
|
+
|
|
179
|
+
if (ctxMgrExpr) {
|
|
180
|
+
// Found context manager via backward scan
|
|
181
|
+
// CRITICAL: Before accepting as WITH statement, check if this is actually try-except
|
|
182
|
+
// Python 3.8: try-except uses SETUP_FINALLY, but handler has COMPARE_OP (arg=10) for exception match
|
|
183
|
+
// Python 3.8: real WITH uses SETUP_FINALLY, but handler has WITH_CLEANUP_START/FINISH
|
|
184
|
+
|
|
185
|
+
let handlerOffset = this.code.Current.JumpTarget;
|
|
186
|
+
if (handlerOffset > 0) {
|
|
187
|
+
// Scan exception handler to distinguish WITH from try-except
|
|
188
|
+
let scanLimit = 10; // Check first 10 instructions in handler
|
|
189
|
+
for (let i = 0; i < scanLimit; i++) {
|
|
190
|
+
let handlerInstr = this.code.PeekInstructionAtOffset(handlerOffset + i * 2); // Python 3.6+ uses 2-byte instructions
|
|
191
|
+
if (!handlerInstr) break;
|
|
192
|
+
|
|
193
|
+
// Check for COMPARE_OP with arg=10 (exception match) - this means try-except
|
|
194
|
+
if (handlerInstr.OpCodeID === this.OpCodes.COMPARE_OP_A && handlerInstr.Argument === 10) {
|
|
195
|
+
if (global.g_cliArgs?.debug) {
|
|
196
|
+
console.log(`False positive: Found COMPARE_OP EXCEPTION MATCH at offset ${handlerInstr.Offset} - this is try-except, not WITH`);
|
|
197
|
+
}
|
|
198
|
+
return null; // Not a WITH statement, it's try-except
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check for genuine WITH cleanup opcodes
|
|
202
|
+
if (handlerInstr.OpCodeID === this.OpCodes.WITH_CLEANUP_START ||
|
|
203
|
+
handlerInstr.OpCodeID === this.OpCodes.WITH_CLEANUP_FINISH) {
|
|
204
|
+
if (global.g_cliArgs?.debug) {
|
|
205
|
+
console.log(`Confirmed WITH statement: Found ${handlerInstr.InstructionName} at offset ${handlerInstr.Offset}`);
|
|
206
|
+
}
|
|
207
|
+
break; // Confirmed WITH, continue processing
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// For Python 3.0+, try to extract variable name from forward sequence
|
|
213
|
+
let asVariable = null;
|
|
214
|
+
let skipCount = 0;
|
|
215
|
+
|
|
216
|
+
// Python 3.0+ pattern after SETUP_FINALLY: complex setup code before body
|
|
217
|
+
// Strategy: Skip all setup instructions until we find the WITH body start
|
|
218
|
+
// Setup instructions: LOAD_FAST (temp vars), CALL_FUNCTION (__enter__), STORE_FAST (temp)
|
|
219
|
+
// Body starts at: LOAD_FAST (using actual variables), SETUP_FINALLY (nested), or other control flow
|
|
220
|
+
|
|
221
|
+
let searchLimit = 15;
|
|
222
|
+
let foundBodyStart = false;
|
|
223
|
+
|
|
224
|
+
for (let i = 0; i < searchLimit; i++) {
|
|
225
|
+
let instr = this.code.PeekInstructionAtOffset(this.code.Next.Offset + i * 3);
|
|
226
|
+
if (!instr) break;
|
|
227
|
+
|
|
228
|
+
// Check if this is variable assignment (WITH ... as var:)
|
|
229
|
+
if (instr.OpCodeID === this.OpCodes.STORE_FAST_A && i < 5) {
|
|
230
|
+
// Found variable assignment early in sequence
|
|
231
|
+
asVariable = new AST.ASTName(instr.Name?.toString() || "###FIXME###");
|
|
232
|
+
skipCount = i + 1;
|
|
233
|
+
foundBodyStart = true;
|
|
234
|
+
if (global.g_cliArgs?.debug) {
|
|
235
|
+
console.log(`Python 3.0+ variable found: ${instr.Name} at offset ${instr.Offset}, skipCount=${skipCount}`);
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check for body start markers
|
|
241
|
+
if (instr.OpCodeID === this.OpCodes.SETUP_FINALLY_A ||
|
|
242
|
+
instr.OpCodeID === this.OpCodes.SETUP_WITH_A ||
|
|
243
|
+
(instr.OpCodeID === this.OpCodes.LOAD_FAST_A && i > 3) || // LOAD_FAST after setup
|
|
244
|
+
instr.OpCodeID === this.OpCodes.LOAD_CONST ||
|
|
245
|
+
instr.OpCodeID === this.OpCodes.POP_BLOCK ||
|
|
246
|
+
instr.OpCodeID === this.OpCodes.CONTINUE_LOOP) {
|
|
247
|
+
// Body starts here, no "as" variable
|
|
248
|
+
skipCount = i;
|
|
249
|
+
foundBodyStart = true;
|
|
250
|
+
if (global.g_cliArgs?.debug) {
|
|
251
|
+
console.log(`Python 3.0+ body start found at offset ${instr.Offset} (${instr.InstructionName}), skipCount=${skipCount}`);
|
|
252
|
+
}
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!foundBodyStart) {
|
|
258
|
+
// Fallback: skip first 3 instructions (common setup pattern)
|
|
259
|
+
skipCount = 3;
|
|
260
|
+
if (global.g_cliArgs?.debug) {
|
|
261
|
+
console.log(`Python 3.0+ using fallback skipCount=3`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!asVariable) {
|
|
266
|
+
// No variable found - WITH statement without "as" clause
|
|
267
|
+
asVariable = null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (global.g_cliArgs?.debug) {
|
|
271
|
+
console.log(`Python 3.0+ WITH detected: expr=${ctxMgrExpr.name}, var=${asVariable?.name || 'none'}, skip=${skipCount}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
contextManager: ctxMgrExpr,
|
|
276
|
+
asVariable: asVariable,
|
|
277
|
+
skipCount: skipCount
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// No WITH pattern detected
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Extract context manager expression by scanning backward for DUP_TOP pattern
|
|
287
|
+
* Common logic for both Python 2.7 and 3.0+
|
|
288
|
+
*/
|
|
289
|
+
function extractContextManager() {
|
|
290
|
+
let ctxMgrExpr = null;
|
|
291
|
+
|
|
292
|
+
if (global.g_cliArgs?.debug) {
|
|
293
|
+
console.log(`Scanning backward from index ${this.code.CurrentInstructionIndex}, offset ${this.code.Current.Offset}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Walk back to find LOAD_* instruction (context manager)
|
|
297
|
+
// Scan by instruction index (not offset - instruction sizes vary!)
|
|
298
|
+
for (let idx = this.code.CurrentInstructionIndex - 1; idx >= 0; idx--) {
|
|
299
|
+
let instr = this.code.Instructions[idx];
|
|
300
|
+
if (!instr) continue;
|
|
301
|
+
|
|
302
|
+
if (global.g_cliArgs?.debug) {
|
|
303
|
+
console.log(` [${idx}] offset=${instr.Offset}, opcode=${instr.InstructionName} (${instr.OpCodeID})`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if ([this.OpCodes.LOAD_FAST_A, this.OpCodes.LOAD_GLOBAL_A, this.OpCodes.LOAD_NAME_A,
|
|
307
|
+
this.OpCodes.LOAD_DEREF_A, this.OpCodes.CALL_FUNCTION, this.OpCodes.CALL_FUNCTION_A,
|
|
308
|
+
this.OpCodes.BINARY_SUBSCR, this.OpCodes.LOAD_ATTR_A].includes(instr.OpCodeID)) {
|
|
309
|
+
|
|
310
|
+
// Check if next instruction (by index) is DUP_TOP
|
|
311
|
+
let afterLoad = this.code.Instructions[idx + 1];
|
|
312
|
+
if (global.g_cliArgs?.debug) {
|
|
313
|
+
console.log(` Candidate! afterLoad=${afterLoad?.Offset}:${afterLoad?.InstructionName}, DUP_TOP=${this.OpCodes.DUP_TOP}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (afterLoad && afterLoad.OpCodeID === this.OpCodes.DUP_TOP) {
|
|
317
|
+
// Found it! Create AST node for the context manager
|
|
318
|
+
if (global.g_cliArgs?.debug) {
|
|
319
|
+
console.log(` FOUND! InstructionArg=${instr.InstructionArg}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (instr.OpCodeID === this.OpCodes.LOAD_FAST_A ||
|
|
323
|
+
instr.OpCodeID === this.OpCodes.LOAD_GLOBAL_A ||
|
|
324
|
+
instr.OpCodeID === this.OpCodes.LOAD_NAME_A ||
|
|
325
|
+
instr.OpCodeID === this.OpCodes.LOAD_DEREF_A) {
|
|
326
|
+
ctxMgrExpr = new AST.ASTName(instr.Name?.toString() || "###FIXME###");
|
|
327
|
+
} else {
|
|
328
|
+
// For CALL_FUNCTION, LOAD_ATTR, etc., use placeholder
|
|
329
|
+
ctxMgrExpr = new AST.ASTName("###FIXME###");
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (global.g_cliArgs?.debug) {
|
|
337
|
+
console.log(`Result: ctxMgrExpr=${ctxMgrExpr?.name}`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return ctxMgrExpr;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function handlePopBlock() {
|
|
344
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Container ||
|
|
345
|
+
this.curBlock.blockType == AST.ASTBlock.BlockType.Finally) {
|
|
346
|
+
/* These should only be popped by an END_FINALLY */
|
|
347
|
+
if (this.code.Prev?.OpCodeID == this.OpCodes.END_FINALLY && this.curBlock.blockType == AST.ASTBlock.BlockType.Container && this.curBlock.finally == this.code.Next.Offset + 3) {
|
|
348
|
+
let finallyBlock = new AST.ASTBlock(AST.ASTBlock.BlockType.Finally, this.curBlock.finally, 0, true);
|
|
349
|
+
this.blocks.push(finallyBlock);
|
|
350
|
+
this.curBlock = this.blocks.top();
|
|
351
|
+
this.code.GoNext();
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.With) {
|
|
357
|
+
// This should only be popped by a WITH_CLEANUP
|
|
358
|
+
if (global.g_cliArgs?.debug) {
|
|
359
|
+
console.log(`POP_BLOCK on WITH block ignored at offset ${this.code.Current.Offset}`);
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (this.curBlock.nodes.length &&
|
|
365
|
+
this.curBlock.nodes.top() instanceof AST.ASTKeyword) {
|
|
366
|
+
// Don't remove keywords from AsyncFor blocks - they're real loop bodies, not placeholders
|
|
367
|
+
if (this.curBlock.blockType !== AST.ASTBlock.BlockType.AsyncFor) {
|
|
368
|
+
this.curBlock.removeLast();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let tmp = this.blocks.pop();
|
|
373
|
+
|
|
374
|
+
if (!this.blocks.empty()) {
|
|
375
|
+
this.curBlock = this.blocks.top();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (!this.blocks.empty() && !(tmp.blockType == AST.ASTBlock.BlockType.Else && tmp.nodes.empty())) {
|
|
379
|
+
this.curBlock.append(tmp);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if ([AST.ASTBlock.BlockType.For, AST.ASTBlock.BlockType.While].includes(tmp.blockType) && tmp.end >= this.code.Next?.Offset) {
|
|
383
|
+
let blkElse = new AST.ASTBlock(AST.ASTBlock.BlockType.Else, this.code.Current.Offset, tmp.end);
|
|
384
|
+
this.blocks.push(blkElse);
|
|
385
|
+
this.curBlock = this.blocks.top();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Try
|
|
389
|
+
&& tmp.blockType != AST.ASTBlock.BlockType.For
|
|
390
|
+
&& tmp.blockType != AST.ASTBlock.BlockType.AsyncFor
|
|
391
|
+
&& tmp.blockType != AST.ASTBlock.BlockType.While) {
|
|
392
|
+
tmp = this.curBlock;
|
|
393
|
+
this.blocks.pop();
|
|
394
|
+
this.curBlock = this.blocks.top();
|
|
395
|
+
|
|
396
|
+
if (!(tmp.blockType == AST.ASTBlock.BlockType.Else
|
|
397
|
+
&& tmp.nodes.empty())) {
|
|
398
|
+
this.curBlock.append(tmp);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Container) {
|
|
403
|
+
if (tmp.blockType == AST.ASTBlock.BlockType.Else && !this.curBlock.hasFinally) {
|
|
404
|
+
|
|
405
|
+
/* Pop the container */
|
|
406
|
+
let cont = this.curBlock;
|
|
407
|
+
this.blocks.pop();
|
|
408
|
+
this.curBlock = this.blocks.top();
|
|
409
|
+
this.curBlock.append(cont);
|
|
410
|
+
|
|
411
|
+
} else if (
|
|
412
|
+
(tmp.blockType == AST.ASTBlock.BlockType.Else && this.curBlock.hasFinally) ||
|
|
413
|
+
(tmp.blockType == AST.ASTBlock.BlockType.Try && !this.curBlock.hasExcept)
|
|
414
|
+
) {
|
|
415
|
+
|
|
416
|
+
let final = new AST.ASTBlock(AST.ASTBlock.BlockType.Finally, tmp.start, tmp.end, true);
|
|
417
|
+
this.blocks.push(final);
|
|
418
|
+
this.curBlock = this.blocks.top();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if ((this.curBlock.blockType == AST.ASTBlock.BlockType.For ||
|
|
423
|
+
this.curBlock.blockType == AST.ASTBlock.BlockType.AsyncFor)
|
|
424
|
+
&& this.curBlock.end == this.code.Next?.Offset) {
|
|
425
|
+
this.blocks.pop();
|
|
426
|
+
this.blocks.top().append(this.curBlock);
|
|
427
|
+
this.curBlock = this.blocks.top();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (this.blocks.empty() && tmp.blockType == AST.ASTBlock.BlockType.Main) {
|
|
431
|
+
this.blocks.push(tmp);
|
|
432
|
+
this.curBlock = this.blocks.top();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function handlePopExcept() {
|
|
437
|
+
if (!this.blocks.length || this.blocks.length <= 1) {
|
|
438
|
+
// Nothing structured to pop; keep current block stable.
|
|
439
|
+
this.curBlock = this.blocks.top?.() || this.curBlock || this.defBlock;
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
let exceptBlock = this.blocks.pop();
|
|
444
|
+
this.curBlock = this.blocks.top();
|
|
445
|
+
|
|
446
|
+
if (!exceptBlock) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (!this.curBlock) {
|
|
450
|
+
this.curBlock = this.defBlock || exceptBlock;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
let bodyNode = exceptBlock.nodes;
|
|
454
|
+
|
|
455
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Try) {
|
|
456
|
+
const pendingEgType = this.egMatchTypeStack?.shift?.() || this.pendingEgMatchType;
|
|
457
|
+
const isExceptStar = !!(this.inExceptionGroup || pendingEgType);
|
|
458
|
+
let matchType = pendingEgType || exceptBlock.condition;
|
|
459
|
+
if (matchType && typeof matchType === 'object' && 'src' in matchType && 'dest' in matchType) {
|
|
460
|
+
const storeSrc = matchType.src;
|
|
461
|
+
if (storeSrc instanceof AST.ASTCall &&
|
|
462
|
+
storeSrc.func instanceof AST.ASTName &&
|
|
463
|
+
storeSrc.func.name === "__check_eg_match__") {
|
|
464
|
+
matchType = storeSrc.pparams?.[1] || storeSrc;
|
|
465
|
+
} else {
|
|
466
|
+
matchType = matchType.src || matchType.dest || matchType;
|
|
467
|
+
}
|
|
468
|
+
} else if (matchType instanceof AST.ASTCall &&
|
|
469
|
+
matchType.func instanceof AST.ASTName &&
|
|
470
|
+
matchType.func.name === "__check_eg_match__") {
|
|
471
|
+
matchType = matchType.pparams?.[1] || matchType;
|
|
472
|
+
}
|
|
473
|
+
if (!matchType || matchType instanceof AST.ASTStore) {
|
|
474
|
+
matchType = pendingEgType || matchType;
|
|
475
|
+
}
|
|
476
|
+
let catchBlock = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, exceptBlock.start, exceptBlock.end, matchType, false);
|
|
477
|
+
catchBlock.line = exceptBlock.line;
|
|
478
|
+
catchBlock.nodes = bodyNode.nodes;
|
|
479
|
+
catchBlock.isExceptStar = isExceptStar;
|
|
480
|
+
if (catchBlock.condition && typeof catchBlock.condition === 'object' && 'src' in catchBlock.condition) {
|
|
481
|
+
const storeSrc = catchBlock.condition.src;
|
|
482
|
+
if (storeSrc instanceof AST.ASTCall &&
|
|
483
|
+
storeSrc.func instanceof AST.ASTName &&
|
|
484
|
+
storeSrc.func.name === "__check_eg_match__") {
|
|
485
|
+
catchBlock.condition = storeSrc.pparams?.[1] || storeSrc;
|
|
486
|
+
} else if (storeSrc) {
|
|
487
|
+
catchBlock.condition = storeSrc;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
this.curBlock.append(catchBlock);
|
|
491
|
+
this.inExceptionGroup = false;
|
|
492
|
+
this.inExceptionTableHandler = false;
|
|
493
|
+
this.pendingEgMatchType = null;
|
|
494
|
+
} else {
|
|
495
|
+
// If blocks stack is misaligned (e.g., exception table path), append defensively
|
|
496
|
+
if (!this.curBlock) {
|
|
497
|
+
this.curBlock = this.blocks.top() || exceptBlock;
|
|
498
|
+
}
|
|
499
|
+
if (!exceptBlock.condition) {
|
|
500
|
+
let matchType = this.egMatchTypeStack?.shift?.() || this.pendingEgMatchType || exceptBlock.condition;
|
|
501
|
+
if (matchType instanceof AST.ASTCall &&
|
|
502
|
+
matchType.func instanceof AST.ASTName &&
|
|
503
|
+
matchType.func.name === "__check_eg_match__") {
|
|
504
|
+
matchType = matchType.pparams?.[1] || matchType;
|
|
505
|
+
} else if (matchType && typeof matchType === 'object' && 'src' in matchType && matchType.src) {
|
|
506
|
+
matchType = matchType.src;
|
|
507
|
+
}
|
|
508
|
+
exceptBlock.condition = matchType;
|
|
509
|
+
}
|
|
510
|
+
exceptBlock.isExceptStar = !!(this.inExceptionGroup || this.pendingEgMatchType || this.egMatchTypeStack?.length);
|
|
511
|
+
this.curBlock.append(exceptBlock);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function handleEndFinally() {
|
|
516
|
+
if (global.g_cliArgs?.debug) {
|
|
517
|
+
console.log(`[handleEndFinally] curBlock=${this.curBlock.type_str}, stack depth=${this.blocks.length}`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
let isFinally = false;
|
|
521
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Finally) {
|
|
522
|
+
let final = this.curBlock;
|
|
523
|
+
this.blocks.pop();
|
|
524
|
+
|
|
525
|
+
this.curBlock = this.blocks.top();
|
|
526
|
+
this.curBlock.nodes.push(final);
|
|
527
|
+
isFinally = true;
|
|
528
|
+
} else if (this.curBlock.blockType == AST.ASTBlock.BlockType.Except) {
|
|
529
|
+
this.blocks.pop();
|
|
530
|
+
let prev = this.curBlock;
|
|
531
|
+
|
|
532
|
+
let isUninitAsyncFor = false;
|
|
533
|
+
if (this.blocks.top().blockType == AST.ASTBlock.BlockType.Container) {
|
|
534
|
+
let container = this.blocks.pop();
|
|
535
|
+
let asyncForBlock = this.blocks.top();
|
|
536
|
+
isUninitAsyncFor = asyncForBlock.blockType == AST.ASTBlock.BlockType.AsyncFor && !asyncForBlock.inited;
|
|
537
|
+
|
|
538
|
+
if (global.g_cliArgs?.debug) {
|
|
539
|
+
console.log(`[handleEndFinally] Container found. Next block: ${asyncForBlock.type_str}, inited=${asyncForBlock.inited}, isUninitAsyncFor=${isUninitAsyncFor}`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (isUninitAsyncFor) {
|
|
543
|
+
let tryBlock = container.nodes[0];
|
|
544
|
+
if (global.g_cliArgs?.debug) {
|
|
545
|
+
console.log(` Container has ${container.nodes.length} nodes, first is ${tryBlock?.type_str}`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (!tryBlock.nodes.empty() && tryBlock.blockType == AST.ASTBlock.BlockType.Try) {
|
|
549
|
+
let store = tryBlock.nodes[0];
|
|
550
|
+
if (global.g_cliArgs?.debug) {
|
|
551
|
+
console.log(` Try block has ${tryBlock.nodes.length} nodes, first is ${store?.constructor.name}`);
|
|
552
|
+
console.log(` Store.dest = ${store?.dest?.constructor.name}: ${store?.dest?.codeFragment ? store.dest.codeFragment() : 'no codeFragment'}`);
|
|
553
|
+
}
|
|
554
|
+
if (store) {
|
|
555
|
+
asyncForBlock.index = store.dest;
|
|
556
|
+
if (global.g_cliArgs?.debug) {
|
|
557
|
+
console.log(` Set asyncForBlock.index = ${asyncForBlock.index?.constructor.name}`);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
this.curBlock = this.blocks.top();
|
|
562
|
+
|
|
563
|
+
if (!this.curBlock.inited) {
|
|
564
|
+
console.error("Error when decompiling 'async for'.\n");
|
|
565
|
+
}
|
|
566
|
+
} else {
|
|
567
|
+
this.blocks.push(container);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (!isUninitAsyncFor) {
|
|
572
|
+
if (!this.curBlock.empty()) {
|
|
573
|
+
this.blocks.top().append(this.curBlock);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
this.curBlock = this.blocks.top();
|
|
577
|
+
|
|
578
|
+
/* Turn it into an else statement. */
|
|
579
|
+
if (this.curBlock.end != this.code.Next?.Offset || this.curBlock.hasFinally) {
|
|
580
|
+
let elseblk = new AST.ASTBlock(AST.ASTBlock.BlockType.Else, this.code.Current.Offset, prev.end);
|
|
581
|
+
elseblk.init();
|
|
582
|
+
this.blocks.push(elseblk);
|
|
583
|
+
this.curBlock = this.blocks.top();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Handle Python 3.6+ case: curBlock is "if" inside CONTAINER inside initialized AsyncFor
|
|
589
|
+
// This happens because END_FINALLY executes before the "if" block closes
|
|
590
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.If) {
|
|
591
|
+
// Check if we're in the pattern: if → CONTAINER → AsyncFor (initialized)
|
|
592
|
+
if (this.blocks.length >= 3) {
|
|
593
|
+
let parentContainer = this.blocks[this.blocks.length - 2];
|
|
594
|
+
let grandparentAsyncFor = this.blocks[this.blocks.length - 3];
|
|
595
|
+
|
|
596
|
+
if (parentContainer?.blockType == AST.ASTBlock.BlockType.Container &&
|
|
597
|
+
grandparentAsyncFor?.blockType == AST.ASTBlock.BlockType.AsyncFor &&
|
|
598
|
+
grandparentAsyncFor.inited) {
|
|
599
|
+
|
|
600
|
+
if (global.g_cliArgs?.debug) {
|
|
601
|
+
console.log(`[handleEndFinally-If] Found if→CONTAINER→AsyncFor(inited) pattern`);
|
|
602
|
+
console.log(` Closing if block and will hide CONTAINER`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Close the if block first
|
|
606
|
+
this.blocks.pop();
|
|
607
|
+
// Now curBlock should be CONTAINER - let it fall through to the next check
|
|
608
|
+
this.curBlock = this.blocks.top();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Container) {
|
|
614
|
+
/* This marks the end of the except block(s). */
|
|
615
|
+
let cont = this.curBlock;
|
|
616
|
+
|
|
617
|
+
// Set end offset for CONTAINER block (was 0 from constructor)
|
|
618
|
+
if (cont.end === 0) {
|
|
619
|
+
cont.end = this.code.Current.Offset;
|
|
620
|
+
if (global.g_cliArgs?.debug) {
|
|
621
|
+
console.log(`[handleEndFinally-Container] Set end=${cont.end} for CONTAINER(${cont.start}-${cont.end})`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Check if parent is an AsyncFor block (initialized or uninitialized)
|
|
626
|
+
if (this.blocks.length > 1) {
|
|
627
|
+
let parentBlock = this.blocks[this.blocks.length - 2];
|
|
628
|
+
let isUninitAsyncFor = parentBlock &&
|
|
629
|
+
parentBlock.blockType == AST.ASTBlock.BlockType.AsyncFor &&
|
|
630
|
+
!parentBlock.inited;
|
|
631
|
+
let isInitAsyncFor = parentBlock &&
|
|
632
|
+
parentBlock.blockType == AST.ASTBlock.BlockType.AsyncFor &&
|
|
633
|
+
parentBlock.inited;
|
|
634
|
+
|
|
635
|
+
if (global.g_cliArgs?.debug) {
|
|
636
|
+
console.log(`[handleEndFinally-Container] Parent=${parentBlock?.type_str}, isUninitAsyncFor=${isUninitAsyncFor}, isInitAsyncFor=${isInitAsyncFor}`);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (isUninitAsyncFor) {
|
|
640
|
+
// Extract index from Try block inside Container
|
|
641
|
+
if (global.g_cliArgs?.debug) {
|
|
642
|
+
console.log(` Container has ${cont.nodes.length} nodes:`);
|
|
643
|
+
cont.nodes.forEach((n, i) => console.log(` [${i}] ${n?.type_str || n?.constructor.name}`));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
let tryBlock = cont.nodes.find(n => n.blockType == AST.ASTBlock.BlockType.Try);
|
|
647
|
+
if (global.g_cliArgs?.debug) {
|
|
648
|
+
console.log(` TryBlock found: ${!!tryBlock}, nodes: ${tryBlock?.nodes.length}`);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (tryBlock && !tryBlock.nodes.empty()) {
|
|
652
|
+
// Find ASTStore node (the assignment of iterator value to loop variable)
|
|
653
|
+
const AST = require('../ast/ast_node');
|
|
654
|
+
let store = tryBlock.nodes.find(n => n instanceof AST.ASTStore);
|
|
655
|
+
|
|
656
|
+
if (global.g_cliArgs?.debug) {
|
|
657
|
+
console.log(` Found ASTStore: ${!!store}, has dest: ${!!store?.dest}`);
|
|
658
|
+
if (store) console.log(` dest type: ${store.dest?.constructor.name}`);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (store && store.dest) {
|
|
662
|
+
parentBlock.index = store.dest;
|
|
663
|
+
parentBlock.init();
|
|
664
|
+
if (global.g_cliArgs?.debug) {
|
|
665
|
+
console.log(` ✓ Extracted index: ${store.dest.codeFragment?.() || store.dest.constructor.name}`);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Extract the actual loop body (everything after the store in try block)
|
|
669
|
+
// Skip yield from / await calls which are implementation details
|
|
670
|
+
const storeIdx = tryBlock.nodes.indexOf(store);
|
|
671
|
+
for (let i = storeIdx + 1; i < tryBlock.nodes.length; i++) {
|
|
672
|
+
let node = tryBlock.nodes[i];
|
|
673
|
+
// Skip yield from / await implementation
|
|
674
|
+
if (node && node.constructor.name !== 'ASTReturn') {
|
|
675
|
+
parentBlock.nodes.push(node);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (parentBlock.nodes.length === 0) {
|
|
680
|
+
// Empty body - add pass
|
|
681
|
+
const AST = require('../ast/ast_node');
|
|
682
|
+
let passNode = new AST.ASTKeyword(AST.ASTKeyword.Word.Pass);
|
|
683
|
+
passNode.line = store.line;
|
|
684
|
+
parentBlock.nodes.push(passNode);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (global.g_cliArgs?.debug) {
|
|
688
|
+
console.log(` ✓ Added ${parentBlock.nodes.length} node(s) to async for body`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
// Don't append the Container to AsyncFor - it's just implementation detail
|
|
693
|
+
this.blocks.pop();
|
|
694
|
+
this.curBlock = this.blocks.top();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (isInitAsyncFor) {
|
|
699
|
+
// Python 3.6+: AsyncFor already initialized by STORE_FAST
|
|
700
|
+
// Just need to extract loop body and hide CONTAINER
|
|
701
|
+
if (global.g_cliArgs?.debug) {
|
|
702
|
+
console.log(` [InitAsyncFor] AsyncFor already has ${parentBlock.nodes.length} nodes`);
|
|
703
|
+
parentBlock.nodes.forEach((n, i) => console.log(` Existing[${i}] ${n?.constructor?.name}`));
|
|
704
|
+
console.log(` Container has ${cont.nodes.length} nodes:`);
|
|
705
|
+
cont.nodes.forEach((n, i) => console.log(` [${i}] ${n?.type_str || n?.constructor.name}`));
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
let tryBlock = cont.nodes.find(n => n.blockType == AST.ASTBlock.BlockType.Try);
|
|
709
|
+
if (tryBlock && !tryBlock.nodes.empty()) {
|
|
710
|
+
// Find ASTStore node (the STORE_FAST that set the index)
|
|
711
|
+
const AST = require('../ast/ast_node');
|
|
712
|
+
|
|
713
|
+
if (global.g_cliArgs?.debug) {
|
|
714
|
+
console.log(` Try block has ${tryBlock.nodes.length} nodes:`);
|
|
715
|
+
tryBlock.nodes.forEach((n, i) => console.log(` [${i}] ${n?.constructor?.name}`));
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
let store = tryBlock.nodes.find(n => n instanceof AST.ASTStore);
|
|
719
|
+
|
|
720
|
+
// Extract loop body - skip the STORE (which sets the index, already done by processStore)
|
|
721
|
+
// and skip async implementation details (yield from, await calls without actual code)
|
|
722
|
+
let startIdx = store ? tryBlock.nodes.indexOf(store) + 1 : 0;
|
|
723
|
+
|
|
724
|
+
for (let i = startIdx; i < tryBlock.nodes.length; i++) {
|
|
725
|
+
let node = tryBlock.nodes[i];
|
|
726
|
+
// Skip yield from / await calls which are implementation details
|
|
727
|
+
// ASTReturn = yield from, ASTCall with await = implementation
|
|
728
|
+
if (node && node.constructor.name !== 'ASTReturn' &&
|
|
729
|
+
!(node.constructor.name === 'ASTCall' && node.func?.name === 'await')) {
|
|
730
|
+
parentBlock.nodes.push(node);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (parentBlock.nodes.length === 0) {
|
|
735
|
+
// Empty body - add pass
|
|
736
|
+
let passNode = new AST.ASTKeyword(AST.ASTKeyword.Word.Pass);
|
|
737
|
+
passNode.line = tryBlock.line;
|
|
738
|
+
parentBlock.nodes.push(passNode);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (global.g_cliArgs?.debug) {
|
|
742
|
+
console.log(` ✓ Added ${parentBlock.nodes.length} node(s) to initialized async for body`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Don't append the Container to AsyncFor - it's just implementation detail
|
|
747
|
+
this.blocks.pop();
|
|
748
|
+
this.curBlock = this.blocks.top();
|
|
749
|
+
|
|
750
|
+
// Close the AsyncFor block immediately - everything after CONTAINER is implementation detail
|
|
751
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.AsyncFor) {
|
|
752
|
+
let asyncForEnd = this.curBlock.end;
|
|
753
|
+
this.blocks.pop();
|
|
754
|
+
this.blocks.top().append(this.curBlock);
|
|
755
|
+
this.curBlock = this.blocks.top();
|
|
756
|
+
|
|
757
|
+
// Mark everything until the original AsyncFor end as unreachable (implementation details)
|
|
758
|
+
this.unreachableUntil = asyncForEnd;
|
|
759
|
+
|
|
760
|
+
if (global.g_cliArgs?.debug) {
|
|
761
|
+
console.log(` ✓ Closed AsyncFor block early, marking ${this.code.Current.Offset}-${asyncForEnd} as unreachable`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (!cont.hasFinally || isFinally) {
|
|
769
|
+
/* If there's no finally block, pop the container. */
|
|
770
|
+
this.blocks.pop();
|
|
771
|
+
this.curBlock = this.blocks.top();
|
|
772
|
+
this.curBlock.append(cont);
|
|
773
|
+
}
|
|
774
|
+
if (cont. hasFinally) {
|
|
775
|
+
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function handleRaiseVarargsA() {
|
|
781
|
+
let paramList = [];
|
|
782
|
+
for (let idx = 0; idx < this.code.Current.Argument; idx++) {
|
|
783
|
+
paramList.unshift(this.dataStack.pop());
|
|
784
|
+
}
|
|
785
|
+
let node = new AST.ASTRaise(paramList);
|
|
786
|
+
node.line = this.code.Current.LineNo;
|
|
787
|
+
this.curBlock.append(node);
|
|
788
|
+
|
|
789
|
+
if ((this.curBlock.blockType == AST.ASTBlock.BlockType.If
|
|
790
|
+
|| this.curBlock.blockType == AST.ASTBlock.BlockType.Else)
|
|
791
|
+
&& (this.object.Reader.versionCompare(2, 6) >= 0)) {
|
|
792
|
+
let prev = this.curBlock;
|
|
793
|
+
this.blocks.pop();
|
|
794
|
+
this.curBlock = this.blocks.top();
|
|
795
|
+
this.curBlock.append(prev);
|
|
796
|
+
|
|
797
|
+
// REMOVED: this.code.GoNext() - main loop already calls GoNext(), this was causing instructions to be skipped
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function handleWithExceptStart() {
|
|
802
|
+
// Simulate calling __exit__(exc, val, tb) and push result for POP_JUMP_IF_TRUE.
|
|
803
|
+
const exitFunc = this.dataStack.pop();
|
|
804
|
+
const tb = this.dataStack.pop();
|
|
805
|
+
const val = this.dataStack.pop();
|
|
806
|
+
const exc = this.dataStack.pop();
|
|
807
|
+
|
|
808
|
+
if (!exitFunc || exc === undefined) {
|
|
809
|
+
if (global.g_cliArgs?.debug) {
|
|
810
|
+
console.error("[WITH_EXCEPT_START] Stack underflow or missing exit function");
|
|
811
|
+
}
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const callNode = new AST.ASTCall(exitFunc, [exc, val, tb], []);
|
|
816
|
+
callNode.line = this.code.Current.LineNo;
|
|
817
|
+
this.dataStack.push(callNode);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function handleWithExceptStartA() {
|
|
821
|
+
handleWithExceptStart.call(this);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function handleLoadAssertionError() {
|
|
825
|
+
this.dataStack.push(new AST.ASTName("AssertionError"));
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function handleBeginFinally() {
|
|
829
|
+
// I might need to adjust SETUP_FINALLY_A logic based on this
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function handleCallFinallyA() {
|
|
833
|
+
// Python 3.8 CALL_FINALLY: branches to finally handler.
|
|
834
|
+
// For decompilation we can treat as a jump with no stack effect.
|
|
835
|
+
if (global.g_cliArgs?.debug) {
|
|
836
|
+
console.log(`[CALL_FINALLY] offset=${this.code.Current.Offset} -> ${this.code.Current.JumpTarget}`);
|
|
837
|
+
}
|
|
838
|
+
// Try to jump to target to continue processing finally block structure.
|
|
839
|
+
this.code.GoToOffset(this.code.Current.JumpTarget);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function handlePopFinallyA() {
|
|
843
|
+
// Logic might be needed within your END_FINALLY handling
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function handleReraise() {
|
|
847
|
+
// Python 3.9-3.12 RERAISE opcode
|
|
848
|
+
// Re-raises the current exception in an exception handler
|
|
849
|
+
// For decompilation: usually implicit in exception handler flow
|
|
850
|
+
if (global.g_cliArgs?.debug) {
|
|
851
|
+
console.log(`[RERAISE] at offset ${this.code.Current.Offset}`);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Close current except block if present
|
|
855
|
+
if (this.curBlock && this.curBlock.blockType === AST.ASTBlock.BlockType.Except) {
|
|
856
|
+
let excBlock = this.blocks.pop();
|
|
857
|
+
this.curBlock = this.blocks.top();
|
|
858
|
+
if (!this.curBlock) {
|
|
859
|
+
this.curBlock = this.defBlock;
|
|
860
|
+
}
|
|
861
|
+
if (this.curBlock && this.curBlock.blockType === AST.ASTBlock.BlockType.Try) {
|
|
862
|
+
let catchBlock = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, excBlock.start, excBlock.end, excBlock.condition, false);
|
|
863
|
+
catchBlock.line = excBlock.line;
|
|
864
|
+
// Copy nodes using m_nodes directly (no setter for nodes property)
|
|
865
|
+
catchBlock.m_nodes = excBlock.m_nodes;
|
|
866
|
+
catchBlock.isExceptStar = !!this.inExceptionGroup;
|
|
867
|
+
this.curBlock.append(catchBlock);
|
|
868
|
+
this.inExceptionGroup = false;
|
|
869
|
+
this.inExceptionTableHandler = false;
|
|
870
|
+
} else {
|
|
871
|
+
this.curBlock.append(excBlock);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function handleReraiseA() {
|
|
877
|
+
// Python 3.10+ RERAISE with argument
|
|
878
|
+
// Argument specifies reraise behavior
|
|
879
|
+
handleReraise.call(this);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function handlePushExcInfo() {
|
|
883
|
+
// Python 3.11+ bookkeeping opcode for exception groups
|
|
884
|
+
// Treat as a marker: flag only when followed by CHECK_EG_MATCH (except*)
|
|
885
|
+
const nextOp = this.code.Next;
|
|
886
|
+
this.inExceptionGroup = nextOp && nextOp.OpCodeID === this.OpCodes.CHECK_EG_MATCH;
|
|
887
|
+
|
|
888
|
+
// Preserve current exception value on the stack for CHECK_EG_MATCH flows.
|
|
889
|
+
this.dataStack.push(new AST.ASTName("__exception__"));
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function handleCheckExcMatch() {
|
|
893
|
+
let matchType = this.dataStack.pop();
|
|
894
|
+
let excValue = this.dataStack.pop();
|
|
895
|
+
let compare = new AST.ASTCompare(excValue, matchType, AST.ASTCompare.CompareOp.Exception);
|
|
896
|
+
compare.line = this.code.Current.LineNo;
|
|
897
|
+
this.dataStack.push(compare);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function handleCheckEgMatch() {
|
|
901
|
+
let matchType = this.dataStack.pop();
|
|
902
|
+
let excValue = this.dataStack.pop();
|
|
903
|
+
let callNode = new AST.ASTCall(new AST.ASTName("__check_eg_match__"), [excValue, matchType], []);
|
|
904
|
+
callNode.line = this.code.Current.LineNo;
|
|
905
|
+
this.dataStack.push(callNode);
|
|
906
|
+
|
|
907
|
+
// Mark that we are in an except* path
|
|
908
|
+
this.inExceptionGroup = true;
|
|
909
|
+
this.pendingEgMatchType = matchType;
|
|
910
|
+
if (!this.egMatchTypeStack) {
|
|
911
|
+
this.egMatchTypeStack = [];
|
|
912
|
+
}
|
|
913
|
+
this.egMatchTypeStack.push(matchType);
|
|
914
|
+
|
|
915
|
+
// If we're inside an exception-table handler, attach the match type directly.
|
|
916
|
+
if (this.curBlock?.blockType === AST.ASTBlock.BlockType.Except && !this.curBlock.condition) {
|
|
917
|
+
this.curBlock.condition = matchType;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function handlePrepReraiseStar() {
|
|
922
|
+
// PREP_RERAISE_STAR separates matched/unmatched parts. Represent as helper call.
|
|
923
|
+
let value = this.dataStack.pop();
|
|
924
|
+
let helper = new AST.ASTCall(new AST.ASTName("__prep_reraise_star__"), [value], []);
|
|
925
|
+
helper.line = this.code.Current.LineNo;
|
|
926
|
+
this.dataStack.push(helper);
|
|
927
|
+
|
|
928
|
+
// Maintain group context for subsequent handlers
|
|
929
|
+
this.inExceptionGroup = true;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
module.exports = {
|
|
933
|
+
handleEndFinally,
|
|
934
|
+
handlePopBlock,
|
|
935
|
+
handlePopExcept,
|
|
936
|
+
handleRaiseVarargsA,
|
|
937
|
+
handleSetupExceptA,
|
|
938
|
+
handleSetupFinallyA,
|
|
939
|
+
handleSetupCleanupA,
|
|
940
|
+
handleBeginFinally,
|
|
941
|
+
handleCallFinallyA,
|
|
942
|
+
handlePopFinallyA,
|
|
943
|
+
handleWithExceptStart,
|
|
944
|
+
handleWithExceptStartA,
|
|
945
|
+
handleLoadAssertionError,
|
|
946
|
+
handleReraise,
|
|
947
|
+
handleReraiseA,
|
|
948
|
+
handlePushExcInfo,
|
|
949
|
+
handleCheckExcMatch,
|
|
950
|
+
handleCheckEgMatch,
|
|
951
|
+
handlePrepReraiseStar
|
|
952
|
+
};
|