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,954 @@
|
|
|
1
|
+
const AST = require('../ast/ast_node');
|
|
2
|
+
|
|
3
|
+
function isInsideLoop(blocks) {
|
|
4
|
+
return blocks.some(b => [AST.ASTBlock.BlockType.While, AST.ASTBlock.BlockType.For, AST.ASTBlock.BlockType.AsyncFor].includes(b.blockType));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function findBackwardJump(code, startIndex, currentOffset, backJumpOpcodes) {
|
|
8
|
+
const instructions = code.Instructions || [];
|
|
9
|
+
const maxLookahead = 100;
|
|
10
|
+
for (let i = startIndex + 1; i < instructions.length && i <= startIndex + maxLookahead; i++) {
|
|
11
|
+
const instr = instructions[i];
|
|
12
|
+
if (!instr) {
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
if (backJumpOpcodes.includes(instr.OpCodeID) && instr.JumpTarget <= currentOffset) {
|
|
16
|
+
return instr;
|
|
17
|
+
}
|
|
18
|
+
// Bail out once we are well past the current conditional to avoid accidental matches.
|
|
19
|
+
if (instr.Offset - currentOffset > 300) {
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function tryStartConditionalExpression(ctx, cond, falseOffset) {
|
|
27
|
+
if (!cond || falseOffset <= ctx.code.Current.Offset) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const falseIdx = ctx.code.GetIndexByOffset(falseOffset);
|
|
31
|
+
if (falseIdx < 0) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let joinInstr = null;
|
|
36
|
+
for (let i = ctx.code.CurrentInstructionIndex + 1; i < falseIdx; i++) {
|
|
37
|
+
const instr = ctx.code.Instructions[i];
|
|
38
|
+
if (!instr) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if ([ctx.OpCodes.JUMP_FORWARD_A, ctx.OpCodes.JUMP_ABSOLUTE_A].includes(instr.OpCodeID)) {
|
|
42
|
+
const target = instr.JumpTarget ?? -1;
|
|
43
|
+
if (target > falseOffset && target <= falseOffset + 50) {
|
|
44
|
+
joinInstr = instr;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!joinInstr) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
ctx.pendingConditionalExprs ||= [];
|
|
55
|
+
ctx.pendingConditionalExprs.push({
|
|
56
|
+
cond,
|
|
57
|
+
falseOffset,
|
|
58
|
+
joinOffset: joinInstr.JumpTarget,
|
|
59
|
+
trueValue: null,
|
|
60
|
+
startOffset: ctx.code.Current.Offset
|
|
61
|
+
});
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function captureTrueBranchForConditional(ctx) {
|
|
66
|
+
if (!ctx.pendingConditionalExprs?.length) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const target = ctx.code.Current.JumpTarget;
|
|
70
|
+
const pending = ctx.pendingConditionalExprs.find(p => p.joinOffset === target);
|
|
71
|
+
if (!pending) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
pending.trueValue = ctx.dataStack.pop();
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function handleJumpIfFalseA() {
|
|
79
|
+
processJumpOps.call(this);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function handleJumpIfTrueA() {
|
|
83
|
+
processJumpOps.call(this);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleJumpIfFalseOrPopA() {
|
|
87
|
+
processJumpOps.call(this);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function handleJumpIfTrueOrPopA() {
|
|
91
|
+
processJumpOps.call(this);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handlePopJumpIfFalseA() {
|
|
95
|
+
processJumpOps.call(this);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handlePopJumpIfTrueA() {
|
|
99
|
+
processJumpOps.call(this);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function handlePopJumpForwardIfFalseA() {
|
|
103
|
+
processJumpOps.call(this);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function handlePopJumpForwardIfTrueA() {
|
|
107
|
+
processJumpOps.call(this);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handlePopJumpForwardIfNoneA() {
|
|
111
|
+
processJumpOps.call(this);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function handlePopJumpForwardIfNotNoneA() {
|
|
115
|
+
processJumpOps.call(this);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function handlePopJumpBackwardIfNoneA() {
|
|
119
|
+
processJumpOps.call(this);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function handlePopJumpBackwardIfNotNoneA() {
|
|
123
|
+
processJumpOps.call(this);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function handlePopJumpIfNoneA() {
|
|
127
|
+
processJumpOps.call(this);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function handlePopJumpIfNotNoneA() {
|
|
131
|
+
processJumpOps.call(this);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function handleInstrumentedPopJumpIfFalseA() {
|
|
135
|
+
processJumpOps.call(this);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handleInstrumentedPopJumpIfTrueA() {
|
|
139
|
+
processJumpOps.call(this);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function handleInstrumentedPopJumpIfNoneA() {
|
|
143
|
+
processJumpOps.call(this);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function handleInstrumentedPopJumpIfNotNoneA() {
|
|
147
|
+
processJumpOps.call(this);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function processJumpOps() {
|
|
151
|
+
if (this.skipNextJump) {
|
|
152
|
+
this.skipNextJump = false;
|
|
153
|
+
if (this.code.Next?.OpCodeID == this.OpCodes.POP_TOP) {
|
|
154
|
+
this.code.GoNext();
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const wrapNoneComparison = (expectNone) => {
|
|
160
|
+
let value = this.dataStack.pop();
|
|
161
|
+
let cmp = new AST.ASTCompare(
|
|
162
|
+
value,
|
|
163
|
+
new AST.ASTNone(),
|
|
164
|
+
expectNone ? AST.ASTCompare.CompareOp.Is : AST.ASTCompare.CompareOp.IsNot
|
|
165
|
+
);
|
|
166
|
+
cmp.line = this.code.Current.LineNo;
|
|
167
|
+
this.dataStack.push(cmp);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const jumpIfNotNoneOpcodes = [
|
|
171
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_NOT_NONE_A,
|
|
172
|
+
this.OpCodes.POP_JUMP_BACKWARD_IF_NOT_NONE_A,
|
|
173
|
+
this.OpCodes.POP_JUMP_IF_NOT_NONE_A,
|
|
174
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NOT_NONE_A
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const jumpIfNoneOpcodes = [
|
|
178
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_NONE_A,
|
|
179
|
+
this.OpCodes.POP_JUMP_BACKWARD_IF_NONE_A,
|
|
180
|
+
this.OpCodes.POP_JUMP_IF_NONE_A,
|
|
181
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NONE_A
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
if (jumpIfNoneOpcodes.includes(this.code.Current.OpCodeID)) {
|
|
185
|
+
wrapNoneComparison(true);
|
|
186
|
+
} else if (jumpIfNotNoneOpcodes.includes(this.code.Current.OpCodeID)) {
|
|
187
|
+
wrapNoneComparison(false);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this.inMatchPattern) {
|
|
191
|
+
this.debug(`[processJumpOps] Inside match statement - tracking pattern instead of creating if block`);
|
|
192
|
+
|
|
193
|
+
if ([
|
|
194
|
+
this.OpCodes.POP_JUMP_IF_FALSE_A,
|
|
195
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_FALSE_A,
|
|
196
|
+
this.OpCodes.POP_JUMP_IF_TRUE_A,
|
|
197
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_TRUE_A,
|
|
198
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_NONE_A,
|
|
199
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_NOT_NONE_A,
|
|
200
|
+
this.OpCodes.POP_JUMP_BACKWARD_IF_NONE_A,
|
|
201
|
+
this.OpCodes.POP_JUMP_BACKWARD_IF_NOT_NONE_A,
|
|
202
|
+
this.OpCodes.POP_JUMP_IF_NONE_A,
|
|
203
|
+
this.OpCodes.POP_JUMP_IF_NOT_NONE_A,
|
|
204
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_FALSE_A,
|
|
205
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_TRUE_A,
|
|
206
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NONE_A,
|
|
207
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NOT_NONE_A
|
|
208
|
+
].includes(this.code.Current.OpCodeID)) {
|
|
209
|
+
this.dataStack.pop();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// CRITICAL: Close blocks that have ended before creating new conditional block
|
|
216
|
+
// This ensures proper sibling relationships between if/elif blocks
|
|
217
|
+
while (this.curBlock.end > 0 &&
|
|
218
|
+
this.curBlock.end <= this.code.Current.Offset &&
|
|
219
|
+
this.curBlock.blockType != AST.ASTBlock.BlockType.Main &&
|
|
220
|
+
this.blocks.length > 1) {
|
|
221
|
+
|
|
222
|
+
if (global.g_cliArgs?.debug) {
|
|
223
|
+
console.log(`[processJumpOps] Closing ended block ${this.curBlock.type_str}(${this.curBlock.start}-${this.curBlock.end}) at offset ${this.code.Current.Offset}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let closedBlock = this.blocks.pop();
|
|
227
|
+
this.curBlock = this.blocks.top();
|
|
228
|
+
this.curBlock.append(closedBlock);
|
|
229
|
+
|
|
230
|
+
if (global.g_cliArgs?.debug) {
|
|
231
|
+
console.log(` → Appended to ${this.curBlock.type_str}(${this.curBlock.start}-${this.curBlock.end}), now has ${this.curBlock.nodes.length} nodes`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (this.ignoreNextConditional) {
|
|
236
|
+
this.ignoreNextConditional = false;
|
|
237
|
+
const baseDepth = this.cleanupStackDepth || this.dataStack.length;
|
|
238
|
+
while (this.dataStack.length >= baseDepth) {
|
|
239
|
+
this.dataStack.pop();
|
|
240
|
+
}
|
|
241
|
+
this.cleanupStackDepth = null;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let cond = this.dataStack.top();
|
|
246
|
+
let ifblk = null;
|
|
247
|
+
let popped = AST.ASTCondBlock.InitCondition.Uninited;
|
|
248
|
+
|
|
249
|
+
if ([
|
|
250
|
+
this.OpCodes.POP_JUMP_IF_FALSE_A,
|
|
251
|
+
this.OpCodes.POP_JUMP_IF_TRUE_A,
|
|
252
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_FALSE_A,
|
|
253
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_TRUE_A,
|
|
254
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_NONE_A,
|
|
255
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_NOT_NONE_A,
|
|
256
|
+
this.OpCodes.POP_JUMP_BACKWARD_IF_NONE_A,
|
|
257
|
+
this.OpCodes.POP_JUMP_BACKWARD_IF_NOT_NONE_A,
|
|
258
|
+
this.OpCodes.POP_JUMP_IF_NONE_A,
|
|
259
|
+
this.OpCodes.POP_JUMP_IF_NOT_NONE_A,
|
|
260
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_FALSE_A,
|
|
261
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_TRUE_A,
|
|
262
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NONE_A,
|
|
263
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NOT_NONE_A
|
|
264
|
+
].includes(this.code.Current.OpCodeID)) {
|
|
265
|
+
|
|
266
|
+
/* Pop condition before the jump */
|
|
267
|
+
this.dataStack.pop();
|
|
268
|
+
popped = AST.ASTCondBlock.InitCondition.PrePopped;
|
|
269
|
+
} else if ([
|
|
270
|
+
this.OpCodes.JUMP_IF_FALSE_OR_POP_A,
|
|
271
|
+
this.OpCodes.JUMP_IF_TRUE_OR_POP_A,
|
|
272
|
+
this.OpCodes.JUMP_IF_FALSE_A,
|
|
273
|
+
this.OpCodes.JUMP_IF_TRUE_A
|
|
274
|
+
].includes(this.code.Current.OpCodeID)) {
|
|
275
|
+
/* Pop condition only if condition is met */
|
|
276
|
+
this.dataStack.pop();
|
|
277
|
+
popped = AST.ASTCondBlock.InitCondition.Popped;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* "Jump if true" means "Jump if not false" */
|
|
281
|
+
let neg = [
|
|
282
|
+
this.OpCodes.JUMP_IF_TRUE_A, this.OpCodes.JUMP_IF_TRUE_OR_POP_A,
|
|
283
|
+
this.OpCodes.POP_JUMP_IF_TRUE_A, this.OpCodes.POP_JUMP_FORWARD_IF_TRUE_A,
|
|
284
|
+
this.OpCodes.POP_JUMP_BACKWARD_IF_TRUE_A, this.OpCodes.INSTRUMENTED_POP_JUMP_IF_TRUE_A
|
|
285
|
+
].includes(this.code.Current.OpCodeID);
|
|
286
|
+
|
|
287
|
+
let offs = this.code.Current.Argument;
|
|
288
|
+
if (this.object.Reader.versionCompare(3, 10) >= 0)
|
|
289
|
+
offs *= 2; // // BPO-27129
|
|
290
|
+
if (this.object.Reader.versionCompare(3, 12) >= 0
|
|
291
|
+
|| [
|
|
292
|
+
this.OpCodes.JUMP_IF_FALSE_A, this.OpCodes.JUMP_IF_TRUE_A,
|
|
293
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_TRUE_A, this.OpCodes.POP_JUMP_FORWARD_IF_FALSE_A,
|
|
294
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_NONE_A, this.OpCodes.POP_JUMP_FORWARD_IF_NOT_NONE_A,
|
|
295
|
+
this.OpCodes.POP_JUMP_IF_NONE_A, this.OpCodes.POP_JUMP_IF_NOT_NONE_A,
|
|
296
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NONE_A, this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NOT_NONE_A
|
|
297
|
+
].includes(this.code.Current.OpCodeID)) {
|
|
298
|
+
/* Offset is relative in these cases */
|
|
299
|
+
offs += this.code.Next?.Offset;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const rawJumpTarget = offs; // target before block end normalization
|
|
303
|
+
[offs] = this.code.FindEndOfBlock(offs);
|
|
304
|
+
|
|
305
|
+
const condJumpOpcodes = new Set([
|
|
306
|
+
this.OpCodes.POP_JUMP_IF_FALSE_A,
|
|
307
|
+
this.OpCodes.POP_JUMP_IF_TRUE_A,
|
|
308
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_FALSE_A,
|
|
309
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_TRUE_A,
|
|
310
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_NONE_A,
|
|
311
|
+
this.OpCodes.POP_JUMP_FORWARD_IF_NOT_NONE_A,
|
|
312
|
+
this.OpCodes.POP_JUMP_BACKWARD_IF_NONE_A,
|
|
313
|
+
this.OpCodes.POP_JUMP_BACKWARD_IF_NOT_NONE_A,
|
|
314
|
+
this.OpCodes.POP_JUMP_IF_NONE_A,
|
|
315
|
+
this.OpCodes.POP_JUMP_IF_NOT_NONE_A,
|
|
316
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_FALSE_A,
|
|
317
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_TRUE_A,
|
|
318
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NONE_A,
|
|
319
|
+
this.OpCodes.INSTRUMENTED_POP_JUMP_IF_NOT_NONE_A
|
|
320
|
+
]);
|
|
321
|
+
const backJumpOpcodes = [
|
|
322
|
+
this.OpCodes.JUMP_ABSOLUTE_A,
|
|
323
|
+
this.OpCodes.JUMP_BACKWARD_A,
|
|
324
|
+
this.OpCodes.JUMP_BACKWARD_NO_INTERRUPT_A
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
// Python 3.8+ while-loops no longer emit SETUP_LOOP; detect via backward jumps.
|
|
328
|
+
if (!this.inMatchPattern &&
|
|
329
|
+
condJumpOpcodes.has(this.code.Current.OpCodeID) &&
|
|
330
|
+
!isInsideLoop(this.blocks)) {
|
|
331
|
+
const backJump = findBackwardJump(this.code, this.code.CurrentInstructionIndex, this.code.Current.Offset, backJumpOpcodes);
|
|
332
|
+
// Treat as a while-loop only when the body extends past the backward jump target.
|
|
333
|
+
if (backJump && offs > backJump.Offset) {
|
|
334
|
+
const loopStart = backJump.JumpTarget;
|
|
335
|
+
const whileBlk = new AST.ASTCondBlock(AST.ASTBlock.BlockType.While, loopStart, offs, null, false);
|
|
336
|
+
whileBlk.line = this.code.Current.LineNo;
|
|
337
|
+
this.blocks.push(whileBlk);
|
|
338
|
+
this.curBlock = whileBlk;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Conditional expression (a if cond else b) heuristic:
|
|
343
|
+
// POP_JUMP_IF_* to falseOffset, then JUMP_FORWARD to joinOffset > falseOffset.
|
|
344
|
+
if (!this.inMatchPattern &&
|
|
345
|
+
condJumpOpcodes.has(this.code.Current.OpCodeID) &&
|
|
346
|
+
tryStartConditionalExpression(this, cond, rawJumpTarget)) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if ([ this.OpCodes.JUMP_IF_FALSE_A,
|
|
351
|
+
this.OpCodes.JUMP_IF_TRUE_A
|
|
352
|
+
].includes(this.code.Current.OpCodeID) &&
|
|
353
|
+
this.code.Next?.OpCodeID == this.OpCodes.POP_TOP
|
|
354
|
+
) {
|
|
355
|
+
this.code.GoNext();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (global.g_cliArgs?.debug) {
|
|
359
|
+
console.log(`\nConditional jump at offset ${this.code.Current.Offset}: curBlock=${this.curBlock.type_str} (type=${this.curBlock.blockType}), size=${this.curBlock.size}, inited=${this.curBlock.inited}`);
|
|
360
|
+
if (this.curBlock.size > 0) {
|
|
361
|
+
console.log(` Block nodes:`, this.curBlock.nodes.map(n => `${n.constructor.name}`));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const isEgMatchCall = cond instanceof AST.ASTCall &&
|
|
366
|
+
cond.func instanceof AST.ASTName &&
|
|
367
|
+
cond.func.name === "__check_eg_match__";
|
|
368
|
+
const isExceptCompare = cond instanceof AST.ASTCompare &&
|
|
369
|
+
cond.op == AST.ASTCompare.CompareOp.Exception;
|
|
370
|
+
|
|
371
|
+
if (isExceptCompare || isEgMatchCall) {
|
|
372
|
+
if (global.g_cliArgs?.debug) {
|
|
373
|
+
const matchType = isExceptCompare ? cond.right : cond.pparams?.[1];
|
|
374
|
+
console.log(` EXCEPTION MATCH detected: Creating Except block with condition=${matchType?.constructor?.name} ${matchType?.codeFragment?.()}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const handlerEnd = this.findExceptionHandlerEnd ? this.findExceptionHandlerEnd(this.code.Current.Offset) : null;
|
|
378
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Except
|
|
379
|
+
&& this.curBlock.condition == null) {
|
|
380
|
+
// Reuse current except block (from exception table) instead of creating a nested one
|
|
381
|
+
this.curBlock.condition = isExceptCompare ? cond.right : cond.pparams?.[1];
|
|
382
|
+
if (handlerEnd) {
|
|
383
|
+
this.curBlock.end = handlerEnd;
|
|
384
|
+
}
|
|
385
|
+
this.curBlock.init();
|
|
386
|
+
ifblk = null; // no extra push
|
|
387
|
+
} else {
|
|
388
|
+
const end = handlerEnd || offs;
|
|
389
|
+
const matchType = isExceptCompare ? cond.right : cond.pparams?.[1];
|
|
390
|
+
ifblk = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, this.code.Current.Offset, end, matchType, false);
|
|
391
|
+
this.inExceptionTableHandler = true;
|
|
392
|
+
}
|
|
393
|
+
} else if (this.curBlock.blockType == AST.ASTBlock.BlockType.Else) {
|
|
394
|
+
if (global.g_cliArgs?.debug) {
|
|
395
|
+
console.log(` Checking ELIF conditions: size=${this.curBlock.size}, blockType=${this.curBlock.blockType}`);
|
|
396
|
+
if (this.curBlock.size == 1) {
|
|
397
|
+
console.log(` First node: ${this.curBlock.nodes[0].constructor.name}, blockType=${this.curBlock.nodes[0].blockType}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (this.curBlock.size == 0 ||
|
|
402
|
+
(this.curBlock.size == 1 &&
|
|
403
|
+
this.curBlock.nodes[0] instanceof AST.ASTCondBlock &&
|
|
404
|
+
this.curBlock.nodes[0].blockType == AST.ASTBlock.BlockType.If)) {
|
|
405
|
+
/* Collapse into elif statement */
|
|
406
|
+
if (global.g_cliArgs?.debug) {
|
|
407
|
+
console.log(`ELIF DETECTED: else block size=${this.curBlock.size}, converting to elif at offset ${this.code.Current.Offset}`);
|
|
408
|
+
}
|
|
409
|
+
let startOffset = this.curBlock.start;
|
|
410
|
+
|
|
411
|
+
// If else block contains an if statement, remove it (we're converting it to elif)
|
|
412
|
+
if (this.curBlock.size == 1) {
|
|
413
|
+
this.curBlock.nodes.pop();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
this.blocks.pop();
|
|
417
|
+
ifblk = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Elif, startOffset, offs, cond, neg);
|
|
418
|
+
}
|
|
419
|
+
} else if (this.curBlock.blockType == AST.ASTBlock.BlockType.Else
|
|
420
|
+
&& this.curBlock.size > 0) {
|
|
421
|
+
/* Else block not empty - elif not possible */
|
|
422
|
+
if (global.g_cliArgs?.debug) {
|
|
423
|
+
console.log(`ELIF NOT CREATED: else block size=${this.curBlock.size} (not 0) at offset ${this.code.Current.Offset}, nodes:`, this.curBlock.nodes.map(n => n.constructor.name));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (this.curBlock.size == 0 && !this.curBlock.inited
|
|
427
|
+
&& this.curBlock.blockType == AST.ASTBlock.BlockType.While
|
|
428
|
+
&& this.code.Current.LineNo == this.curBlock.line) {
|
|
429
|
+
/* The condition for a while loop */
|
|
430
|
+
let top = this.blocks.top();
|
|
431
|
+
top.condition = cond;
|
|
432
|
+
top.negative = neg;
|
|
433
|
+
if (popped) {
|
|
434
|
+
top.init(popped);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (global.g_cliArgs?.debug) {
|
|
438
|
+
console.log(`[processJumpOps] Set while condition at offset ${this.code.Current.Offset}: ${cond?.constructor?.name} = ${cond?.codeFragment ? cond.codeFragment() : cond}`);
|
|
439
|
+
}
|
|
440
|
+
return;
|
|
441
|
+
} else if (this.curBlock.size == 0 && this.curBlock.end <= offs
|
|
442
|
+
&& [ AST.ASTBlock.BlockType.If,
|
|
443
|
+
AST.ASTBlock.BlockType.Elif,
|
|
444
|
+
AST.ASTBlock.BlockType.While
|
|
445
|
+
].includes(this.curBlock.blockType)) {
|
|
446
|
+
let newcond;
|
|
447
|
+
let top = this.curBlock;
|
|
448
|
+
let cond1 = top.condition;
|
|
449
|
+
this.blocks.pop();
|
|
450
|
+
|
|
451
|
+
if (this.curBlock.end == offs
|
|
452
|
+
|| (this.curBlock.end == this.code.Next?.Offset && !top.negative)) {
|
|
453
|
+
/* if blah and blah */
|
|
454
|
+
newcond = new AST.ASTBinary(cond1, cond, AST.ASTBinary.BinOp.LogicalAnd);
|
|
455
|
+
} else {
|
|
456
|
+
/* if <condition 1> or <condition 2> */
|
|
457
|
+
newcond = new AST.ASTBinary(cond1, cond, AST.ASTBinary.BinOp.LogicalOr);
|
|
458
|
+
}
|
|
459
|
+
newcond.line = this.code.Current.LineNo;
|
|
460
|
+
ifblk = new AST.ASTCondBlock(top.blockType, top.start, offs, newcond, neg);
|
|
461
|
+
} else if (this.curBlock.blockType == AST.ASTBlock.BlockType.For
|
|
462
|
+
&& this.curBlock.comprehension
|
|
463
|
+
&& this.object.Reader.versionCompare(2, 7) >= 0) {
|
|
464
|
+
/* Comprehension condition */
|
|
465
|
+
this.curBlock.condition = cond;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (!ifblk) {
|
|
470
|
+
/* Plain old if statement - but check if it should be elif */
|
|
471
|
+
let shouldBeElif = false;
|
|
472
|
+
|
|
473
|
+
// Check if this should be an elif instead of if
|
|
474
|
+
// This happens when there's no else block (e.g., when previous if/elif has return)
|
|
475
|
+
if (this.blocks.length > 0) {
|
|
476
|
+
let parent = this.blocks.top();
|
|
477
|
+
if (parent.size > 0) {
|
|
478
|
+
let lastNode = parent.nodes[parent.size - 1];
|
|
479
|
+
|
|
480
|
+
// If the last node in parent is an if/elif block, this should be elif
|
|
481
|
+
// The key insight: if lastNode is CLOSED (not in block stack), it's a sibling
|
|
482
|
+
// Check if lastNode is in block stack - if not, it's closed and safe to use as elif base
|
|
483
|
+
let lastNodeInStack = false;
|
|
484
|
+
for (let i = 0; i < this.blocks.length; i++) {
|
|
485
|
+
if (this.blocks[i] === lastNode) {
|
|
486
|
+
lastNodeInStack = true;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (lastNode instanceof AST.ASTCondBlock &&
|
|
492
|
+
(lastNode.blockType == AST.ASTBlock.BlockType.If ||
|
|
493
|
+
lastNode.blockType == AST.ASTBlock.BlockType.Elif) &&
|
|
494
|
+
!lastNodeInStack) { // CLOSED, not in stack = sibling!
|
|
495
|
+
|
|
496
|
+
shouldBeElif = true;
|
|
497
|
+
|
|
498
|
+
if (global.g_cliArgs?.debug) {
|
|
499
|
+
console.log(`ELIF DETECTED: Creating elif at offset ${this.code.Current.Offset} (follows ${lastNode.type_str} at ${lastNode.start})`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (shouldBeElif) {
|
|
506
|
+
ifblk = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Elif, this.code.Current.Offset, offs, cond, neg);
|
|
507
|
+
} else {
|
|
508
|
+
ifblk = new AST.ASTCondBlock(AST.ASTBlock.BlockType.If, this.code.Current.Offset, offs, cond, neg);
|
|
509
|
+
}
|
|
510
|
+
ifblk.line = this.code.Current.LineNo;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (ifblk) {
|
|
514
|
+
if (popped)
|
|
515
|
+
ifblk.init(popped);
|
|
516
|
+
|
|
517
|
+
this.blocks.push(ifblk);
|
|
518
|
+
this.curBlock = this.blocks.top();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function handleJumpAbsoluteA() {
|
|
523
|
+
if (this.skipNextJump) {
|
|
524
|
+
this.skipNextJump = false;
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// CRITICAL: Close blocks that have ended before processing jump
|
|
529
|
+
// Unconditional jumps often mark the end of blocks (especially loops)
|
|
530
|
+
while (this.curBlock.end > 0 &&
|
|
531
|
+
this.curBlock.end <= this.code.Current.Offset &&
|
|
532
|
+
this.curBlock.blockType != AST.ASTBlock.BlockType.Main &&
|
|
533
|
+
this.blocks.length > 1) {
|
|
534
|
+
|
|
535
|
+
if (global.g_cliArgs?.debug) {
|
|
536
|
+
console.log(`[handleJumpAbsolute] Closing ended block ${this.curBlock.type_str}(${this.curBlock.start}-${this.curBlock.end}) at offset ${this.code.Current.Offset}`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
let closedBlock = this.blocks.pop();
|
|
540
|
+
this.curBlock = this.blocks.top();
|
|
541
|
+
this.curBlock.append(closedBlock);
|
|
542
|
+
|
|
543
|
+
if (global.g_cliArgs?.debug) {
|
|
544
|
+
console.log(` → Appended to ${this.curBlock.type_str}(${this.curBlock.start}-${this.curBlock.end}), now has ${this.curBlock.nodes.length} nodes`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
let offs = this.code.Current.Argument;
|
|
549
|
+
if (this.object.Reader.versionCompare(3, 10) >= 0) {
|
|
550
|
+
offs *= 2; // 2 bytes size - BPO-27129
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// [offs] = this.code.FindEndOfBlock(offs);
|
|
554
|
+
|
|
555
|
+
if (offs <= this.code.Next?.Offset) {
|
|
556
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.For) {
|
|
557
|
+
let is_jump_to_start = offs == this.curBlock.start;
|
|
558
|
+
let should_pop_for_block = this.curBlock.comprehension;
|
|
559
|
+
// in v3.8, SETUP_LOOP is deprecated and for blocks aren't terminated by POP_BLOCK, so we add them here
|
|
560
|
+
let should_add_for_block = this.object.Reader.versionCompare(3, 8) >= 0 && is_jump_to_start && !this.curBlock.comprehension; // ||
|
|
561
|
+
// this.object.Reader.versionCompare(3, 8) < 0 && is_jump_to_start && this.curBlock.comprehension;
|
|
562
|
+
|
|
563
|
+
if (should_pop_for_block || should_add_for_block) {
|
|
564
|
+
let top = this.dataStack.top();
|
|
565
|
+
|
|
566
|
+
if (top instanceof AST.ASTComprehension) {
|
|
567
|
+
let comp = this.dataStack.pop();
|
|
568
|
+
comp.addGenerator(this.curBlock);
|
|
569
|
+
this.blocks.pop();
|
|
570
|
+
this.curBlock = this.blocks.top();
|
|
571
|
+
this.curBlock.append(comp);
|
|
572
|
+
} else {
|
|
573
|
+
let tmp = this.curBlock;
|
|
574
|
+
this.blocks.pop();
|
|
575
|
+
this.curBlock = this.blocks.top();
|
|
576
|
+
if (should_add_for_block ||
|
|
577
|
+
(this.curBlock === this.blocks[0] && this.curBlock.nodes.length == 0)) {
|
|
578
|
+
this.curBlock.append(tmp);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} else if (this.curBlock.blockType == AST.ASTBlock.BlockType.Else) {
|
|
583
|
+
this.blocks.pop();
|
|
584
|
+
this.blocks.top().append(this.curBlock);
|
|
585
|
+
this.curBlock = this.blocks.top();
|
|
586
|
+
|
|
587
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Container
|
|
588
|
+
&& !this.curBlock.hasFinally) {
|
|
589
|
+
this.blocks.pop();
|
|
590
|
+
this.blocks.top().append(this.curBlock);
|
|
591
|
+
this.curBlock = this.blocks.top();
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
// First of all we have to figure out if there is any While or For blocks wer are in
|
|
595
|
+
let loopBlock = null;
|
|
596
|
+
for (let blockIdx = this.blocks.length - 1; blockIdx > 0; blockIdx--) {
|
|
597
|
+
if ([AST.ASTBlock.BlockType.While, AST.ASTBlock.BlockType.For, AST.ASTBlock.BlockType.AsyncFor].includes(this.blocks[blockIdx].blockType)) {
|
|
598
|
+
loopBlock = this.blocks[blockIdx];
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (!loopBlock) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// CRITICAL FIX: If JUMP target is OUTSIDE current loop (jump to outer loop or beyond),
|
|
608
|
+
// this marks the END of the current loop! Correct the loop's end offset.
|
|
609
|
+
// This handles nested loops where inner loop jumps to outer loop start.
|
|
610
|
+
if (loopBlock.start > offs) {
|
|
611
|
+
// Jump target is BEFORE loop start = jumping to outer scope
|
|
612
|
+
// Current offset should be the TRUE end of this loop
|
|
613
|
+
if (loopBlock.end > this.code.Current.Offset) {
|
|
614
|
+
if (global.g_cliArgs?.debug) {
|
|
615
|
+
console.log(`[handleJumpAbsolute] Correcting loop end: ${loopBlock.type_str}(${loopBlock.start}-${loopBlock.end}) → end=${this.code.Current.Offset} (jump to outer at ${offs})`);
|
|
616
|
+
}
|
|
617
|
+
loopBlock.end = this.code.Current.Offset;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (this.curBlock.end == this.code.Next?.Offset) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if ([this.OpCodes.JUMP_ABSOLUTE_A, this.OpCodes.JUMP_FORWARD_A].includes(this.code.Prev?.OpCodeID)) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Check if current block ends with a terminating keyword (break/continue/return)
|
|
630
|
+
// This check is recursive - it looks into nested blocks to find terminators
|
|
631
|
+
if (this.hasTerminatingKeyword(this.curBlock)) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if ([AST.ASTBlock.BlockType.If, AST.ASTBlock.BlockType.Elif, AST.ASTBlock.BlockType.Else].includes(this.curBlock.blockType) && this.curBlock.nodes.length == 0) {
|
|
636
|
+
this.curBlock.append(new AST.ASTKeyword(AST.ASTKeyword.Word.Continue));
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Let's find actual end of block
|
|
641
|
+
let blockEnd = loopBlock.end;
|
|
642
|
+
let instr = this.code.PeekInstructionAtOffset(blockEnd);
|
|
643
|
+
if (!instr) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
let currentIndex = instr.InstructionIndex;
|
|
647
|
+
|
|
648
|
+
while (blockEnd > loopBlock.start) {
|
|
649
|
+
if (instr.OpCodeID == this.OpCodes.JUMP_ABSOLUTE_A &&
|
|
650
|
+
(instr.JumpTarget == loopBlock.start + 3 ||
|
|
651
|
+
instr.JumpTarget < loopBlock.start)
|
|
652
|
+
) {
|
|
653
|
+
currentIndex--;
|
|
654
|
+
instr = this.code.PeekInstructionAt(currentIndex);
|
|
655
|
+
blockEnd = instr.Offset;
|
|
656
|
+
} else {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (this.code.Current.Offset < blockEnd) {
|
|
662
|
+
this.curBlock.append(new AST.ASTKeyword(AST.ASTKeyword.Word.Continue));
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/* We're in a loop, this jumps back to the start */
|
|
667
|
+
/* I think we'll just ignore this case... */
|
|
668
|
+
return; // Bad idea? Probably!
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Container) {
|
|
672
|
+
let cont = this.curBlock;
|
|
673
|
+
// EXPERIMENT
|
|
674
|
+
if (cont.hasExcept && this.code.Next?.Offset <= cont.except) {
|
|
675
|
+
let except = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, this.code.Current.Offset, this.code.Current.JumpTarget, null, false);
|
|
676
|
+
except.init();
|
|
677
|
+
this.blocks.push(except);
|
|
678
|
+
this.curBlock = this.blocks.top();
|
|
679
|
+
}
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
let prev = this.curBlock;
|
|
684
|
+
|
|
685
|
+
if (this.blocks.length > 1) {
|
|
686
|
+
do {
|
|
687
|
+
this.blocks.pop();
|
|
688
|
+
this.blocks.top().append(prev);
|
|
689
|
+
|
|
690
|
+
if ([
|
|
691
|
+
AST.ASTBlock.BlockType.If,
|
|
692
|
+
AST.ASTBlock.BlockType.Elif
|
|
693
|
+
].includes(prev.blockType)) {
|
|
694
|
+
let top = this.blocks.top();
|
|
695
|
+
let next = new AST.ASTBlock(AST.ASTBlock.BlockType.Else, this.code.Current.Offset, top.end);
|
|
696
|
+
top.end = this.code.Current.Offset;
|
|
697
|
+
if (prev.inited == AST.ASTCondBlock.InitCondition.PrePopped) {
|
|
698
|
+
next.init(AST.ASTCondBlock.InitCondition.PrePopped);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (global.g_cliArgs?.debug) {
|
|
702
|
+
console.log(`ELSE BLOCK CREATED at offset ${this.code.Current.Offset}, end=${next.end}`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
this.blocks.push(next);
|
|
706
|
+
prev = null;
|
|
707
|
+
} else if (prev.blockType == AST.ASTBlock.BlockType.Except) {
|
|
708
|
+
let top = this.blocks.top();
|
|
709
|
+
let next = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, top.start, top.end, null, false);
|
|
710
|
+
next.init();
|
|
711
|
+
|
|
712
|
+
this.blocks.push(next);
|
|
713
|
+
prev = null;
|
|
714
|
+
} else if (prev.blockType == AST.ASTBlock.BlockType.Else) {
|
|
715
|
+
/* Special case */
|
|
716
|
+
if (this.blocks.top().blockType != AST.ASTBlock.BlockType.Main) {
|
|
717
|
+
prev = this.blocks.top();
|
|
718
|
+
} else {
|
|
719
|
+
prev = null;
|
|
720
|
+
}
|
|
721
|
+
} else {
|
|
722
|
+
prev = null;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
} while (prev != null);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
this.curBlock = this.blocks.top();
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function handleJumpForwardA() {
|
|
732
|
+
processJumpForward.call(this);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function handleInstrumentedJumpForwardA() {
|
|
736
|
+
processJumpForward.call(this);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function handleJumpA() {
|
|
740
|
+
// Python 3.13+ JUMP: treat as forward jump
|
|
741
|
+
processJumpForward.call(this);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function handleJumpNoInterruptA() {
|
|
745
|
+
// Python 3.13+ JUMP_NO_INTERRUPT: same as JUMP for decompilation
|
|
746
|
+
processJumpForward.call(this);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function processJumpForward() {
|
|
750
|
+
if (this.skipNextJump) {
|
|
751
|
+
this.skipNextJump = false;
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Capture true-branch value for conditional expression (ternary) rewrites.
|
|
756
|
+
if (captureTrueBranchForConditional(this)) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
let offs = this.code.Current.Argument;
|
|
761
|
+
if (this.object.Reader.versionCompare(3, 10) >= 0)
|
|
762
|
+
offs *= 2; // 2 bytes per offset as per BPO-27129
|
|
763
|
+
|
|
764
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Container) {
|
|
765
|
+
let cont = this.curBlock;
|
|
766
|
+
if (cont.hasExcept) {
|
|
767
|
+
this.curBlock.end = this.code.Next?.Offset + offs;
|
|
768
|
+
let except = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, this.code.Current.Offset, this.curBlock.end, null, false);
|
|
769
|
+
except.init();
|
|
770
|
+
this.blocks.push(except);
|
|
771
|
+
this.curBlock = this.blocks.top();
|
|
772
|
+
}
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
let prev = this.curBlock;
|
|
777
|
+
|
|
778
|
+
if (this.blocks.length > 1) {
|
|
779
|
+
do {
|
|
780
|
+
this.blocks.pop();
|
|
781
|
+
|
|
782
|
+
if (!this.blocks.empty())
|
|
783
|
+
this.blocks.top().append(prev);
|
|
784
|
+
|
|
785
|
+
if (prev.blockType == AST.ASTBlock.BlockType.If
|
|
786
|
+
|| prev.blockType == AST.ASTBlock.BlockType.Elif) {
|
|
787
|
+
if (offs < 3) {
|
|
788
|
+
prev = null;
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
let next = new AST.ASTBlock(AST.ASTBlock.BlockType.Else, this.code.Current.Offset, this.code.Next?.Offset + offs);
|
|
792
|
+
if (prev.inited == AST.ASTCondBlock.InitCondition.PrePopped) {
|
|
793
|
+
next.init(AST.ASTCondBlock.InitCondition.PrePopped);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
this.blocks.push(next);
|
|
797
|
+
prev = null;
|
|
798
|
+
} else if (prev.blockType == AST.ASTBlock.BlockType.Except && offs > 2) {
|
|
799
|
+
// For exception groups: only create chained except if there's another CHECK_EG_MATCH ahead.
|
|
800
|
+
// Internal cleanup jumps should not create new except blocks.
|
|
801
|
+
if (this.inExceptionGroup) {
|
|
802
|
+
const jumpTarget = this.code.Next?.Offset + offs;
|
|
803
|
+
let hasNextEgMatch = false;
|
|
804
|
+
// Scan ahead to see if there's a CHECK_EG_MATCH within reasonable range
|
|
805
|
+
let scanOffset = this.code.Next?.Offset;
|
|
806
|
+
for (let i = 0; i < 40 && scanOffset <= jumpTarget + 20; i++) {
|
|
807
|
+
const instr = this.code.PeekInstructionAtOffset(scanOffset);
|
|
808
|
+
if (!instr) break;
|
|
809
|
+
if (instr.OpCodeID === this.OpCodes.CHECK_EG_MATCH) {
|
|
810
|
+
hasNextEgMatch = true;
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
if (instr.OpCodeID === this.OpCodes.CALL_INTRINSIC_2 ||
|
|
814
|
+
instr.OpCodeID === this.OpCodes.POP_EXCEPT ||
|
|
815
|
+
instr.OpCodeID === this.OpCodes.RERAISE) {
|
|
816
|
+
// Reached cleanup, no more handlers
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
scanOffset += 2;
|
|
820
|
+
}
|
|
821
|
+
if (!hasNextEgMatch) {
|
|
822
|
+
prev = null;
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
let next = null;
|
|
827
|
+
|
|
828
|
+
if (this.code.Next?.OpCodeID == this.OpCodes.END_FINALLY) {
|
|
829
|
+
next = new AST.ASTBlock(AST.ASTBlock.BlockType.Else, this.code.Current.Offset, this.code.Current.JumpTarget);
|
|
830
|
+
next.init();
|
|
831
|
+
} else {
|
|
832
|
+
next = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, this.code.Current.Offset, this.code.Next?.Offset + offs, null, false);
|
|
833
|
+
next.init();
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
this.blocks.push(next);
|
|
837
|
+
prev = null;
|
|
838
|
+
} else if (prev.blockType == AST.ASTBlock.BlockType.Else) {
|
|
839
|
+
/* Special case */
|
|
840
|
+
prev = this.blocks.top();
|
|
841
|
+
|
|
842
|
+
if (prev.blockType == AST.ASTBlock.BlockType.Main) {
|
|
843
|
+
/* Something went out of the control! */
|
|
844
|
+
prev = null;
|
|
845
|
+
}
|
|
846
|
+
} else if (prev.blockType == AST.ASTBlock.BlockType.Try
|
|
847
|
+
&& prev.end < this.code.Next?.Offset + offs) {
|
|
848
|
+
this.dataStack.pop();
|
|
849
|
+
|
|
850
|
+
if (this.blocks.top().blockType == AST.ASTBlock.BlockType.Container) {
|
|
851
|
+
let cont = this.blocks.top();
|
|
852
|
+
if (cont.hasExcept) {
|
|
853
|
+
|
|
854
|
+
let except = new AST.ASTCondBlock(AST.ASTBlock.BlockType.Except, prev.end, this.code.Next?.Offset + offs, null, false);
|
|
855
|
+
except.init();
|
|
856
|
+
this.blocks.push(except);
|
|
857
|
+
}
|
|
858
|
+
} else {
|
|
859
|
+
if (global.g_cliArgs?.debug) {
|
|
860
|
+
console.error("Something TERRIBLE happened!!\n");
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
prev = null;
|
|
864
|
+
} else {
|
|
865
|
+
prev = null;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
} while (prev != null);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
this.curBlock = this.blocks.top();
|
|
872
|
+
|
|
873
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.Except) {
|
|
874
|
+
this.curBlock.end = this.code.Next?.Offset + offs;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function handleJumpBackwardA() {
|
|
879
|
+
// Python 3.11+ JUMP_BACKWARD opcode
|
|
880
|
+
// Unconditional backward jump (used in loops)
|
|
881
|
+
// Similar to JUMP_ABSOLUTE but with relative negative offset
|
|
882
|
+
|
|
883
|
+
if (global.g_cliArgs?.debug) {
|
|
884
|
+
console.log(`[JUMP_BACKWARD] at offset ${this.code.Current.Offset}, target_delta=${this.code.Current.Argument}`);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// For loops (for/while/async for), this jumps back to loop start
|
|
888
|
+
// In decompiler context, we don't need to generate explicit "continue" or "goto"
|
|
889
|
+
// The loop structure is already represented by For/While/AsyncFor blocks
|
|
890
|
+
|
|
891
|
+
// No action needed - loop control is implicit in block structure
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function handleJumpBackwardNoInterruptA() {
|
|
895
|
+
// Python 3.11+ JUMP_BACKWARD_NO_INTERRUPT
|
|
896
|
+
// Like JUMP_BACKWARD but doesn't check for pending signals
|
|
897
|
+
// Used in tight loops for optimization
|
|
898
|
+
|
|
899
|
+
if (global.g_cliArgs?.debug) {
|
|
900
|
+
console.log(`[JUMP_BACKWARD_NO_INTERRUPT] at offset ${this.code.Current.Offset}`);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Same as JUMP_BACKWARD for decompiler purposes
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function handleJumpIfNotExcMatchA() {
|
|
907
|
+
// Use same logic as other conditional jumps; stack top is comparison result.
|
|
908
|
+
processJumpOps.call(this);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function handleNotTaken() {
|
|
912
|
+
// Instrumentation hint (3.13+) used in instrumented builds; ignore for decompilation.
|
|
913
|
+
if (global.g_cliArgs?.debug) {
|
|
914
|
+
console.log(`[NOT_TAKEN] at offset ${this.code.Current.Offset}`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function handleInstrumentedNotTakenA() {
|
|
919
|
+
// Instrumentation marker for untaken branch; no stack effect.
|
|
920
|
+
if (global.g_cliArgs?.debug) {
|
|
921
|
+
console.log(`[INSTRUMENTED_NOT_TAKEN] at offset ${this.code.Current.Offset}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
module.exports = {
|
|
926
|
+
handleJumpIfFalseA,
|
|
927
|
+
handleJumpIfTrueA,
|
|
928
|
+
handleJumpIfFalseOrPopA,
|
|
929
|
+
handleJumpIfTrueOrPopA,
|
|
930
|
+
handlePopJumpIfFalseA,
|
|
931
|
+
handlePopJumpIfTrueA,
|
|
932
|
+
handlePopJumpForwardIfFalseA,
|
|
933
|
+
handlePopJumpForwardIfTrueA,
|
|
934
|
+
handlePopJumpForwardIfNoneA,
|
|
935
|
+
handlePopJumpForwardIfNotNoneA,
|
|
936
|
+
handlePopJumpBackwardIfNoneA,
|
|
937
|
+
handlePopJumpBackwardIfNotNoneA,
|
|
938
|
+
handlePopJumpIfNoneA,
|
|
939
|
+
handlePopJumpIfNotNoneA,
|
|
940
|
+
handleInstrumentedPopJumpIfFalseA,
|
|
941
|
+
handleInstrumentedPopJumpIfTrueA,
|
|
942
|
+
handleInstrumentedPopJumpIfNoneA,
|
|
943
|
+
handleInstrumentedPopJumpIfNotNoneA,
|
|
944
|
+
handleJumpAbsoluteA,
|
|
945
|
+
handleJumpForwardA,
|
|
946
|
+
handleInstrumentedJumpForwardA,
|
|
947
|
+
handleJumpA,
|
|
948
|
+
handleJumpNoInterruptA,
|
|
949
|
+
handleJumpBackwardA,
|
|
950
|
+
handleJumpBackwardNoInterruptA,
|
|
951
|
+
handleJumpIfNotExcMatchA,
|
|
952
|
+
handleNotTaken,
|
|
953
|
+
handleInstrumentedNotTakenA
|
|
954
|
+
};
|