depyo 1.0.2 → 1.1.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/depyo.js +43 -2
- package/lib/OpCodes.js +22 -5
- package/lib/PycDecompiler.js +1050 -40
- package/lib/PycDisassembler.js +1 -1
- package/lib/PycReader.js +22 -3
- package/lib/PythonObject.js +42 -6
- package/lib/ast/ast_node.js +381 -88
- 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/comparisons.js +3 -10
- package/lib/handlers/context_managers.js +202 -13
- package/lib/handlers/control_flow_jumps.js +516 -23
- package/lib/handlers/exceptions_blocks.js +92 -24
- package/lib/handlers/formatting.js +60 -17
- package/lib/handlers/function_calls.js +474 -58
- package/lib/handlers/function_class_build.js +170 -65
- 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 +253 -44
- 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 +2 -2
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
const AST = require('../ast/ast_node');
|
|
2
2
|
|
|
3
|
+
// CPython Include/internal/pycore_intrinsics.h
|
|
4
|
+
const INTRINSIC_1 = Object.freeze({
|
|
5
|
+
INVALID: 0,
|
|
6
|
+
PRINT: 1,
|
|
7
|
+
IMPORT_STAR: 2,
|
|
8
|
+
STOPITERATION_ERROR: 3,
|
|
9
|
+
ASYNC_GEN_WRAP: 4,
|
|
10
|
+
UNARY_POSITIVE: 5,
|
|
11
|
+
LIST_TO_TUPLE: 6,
|
|
12
|
+
TYPEVAR: 7,
|
|
13
|
+
PARAMSPEC: 8,
|
|
14
|
+
TYPEVARTUPLE: 9,
|
|
15
|
+
SUBSCRIPT_GENERIC: 10,
|
|
16
|
+
TYPEALIAS: 11,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const INTRINSIC_2 = Object.freeze({
|
|
20
|
+
INVALID: 0,
|
|
21
|
+
PREP_RERAISE_STAR: 1,
|
|
22
|
+
TYPEVAR_WITH_BOUND: 2,
|
|
23
|
+
TYPEVAR_WITH_CONSTRAINTS: 3,
|
|
24
|
+
SET_FUNCTION_TYPE_PARAMS: 4,
|
|
25
|
+
SET_TYPEPARAM_DEFAULT: 5, // 3.13+
|
|
26
|
+
});
|
|
27
|
+
|
|
3
28
|
function handleKwNamesA() {
|
|
4
29
|
let astNode = new AST.ASTKwNamesMap();
|
|
5
30
|
let keys = this.code.Current.ConstantObject;
|
|
@@ -20,8 +45,55 @@ function handleCallFunctionA() {
|
|
|
20
45
|
}
|
|
21
46
|
|
|
22
47
|
function handleInstrumentedCallKwA() {
|
|
23
|
-
// Instrumented CALL_KW behaves like
|
|
24
|
-
|
|
48
|
+
// Instrumented CALL_KW behaves like CALL_KW.
|
|
49
|
+
handleCallKwA.call(this);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function handleCallKwA() {
|
|
53
|
+
// Python 3.13 CALL_KW: (callable, self_or_null, args[oparg], kwnames -- res)
|
|
54
|
+
// kwnames is a tuple of strings; the last len(kwnames) of args[] are the kw values.
|
|
55
|
+
let kwnamesNode = this.dataStack.pop();
|
|
56
|
+
let kwNamesList = [];
|
|
57
|
+
const toKwName = (v) => {
|
|
58
|
+
// Render kwarg keys as bare identifiers (foo=1), not string literals ("foo"=1).
|
|
59
|
+
const raw = v?.Value ?? v?.name ?? v;
|
|
60
|
+
const name = typeof raw === 'string' ? raw : String(raw);
|
|
61
|
+
return new AST.ASTName(name.replace(/^['"]|['"]$/g, ''));
|
|
62
|
+
};
|
|
63
|
+
if (kwnamesNode instanceof AST.ASTObject) {
|
|
64
|
+
const obj = kwnamesNode.object;
|
|
65
|
+
if (obj && (obj.ClassName === 'Py_Tuple' || obj.ClassName === 'Py_SmallTuple') && Array.isArray(obj.Value)) {
|
|
66
|
+
kwNamesList = obj.Value.map(toKwName);
|
|
67
|
+
}
|
|
68
|
+
} else if (kwnamesNode instanceof AST.ASTTuple) {
|
|
69
|
+
kwNamesList = (kwnamesNode.values || []).map(toKwName);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const totalArgs = this.code.Current.Argument;
|
|
73
|
+
const kwcount = kwNamesList.length;
|
|
74
|
+
const pcount = Math.max(0, totalArgs - kwcount);
|
|
75
|
+
|
|
76
|
+
let kwparamList = [];
|
|
77
|
+
for (let i = kwcount - 1; i >= 0; i--) {
|
|
78
|
+
let value = this.dataStack.pop();
|
|
79
|
+
kwparamList.unshift({ key: kwNamesList[i], value });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let pparamList = [];
|
|
83
|
+
for (let i = 0; i < pcount; i++) {
|
|
84
|
+
pparamList.unshift(this.dataStack.pop());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let func = this.dataStack.pop();
|
|
88
|
+
if (func === null && this.dataStack.length > 0) {
|
|
89
|
+
func = this.dataStack.pop();
|
|
90
|
+
} else if (this.dataStack.length > 0 && this.dataStack.top() == null) {
|
|
91
|
+
this.dataStack.pop();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let callNode = new AST.ASTCall(func, pparamList, kwparamList);
|
|
95
|
+
callNode.line = this.code.Current.LineNo;
|
|
96
|
+
this.dataStack.push(callNode);
|
|
25
97
|
}
|
|
26
98
|
|
|
27
99
|
function handleEnterExecutorA() {
|
|
@@ -36,34 +108,66 @@ function handleInstrumentedCallA() {
|
|
|
36
108
|
let pparams = (this.code.Current.Argument & 0xFF);
|
|
37
109
|
let kwparamList = [];
|
|
38
110
|
let pparamList = [];
|
|
39
|
-
let loadBuildClassFound = false;
|
|
40
111
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
112
|
+
// This CALL invokes __build_class__ iff a LoadBuildClass marker sits on the
|
|
113
|
+
// stack at exactly the depth this CALL will consume down to. Otherwise a
|
|
114
|
+
// nested CALL (e.g. `type(Y)` in `class X(type(Y)):`) will falsely trigger
|
|
115
|
+
// the build-class branch because LoadBuildClass is still somewhere below.
|
|
116
|
+
//
|
|
117
|
+
// Stack layout (bottom → top) varies by version:
|
|
118
|
+
// pre-3.6 CALL_FUNCTION: [LBC, fn, name, ...bases, k1,v1, ...]; LBC at pparams + 2*kwparams
|
|
119
|
+
// 3.6+ CALL_FUNCTION: [LBC, fn, name, ...bases]; LBC at pparams
|
|
120
|
+
// 3.11/3.12 CALL: [NULL, LBC, fn, name, ...bases]; LBC at pparams
|
|
121
|
+
// (PUSH_NULL emitted before LOAD_BUILD_CLASS, NULL below LBC)
|
|
122
|
+
// 3.13+ CALL: [LBC, NULL, fn, name, ...bases]; LBC at pparams + 1
|
|
123
|
+
// (LOAD_BUILD_CLASS emitted before PUSH_NULL, NULL above LBC; peel in pop loop)
|
|
124
|
+
const stackHas = (d) => {
|
|
125
|
+
const idx = this.dataStack.length - 1 - d;
|
|
126
|
+
return idx >= 0 && this.dataStack[idx] instanceof AST.ASTLoadBuildClass;
|
|
127
|
+
};
|
|
128
|
+
let loadBuildClassFound = false;
|
|
129
|
+
if (this.object.Reader.versionCompare(3, 6) >= 0) {
|
|
130
|
+
loadBuildClassFound = stackHas(pparams) || stackHas(pparams + 1);
|
|
131
|
+
} else {
|
|
132
|
+
loadBuildClassFound = stackHas(pparams + 2 * kwparams);
|
|
46
133
|
}
|
|
47
134
|
|
|
48
135
|
if (loadBuildClassFound) {
|
|
49
136
|
let bases = [];
|
|
50
|
-
let
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
137
|
+
let lbcKwparamList = [];
|
|
138
|
+
const nbases = Math.max(0, pparams - 2);
|
|
139
|
+
|
|
140
|
+
if (this.object.Reader.versionCompare(3, 6) < 0) {
|
|
141
|
+
// Pre-3.6: kwargs are alternating key/value pairs on stack.
|
|
142
|
+
for (let i = 0; i < kwparams; i++) {
|
|
143
|
+
let value = this.dataStack.pop();
|
|
144
|
+
let key = this.dataStack.pop();
|
|
145
|
+
lbcKwparamList.unshift({key, value});
|
|
146
|
+
}
|
|
147
|
+
for (let i = 0; i < nbases; i++) {
|
|
148
|
+
bases.unshift(this.dataStack.pop());
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// 3.6+: pop exactly nbases items; kwargs flow via CALL_FUNCTION_KW/KW_NAMES.
|
|
152
|
+
// Bases can be arbitrary expressions (ASTName, ASTBinary, ASTCall, ASTSubscr, …).
|
|
153
|
+
for (let i = 0; i < nbases; i++) {
|
|
154
|
+
bases.unshift(this.dataStack.pop());
|
|
155
|
+
}
|
|
56
156
|
}
|
|
57
157
|
|
|
58
|
-
// qualified name is PycString at TOS
|
|
59
158
|
let name = this.dataStack.pop();
|
|
60
159
|
let functionNode = this.dataStack.pop();
|
|
61
160
|
let loadbuild = this.dataStack.pop();
|
|
161
|
+
// 3.11+: PUSH_NULL sits between __build_class__ and the function; peel it off.
|
|
162
|
+
if (loadbuild === null && this.dataStack.top() instanceof AST.ASTLoadBuildClass) {
|
|
163
|
+
loadbuild = this.dataStack.pop();
|
|
164
|
+
}
|
|
62
165
|
if (loadbuild instanceof AST.ASTLoadBuildClass) {
|
|
63
|
-
let callNode = new AST.ASTCall(functionNode,
|
|
166
|
+
let callNode = new AST.ASTCall(functionNode, [], lbcKwparamList);
|
|
64
167
|
callNode.line = this.code.Current.LineNo;
|
|
65
168
|
let classNode = new AST.ASTClass(callNode, new AST.ASTTuple(bases), name);
|
|
66
169
|
classNode.line = this.code.Current.LineNo;
|
|
170
|
+
classNode.kwargs = lbcKwparamList;
|
|
67
171
|
this.dataStack.push(classNode);
|
|
68
172
|
return;
|
|
69
173
|
}
|
|
@@ -104,6 +208,15 @@ function handleInstrumentedCallA() {
|
|
|
104
208
|
skipCallNode = true;
|
|
105
209
|
break;
|
|
106
210
|
}
|
|
211
|
+
} else if (param instanceof AST.ASTClass && pparams == 1 && kwparamList.length === 0) {
|
|
212
|
+
// Class decorator: decorator(class) — attach decorator and re-push class
|
|
213
|
+
// so chained class decorators (e.g. @author("Me")\n@author("You")) unwind
|
|
214
|
+
// naturally on the data stack.
|
|
215
|
+
let decorator = this.dataStack.pop();
|
|
216
|
+
param.add_decorator(decorator);
|
|
217
|
+
this.dataStack.push(param);
|
|
218
|
+
skipCallNode = true;
|
|
219
|
+
break;
|
|
107
220
|
} else {
|
|
108
221
|
pparamList.unshift(param);
|
|
109
222
|
}
|
|
@@ -114,38 +227,68 @@ function handleInstrumentedCallA() {
|
|
|
114
227
|
}
|
|
115
228
|
|
|
116
229
|
let func = this.dataStack.pop();
|
|
117
|
-
// 3.11+
|
|
230
|
+
// 3.11+ CALL convention: stack is [callable, self_or_null, args...].
|
|
231
|
+
// We just popped the args; `func` is actually self_or_null, and the real callable is underneath.
|
|
118
232
|
if ([this.OpCodes.CALL_A, this.OpCodes.INSTRUMENTED_CALL_A].includes(this.code.Current.OpCodeID)) {
|
|
119
233
|
if (func === null && this.dataStack.length > 0) {
|
|
234
|
+
// Normal call: PUSH_NULL was self_or_null, real callable below.
|
|
120
235
|
func = this.dataStack.pop();
|
|
121
236
|
} else if (this.dataStack.length > 0 && this.dataStack.top() == null) {
|
|
237
|
+
// Edge case: null sits one slot deeper (rare ordering). Drop it.
|
|
122
238
|
this.dataStack.pop();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// pop the self since it's consumed by the unbound method call.
|
|
127
|
-
else if (func instanceof AST.ASTBinary && func.op === AST.ASTBinary.BinOp.Attr) {
|
|
239
|
+
} else if (func instanceof AST.ASTBinary && func.op === AST.ASTBinary.BinOp.Attr) {
|
|
240
|
+
// Python 3.14 LOAD_SPECIAL pushes [self, method] for __enter__/__exit__ etc.;
|
|
241
|
+
// the self below is consumed by the unbound special-method call.
|
|
128
242
|
const attrName = func.right?.name;
|
|
129
243
|
if (['__enter__', '__exit__', '__aenter__', '__aexit__'].includes(attrName)) {
|
|
130
244
|
if (this.dataStack.length > 0) {
|
|
131
|
-
this.dataStack.pop();
|
|
245
|
+
this.dataStack.pop();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} else if (this.dataStack.length > 0) {
|
|
249
|
+
// Bound method / decorator pattern: self_or_null is non-null, so
|
|
250
|
+
// the call is real_callable(func, *args). Pop the real callable and
|
|
251
|
+
// promote `func` to the first positional arg.
|
|
252
|
+
const below = this.dataStack.top();
|
|
253
|
+
if (below !== null && below !== undefined && !(below instanceof AST.ASTLoadBuildClass)) {
|
|
254
|
+
const realCallable = this.dataStack.pop();
|
|
255
|
+
// Decorator syntax: single ASTFunction arg with a real name → attach decorator,
|
|
256
|
+
// push the decorated function back so the next STORE renders as `@decorator\ndef name(...)`.
|
|
257
|
+
if (pparamList.length === 0 &&
|
|
258
|
+
func instanceof AST.ASTFunction &&
|
|
259
|
+
func.code?.object?.Name &&
|
|
260
|
+
func.code.object.Name !== "<lambda>") {
|
|
261
|
+
func.add_decorator(realCallable);
|
|
262
|
+
this.dataStack.push(func);
|
|
263
|
+
return;
|
|
132
264
|
}
|
|
265
|
+
pparamList.unshift(func);
|
|
266
|
+
func = realCallable;
|
|
133
267
|
}
|
|
134
268
|
}
|
|
135
269
|
}
|
|
136
270
|
|
|
137
271
|
if (func instanceof AST.ASTFunction) {
|
|
138
|
-
const compNames = new Set(["<listcomp>", "<setcomp>", "<dictcomp>", "<genexpr>"]);
|
|
272
|
+
const compNames = new Set(["<listcomp>", "<setcomp>", "<dictcomp>", "<genexpr>", "<generator expression>"]);
|
|
139
273
|
const codeObj = func.code?.object;
|
|
140
274
|
if (codeObj && !codeObj.SourceCode) {
|
|
275
|
+
const PycDecompiler = require('../PycDecompiler');
|
|
276
|
+
const innerDecompiler = new PycDecompiler(codeObj);
|
|
141
277
|
try {
|
|
142
|
-
|
|
143
|
-
codeObj.SourceCode = new PycDecompiler(codeObj).decompile();
|
|
278
|
+
codeObj.SourceCode = innerDecompiler.decompile();
|
|
144
279
|
} catch (e) {
|
|
280
|
+
if (global.g_cliArgs?.strict) throw e;
|
|
281
|
+
this.errors.push({
|
|
282
|
+
opcode: 'NESTED_DECOMPILE',
|
|
283
|
+
codeObject: codeObj?.Name?.toString?.() || '<unknown>',
|
|
284
|
+
message: e?.message || String(e),
|
|
285
|
+
stack: e?.stack
|
|
286
|
+
});
|
|
145
287
|
if (global.g_cliArgs?.debug) {
|
|
146
288
|
console.error(`[CALL] Failed to decompile nested function: ${e?.message}`);
|
|
147
289
|
}
|
|
148
290
|
}
|
|
291
|
+
if (innerDecompiler.errors.length) this.errors.push(...innerDecompiler.errors);
|
|
149
292
|
}
|
|
150
293
|
|
|
151
294
|
const sourceTop = codeObj?.SourceCode?.list?.top?.() || null;
|
|
@@ -154,7 +297,10 @@ function handleInstrumentedCallA() {
|
|
|
154
297
|
const topType = sourceTop?.constructor?.name || 'null';
|
|
155
298
|
console.log(`[CALL] func=${codeObj?.Name || codeObj?.QualName?.Value || '<?>'} sourceTop=${topType} compNode=${compNode?.constructor?.name || 'null'} hasSource=${!!codeObj?.SourceCode} pparams=${pparamList.length}`);
|
|
156
299
|
}
|
|
157
|
-
|
|
300
|
+
// Name is a PythonObject (Py_String/Py_Unicode), not a raw string — compare
|
|
301
|
+
// via toString() so the Set lookup actually matches.
|
|
302
|
+
const codeName = func.code?.object?.Name?.toString?.() || '';
|
|
303
|
+
const looksLikeComp = compNames.has(codeName) ||
|
|
158
304
|
sourceTop instanceof AST.ASTComprehension ||
|
|
159
305
|
compNode instanceof AST.ASTComprehension ||
|
|
160
306
|
(sourceTop instanceof AST.ASTReturn && sourceTop.value instanceof AST.ASTComprehension);
|
|
@@ -164,6 +310,100 @@ function handleInstrumentedCallA() {
|
|
|
164
310
|
resultNode = resultNode.value;
|
|
165
311
|
}
|
|
166
312
|
|
|
313
|
+
// Async comprehension: inner code uses GET_ANEXT instead of
|
|
314
|
+
// FOR_ITER, so no ASTComprehension was produced. Reconstruct
|
|
315
|
+
// from the decompiled source tree.
|
|
316
|
+
if (!(resultNode instanceof AST.ASTComprehension) && codeObj?.SourceCode) {
|
|
317
|
+
let yieldExpr = null;
|
|
318
|
+
let loopVar = null;
|
|
319
|
+
// Seed async-ness from the code flags: async comprehensions and
|
|
320
|
+
// async generator expressions carry CO_ASYNC_GENERATOR /
|
|
321
|
+
// CO_COROUTINE on the inner code object, so use that as the
|
|
322
|
+
// default in case the inner tree lacks an explicit AsyncFor
|
|
323
|
+
// block (e.g. pre-3.8 async comprehensions use GET_ANEXT +
|
|
324
|
+
// STORE instead of a FOR_ITER-driven iter block).
|
|
325
|
+
const flags = codeObj?.Flags | 0;
|
|
326
|
+
let isAsyncLoop = !!(flags & AST.ASTFunction.CodeFlags.CO_ASYNC_GENERATOR) ||
|
|
327
|
+
!!(flags & AST.ASTFunction.CodeFlags.CO_COROUTINE);
|
|
328
|
+
const searchNodes = (nodes) => {
|
|
329
|
+
if (!nodes) return;
|
|
330
|
+
for (const node of nodes) {
|
|
331
|
+
if (node instanceof AST.ASTReturn && node.rettype === AST.ASTReturn.RetType.Yield && node.value) {
|
|
332
|
+
yieldExpr = node.value;
|
|
333
|
+
}
|
|
334
|
+
if (node instanceof AST.ASTStore && !loopVar) {
|
|
335
|
+
loopVar = node.dest;
|
|
336
|
+
}
|
|
337
|
+
if (node instanceof AST.ASTIterBlock &&
|
|
338
|
+
(node.blockType === AST.ASTBlock.BlockType.AsyncFor ||
|
|
339
|
+
node.blockType === AST.ASTBlock.BlockType.For)) {
|
|
340
|
+
if (node.index) {
|
|
341
|
+
loopVar = node.index;
|
|
342
|
+
isAsyncLoop = (node.blockType === AST.ASTBlock.BlockType.AsyncFor);
|
|
343
|
+
}
|
|
344
|
+
if (node.nodes) searchNodes(node.nodes);
|
|
345
|
+
}
|
|
346
|
+
if (node?.nodes) searchNodes(node.nodes);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
searchNodes(codeObj.SourceCode.list);
|
|
350
|
+
// Listcomp/setcomp/dictcomp fallback: the yield expression was
|
|
351
|
+
// saved by processListAppend/MAP_ADD when no For+comp block
|
|
352
|
+
// existed.
|
|
353
|
+
if (!yieldExpr && codeObj._asyncCompYieldExpr) {
|
|
354
|
+
yieldExpr = codeObj._asyncCompYieldExpr;
|
|
355
|
+
}
|
|
356
|
+
// In 3.8+ inline async comprehensions the STORE_FAST lands
|
|
357
|
+
// inside a try/except block and disappears into the block
|
|
358
|
+
// structure before reaching SourceCode.list. Scan the inner
|
|
359
|
+
// code's instruction list: first STORE_* after GET_ANEXT is
|
|
360
|
+
// the loop variable.
|
|
361
|
+
if (!loopVar) {
|
|
362
|
+
const innerOps = new this.OpCodes(codeObj);
|
|
363
|
+
let sawAnext = false;
|
|
364
|
+
for (const op of innerOps.Instructions || []) {
|
|
365
|
+
if (!op) continue;
|
|
366
|
+
if (op.OpCodeID === this.OpCodes.GET_ANEXT) {
|
|
367
|
+
sawAnext = true;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (sawAnext && (op.OpCodeID === this.OpCodes.STORE_FAST_A ||
|
|
371
|
+
op.OpCodeID === this.OpCodes.STORE_NAME_A ||
|
|
372
|
+
op.OpCodeID === this.OpCodes.STORE_DEREF_A)) {
|
|
373
|
+
const varName = op.Name?.toString?.() || '';
|
|
374
|
+
if (varName) {
|
|
375
|
+
loopVar = new AST.ASTName(varName);
|
|
376
|
+
isAsyncLoop = true;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (global.g_cliArgs?.debug) {
|
|
383
|
+
console.log(`[CALL-RECONSTRUCT] ${codeObj.Name?.toString?.()} yield=${yieldExpr?.constructor?.name} loopVar=${loopVar?.name} isAsyncLoop=${isAsyncLoop}`);
|
|
384
|
+
}
|
|
385
|
+
if (yieldExpr && loopVar) {
|
|
386
|
+
let kind = AST.ASTComprehension.GENERATOR;
|
|
387
|
+
const objName = codeObj.Name?.toString?.() || '';
|
|
388
|
+
if (objName.includes('listcomp')) kind = AST.ASTComprehension.LIST;
|
|
389
|
+
else if (objName.includes('setcomp')) kind = AST.ASTComprehension.SET;
|
|
390
|
+
else if (objName.includes('dictcomp')) kind = AST.ASTComprehension.DICT;
|
|
391
|
+
let comp;
|
|
392
|
+
if (kind === AST.ASTComprehension.DICT && codeObj._asyncCompYieldKey) {
|
|
393
|
+
comp = new AST.ASTComprehension(yieldExpr, codeObj._asyncCompYieldKey);
|
|
394
|
+
} else {
|
|
395
|
+
comp = new AST.ASTComprehension(yieldExpr);
|
|
396
|
+
}
|
|
397
|
+
comp.kind = kind;
|
|
398
|
+
const genBlockType = isAsyncLoop ? AST.ASTBlock.BlockType.AsyncFor : AST.ASTBlock.BlockType.For;
|
|
399
|
+
let gen = new AST.ASTIterBlock(genBlockType, 0, 0, new AST.ASTName('.0'));
|
|
400
|
+
gen.index = loopVar;
|
|
401
|
+
gen.comprehension = true;
|
|
402
|
+
comp.addGenerator(gen);
|
|
403
|
+
resultNode = comp;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
167
407
|
// Map placeholder iter (.0) to actual argument for comprehensions.
|
|
168
408
|
if (resultNode?.generators) {
|
|
169
409
|
if (global.g_cliArgs?.debug) {
|
|
@@ -177,18 +417,40 @@ function handleInstrumentedCallA() {
|
|
|
177
417
|
if (global.g_cliArgs?.debug) {
|
|
178
418
|
console.log(`[CALL] remapped iter .${paramIdx} -> ${gen.iter?.constructor?.name}`);
|
|
179
419
|
}
|
|
420
|
+
} else if (gen.iter instanceof AST.ASTName && gen.iter.name === "[outmost-iterable]") {
|
|
421
|
+
// Py2.4 names the genexpr's first parameter `[outmost-iterable]`
|
|
422
|
+
const param = pparamList[0];
|
|
423
|
+
gen.iter = param instanceof AST.ASTIteratorValue ? param.value : param;
|
|
180
424
|
}
|
|
181
425
|
}
|
|
182
426
|
}
|
|
183
427
|
|
|
184
428
|
if (resultNode) {
|
|
429
|
+
// Async listcomp/setcomp/dictcomp: the outer function
|
|
430
|
+
// awaits the comprehension coroutine with GET_AWAITABLE +
|
|
431
|
+
// LOAD_CONST None + YIELD_FROM. Skip these — the await is
|
|
432
|
+
// implicit in the async comprehension syntax.
|
|
433
|
+
if (resultNode instanceof AST.ASTComprehension &&
|
|
434
|
+
resultNode.kind !== AST.ASTComprehension.GENERATOR) {
|
|
435
|
+
const nx1 = this.code.Next;
|
|
436
|
+
const nx2 = nx1?.Next;
|
|
437
|
+
const nx3 = nx2?.Next;
|
|
438
|
+
if (nx1 && nx2 && nx3 &&
|
|
439
|
+
(nx1.OpCodeID == this.OpCodes.GET_AWAITABLE ||
|
|
440
|
+
nx1.OpCodeID == this.OpCodes.GET_AWAITABLE_A) &&
|
|
441
|
+
nx2.OpCodeID == this.OpCodes.LOAD_CONST_A &&
|
|
442
|
+
nx3.OpCodeID == this.OpCodes.YIELD_FROM) {
|
|
443
|
+
this.code.GoNext(3);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
185
446
|
this.dataStack.push(resultNode);
|
|
186
447
|
return;
|
|
187
448
|
}
|
|
188
449
|
}
|
|
189
450
|
}
|
|
190
451
|
|
|
191
|
-
if ([this.OpCodes.GET_ITER, this.OpCodes.GET_AITER].includes(this.code.Prev.OpCodeID)
|
|
452
|
+
if ([this.OpCodes.GET_ITER, this.OpCodes.GET_AITER].includes(this.code.Prev.OpCodeID) &&
|
|
453
|
+
func instanceof AST.ASTFunction && func.code?.object?.SourceCode?.list?.top) {
|
|
192
454
|
let ast = func.code.object.SourceCode.list.top();
|
|
193
455
|
if (!(ast instanceof AST.ASTKeyword)) {
|
|
194
456
|
if (ast instanceof AST.ASTReturn) {
|
|
@@ -203,6 +465,12 @@ function handleInstrumentedCallA() {
|
|
|
203
465
|
param = param.value;
|
|
204
466
|
}
|
|
205
467
|
gen.iter = param;
|
|
468
|
+
} else if (gen.iter instanceof AST.ASTName && gen.iter.name === "[outmost-iterable]") {
|
|
469
|
+
let param = pparamList[0];
|
|
470
|
+
if (param instanceof AST.ASTIteratorValue) {
|
|
471
|
+
param = param.value;
|
|
472
|
+
}
|
|
473
|
+
gen.iter = param;
|
|
206
474
|
}
|
|
207
475
|
}
|
|
208
476
|
}
|
|
@@ -250,6 +518,66 @@ function handleCallFunctionVarA() {
|
|
|
250
518
|
}
|
|
251
519
|
|
|
252
520
|
function handleCallFunctionKwA() {
|
|
521
|
+
if (this.object.Reader.versionCompare(3, 6) >= 0) {
|
|
522
|
+
// Py 3.6+: CALL_FUNCTION_KW(argc) — TOS is a tuple of kwarg names,
|
|
523
|
+
// argc is total args, last len(names) are kwargs.
|
|
524
|
+
let kwnamesNode = this.dataStack.pop();
|
|
525
|
+
let kwNamesList = [];
|
|
526
|
+
const toKwName = (v) => {
|
|
527
|
+
const raw = v?.Value ?? v?.name ?? v;
|
|
528
|
+
const name = typeof raw === 'string' ? raw : String(raw);
|
|
529
|
+
return new AST.ASTName(name.replace(/^['"]|['"]$/g, ''));
|
|
530
|
+
};
|
|
531
|
+
if (kwnamesNode instanceof AST.ASTObject) {
|
|
532
|
+
const obj = kwnamesNode.object;
|
|
533
|
+
if (obj && (obj.ClassName === 'Py_Tuple' || obj.ClassName === 'Py_SmallTuple') && Array.isArray(obj.Value)) {
|
|
534
|
+
kwNamesList = obj.Value.map(toKwName);
|
|
535
|
+
}
|
|
536
|
+
} else if (kwnamesNode instanceof AST.ASTTuple) {
|
|
537
|
+
kwNamesList = (kwnamesNode.values || []).map(toKwName);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const totalArgs = this.code.Current.Argument;
|
|
541
|
+
const kwcount = kwNamesList.length;
|
|
542
|
+
const pcount = Math.max(0, totalArgs - kwcount);
|
|
543
|
+
|
|
544
|
+
let kwparamList = [];
|
|
545
|
+
for (let i = kwcount - 1; i >= 0; i--) {
|
|
546
|
+
let value = this.dataStack.pop();
|
|
547
|
+
kwparamList.unshift({ key: kwNamesList[i], value });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
let pparamList = [];
|
|
551
|
+
for (let i = 0; i < pcount; i++) {
|
|
552
|
+
pparamList.unshift(this.dataStack.pop());
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
let func = this.dataStack.pop();
|
|
556
|
+
|
|
557
|
+
// 3.6+ class with kwargs uses CALL_FUNCTION_KW:
|
|
558
|
+
// LOAD_BUILD_CLASS / MAKE_FUNCTION / LOAD_CONST name / <bases>
|
|
559
|
+
// / LOAD_CONST (kwarg_names_tuple) / CALL_FUNCTION_KW argc
|
|
560
|
+
// pparamList is [body_func, class_name, ...bases]; kwparamList carries
|
|
561
|
+
// the class kwargs (metaclass, **__init_subclass__ kwargs, ...).
|
|
562
|
+
if (func instanceof AST.ASTLoadBuildClass && pparamList.length >= 2) {
|
|
563
|
+
const functionNode = pparamList[0];
|
|
564
|
+
const nameNode = pparamList[1];
|
|
565
|
+
const bases = pparamList.slice(2);
|
|
566
|
+
const classCall = new AST.ASTCall(functionNode, [], kwparamList);
|
|
567
|
+
classCall.line = this.code.Current.LineNo;
|
|
568
|
+
const classNode = new AST.ASTClass(classCall, new AST.ASTTuple(bases), nameNode);
|
|
569
|
+
classNode.line = this.code.Current.LineNo;
|
|
570
|
+
classNode.kwargs = kwparamList;
|
|
571
|
+
this.dataStack.push(classNode);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let callNode = new AST.ASTCall(func, pparamList, kwparamList);
|
|
576
|
+
callNode.line = this.code.Current.LineNo;
|
|
577
|
+
this.dataStack.push(callNode);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
253
581
|
let kw = this.dataStack.pop();
|
|
254
582
|
let kwparams = (this.code.Current.Argument & 0xFF00) >> 8;
|
|
255
583
|
let pparams = (this.code.Current.Argument & 0xFF);
|
|
@@ -339,34 +667,67 @@ function handleBinaryCall() {
|
|
|
339
667
|
this.dataStack.push(callNode);
|
|
340
668
|
}
|
|
341
669
|
|
|
670
|
+
function mapKeysAreIdentifiers(mapNode) {
|
|
671
|
+
const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
672
|
+
for (const entry of mapNode.values || []) {
|
|
673
|
+
const keyObj = entry?.key;
|
|
674
|
+
const cls = keyObj?.object?.ClassName;
|
|
675
|
+
if (cls !== 'Py_String' && cls !== 'Py_Unicode') {
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
const s = keyObj.object.toString();
|
|
679
|
+
if (!IDENT.test(s)) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
|
|
342
686
|
function handleCallFunctionExA() {
|
|
687
|
+
// CPython 3.6+: oparg & 0x01 means kwargs mapping is on TOS; the positional
|
|
688
|
+
// tuple and the callable are ALWAYS popped regardless of the flag.
|
|
343
689
|
let flags = this.code.Current.Argument;
|
|
344
690
|
let kwparams = [];
|
|
345
691
|
let pparams = [];
|
|
692
|
+
let varArg = null;
|
|
693
|
+
let kwSpread = null;
|
|
694
|
+
let kwSingle = null;
|
|
346
695
|
if (flags & 0x01) { // **kwargs
|
|
347
696
|
let kw = this.dataStack.pop();
|
|
348
|
-
if (kw instanceof AST.
|
|
349
|
-
|
|
697
|
+
if (kw instanceof AST.ASTMapUnpack) {
|
|
698
|
+
// Py 3.6+ f(**a, **b) path: preserve individual ** sources for rendering.
|
|
699
|
+
kwSpread = kw;
|
|
700
|
+
} else if (kw instanceof AST.ASTMap) {
|
|
701
|
+
if (mapKeysAreIdentifiers(kw)) {
|
|
702
|
+
kwparams = kw.values;
|
|
703
|
+
} else {
|
|
704
|
+
// Non-identifier keys (e.g. '"', 'with space') must render as **{...}.
|
|
705
|
+
kwSingle = kw;
|
|
706
|
+
}
|
|
350
707
|
} else if (kw?.object?.ClassName === "Py_Dict" && kw.object.Value) {
|
|
351
708
|
kwparams = kw.object.Value.map(entry => ({key: new AST.ASTObject(entry.key), value: new AST.ASTObject(entry.value)}));
|
|
352
709
|
} else {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
710
|
+
// Single **dict argument where dict is a name/expression (not literal).
|
|
711
|
+
kwSingle = kw;
|
|
356
712
|
}
|
|
357
713
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
console.error("Expected a tuple or list for CALL_FUNCTION_EX args");
|
|
365
|
-
}
|
|
366
|
-
}
|
|
714
|
+
let args = this.dataStack.pop();
|
|
715
|
+
if (args instanceof AST.ASTTuple || args instanceof AST.ASTList) {
|
|
716
|
+
pparams = args.values;
|
|
717
|
+
} else {
|
|
718
|
+
// *args as a non-literal expression (e.g. f(*tup_name, **kw)).
|
|
719
|
+
varArg = args;
|
|
367
720
|
}
|
|
368
721
|
let func = this.dataStack.pop();
|
|
369
722
|
let callNode = new AST.ASTCall(func, pparams, kwparams);
|
|
723
|
+
if (varArg) {
|
|
724
|
+
callNode.var = varArg;
|
|
725
|
+
}
|
|
726
|
+
if (kwSpread) {
|
|
727
|
+
callNode.kw = kwSpread;
|
|
728
|
+
} else if (kwSingle) {
|
|
729
|
+
callNode.kw = kwSingle;
|
|
730
|
+
}
|
|
370
731
|
callNode.line = this.code.Current.LineNo;
|
|
371
732
|
this.dataStack.push(callNode);
|
|
372
733
|
}
|
|
@@ -374,19 +735,15 @@ function handleCallFunctionExA() {
|
|
|
374
735
|
function handleCallIntrinsic1() {
|
|
375
736
|
// Python 3.12+ CALL_INTRINSIC_1 opcode
|
|
376
737
|
// Calls built-in intrinsic functions (argument specifies which)
|
|
377
|
-
// Intrinsic 3: ASYNC_GEN_WRAP (wraps async generator function)
|
|
378
|
-
// Intrinsic 4: STOPITERATION_ERROR, etc.
|
|
379
738
|
|
|
380
|
-
// Intrinsic takes 1 arg from stack, returns result
|
|
381
739
|
let arg = this.dataStack.pop();
|
|
382
740
|
|
|
383
741
|
if (global.g_cliArgs?.debug) {
|
|
384
742
|
console.log(`[CALL_INTRINSIC_1] intrinsic=${this.code.Current.Argument}, arg=${arg?.constructor?.name}`);
|
|
385
743
|
}
|
|
386
744
|
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
if (this.code.Current.Argument === TYPE_ALIAS_INTRINSIC && arg instanceof AST.ASTTuple) {
|
|
745
|
+
// PEP 695 type statement wraps tuple(name, qualname, type-fn)
|
|
746
|
+
if (this.code.Current.Argument === INTRINSIC_1.TYPEALIAS && arg instanceof AST.ASTTuple) {
|
|
390
747
|
const values = arg.values || [];
|
|
391
748
|
const aliasName = values[0]?.object?.Value || values[0]?.name || values[0]?.codeFragment?.();
|
|
392
749
|
const typeFunc = values[2];
|
|
@@ -432,9 +789,9 @@ function handleCallIntrinsic2() {
|
|
|
432
789
|
const argA = this.dataStack.pop();
|
|
433
790
|
const args = [argA, argB];
|
|
434
791
|
|
|
435
|
-
//
|
|
792
|
+
// PREP_RERAISE_STAR equivalent in 3.12+.
|
|
436
793
|
// Do not emit; mark to skip the following conditional jump.
|
|
437
|
-
if (this.code.Current.Argument ===
|
|
794
|
+
if (this.code.Current.Argument === INTRINSIC_2.PREP_RERAISE_STAR) {
|
|
438
795
|
this.ignoreNextConditional = true;
|
|
439
796
|
this.cleanupStackDepth = this.dataStack.length + 1; // after we push a placeholder
|
|
440
797
|
this.dataStack.push(new AST.ASTNone());
|
|
@@ -442,23 +799,81 @@ function handleCallIntrinsic2() {
|
|
|
442
799
|
return;
|
|
443
800
|
}
|
|
444
801
|
|
|
445
|
-
//
|
|
446
|
-
|
|
802
|
+
// SET_FUNCTION_TYPE_PARAMS — build generic function from
|
|
803
|
+
// type parameters tuple + function
|
|
804
|
+
if (this.code.Current.Argument === INTRINSIC_2.SET_FUNCTION_TYPE_PARAMS) {
|
|
447
805
|
const funcNode = args.find(a => a instanceof AST.ASTFunction);
|
|
448
806
|
const typeArg = args.find(a => a instanceof AST.ASTTuple || a instanceof AST.ASTList);
|
|
449
807
|
if (funcNode && typeArg) {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
808
|
+
const params = (typeArg.values || []).map(v => {
|
|
809
|
+
// COPY/STORE_FAST inside the wrapper wraps each typevar in a
|
|
810
|
+
// walrus ASTNamedExpr(name := typevar). Unwrap to reach the
|
|
811
|
+
// underlying typevar (which carries pepDefault for PEP 696).
|
|
812
|
+
let source = v;
|
|
813
|
+
if (v instanceof AST.ASTNamedExpr) {
|
|
814
|
+
source = v.value;
|
|
815
|
+
}
|
|
816
|
+
let name;
|
|
817
|
+
if (v instanceof AST.ASTNamedExpr && v.target?.name) {
|
|
818
|
+
name = v.target.name;
|
|
819
|
+
} else if (source instanceof AST.ASTName) {
|
|
820
|
+
name = source.name;
|
|
821
|
+
} else if (source?.object?.Value) {
|
|
822
|
+
name = source.object.Value;
|
|
823
|
+
} else {
|
|
824
|
+
const frag = source?.codeFragment?.();
|
|
825
|
+
name = frag?.toString?.() || 'T';
|
|
826
|
+
}
|
|
827
|
+
if (source?.pepDefault != null) {
|
|
828
|
+
return { name, default: source.pepDefault };
|
|
829
|
+
}
|
|
830
|
+
return name;
|
|
455
831
|
});
|
|
456
|
-
funcNode.typeParams =
|
|
832
|
+
funcNode.typeParams = params;
|
|
457
833
|
this.dataStack.push(funcNode);
|
|
458
834
|
return;
|
|
459
835
|
}
|
|
460
836
|
}
|
|
461
837
|
|
|
838
|
+
// SET_TYPEPARAM_DEFAULT (PEP 696, 3.13+)
|
|
839
|
+
// Stack: [..., typevar, default_fn] → [..., typevar_with_default]
|
|
840
|
+
// The default wrapper is a zero-arg function whose return value is the default.
|
|
841
|
+
if (this.code.Current.Argument === INTRINSIC_2.SET_TYPEPARAM_DEFAULT) {
|
|
842
|
+
const defaultFn = argB;
|
|
843
|
+
const typevar = argA;
|
|
844
|
+
let defaultValue = null;
|
|
845
|
+
const extractReturn = (body) => {
|
|
846
|
+
const list = body?.list || [];
|
|
847
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
848
|
+
if (list[i] instanceof AST.ASTReturn && list[i].value) {
|
|
849
|
+
return list[i].value;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return null;
|
|
853
|
+
};
|
|
854
|
+
if (defaultFn instanceof AST.ASTFunction) {
|
|
855
|
+
defaultValue = extractReturn(defaultFn.code?.object?.SourceCode);
|
|
856
|
+
} else if (defaultFn instanceof AST.ASTObject &&
|
|
857
|
+
(defaultFn.object?.ClassName === 'Py_CodeObject' ||
|
|
858
|
+
defaultFn.object?.ClassName === 'Py_CodeObject2')) {
|
|
859
|
+
try {
|
|
860
|
+
const PycDecompiler = require('../PycDecompiler');
|
|
861
|
+
const dd = new PycDecompiler(defaultFn.object);
|
|
862
|
+
const body = dd.decompile();
|
|
863
|
+
defaultFn.object.SourceCode = body;
|
|
864
|
+
if (dd.errors.length) this.errors.push(...dd.errors);
|
|
865
|
+
defaultValue = extractReturn(body);
|
|
866
|
+
} catch (_) { /* bare typevar */ }
|
|
867
|
+
}
|
|
868
|
+
if (typevar != null && defaultValue != null) {
|
|
869
|
+
typevar.pepDefault = defaultValue;
|
|
870
|
+
}
|
|
871
|
+
if (typevar !== undefined) {
|
|
872
|
+
this.dataStack.push(typevar);
|
|
873
|
+
}
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
|
|
462
877
|
// Default: preserve the left argument to avoid stack underflow
|
|
463
878
|
if (argA !== undefined) {
|
|
464
879
|
this.dataStack.push(argA);
|
|
@@ -478,6 +893,7 @@ function handleCallIntrinsic2A() {
|
|
|
478
893
|
module.exports = {
|
|
479
894
|
handleKwNamesA,
|
|
480
895
|
handleCallA,
|
|
896
|
+
handleCallKwA,
|
|
481
897
|
handleInstrumentedCallKwA,
|
|
482
898
|
handleCallFunctionA,
|
|
483
899
|
handleInstrumentedCallA,
|