depyo 1.2.2 → 1.2.4

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.
@@ -26,10 +26,32 @@ const INTRINSIC_2 = Object.freeze({
26
26
  });
27
27
 
28
28
  function handleKwNamesA() {
29
- let astNode = new AST.ASTKwNamesMap();
30
- let keys = this.code.Current.ConstantObject;
31
- for (let idx = keys.length - 1; idx >= 0; idx--) {
32
- astNode.add(keys[idx], this.dataStack.pop());
29
+ // CPython 3.11/3.12: KW_NAMES(consti) stores a reference to co_consts[consti],
30
+ // which is guaranteed to be a tuple of strings. The following PRECALL/CALL pair
31
+ // pops that many trailing positional slots and rebinds them as kwargs.
32
+ //
33
+ // ConstantObject here is a PythonObject (Py_Tuple or Py_SmallTuple) — NOT a
34
+ // raw JS array — and each element is a PythonObject (Py_Unicode/Py_String).
35
+ // The original code did `keys.length - 1` (works via PythonObject.length) but
36
+ // then `keys[idx]` returned undefined because indexed access does not reach
37
+ // into `.Value`. The result was an ASTKwNamesMap with `undefined` keys and a
38
+ // shifted stack that mangled the next CALL into `##ERROR##(func, =val)`.
39
+ // GitHub depyo.js issue #11.
40
+ const constObj = this.code.Current.ConstantObject;
41
+ let keyObjs = [];
42
+ if (constObj && (constObj.ClassName === 'Py_Tuple' || constObj.ClassName === 'Py_SmallTuple') && Array.isArray(constObj.Value)) {
43
+ keyObjs = constObj.Value;
44
+ }
45
+
46
+ const toKwName = (v) => {
47
+ const raw = v?.Value ?? v?.name ?? v;
48
+ const name = typeof raw === 'string' ? raw : String(raw);
49
+ return new AST.ASTName(name.replace(/^['"]|['"]$/g, ''));
50
+ };
51
+
52
+ const astNode = new AST.ASTKwNamesMap();
53
+ for (let idx = keyObjs.length - 1; idx >= 0; idx--) {
54
+ astNode.add(toKwName(keyObjs[idx]), this.dataStack.pop());
33
55
  }
34
56
 
35
57
  astNode.line = this.code.Current.LineNo;
@@ -177,9 +199,16 @@ function handleInstrumentedCallA() {
177
199
  let kwparams_map = this.dataStack.top();
178
200
  if (kwparams_map instanceof AST.ASTKwNamesMap) {
179
201
  this.dataStack.pop();
202
+ // CPython 3.11/3.12: KW_NAMES sets the trailing-keyword count for the
203
+ // next CALL. CALL's oparg is the total positional+keyword count packed
204
+ // into the low byte (`pparams` here); the high byte (`kwparams`) is
205
+ // always zero for these versions. Each keyword consumed by KW_NAMES
206
+ // already popped its value from the stack, so the CALL must pop only
207
+ // (total - kwcount) positional slots — i.e. decrement `pparams`, not
208
+ // `kwparams`. depyo.js issue #11.
180
209
  for (let kwParam of kwparams_map.values) {
181
210
  kwparamList.unshift(kwParam);
182
- kwparams--;
211
+ pparams--;
183
212
  }
184
213
  }
185
214
  }
@@ -1116,17 +1116,14 @@ function handleReturnValue() {
1116
1116
 
1117
1117
  if (!this.currentCase && [AST.ASTBlock.BlockType.If, AST.ASTBlock.BlockType.Else].includes(this.curBlock.blockType)
1118
1118
  && (this.object.Reader.versionCompare(2, 6) >= 0)) {
1119
- let prev = this.curBlock;
1119
+ // issue #3: do NOT fold `if cond: return x` into `return cond and x`.
1120
+ // The closing jump for a real if-return is POP_JUMP_IF_FALSE (non-sticky)
1121
+ // and the rewrite drops the `x` return path when x is falsy (e.g. 0, None, "").
1122
+ // Legitimate short-circuit `cond and x` is already handled via JUMP_IF_FALSE_OR_POP
1123
+ // in control_flow_jumps, so keeping the if-block here is always correct.
1124
+ const prev = this.curBlock;
1120
1125
  this.blocks.pop();
1121
1126
  this.curBlock = this.blocks.top();
1122
- if (
1123
- prev instanceof AST.ASTCondBlock &&
1124
- prev.nodes.length == 1 &&
1125
- prev.line == value.line
1126
- ) {
1127
- prev = new AST.ASTReturn(new AST.ASTBinary(prev.condition, value, prev.negative ? AST.ASTBinary.BinOp.LogicalOr : AST.ASTBinary.BinOp.LogicalAnd));
1128
- }
1129
-
1130
1127
  this.curBlock.append(prev);
1131
1128
 
1132
1129
  if ([this.OpCodes.JUMP_ABSOLUTE_A, this.OpCodes.JUMP_FORWARD_A].includes(this.code.Next?.OpCodeID)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depyo",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Python bytecode decompiler (Python 1.0–3.15) implemented in Node.js",
5
5
  "bin": {
6
6
  "depyo": "./depyo.js"