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
package/depyo.js
CHANGED
|
@@ -22,6 +22,7 @@ global.g_cliArgs = {
|
|
|
22
22
|
sendToStdout: false,
|
|
23
23
|
marshal: false,
|
|
24
24
|
marshalScan: false,
|
|
25
|
+
strict: false,
|
|
25
26
|
pyVersion: null,
|
|
26
27
|
silent: false,
|
|
27
28
|
fileExt: 'py',
|
|
@@ -29,6 +30,8 @@ global.g_cliArgs = {
|
|
|
29
30
|
filenames: []
|
|
30
31
|
};
|
|
31
32
|
|
|
33
|
+
let g_dirtyFiles = []; // Files where decompiler caught at least one opcode exception.
|
|
34
|
+
|
|
32
35
|
let g_totalInThroughput = 0;
|
|
33
36
|
let g_totalOutThroughput = 0;
|
|
34
37
|
let g_totalExecTime = 0;
|
|
@@ -51,6 +54,7 @@ Options:
|
|
|
51
54
|
--out Print decompiled source to stdout instead of files
|
|
52
55
|
--marshal Treat input as raw marshalled data (no .pyc header)
|
|
53
56
|
--marshal-scan Fast scan of marshal blobs (no decompile, prints version)
|
|
57
|
+
--strict Re-throw on first opcode handler exception (default: log + continue + non-zero exit)
|
|
54
58
|
--py-version <x.y> Python bytecode version hint (auto-scan if omitted)
|
|
55
59
|
--basedir <path> Output base directory (default: alongside input)
|
|
56
60
|
--file-ext <ext> Extension for generated source (default: py)
|
|
@@ -83,6 +87,8 @@ function parseCLIParams() {
|
|
|
83
87
|
} else if (cliParam.toLowerCase() == "--marshal-scan" || cliParam.toLowerCase() == "--marshal-smoke") {
|
|
84
88
|
g_cliArgs.marshalScan = true;
|
|
85
89
|
g_cliArgs.marshal = true;
|
|
90
|
+
} else if (cliParam.toLowerCase() == "--strict") {
|
|
91
|
+
g_cliArgs.strict = true;
|
|
86
92
|
} else if (cliParam.toLowerCase() == "--py-version") {
|
|
87
93
|
g_cliArgs.pyVersion = process.argv[++idx];
|
|
88
94
|
} else if (cliParam.toLowerCase() == "--basedir") {
|
|
@@ -341,9 +347,33 @@ function decompilePycObject(data) {
|
|
|
341
347
|
let genStartTS = process.hrtime.bigint();
|
|
342
348
|
let decompiler = new PycDecompiler(obj);
|
|
343
349
|
let ast = decompiler.decompile();
|
|
344
|
-
let
|
|
345
|
-
|
|
350
|
+
let renderError = null;
|
|
351
|
+
try {
|
|
352
|
+
let pycResult = ast.codeFragment();
|
|
353
|
+
pySrc = pycResult.toString();
|
|
354
|
+
} catch (ex) {
|
|
355
|
+
if (g_cliArgs.strict) throw ex;
|
|
356
|
+
renderError = ex;
|
|
357
|
+
decompiler.errors.push({
|
|
358
|
+
opcode: 'RENDER',
|
|
359
|
+
codeObject: obj?.Name?.toString?.() || '<root>',
|
|
360
|
+
message: ex.message,
|
|
361
|
+
stack: ex.stack
|
|
362
|
+
});
|
|
363
|
+
decompiler.cleanBuild = false;
|
|
364
|
+
pySrc = `# DECOMPILER ERROR: codeFragment() threw: ${ex.message}\n`;
|
|
365
|
+
if (!g_cliArgs.silent) {
|
|
366
|
+
console.error(`RENDER EXCEPTION in '${obj?.Name}': ${ex.message}`);
|
|
367
|
+
if (g_cliArgs.debug) console.error(ex.stack);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
346
370
|
genSecs = Number(process.hrtime.bigint() - genStartTS) / 1000000000;
|
|
371
|
+
if (!decompiler.cleanBuild) {
|
|
372
|
+
g_dirtyFiles.push({
|
|
373
|
+
file: typeof data === 'string' ? data : (obj?.FileName || '<buffer>'),
|
|
374
|
+
errors: decompiler.errors.length
|
|
375
|
+
});
|
|
376
|
+
}
|
|
347
377
|
}
|
|
348
378
|
if (!pySrc.endsWith("\n")) {
|
|
349
379
|
pySrc += "\n";
|
|
@@ -434,3 +464,14 @@ if (!g_cliArgs.sendToStdout) {
|
|
|
434
464
|
const outRate = (g_totalOutThroughput / g_totalExecTime).toFixed(2);
|
|
435
465
|
console.log(`Processed ${g_totalFiles} files in ${g_totalExecTime.toFixed(3)}s. In: ${g_totalInThroughput} bytes (${inRate} B/s). Out: ${g_totalOutThroughput} bytes (${outRate} B/s).`);
|
|
436
466
|
}
|
|
467
|
+
|
|
468
|
+
if (g_dirtyFiles.length > 0) {
|
|
469
|
+
console.error(`\nDirty decompile: ${g_dirtyFiles.length} file(s) had handler exceptions (output may be partial):`);
|
|
470
|
+
for (const d of g_dirtyFiles.slice(0, 20)) {
|
|
471
|
+
console.error(` - ${d.file} (${d.errors} opcode error${d.errors === 1 ? '' : 's'})`);
|
|
472
|
+
}
|
|
473
|
+
if (g_dirtyFiles.length > 20) {
|
|
474
|
+
console.error(` ... and ${g_dirtyFiles.length - 20} more`);
|
|
475
|
+
}
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
package/lib/OpCodes.js
CHANGED
|
@@ -329,6 +329,9 @@ class OpCodes
|
|
|
329
329
|
static LOAD_ZERO_SUPER_ATTR_A = 319; // Python 3.13+ zero-cost super attr
|
|
330
330
|
static LOAD_ZERO_SUPER_METHOD_A = 320; // Python 3.13+ zero-cost super method
|
|
331
331
|
|
|
332
|
+
// Python 3.15 new opcodes
|
|
333
|
+
static TRACE_RECORD_A = 321; // Python 3.15 -> trace recording (ignore)
|
|
334
|
+
|
|
332
335
|
|
|
333
336
|
// enum cmp_op
|
|
334
337
|
// {
|
|
@@ -391,7 +394,7 @@ class OpCodes
|
|
|
391
394
|
|
|
392
395
|
if (reader.versionCompare(3, 6) >= 0) {
|
|
393
396
|
while (opCodeID == OpCodes.EXTENDED_ARG_A) {
|
|
394
|
-
argument = argument | code[++opOffset]
|
|
397
|
+
argument = (argument << 8) | code[++opOffset];
|
|
395
398
|
opCodeID = this.GetOpCodeID(code, ++opOffset);
|
|
396
399
|
|
|
397
400
|
// Break if we hit end of bytecode
|
|
@@ -401,9 +404,15 @@ class OpCodes
|
|
|
401
404
|
}
|
|
402
405
|
argument <<= 8;
|
|
403
406
|
} else {
|
|
407
|
+
// Pre-3.6: EXTENDED_ARG carries a 16-bit operand that becomes
|
|
408
|
+
// the upper 16 bits of the next instruction's argument. After
|
|
409
|
+
// reading both operand bytes we must advance opOffset one more
|
|
410
|
+
// step so the caller lands on the real opcode (not the trailing
|
|
411
|
+
// operand byte of EXTENDED_ARG).
|
|
404
412
|
if (opCodeID == OpCodes.EXTENDED_ARG_A) {
|
|
405
413
|
argument = code[++opOffset] | code[++opOffset] << 8;
|
|
406
414
|
argument <<= 16;
|
|
415
|
+
opOffset++;
|
|
407
416
|
}
|
|
408
417
|
}
|
|
409
418
|
|
|
@@ -522,10 +531,18 @@ class OpCodes
|
|
|
522
531
|
opCode.Name = this.CodeObject.Names.Value[opCode.Argument].toString();
|
|
523
532
|
}
|
|
524
533
|
} else if (opCode.HasFree) {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
534
|
+
// 3.11+ stores cells/frees inside localsplus, so the opcode argument
|
|
535
|
+
// is an index into [locals | cells | frees]. Strip the locals prefix
|
|
536
|
+
// before looking up into the split CellVars/FreeVars tuples.
|
|
537
|
+
const isNewLayout = this.CodeObject.Reader?.versionCompare?.(3, 11) >= 0;
|
|
538
|
+
const localsLen = isNewLayout ? (this.CodeObject.VarNames?.Value?.length ?? 0) : 0;
|
|
539
|
+
const freeIdx = opCode.Argument - localsLen;
|
|
540
|
+
const cellLen = this.CodeObject.CellVars?.Value?.length ?? 0;
|
|
541
|
+
const freeLen = this.CodeObject.FreeVars?.Value?.length ?? 0;
|
|
542
|
+
if (freeIdx >= 0 && freeIdx < cellLen) {
|
|
543
|
+
opCode.FreeName = this.CodeObject.CellVars.Value[freeIdx].toString();
|
|
544
|
+
} else if (freeIdx >= cellLen && (freeIdx - cellLen) < freeLen) {
|
|
545
|
+
opCode.FreeName = this.CodeObject.FreeVars.Value[freeIdx - cellLen].toString();
|
|
529
546
|
} else {
|
|
530
547
|
opCode.FreeName = `##FREEVAR_${opCode.Argument}##`;
|
|
531
548
|
}
|