depyo 1.0.2 → 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/depyo.js +43 -2
- package/lib/OpCodes.js +22 -5
- package/lib/PycDecompiler.js +402 -39
- package/lib/PycDisassembler.js +1 -1
- package/lib/PycReader.js +22 -3
- 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
|
@@ -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;
|
|
@@ -164,6 +307,84 @@ function handleInstrumentedCallA() {
|
|
|
164
307
|
resultNode = resultNode.value;
|
|
165
308
|
}
|
|
166
309
|
|
|
310
|
+
// Async comprehension: inner code uses GET_ANEXT instead of
|
|
311
|
+
// FOR_ITER, so no ASTComprehension was produced. Reconstruct
|
|
312
|
+
// from the decompiled source tree.
|
|
313
|
+
if (!(resultNode instanceof AST.ASTComprehension) && codeObj?.SourceCode) {
|
|
314
|
+
let yieldExpr = null;
|
|
315
|
+
let loopVar = null;
|
|
316
|
+
const searchNodes = (nodes) => {
|
|
317
|
+
if (!nodes) return;
|
|
318
|
+
for (const node of nodes) {
|
|
319
|
+
if (node instanceof AST.ASTReturn && node.rettype === AST.ASTReturn.RetType.Yield && node.value) {
|
|
320
|
+
yieldExpr = node.value;
|
|
321
|
+
}
|
|
322
|
+
if (node instanceof AST.ASTStore && !loopVar) {
|
|
323
|
+
loopVar = node.dest;
|
|
324
|
+
}
|
|
325
|
+
if (node instanceof AST.ASTIterBlock && node.blockType === AST.ASTBlock.BlockType.AsyncFor) {
|
|
326
|
+
if (node.index) loopVar = node.index;
|
|
327
|
+
if (node.nodes) searchNodes(node.nodes);
|
|
328
|
+
}
|
|
329
|
+
if (node?.nodes) searchNodes(node.nodes);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
searchNodes(codeObj.SourceCode.list);
|
|
333
|
+
// Listcomp/setcomp/dictcomp fallback: the yield expression was
|
|
334
|
+
// saved by processListAppend/MAP_ADD when no For+comp block
|
|
335
|
+
// existed.
|
|
336
|
+
if (!yieldExpr && codeObj._asyncCompYieldExpr) {
|
|
337
|
+
yieldExpr = codeObj._asyncCompYieldExpr;
|
|
338
|
+
}
|
|
339
|
+
// In 3.8+ inline async comprehensions the STORE_FAST lands
|
|
340
|
+
// inside a try/except block and disappears into the block
|
|
341
|
+
// structure before reaching SourceCode.list. Scan the inner
|
|
342
|
+
// code's instruction list: first STORE_* after GET_ANEXT is
|
|
343
|
+
// the loop variable.
|
|
344
|
+
if (!loopVar) {
|
|
345
|
+
const innerOps = new this.OpCodes(codeObj);
|
|
346
|
+
let sawAnext = false;
|
|
347
|
+
for (const op of innerOps.Instructions || []) {
|
|
348
|
+
if (!op) continue;
|
|
349
|
+
if (op.OpCodeID === this.OpCodes.GET_ANEXT) {
|
|
350
|
+
sawAnext = true;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (sawAnext && (op.OpCodeID === this.OpCodes.STORE_FAST_A ||
|
|
354
|
+
op.OpCodeID === this.OpCodes.STORE_NAME_A ||
|
|
355
|
+
op.OpCodeID === this.OpCodes.STORE_DEREF_A)) {
|
|
356
|
+
const varName = op.Name?.toString?.() || '';
|
|
357
|
+
if (varName) {
|
|
358
|
+
loopVar = new AST.ASTName(varName);
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (global.g_cliArgs?.debug) {
|
|
365
|
+
console.log(`[CALL-RECONSTRUCT] ${codeObj.Name?.toString?.()} yield=${yieldExpr?.constructor?.name} loopVar=${loopVar?.name}`);
|
|
366
|
+
}
|
|
367
|
+
if (yieldExpr && loopVar) {
|
|
368
|
+
let kind = AST.ASTComprehension.GENERATOR;
|
|
369
|
+
const objName = codeObj.Name?.toString?.() || '';
|
|
370
|
+
if (objName.includes('listcomp')) kind = AST.ASTComprehension.LIST;
|
|
371
|
+
else if (objName.includes('setcomp')) kind = AST.ASTComprehension.SET;
|
|
372
|
+
else if (objName.includes('dictcomp')) kind = AST.ASTComprehension.DICT;
|
|
373
|
+
let comp;
|
|
374
|
+
if (kind === AST.ASTComprehension.DICT && codeObj._asyncCompYieldKey) {
|
|
375
|
+
comp = new AST.ASTComprehension(yieldExpr, codeObj._asyncCompYieldKey);
|
|
376
|
+
} else {
|
|
377
|
+
comp = new AST.ASTComprehension(yieldExpr);
|
|
378
|
+
}
|
|
379
|
+
comp.kind = kind;
|
|
380
|
+
let gen = new AST.ASTIterBlock(AST.ASTBlock.BlockType.AsyncFor, 0, 0, new AST.ASTName('.0'));
|
|
381
|
+
gen.index = loopVar;
|
|
382
|
+
gen.comprehension = true;
|
|
383
|
+
comp.addGenerator(gen);
|
|
384
|
+
resultNode = comp;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
167
388
|
// Map placeholder iter (.0) to actual argument for comprehensions.
|
|
168
389
|
if (resultNode?.generators) {
|
|
169
390
|
if (global.g_cliArgs?.debug) {
|
|
@@ -177,18 +398,40 @@ function handleInstrumentedCallA() {
|
|
|
177
398
|
if (global.g_cliArgs?.debug) {
|
|
178
399
|
console.log(`[CALL] remapped iter .${paramIdx} -> ${gen.iter?.constructor?.name}`);
|
|
179
400
|
}
|
|
401
|
+
} else if (gen.iter instanceof AST.ASTName && gen.iter.name === "[outmost-iterable]") {
|
|
402
|
+
// Py2.4 names the genexpr's first parameter `[outmost-iterable]`
|
|
403
|
+
const param = pparamList[0];
|
|
404
|
+
gen.iter = param instanceof AST.ASTIteratorValue ? param.value : param;
|
|
180
405
|
}
|
|
181
406
|
}
|
|
182
407
|
}
|
|
183
408
|
|
|
184
409
|
if (resultNode) {
|
|
410
|
+
// Async listcomp/setcomp/dictcomp: the outer function
|
|
411
|
+
// awaits the comprehension coroutine with GET_AWAITABLE +
|
|
412
|
+
// LOAD_CONST None + YIELD_FROM. Skip these — the await is
|
|
413
|
+
// implicit in the async comprehension syntax.
|
|
414
|
+
if (resultNode instanceof AST.ASTComprehension &&
|
|
415
|
+
resultNode.kind !== AST.ASTComprehension.GENERATOR) {
|
|
416
|
+
const nx1 = this.code.Next;
|
|
417
|
+
const nx2 = nx1?.Next;
|
|
418
|
+
const nx3 = nx2?.Next;
|
|
419
|
+
if (nx1 && nx2 && nx3 &&
|
|
420
|
+
(nx1.OpCodeID == this.OpCodes.GET_AWAITABLE ||
|
|
421
|
+
nx1.OpCodeID == this.OpCodes.GET_AWAITABLE_A) &&
|
|
422
|
+
nx2.OpCodeID == this.OpCodes.LOAD_CONST_A &&
|
|
423
|
+
nx3.OpCodeID == this.OpCodes.YIELD_FROM) {
|
|
424
|
+
this.code.GoNext(3);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
185
427
|
this.dataStack.push(resultNode);
|
|
186
428
|
return;
|
|
187
429
|
}
|
|
188
430
|
}
|
|
189
431
|
}
|
|
190
432
|
|
|
191
|
-
if ([this.OpCodes.GET_ITER, this.OpCodes.GET_AITER].includes(this.code.Prev.OpCodeID)
|
|
433
|
+
if ([this.OpCodes.GET_ITER, this.OpCodes.GET_AITER].includes(this.code.Prev.OpCodeID) &&
|
|
434
|
+
func instanceof AST.ASTFunction && func.code?.object?.SourceCode?.list?.top) {
|
|
192
435
|
let ast = func.code.object.SourceCode.list.top();
|
|
193
436
|
if (!(ast instanceof AST.ASTKeyword)) {
|
|
194
437
|
if (ast instanceof AST.ASTReturn) {
|
|
@@ -203,6 +446,12 @@ function handleInstrumentedCallA() {
|
|
|
203
446
|
param = param.value;
|
|
204
447
|
}
|
|
205
448
|
gen.iter = param;
|
|
449
|
+
} else if (gen.iter instanceof AST.ASTName && gen.iter.name === "[outmost-iterable]") {
|
|
450
|
+
let param = pparamList[0];
|
|
451
|
+
if (param instanceof AST.ASTIteratorValue) {
|
|
452
|
+
param = param.value;
|
|
453
|
+
}
|
|
454
|
+
gen.iter = param;
|
|
206
455
|
}
|
|
207
456
|
}
|
|
208
457
|
}
|
|
@@ -250,6 +499,66 @@ function handleCallFunctionVarA() {
|
|
|
250
499
|
}
|
|
251
500
|
|
|
252
501
|
function handleCallFunctionKwA() {
|
|
502
|
+
if (this.object.Reader.versionCompare(3, 6) >= 0) {
|
|
503
|
+
// Py 3.6+: CALL_FUNCTION_KW(argc) — TOS is a tuple of kwarg names,
|
|
504
|
+
// argc is total args, last len(names) are kwargs.
|
|
505
|
+
let kwnamesNode = this.dataStack.pop();
|
|
506
|
+
let kwNamesList = [];
|
|
507
|
+
const toKwName = (v) => {
|
|
508
|
+
const raw = v?.Value ?? v?.name ?? v;
|
|
509
|
+
const name = typeof raw === 'string' ? raw : String(raw);
|
|
510
|
+
return new AST.ASTName(name.replace(/^['"]|['"]$/g, ''));
|
|
511
|
+
};
|
|
512
|
+
if (kwnamesNode instanceof AST.ASTObject) {
|
|
513
|
+
const obj = kwnamesNode.object;
|
|
514
|
+
if (obj && (obj.ClassName === 'Py_Tuple' || obj.ClassName === 'Py_SmallTuple') && Array.isArray(obj.Value)) {
|
|
515
|
+
kwNamesList = obj.Value.map(toKwName);
|
|
516
|
+
}
|
|
517
|
+
} else if (kwnamesNode instanceof AST.ASTTuple) {
|
|
518
|
+
kwNamesList = (kwnamesNode.values || []).map(toKwName);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const totalArgs = this.code.Current.Argument;
|
|
522
|
+
const kwcount = kwNamesList.length;
|
|
523
|
+
const pcount = Math.max(0, totalArgs - kwcount);
|
|
524
|
+
|
|
525
|
+
let kwparamList = [];
|
|
526
|
+
for (let i = kwcount - 1; i >= 0; i--) {
|
|
527
|
+
let value = this.dataStack.pop();
|
|
528
|
+
kwparamList.unshift({ key: kwNamesList[i], value });
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
let pparamList = [];
|
|
532
|
+
for (let i = 0; i < pcount; i++) {
|
|
533
|
+
pparamList.unshift(this.dataStack.pop());
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let func = this.dataStack.pop();
|
|
537
|
+
|
|
538
|
+
// 3.6+ class with kwargs uses CALL_FUNCTION_KW:
|
|
539
|
+
// LOAD_BUILD_CLASS / MAKE_FUNCTION / LOAD_CONST name / <bases>
|
|
540
|
+
// / LOAD_CONST (kwarg_names_tuple) / CALL_FUNCTION_KW argc
|
|
541
|
+
// pparamList is [body_func, class_name, ...bases]; kwparamList carries
|
|
542
|
+
// the class kwargs (metaclass, **__init_subclass__ kwargs, ...).
|
|
543
|
+
if (func instanceof AST.ASTLoadBuildClass && pparamList.length >= 2) {
|
|
544
|
+
const functionNode = pparamList[0];
|
|
545
|
+
const nameNode = pparamList[1];
|
|
546
|
+
const bases = pparamList.slice(2);
|
|
547
|
+
const classCall = new AST.ASTCall(functionNode, [], kwparamList);
|
|
548
|
+
classCall.line = this.code.Current.LineNo;
|
|
549
|
+
const classNode = new AST.ASTClass(classCall, new AST.ASTTuple(bases), nameNode);
|
|
550
|
+
classNode.line = this.code.Current.LineNo;
|
|
551
|
+
classNode.kwargs = kwparamList;
|
|
552
|
+
this.dataStack.push(classNode);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let callNode = new AST.ASTCall(func, pparamList, kwparamList);
|
|
557
|
+
callNode.line = this.code.Current.LineNo;
|
|
558
|
+
this.dataStack.push(callNode);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
253
562
|
let kw = this.dataStack.pop();
|
|
254
563
|
let kwparams = (this.code.Current.Argument & 0xFF00) >> 8;
|
|
255
564
|
let pparams = (this.code.Current.Argument & 0xFF);
|
|
@@ -339,34 +648,67 @@ function handleBinaryCall() {
|
|
|
339
648
|
this.dataStack.push(callNode);
|
|
340
649
|
}
|
|
341
650
|
|
|
651
|
+
function mapKeysAreIdentifiers(mapNode) {
|
|
652
|
+
const IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
653
|
+
for (const entry of mapNode.values || []) {
|
|
654
|
+
const keyObj = entry?.key;
|
|
655
|
+
const cls = keyObj?.object?.ClassName;
|
|
656
|
+
if (cls !== 'Py_String' && cls !== 'Py_Unicode') {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
const s = keyObj.object.toString();
|
|
660
|
+
if (!IDENT.test(s)) {
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
|
|
342
667
|
function handleCallFunctionExA() {
|
|
668
|
+
// CPython 3.6+: oparg & 0x01 means kwargs mapping is on TOS; the positional
|
|
669
|
+
// tuple and the callable are ALWAYS popped regardless of the flag.
|
|
343
670
|
let flags = this.code.Current.Argument;
|
|
344
671
|
let kwparams = [];
|
|
345
672
|
let pparams = [];
|
|
673
|
+
let varArg = null;
|
|
674
|
+
let kwSpread = null;
|
|
675
|
+
let kwSingle = null;
|
|
346
676
|
if (flags & 0x01) { // **kwargs
|
|
347
677
|
let kw = this.dataStack.pop();
|
|
348
|
-
if (kw instanceof AST.
|
|
349
|
-
|
|
678
|
+
if (kw instanceof AST.ASTMapUnpack) {
|
|
679
|
+
// Py 3.6+ f(**a, **b) path: preserve individual ** sources for rendering.
|
|
680
|
+
kwSpread = kw;
|
|
681
|
+
} else if (kw instanceof AST.ASTMap) {
|
|
682
|
+
if (mapKeysAreIdentifiers(kw)) {
|
|
683
|
+
kwparams = kw.values;
|
|
684
|
+
} else {
|
|
685
|
+
// Non-identifier keys (e.g. '"', 'with space') must render as **{...}.
|
|
686
|
+
kwSingle = kw;
|
|
687
|
+
}
|
|
350
688
|
} else if (kw?.object?.ClassName === "Py_Dict" && kw.object.Value) {
|
|
351
689
|
kwparams = kw.object.Value.map(entry => ({key: new AST.ASTObject(entry.key), value: new AST.ASTObject(entry.value)}));
|
|
352
690
|
} else {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
691
|
+
// Single **dict argument where dict is a name/expression (not literal).
|
|
692
|
+
kwSingle = kw;
|
|
356
693
|
}
|
|
357
694
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
console.error("Expected a tuple or list for CALL_FUNCTION_EX args");
|
|
365
|
-
}
|
|
366
|
-
}
|
|
695
|
+
let args = this.dataStack.pop();
|
|
696
|
+
if (args instanceof AST.ASTTuple || args instanceof AST.ASTList) {
|
|
697
|
+
pparams = args.values;
|
|
698
|
+
} else {
|
|
699
|
+
// *args as a non-literal expression (e.g. f(*tup_name, **kw)).
|
|
700
|
+
varArg = args;
|
|
367
701
|
}
|
|
368
702
|
let func = this.dataStack.pop();
|
|
369
703
|
let callNode = new AST.ASTCall(func, pparams, kwparams);
|
|
704
|
+
if (varArg) {
|
|
705
|
+
callNode.var = varArg;
|
|
706
|
+
}
|
|
707
|
+
if (kwSpread) {
|
|
708
|
+
callNode.kw = kwSpread;
|
|
709
|
+
} else if (kwSingle) {
|
|
710
|
+
callNode.kw = kwSingle;
|
|
711
|
+
}
|
|
370
712
|
callNode.line = this.code.Current.LineNo;
|
|
371
713
|
this.dataStack.push(callNode);
|
|
372
714
|
}
|
|
@@ -374,19 +716,15 @@ function handleCallFunctionExA() {
|
|
|
374
716
|
function handleCallIntrinsic1() {
|
|
375
717
|
// Python 3.12+ CALL_INTRINSIC_1 opcode
|
|
376
718
|
// 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
719
|
|
|
380
|
-
// Intrinsic takes 1 arg from stack, returns result
|
|
381
720
|
let arg = this.dataStack.pop();
|
|
382
721
|
|
|
383
722
|
if (global.g_cliArgs?.debug) {
|
|
384
723
|
console.log(`[CALL_INTRINSIC_1] intrinsic=${this.code.Current.Argument}, arg=${arg?.constructor?.name}`);
|
|
385
724
|
}
|
|
386
725
|
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
if (this.code.Current.Argument === TYPE_ALIAS_INTRINSIC && arg instanceof AST.ASTTuple) {
|
|
726
|
+
// PEP 695 type statement wraps tuple(name, qualname, type-fn)
|
|
727
|
+
if (this.code.Current.Argument === INTRINSIC_1.TYPEALIAS && arg instanceof AST.ASTTuple) {
|
|
390
728
|
const values = arg.values || [];
|
|
391
729
|
const aliasName = values[0]?.object?.Value || values[0]?.name || values[0]?.codeFragment?.();
|
|
392
730
|
const typeFunc = values[2];
|
|
@@ -432,9 +770,9 @@ function handleCallIntrinsic2() {
|
|
|
432
770
|
const argA = this.dataStack.pop();
|
|
433
771
|
const args = [argA, argB];
|
|
434
772
|
|
|
435
|
-
//
|
|
773
|
+
// PREP_RERAISE_STAR equivalent in 3.12+.
|
|
436
774
|
// Do not emit; mark to skip the following conditional jump.
|
|
437
|
-
if (this.code.Current.Argument ===
|
|
775
|
+
if (this.code.Current.Argument === INTRINSIC_2.PREP_RERAISE_STAR) {
|
|
438
776
|
this.ignoreNextConditional = true;
|
|
439
777
|
this.cleanupStackDepth = this.dataStack.length + 1; // after we push a placeholder
|
|
440
778
|
this.dataStack.push(new AST.ASTNone());
|
|
@@ -442,23 +780,81 @@ function handleCallIntrinsic2() {
|
|
|
442
780
|
return;
|
|
443
781
|
}
|
|
444
782
|
|
|
445
|
-
//
|
|
446
|
-
|
|
783
|
+
// SET_FUNCTION_TYPE_PARAMS — build generic function from
|
|
784
|
+
// type parameters tuple + function
|
|
785
|
+
if (this.code.Current.Argument === INTRINSIC_2.SET_FUNCTION_TYPE_PARAMS) {
|
|
447
786
|
const funcNode = args.find(a => a instanceof AST.ASTFunction);
|
|
448
787
|
const typeArg = args.find(a => a instanceof AST.ASTTuple || a instanceof AST.ASTList);
|
|
449
788
|
if (funcNode && typeArg) {
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
789
|
+
const params = (typeArg.values || []).map(v => {
|
|
790
|
+
// COPY/STORE_FAST inside the wrapper wraps each typevar in a
|
|
791
|
+
// walrus ASTNamedExpr(name := typevar). Unwrap to reach the
|
|
792
|
+
// underlying typevar (which carries pepDefault for PEP 696).
|
|
793
|
+
let source = v;
|
|
794
|
+
if (v instanceof AST.ASTNamedExpr) {
|
|
795
|
+
source = v.value;
|
|
796
|
+
}
|
|
797
|
+
let name;
|
|
798
|
+
if (v instanceof AST.ASTNamedExpr && v.target?.name) {
|
|
799
|
+
name = v.target.name;
|
|
800
|
+
} else if (source instanceof AST.ASTName) {
|
|
801
|
+
name = source.name;
|
|
802
|
+
} else if (source?.object?.Value) {
|
|
803
|
+
name = source.object.Value;
|
|
804
|
+
} else {
|
|
805
|
+
const frag = source?.codeFragment?.();
|
|
806
|
+
name = frag?.toString?.() || 'T';
|
|
807
|
+
}
|
|
808
|
+
if (source?.pepDefault != null) {
|
|
809
|
+
return { name, default: source.pepDefault };
|
|
810
|
+
}
|
|
811
|
+
return name;
|
|
455
812
|
});
|
|
456
|
-
funcNode.typeParams =
|
|
813
|
+
funcNode.typeParams = params;
|
|
457
814
|
this.dataStack.push(funcNode);
|
|
458
815
|
return;
|
|
459
816
|
}
|
|
460
817
|
}
|
|
461
818
|
|
|
819
|
+
// SET_TYPEPARAM_DEFAULT (PEP 696, 3.13+)
|
|
820
|
+
// Stack: [..., typevar, default_fn] → [..., typevar_with_default]
|
|
821
|
+
// The default wrapper is a zero-arg function whose return value is the default.
|
|
822
|
+
if (this.code.Current.Argument === INTRINSIC_2.SET_TYPEPARAM_DEFAULT) {
|
|
823
|
+
const defaultFn = argB;
|
|
824
|
+
const typevar = argA;
|
|
825
|
+
let defaultValue = null;
|
|
826
|
+
const extractReturn = (body) => {
|
|
827
|
+
const list = body?.list || [];
|
|
828
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
829
|
+
if (list[i] instanceof AST.ASTReturn && list[i].value) {
|
|
830
|
+
return list[i].value;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return null;
|
|
834
|
+
};
|
|
835
|
+
if (defaultFn instanceof AST.ASTFunction) {
|
|
836
|
+
defaultValue = extractReturn(defaultFn.code?.object?.SourceCode);
|
|
837
|
+
} else if (defaultFn instanceof AST.ASTObject &&
|
|
838
|
+
(defaultFn.object?.ClassName === 'Py_CodeObject' ||
|
|
839
|
+
defaultFn.object?.ClassName === 'Py_CodeObject2')) {
|
|
840
|
+
try {
|
|
841
|
+
const PycDecompiler = require('../PycDecompiler');
|
|
842
|
+
const dd = new PycDecompiler(defaultFn.object);
|
|
843
|
+
const body = dd.decompile();
|
|
844
|
+
defaultFn.object.SourceCode = body;
|
|
845
|
+
if (dd.errors.length) this.errors.push(...dd.errors);
|
|
846
|
+
defaultValue = extractReturn(body);
|
|
847
|
+
} catch (_) { /* bare typevar */ }
|
|
848
|
+
}
|
|
849
|
+
if (typevar != null && defaultValue != null) {
|
|
850
|
+
typevar.pepDefault = defaultValue;
|
|
851
|
+
}
|
|
852
|
+
if (typevar !== undefined) {
|
|
853
|
+
this.dataStack.push(typevar);
|
|
854
|
+
}
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
|
|
462
858
|
// Default: preserve the left argument to avoid stack underflow
|
|
463
859
|
if (argA !== undefined) {
|
|
464
860
|
this.dataStack.push(argA);
|
|
@@ -478,6 +874,7 @@ function handleCallIntrinsic2A() {
|
|
|
478
874
|
module.exports = {
|
|
479
875
|
handleKwNamesA,
|
|
480
876
|
handleCallA,
|
|
877
|
+
handleCallKwA,
|
|
481
878
|
handleInstrumentedCallKwA,
|
|
482
879
|
handleCallFunctionA,
|
|
483
880
|
handleInstrumentedCallA,
|