depyo 1.0.1 → 1.0.3
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/README.md +4 -0
- package/depyo.js +103 -2
- package/lib/OpCodes.js +22 -5
- package/lib/PycDecompiler.js +402 -39
- package/lib/PycDisassembler.js +1 -1
- package/lib/PycReader.js +51 -4
- package/lib/PythonObject.js +40 -6
- package/lib/ast/ast_node.js +292 -71
- package/lib/bytecode/python_3_0.js +1 -1
- package/lib/bytecode/python_3_12.js +1 -1
- package/lib/bytecode/python_3_13.js +13 -13
- package/lib/bytecode/python_3_14.js +13 -13
- package/lib/bytecode/python_3_15.js +183 -0
- package/lib/code_reader.js +107 -146
- package/lib/handlers/collections_update.js +50 -1
- package/lib/handlers/context_managers.js +202 -13
- package/lib/handlers/control_flow_jumps.js +516 -23
- package/lib/handlers/exceptions_blocks.js +85 -22
- package/lib/handlers/formatting.js +60 -17
- package/lib/handlers/function_calls.js +454 -57
- package/lib/handlers/function_class_build.js +159 -64
- package/lib/handlers/generators_async.js +67 -0
- package/lib/handlers/load_store_names.js +190 -57
- package/lib/handlers/loop_iterator.js +162 -6
- package/lib/handlers/misc_other.js +216 -43
- package/lib/handlers/stack_ops.js +81 -19
- package/lib/handlers/subscript_slice.js +103 -1
- package/lib/handlers/unpack.js +18 -16
- package/package.json +1 -1
|
@@ -132,17 +132,18 @@ function extractWithPatternForFinally() {
|
|
|
132
132
|
|
|
133
133
|
if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.STORE_FAST_A) {
|
|
134
134
|
// Has "as variable"
|
|
135
|
-
|
|
135
|
+
if (thirdInstr.Name == null) {
|
|
136
|
+
throw new Error(`SETUP_FINALLY (Py2.7 WITH) at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): STORE_FAST has null Name`);
|
|
137
|
+
}
|
|
138
|
+
asVariable = new AST.ASTName(thirdInstr.Name.toString());
|
|
136
139
|
} else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.POP_TOP) {
|
|
137
140
|
// No "as" clause
|
|
138
141
|
asVariable = null;
|
|
139
142
|
} else if (thirdInstr && thirdInstr.OpCodeID === this.OpCodes.UNPACK_SEQUENCE_A) {
|
|
140
|
-
// Tuple unpacking: with expr as (x, y)
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
// Skip LOAD_FAST + DELETE_FAST + UNPACK_SEQUENCE + n STORE_FAST instructions
|
|
145
|
-
skipCount = 3 + (thirdInstr.Argument || 2);
|
|
143
|
+
// Tuple unpacking: `with expr as (x, y):` — reconstructing the
|
|
144
|
+
// tuple of names from UNPACK_SEQUENCE + STORE_FASTs requires
|
|
145
|
+
// forward instruction collection that this path doesn't yet do.
|
|
146
|
+
throw new Error(`SETUP_FINALLY (Py2.7 WITH) at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): tuple-unpack 'as (...)' clauses not yet supported (UNPACK_SEQUENCE_A at offset ${thirdInstr.Offset})`);
|
|
146
147
|
} else {
|
|
147
148
|
if (global.g_cliArgs?.debug) {
|
|
148
149
|
console.log(`WITH pattern (Python 2.7) check failed: third=${thirdInstr?.InstructionName}`);
|
|
@@ -228,7 +229,10 @@ function extractWithPatternForFinally() {
|
|
|
228
229
|
// Check if this is variable assignment (WITH ... as var:)
|
|
229
230
|
if (instr.OpCodeID === this.OpCodes.STORE_FAST_A && i < 5) {
|
|
230
231
|
// Found variable assignment early in sequence
|
|
231
|
-
|
|
232
|
+
if (instr.Name == null) {
|
|
233
|
+
throw new Error(`SETUP_FINALLY (Py3 WITH) at offset ${this.code.Current.Offset} (file offset ${this.object.codeOffset + this.code.Current.Offset}): STORE_FAST at offset ${instr.Offset} has null Name`);
|
|
234
|
+
}
|
|
235
|
+
asVariable = new AST.ASTName(instr.Name.toString());
|
|
232
236
|
skipCount = i + 1;
|
|
233
237
|
foundBodyStart = true;
|
|
234
238
|
if (global.g_cliArgs?.debug) {
|
|
@@ -323,10 +327,14 @@ function extractContextManager() {
|
|
|
323
327
|
instr.OpCodeID === this.OpCodes.LOAD_GLOBAL_A ||
|
|
324
328
|
instr.OpCodeID === this.OpCodes.LOAD_NAME_A ||
|
|
325
329
|
instr.OpCodeID === this.OpCodes.LOAD_DEREF_A) {
|
|
326
|
-
|
|
330
|
+
if (instr.Name == null) {
|
|
331
|
+
throw new Error(`extractContextManager: ${instr.InstructionName} at offset ${instr.Offset} has null Name`);
|
|
332
|
+
}
|
|
333
|
+
ctxMgrExpr = new AST.ASTName(instr.Name.toString());
|
|
327
334
|
} else {
|
|
328
|
-
//
|
|
329
|
-
|
|
335
|
+
// CALL_FUNCTION/LOAD_ATTR/BINARY_SUBSCR: full expression
|
|
336
|
+
// reconstruction needs stack replay (not implemented here).
|
|
337
|
+
throw new Error(`extractContextManager: unsupported context manager opcode ${instr.InstructionName} at offset ${instr.Offset} — needs stack replay`);
|
|
330
338
|
}
|
|
331
339
|
break;
|
|
332
340
|
}
|
|
@@ -353,10 +361,25 @@ function handlePopBlock() {
|
|
|
353
361
|
return;
|
|
354
362
|
}
|
|
355
363
|
|
|
356
|
-
if (this.curBlock.blockType == AST.ASTBlock.BlockType.With
|
|
364
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.With
|
|
365
|
+
|| this.curBlock.blockType == AST.ASTBlock.BlockType.AsyncWith) {
|
|
357
366
|
// This should only be popped by a WITH_CLEANUP
|
|
358
367
|
if (global.g_cliArgs?.debug) {
|
|
359
|
-
console.log(`POP_BLOCK on
|
|
368
|
+
console.log(`POP_BLOCK on ${this.curBlock.type_str.toUpperCase()} block ignored at offset ${this.code.Current.Offset}`);
|
|
369
|
+
}
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Py2.x nested genexpr/comprehension: inner loop emits SETUP_LOOP/POP_BLOCK
|
|
374
|
+
// around FOR_ITER/JUMP_ABSOLUTE. FOR_ITER consumes the SETUP_LOOP's While and
|
|
375
|
+
// JUMP_ABSOLUTE pops the resulting For, leaving this POP_BLOCK phantom. If it
|
|
376
|
+
// runs here it pops the enclosing For+comprehension prematurely and orphans
|
|
377
|
+
// the ASTComprehension sitting on the data stack.
|
|
378
|
+
if (this.curBlock.blockType == AST.ASTBlock.BlockType.For
|
|
379
|
+
&& this.curBlock.comprehension
|
|
380
|
+
&& this.dataStack.top() instanceof AST.ASTComprehension) {
|
|
381
|
+
if (global.g_cliArgs?.debug) {
|
|
382
|
+
console.log(`POP_BLOCK ignored on For+comprehension at offset ${this.code.Current.Offset} (ASTComprehension on stack)`);
|
|
360
383
|
}
|
|
361
384
|
return;
|
|
362
385
|
}
|
|
@@ -413,7 +436,11 @@ function handlePopBlock() {
|
|
|
413
436
|
(tmp.blockType == AST.ASTBlock.BlockType.Try && !this.curBlock.hasExcept)
|
|
414
437
|
) {
|
|
415
438
|
|
|
416
|
-
|
|
439
|
+
// The Finally body actually starts at container.finally (the SETUP_FINALLY
|
|
440
|
+
// jump target), not at tmp.start which inherits the popped Else/Try offsets.
|
|
441
|
+
// End is left as 0 so closeEndedBlocks doesn't pop it before END_FINALLY runs.
|
|
442
|
+
let finalStart = this.curBlock.finally || tmp.start;
|
|
443
|
+
let final = new AST.ASTBlock(AST.ASTBlock.BlockType.Finally, finalStart, 0, true);
|
|
417
444
|
this.blocks.push(final);
|
|
418
445
|
this.curBlock = this.blocks.top();
|
|
419
446
|
}
|
|
@@ -563,6 +590,36 @@ function handleEndFinally() {
|
|
|
563
590
|
if (!this.curBlock.inited) {
|
|
564
591
|
console.error("Error when decompiling 'async for'.\n");
|
|
565
592
|
}
|
|
593
|
+
} else if (asyncForBlock.blockType == AST.ASTBlock.BlockType.AsyncFor && asyncForBlock.inited) {
|
|
594
|
+
// Initialized AsyncFor: STORE_FAST already set the index.
|
|
595
|
+
// Extract the loop body from the Container's try block,
|
|
596
|
+
// close the AsyncFor, and mark remaining handler as unreachable.
|
|
597
|
+
let tryBlock = container.nodes.find(n => n.blockType == AST.ASTBlock.BlockType.Try);
|
|
598
|
+
if (tryBlock && !tryBlock.nodes.empty()) {
|
|
599
|
+
let store = tryBlock.nodes.find(n => n instanceof AST.ASTStore);
|
|
600
|
+
let startIdx = store ? tryBlock.nodes.indexOf(store) + 1 : 0;
|
|
601
|
+
for (let i = startIdx; i < tryBlock.nodes.length; i++) {
|
|
602
|
+
let node = tryBlock.nodes[i];
|
|
603
|
+
if (node && node.constructor.name !== 'ASTReturn' &&
|
|
604
|
+
!(node.constructor.name === 'ASTCall' && node.func?.name === 'await')) {
|
|
605
|
+
asyncForBlock.nodes.push(node);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (asyncForBlock.nodes.length === 0) {
|
|
609
|
+
let passNode = new AST.ASTKeyword(AST.ASTKeyword.Word.Pass);
|
|
610
|
+
passNode.line = tryBlock.line;
|
|
611
|
+
asyncForBlock.nodes.push(passNode);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
let asyncForEnd = asyncForBlock.end;
|
|
615
|
+
this.blocks.pop();
|
|
616
|
+
this.blocks.top().append(asyncForBlock);
|
|
617
|
+
this.curBlock = this.blocks.top();
|
|
618
|
+
this.unreachableUntil = asyncForEnd;
|
|
619
|
+
if (global.g_cliArgs?.debug) {
|
|
620
|
+
console.log(`[handleEndFinally-Except] Closed initialized AsyncFor, unreachable until ${asyncForEnd}`);
|
|
621
|
+
}
|
|
622
|
+
return;
|
|
566
623
|
} else {
|
|
567
624
|
this.blocks.push(container);
|
|
568
625
|
}
|
|
@@ -782,7 +839,10 @@ function handleRaiseVarargsA() {
|
|
|
782
839
|
for (let idx = 0; idx < this.code.Current.Argument; idx++) {
|
|
783
840
|
paramList.unshift(this.dataStack.pop());
|
|
784
841
|
}
|
|
785
|
-
|
|
842
|
+
const fromClause =
|
|
843
|
+
this.code.Current.Argument === 2 &&
|
|
844
|
+
this.object.Reader.versionCompare(3, 0) >= 0;
|
|
845
|
+
let node = new AST.ASTRaise(paramList, fromClause);
|
|
786
846
|
node.line = this.code.Current.LineNo;
|
|
787
847
|
this.curBlock.append(node);
|
|
788
848
|
|
|
@@ -919,13 +979,16 @@ function handleCheckEgMatch() {
|
|
|
919
979
|
}
|
|
920
980
|
|
|
921
981
|
function handlePrepReraiseStar() {
|
|
922
|
-
//
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
982
|
+
// CPython 3.11: pops orig + excs list, replaces with None-or-new-EG.
|
|
983
|
+
// The following POP_JUMP_IF_NOT_NONE/SWAP/POP_EXCEPT/RERAISE sequence is
|
|
984
|
+
// compiler-generated plumbing for `except*`, not user code. Mirror the
|
|
985
|
+
// 3.12 CALL_INTRINSIC_2(arg=1) handler: consume stack, suppress the
|
|
986
|
+
// next conditional, mark exception-group context.
|
|
987
|
+
this.dataStack.pop();
|
|
988
|
+
this.dataStack.pop();
|
|
989
|
+
this.ignoreNextConditional = true;
|
|
990
|
+
this.cleanupStackDepth = this.dataStack.length + 1;
|
|
991
|
+
this.dataStack.push(new AST.ASTNone());
|
|
929
992
|
this.inExceptionGroup = true;
|
|
930
993
|
}
|
|
931
994
|
|
|
@@ -1,26 +1,69 @@
|
|
|
1
1
|
const AST = require('../ast/ast_node');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
// PEP 750 t-strings (Python 3.14+):
|
|
4
|
+
// strings_tuple ; (value ; str_repr ; [format_spec])+ ; BUILD_INTERPOLATION oparg
|
|
5
|
+
// ... ; BUILD_TUPLE N ; BUILD_TEMPLATE -> Template
|
|
6
|
+
// BUILD_INTERPOLATION oparg layout:
|
|
7
|
+
// oparg & 3 = pop count (2 = no format spec, 3 = has format spec)
|
|
8
|
+
// (oparg >> 2) & 3 = conversion (0=none, 1=!s, 2=!r, 3=!a)
|
|
9
|
+
function handleBuildInterpolationA() {
|
|
10
|
+
const oparg = this.code.Current.Argument || 0;
|
|
11
|
+
const popCount = oparg & 3;
|
|
12
|
+
const convCode = (oparg >> 2) & 3;
|
|
13
|
+
|
|
14
|
+
let formatSpec = null;
|
|
15
|
+
if (popCount === 3) {
|
|
16
|
+
formatSpec = this.dataStack.pop();
|
|
9
17
|
}
|
|
10
|
-
//
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
this.dataStack.pop(); // discard str representation (used at runtime)
|
|
19
|
+
const value = this.dataStack.pop();
|
|
20
|
+
|
|
21
|
+
const conversion = convCode === 0 ? AST.ASTFormattedValue.ConversionFlag.None
|
|
22
|
+
: convCode === 1 ? AST.ASTFormattedValue.ConversionFlag.Str
|
|
23
|
+
: convCode === 2 ? AST.ASTFormattedValue.ConversionFlag.Repr
|
|
24
|
+
: AST.ASTFormattedValue.ConversionFlag.ASCII;
|
|
25
|
+
|
|
26
|
+
const node = new AST.ASTFormattedValue(value, conversion, formatSpec);
|
|
27
|
+
node.line = this.code.Current.LineNo;
|
|
28
|
+
this.dataStack.push(node);
|
|
14
29
|
}
|
|
15
30
|
|
|
16
|
-
function
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
function handleBuildTemplate() {
|
|
32
|
+
// Pops (strings_tuple, interpolations_tuple) and produces a Template node.
|
|
33
|
+
const interpolations = this.dataStack.pop();
|
|
34
|
+
const strings = this.dataStack.pop();
|
|
35
|
+
|
|
36
|
+
let interpVals = [];
|
|
37
|
+
if (interpolations instanceof AST.ASTTuple) {
|
|
38
|
+
interpVals = interpolations.values || [];
|
|
39
|
+
} else if (interpolations?.object?.Value) {
|
|
40
|
+
interpVals = interpolations.object.Value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// strings is loaded via LOAD_CONST as a Py_Tuple of Py_String objects
|
|
44
|
+
let stringVals = [];
|
|
45
|
+
if (strings instanceof AST.ASTTuple) {
|
|
46
|
+
stringVals = strings.values || [];
|
|
47
|
+
} else if (strings?.object?.Value) {
|
|
48
|
+
// Wrap each Py_String in an ASTObject so ASTJoinedStr.codeFragment can render it.
|
|
49
|
+
stringVals = strings.object.Value.map(pyStr => new AST.ASTObject(pyStr));
|
|
22
50
|
}
|
|
23
|
-
|
|
51
|
+
|
|
52
|
+
// Interleave: str[0], interp[0], str[1], interp[1], ..., str[N]
|
|
53
|
+
const pieces = [];
|
|
54
|
+
for (let i = 0; i < stringVals.length; i++) {
|
|
55
|
+
if (stringVals[i]) {
|
|
56
|
+
pieces.push(stringVals[i]);
|
|
57
|
+
}
|
|
58
|
+
if (i < interpVals.length) {
|
|
59
|
+
pieces.push(interpVals[i]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ASTJoinedStr's codeFragment reverses (BUILD_STRING pops in reverse). Compensate.
|
|
64
|
+
pieces.reverse();
|
|
65
|
+
const joined = new AST.ASTJoinedStr(pieces);
|
|
66
|
+
joined.isTemplateString = true;
|
|
24
67
|
joined.line = this.code.Current.LineNo;
|
|
25
68
|
this.dataStack.push(joined);
|
|
26
69
|
}
|