depyo 1.0.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/LICENSE +21 -0
- package/README.md +97 -0
- package/depyo.js +213 -0
- package/lib/BinaryReader.js +153 -0
- package/lib/OpCode.js +90 -0
- package/lib/OpCodes.js +940 -0
- package/lib/PycDecompiler.js +2031 -0
- package/lib/PycDisassembler.js +55 -0
- package/lib/PycReader.js +905 -0
- package/lib/PycResult.js +82 -0
- package/lib/PythonObject.js +242 -0
- package/lib/Unpickle.js +173 -0
- package/lib/ast/ast_node.js +3442 -0
- package/lib/bytecode/python_1_0.js +116 -0
- package/lib/bytecode/python_1_1.js +116 -0
- package/lib/bytecode/python_1_3.js +119 -0
- package/lib/bytecode/python_1_4.js +121 -0
- package/lib/bytecode/python_1_5.js +120 -0
- package/lib/bytecode/python_1_6.js +124 -0
- package/lib/bytecode/python_2_0.js +137 -0
- package/lib/bytecode/python_2_1.js +142 -0
- package/lib/bytecode/python_2_2.js +147 -0
- package/lib/bytecode/python_2_3.js +145 -0
- package/lib/bytecode/python_2_4.js +147 -0
- package/lib/bytecode/python_2_5.js +147 -0
- package/lib/bytecode/python_2_6.js +147 -0
- package/lib/bytecode/python_2_7.js +151 -0
- package/lib/bytecode/python_3_0.js +132 -0
- package/lib/bytecode/python_3_1.js +135 -0
- package/lib/bytecode/python_3_10.js +312 -0
- package/lib/bytecode/python_3_11.js +284 -0
- package/lib/bytecode/python_3_12.js +327 -0
- package/lib/bytecode/python_3_13.js +173 -0
- package/lib/bytecode/python_3_14.js +177 -0
- package/lib/bytecode/python_3_2.js +136 -0
- package/lib/bytecode/python_3_3.js +136 -0
- package/lib/bytecode/python_3_4.js +137 -0
- package/lib/bytecode/python_3_5.js +149 -0
- package/lib/bytecode/python_3_6.js +153 -0
- package/lib/bytecode/python_3_7.js +292 -0
- package/lib/bytecode/python_3_8.js +294 -0
- package/lib/bytecode/python_3_9.js +296 -0
- package/lib/code_reader.js +146 -0
- package/lib/handlers/binary_ops.js +174 -0
- package/lib/handlers/collections_update.js +239 -0
- package/lib/handlers/comparisons.js +95 -0
- package/lib/handlers/context_managers.js +250 -0
- package/lib/handlers/control_flow_jumps.js +954 -0
- package/lib/handlers/exceptions_blocks.js +952 -0
- package/lib/handlers/formatting.js +31 -0
- package/lib/handlers/function_calls.js +496 -0
- package/lib/handlers/function_class_build.js +330 -0
- package/lib/handlers/generators_async.js +172 -0
- package/lib/handlers/imports.js +53 -0
- package/lib/handlers/load_store_names.js +711 -0
- package/lib/handlers/loop_iterator.js +318 -0
- package/lib/handlers/misc_other.js +1201 -0
- package/lib/handlers/pattern_matching.js +226 -0
- package/lib/handlers/stack_ops.js +280 -0
- package/lib/handlers/subscript_slice.js +394 -0
- package/lib/handlers/unary_ops.js +91 -0
- package/lib/handlers/unpack.js +141 -0
- package/lib/stack_history.js +63 -0
- package/lib/zip_reader.js +217 -0
- package/package.json +35 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
const AST = require('../ast/ast_node');
|
|
2
|
+
const { flushCurrentCaseBody } = require('./misc_other');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pattern matching opcodes for match/case statements (Python 3.10+)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
function ensureMatchInitialized(subjectCandidate, options = {}) {
|
|
9
|
+
const skipStackLookup = options.skipStackLookup || false;
|
|
10
|
+
if (this.currentMatch) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Prefer subject from stack (TOS-1), fallback to provided candidate or saved potential
|
|
15
|
+
const stackLen = this.dataStack ? this.dataStack.length : 0;
|
|
16
|
+
let tosMinusOne = (!skipStackLookup && stackLen >= 2) ? this.dataStack[stackLen - 2] : null;
|
|
17
|
+
this.matchSubject = subjectCandidate || tosMinusOne;
|
|
18
|
+
|
|
19
|
+
if (!this.matchSubject && !skipStackLookup && this.dataStack && this.dataStack.length > 0) {
|
|
20
|
+
this.matchSubject = this.dataStack.top();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!this.matchSubject && this.potentialMatchSubject) {
|
|
24
|
+
this.matchSubject = this.potentialMatchSubject;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!this.matchSubject) {
|
|
28
|
+
if (global.g_cliArgs?.debug) {
|
|
29
|
+
console.log(`[MATCH] Unable to initialize match at offset ${this.code.Current.Offset}: no subject available (stackLen=${this.dataStack?.length || 0})`);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.currentMatch = new AST.ASTMatch(this.matchSubject);
|
|
35
|
+
this.currentMatch.line = this.code.Current.LineNo;
|
|
36
|
+
this.matchParentBlock = this.curBlock;
|
|
37
|
+
this.inMatchPattern = true;
|
|
38
|
+
this.patternOps = [];
|
|
39
|
+
if (!this.matchPreNodesStart && this.matchParentBlock) {
|
|
40
|
+
this.matchPreNodesStart = this.matchParentBlock.nodes.length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (global.g_cliArgs?.debug) {
|
|
44
|
+
console.log(`[MATCH] Initialized new ASTMatch at offset ${this.code.Current.Offset} subject=${this.matchSubject?.constructor?.name}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleMatchSequence() {
|
|
49
|
+
// MATCH_SEQUENCE opcode (Python 3.10+)
|
|
50
|
+
// Tests if TOS is a sequence (tuple, list, etc.) but not str/bytes/bytearray
|
|
51
|
+
// Stack: TOS = value (copy from COPY 1), TOS1 = subject
|
|
52
|
+
// IMPORTANT: TOS is NOT popped! Result is pushed on top.
|
|
53
|
+
|
|
54
|
+
// If we have an open case and starting a new pattern, flush the previous case first
|
|
55
|
+
if (this.currentMatch && this.currentCase && !this.inMatchPattern) {
|
|
56
|
+
if (global.g_cliArgs?.debug) {
|
|
57
|
+
console.log(`[MATCH_SEQUENCE] Flushing previous case before new pattern at offset ${this.code.Current.Offset}`);
|
|
58
|
+
}
|
|
59
|
+
flushCurrentCaseBody.call(this);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!this.inMatchPattern && this.currentMatch) {
|
|
63
|
+
this.inMatchPattern = true;
|
|
64
|
+
this.patternOps = [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let value = this.dataStack.top(); // Peek at the copy (don't pop!)
|
|
68
|
+
|
|
69
|
+
if (global.g_cliArgs?.debug) {
|
|
70
|
+
console.log(`[MATCH_SEQUENCE] at offset ${this.code.Current.Offset}`);
|
|
71
|
+
console.log(` currentMatch=${this.currentMatch ? 'active' : 'null'}`);
|
|
72
|
+
console.log(` Stack depth=${this.dataStack.length}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const subjectCandidate = this.dataStack && this.dataStack.length >= 2 ? this.dataStack[this.dataStack.length - 2] : null;
|
|
76
|
+
ensureMatchInitialized.call(this, subjectCandidate);
|
|
77
|
+
|
|
78
|
+
// Mark that we're in pattern checking phase
|
|
79
|
+
this.inMatchPattern = true;
|
|
80
|
+
this.patternOps = []; // Clear pattern operations for new case
|
|
81
|
+
|
|
82
|
+
// Record pattern operation
|
|
83
|
+
this.patternOps.push({type: 'MATCH_SEQUENCE'});
|
|
84
|
+
|
|
85
|
+
// Push True to indicate sequence check passed
|
|
86
|
+
// (In reality, should check type, but for decompilation we assume it passes)
|
|
87
|
+
let node = new AST.ASTObject(new (require('../PythonObject').PythonObject)("Py_Bool", true));
|
|
88
|
+
node.line = this.code.Current.LineNo;
|
|
89
|
+
this.dataStack.push(node);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function handleGetLen() {
|
|
93
|
+
// GET_LEN opcode (Python 3.10+)
|
|
94
|
+
// Pushes len(TOS) onto stack (TOS is not popped)
|
|
95
|
+
// Used in sequence pattern matching to check length
|
|
96
|
+
|
|
97
|
+
let value = this.dataStack.top();
|
|
98
|
+
|
|
99
|
+
// Record pattern operation if in pattern matching
|
|
100
|
+
if (this.inMatchPattern) {
|
|
101
|
+
this.patternOps.push({type: 'GET_LEN'});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Create len(value) call node
|
|
105
|
+
// This will be compared with expected length in subsequent COMPARE_OP
|
|
106
|
+
let lenNode = new AST.ASTCall(
|
|
107
|
+
new AST.ASTName('len'),
|
|
108
|
+
[value],
|
|
109
|
+
[]
|
|
110
|
+
);
|
|
111
|
+
lenNode.line = this.code.Current.LineNo;
|
|
112
|
+
this.dataStack.push(lenNode);
|
|
113
|
+
|
|
114
|
+
if (global.g_cliArgs?.debug) {
|
|
115
|
+
console.log(`[GET_LEN] at offset ${this.code.Current.Offset}, value=${value?.constructor.name}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleMatchMapping() {
|
|
120
|
+
// MATCH_MAPPING opcode (Python 3.10+)
|
|
121
|
+
// Similar to MATCH_SEQUENCE but for mappings (dicts)
|
|
122
|
+
|
|
123
|
+
ensureMatchInitialized.call(this, null, {skipStackLookup: true});
|
|
124
|
+
|
|
125
|
+
let value = this.dataStack.pop();
|
|
126
|
+
if (this.inMatchPattern) {
|
|
127
|
+
this.patternOps.push({type: 'MATCH_MAPPING'});
|
|
128
|
+
}
|
|
129
|
+
let node = new AST.ASTObject(new (require('../PythonObject').PythonObject)("Py_Bool", true));
|
|
130
|
+
node.line = this.code.Current.LineNo;
|
|
131
|
+
this.dataStack.push(node);
|
|
132
|
+
|
|
133
|
+
if (global.g_cliArgs?.debug) {
|
|
134
|
+
console.log(`[MATCH_MAPPING] Stub implementation at offset ${this.code.Current.Offset}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handleMatchClassA() {
|
|
139
|
+
// MATCH_CLASS_A opcode (Python 3.10+)
|
|
140
|
+
// Matches object against a class pattern
|
|
141
|
+
|
|
142
|
+
let count = this.code.Current.Argument;
|
|
143
|
+
|
|
144
|
+
ensureMatchInitialized.call(this, null, {skipStackLookup: true});
|
|
145
|
+
|
|
146
|
+
if (!this.inMatchPattern && this.currentMatch) {
|
|
147
|
+
this.inMatchPattern = true;
|
|
148
|
+
this.patternOps = [];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let attrNames = [];
|
|
152
|
+
const prevInstr = this.code.Prev;
|
|
153
|
+
if (prevInstr?.ConstantObject?.ClassName == "Py_Tuple" && prevInstr.ConstantObject.Value) {
|
|
154
|
+
attrNames = prevInstr.ConstantObject.Value.map(obj => obj?.toString());
|
|
155
|
+
}
|
|
156
|
+
let classExpr = this.dataStack?.[this.dataStack.length - 2] || null;
|
|
157
|
+
|
|
158
|
+
// Pop class object and subject
|
|
159
|
+
for (let i = 0; i < count + 1; i++) {
|
|
160
|
+
this.dataStack.pop();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (this.inMatchPattern) {
|
|
164
|
+
this.patternOps.push({type: 'MATCH_CLASS', count, attrNames, classExpr});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Push success placeholder
|
|
168
|
+
let node = new AST.ASTObject(new (require('../PythonObject').PythonObject)("Py_Bool", true));
|
|
169
|
+
node.line = this.code.Current.LineNo;
|
|
170
|
+
this.dataStack.push(node);
|
|
171
|
+
|
|
172
|
+
if (global.g_cliArgs?.debug) {
|
|
173
|
+
console.log(`[MATCH_CLASS] Stub implementation at offset ${this.code.Current.Offset}, count=${count}, inMatch=${this.inMatchPattern}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function handleMatchKeys() {
|
|
178
|
+
// MATCH_KEYS opcode (Python 3.10+)
|
|
179
|
+
// Used in mapping patterns
|
|
180
|
+
|
|
181
|
+
ensureMatchInitialized.call(this, null, {skipStackLookup: true});
|
|
182
|
+
|
|
183
|
+
const keysNode = this.dataStack.pop(); // keys tuple
|
|
184
|
+
let subject = this.dataStack.top();
|
|
185
|
+
|
|
186
|
+
const keys = extractMatchKeys(keysNode);
|
|
187
|
+
if (this.inMatchPattern) {
|
|
188
|
+
this.patternOps.push({type: 'MATCH_KEYS', keys});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Push (values_tuple, True) or None
|
|
192
|
+
let node = new AST.ASTTuple([]);
|
|
193
|
+
node.line = this.code.Current.LineNo;
|
|
194
|
+
this.dataStack.push(node);
|
|
195
|
+
|
|
196
|
+
if (global.g_cliArgs?.debug) {
|
|
197
|
+
console.log(`[MATCH_KEYS] Stub implementation at offset ${this.code.Current.Offset}`);
|
|
198
|
+
if (keys?.length) {
|
|
199
|
+
console.log(` keys=${keys.map(k => k.codeFragment().toString()).join(', ')}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function extractMatchKeys(keysNode) {
|
|
205
|
+
if (!keysNode) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
if (keysNode instanceof AST.ASTTuple || keysNode instanceof AST.ASTList) {
|
|
209
|
+
return keysNode.values || [];
|
|
210
|
+
}
|
|
211
|
+
if (keysNode instanceof AST.ASTObject) {
|
|
212
|
+
const obj = keysNode.object;
|
|
213
|
+
if (["Py_Tuple", "Py_SmallTuple", "Py_List"].includes(obj?.ClassName)) {
|
|
214
|
+
return (obj.Value || []).map(value => new AST.ASTObject(value));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
handleMatchSequence,
|
|
222
|
+
handleGetLen,
|
|
223
|
+
handleMatchMapping,
|
|
224
|
+
handleMatchClassA,
|
|
225
|
+
handleMatchKeys
|
|
226
|
+
};
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
const AST = require('../ast/ast_node');
|
|
2
|
+
|
|
3
|
+
function handleDupTop() {
|
|
4
|
+
// Track COPY pattern for match detection
|
|
5
|
+
let prevOpCode = this.code.Prev?.OpCodeID;
|
|
6
|
+
let prevOffset = this.code.Prev?.Offset;
|
|
7
|
+
|
|
8
|
+
// Check if this is COPY after LOAD (potential match start)
|
|
9
|
+
if (prevOpCode == this.OpCodes.LOAD_FAST_A ||
|
|
10
|
+
prevOpCode == this.OpCodes.LOAD_NAME_A ||
|
|
11
|
+
prevOpCode == this.OpCodes.LOAD_GLOBAL_A) {
|
|
12
|
+
|
|
13
|
+
// Save subject for later use
|
|
14
|
+
this.potentialMatchSubject = this.dataStack.top();
|
|
15
|
+
|
|
16
|
+
// First COPY after LOAD → potential match candidate
|
|
17
|
+
if (this.matchCandidateStart === -1) {
|
|
18
|
+
this.matchCandidateStart = this.code.Current.Offset;
|
|
19
|
+
this.lastLoadOffset = prevOffset;
|
|
20
|
+
this.matchParentBlock = this.curBlock;
|
|
21
|
+
if (!this.matchPreNodesStart) {
|
|
22
|
+
this.matchPreNodesStart = this.matchParentBlock.nodes.length;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (global.g_cliArgs?.debug) {
|
|
26
|
+
console.log(`[DUP_TOP] Match candidate at offset ${this.matchCandidateStart}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Look ahead to confirm match pattern immediately
|
|
30
|
+
if (!this.currentMatch && this.lookAheadForMatchPattern()) {
|
|
31
|
+
this.currentMatch = new AST.ASTMatch(this.potentialMatchSubject);
|
|
32
|
+
this.currentMatch.line = this.code.Current.LineNo;
|
|
33
|
+
this.matchParentBlock = this.curBlock;
|
|
34
|
+
this.inMatchPattern = true; // Start tracking pattern operations
|
|
35
|
+
this.patternOps = []; // Clear for first case
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Second COPY without intermediate LOAD → confirm match or start new case!
|
|
40
|
+
else if (this.matchCandidateStart > 0 &&
|
|
41
|
+
prevOffset > this.lastLoadOffset &&
|
|
42
|
+
prevOpCode == this.OpCodes.POP_TOP) {
|
|
43
|
+
|
|
44
|
+
// This COPY has no LOAD before it → reusing same subject
|
|
45
|
+
// Match patterns only exist in Python 3.10+
|
|
46
|
+
if (!this.currentMatch && this.potentialMatchSubject &&
|
|
47
|
+
this.object.Reader.versionCompare(3, 10) >= 0) {
|
|
48
|
+
// First time - create match (fallback if look-ahead didn't work)
|
|
49
|
+
this.currentMatch = new AST.ASTMatch(this.potentialMatchSubject);
|
|
50
|
+
this.currentMatch.line = this.code.Current.LineNo;
|
|
51
|
+
this.matchParentBlock = this.curBlock;
|
|
52
|
+
this.inMatchPattern = true;
|
|
53
|
+
this.patternOps = [];
|
|
54
|
+
if (!this.matchPreNodesStart) {
|
|
55
|
+
this.matchPreNodesStart = this.matchParentBlock.nodes.length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.debug(`[DUP_TOP] MATCH CONFIRMED at offset ${this.code.Current.Offset}! Subject: ${this.potentialMatchSubject?.constructor.name}`);
|
|
59
|
+
} else if (this.currentMatch) {
|
|
60
|
+
// Subsequent case in same match - reset pattern tracking
|
|
61
|
+
this.debug(`[DUP_TOP] Starting new case at offset ${this.code.Current.Offset}`);
|
|
62
|
+
this.inMatchPattern = true;
|
|
63
|
+
this.patternOps = [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.dataStack.top() == null) {
|
|
68
|
+
this.dataStack.push(null);
|
|
69
|
+
} else if (this.code.Next?.OpCodeID == this.OpCodes.ROT_THREE) {
|
|
70
|
+
// double compare case
|
|
71
|
+
this.skipNextJump = true;
|
|
72
|
+
this.code.GoNext();
|
|
73
|
+
} else if (this.dataStack.top() instanceof AST.ASTChainStore) {
|
|
74
|
+
let chainstore = this.dataStack.pop();
|
|
75
|
+
this.dataStack.push(this.dataStack.top());
|
|
76
|
+
this.dataStack.push(chainstore);
|
|
77
|
+
} else if (this.code.Next?.OpCodeID == this.OpCodes.LOAD_ATTR_A) {
|
|
78
|
+
// Augmented assign on attribute (a.value += 1)
|
|
79
|
+
// Don't create ChainStore - just duplicate the value
|
|
80
|
+
this.dataStack.push(this.dataStack.top());
|
|
81
|
+
} else {
|
|
82
|
+
// Check if this is a walrus operator pattern:
|
|
83
|
+
// DUP_TOP → STORE_* → <not another STORE>
|
|
84
|
+
let nextOpCode = this.code.Next?.OpCodeID;
|
|
85
|
+
let isStoreOp = (nextOpCode == this.OpCodes.STORE_NAME_A ||
|
|
86
|
+
nextOpCode == this.OpCodes.STORE_FAST_A ||
|
|
87
|
+
nextOpCode == this.OpCodes.STORE_GLOBAL_A);
|
|
88
|
+
|
|
89
|
+
if (isStoreOp && this.code.Next?.Next) {
|
|
90
|
+
let afterStoreOpCode = this.code.Next.Next.OpCodeID;
|
|
91
|
+
let isAnotherStore = (afterStoreOpCode == this.OpCodes.STORE_NAME_A ||
|
|
92
|
+
afterStoreOpCode == this.OpCodes.STORE_FAST_A ||
|
|
93
|
+
afterStoreOpCode == this.OpCodes.STORE_GLOBAL_A ||
|
|
94
|
+
afterStoreOpCode == this.OpCodes.STORE_ATTR_A ||
|
|
95
|
+
afterStoreOpCode == this.OpCodes.STORE_DEREF_A);
|
|
96
|
+
|
|
97
|
+
if (!isAnotherStore) {
|
|
98
|
+
// This is a walrus operator (named expression)
|
|
99
|
+
// Don't duplicate - just set flag. The value will be wrapped in ASTNamedExpr.
|
|
100
|
+
this.isWalrusOperator = true;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// If next opcode is NOT a store operation, this is just a temporary copy
|
|
106
|
+
// (e.g., for match/case pattern matching) - don't create ChainStore
|
|
107
|
+
if (!isStoreOp) {
|
|
108
|
+
this.dataStack.push(this.dataStack.top());
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Regular chained assignment (a = b = 10)
|
|
113
|
+
this.dataStack.push(this.dataStack.top());
|
|
114
|
+
let node = new AST.ASTChainStore ([], this.dataStack.top());
|
|
115
|
+
this.dataStack.push(node);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function handleCopyA() {
|
|
120
|
+
// COPY opcode (Python 3.11+): copies the i-th item to the top of the stack
|
|
121
|
+
// Argument specifies which item: 1 = top, 2 = second, etc.
|
|
122
|
+
let depth = this.code.Current.Argument;
|
|
123
|
+
|
|
124
|
+
// Save potential match subject for COPY 1 (like DUP_TOP)
|
|
125
|
+
if (depth == 1) {
|
|
126
|
+
let prevOpCode = this.code.Prev?.OpCodeID;
|
|
127
|
+
if (prevOpCode == this.OpCodes.LOAD_FAST_A ||
|
|
128
|
+
prevOpCode == this.OpCodes.LOAD_NAME_A ||
|
|
129
|
+
prevOpCode == this.OpCodes.LOAD_GLOBAL_A) {
|
|
130
|
+
this.potentialMatchSubject = this.dataStack.top();
|
|
131
|
+
if (global.g_cliArgs?.debug) {
|
|
132
|
+
console.log(`[COPY] Saved potential match subject: ${this.potentialMatchSubject?.constructor.name}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// COPY 1 is equivalent to DUP_TOP - same walrus detection logic
|
|
137
|
+
handleDupTop.call(this);
|
|
138
|
+
} else {
|
|
139
|
+
// For other depths, just copy the item
|
|
140
|
+
let index = this.dataStack.length - depth;
|
|
141
|
+
if (index >= 0 && index < this.dataStack.length) {
|
|
142
|
+
this.dataStack.push(this.dataStack[index]);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function handleDupTopTwo() {
|
|
148
|
+
let first = this.dataStack.pop();
|
|
149
|
+
let second = this.dataStack.top();
|
|
150
|
+
|
|
151
|
+
this.dataStack.push(first);
|
|
152
|
+
this.dataStack.push(second);
|
|
153
|
+
this.dataStack.push(first);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function handleDupTopxA() {
|
|
157
|
+
let first = [];
|
|
158
|
+
let second = [];
|
|
159
|
+
|
|
160
|
+
for (let idx = 0; idx < this.code.Current.Argument; idx++) {
|
|
161
|
+
let node = this.dataStack.pop();
|
|
162
|
+
first.push(node);
|
|
163
|
+
second.push(node);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
while (first.length) {
|
|
167
|
+
this.dataStack.push(first.pop());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
while (second.length) {
|
|
171
|
+
this.dataStack.push(second.pop());
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function handleSwapA() {
|
|
176
|
+
// Python 3.11+ SWAP opcode: swaps i-th item with TOS
|
|
177
|
+
// Argument specifies depth: 2 = swap top 2, 3 = swap TOS with 3rd, etc.
|
|
178
|
+
let depth = this.code.Current.Argument;
|
|
179
|
+
|
|
180
|
+
// Record pattern operation if in pattern matching
|
|
181
|
+
if (this.inMatchPattern) {
|
|
182
|
+
this.patternOps.push({
|
|
183
|
+
type: 'SWAP',
|
|
184
|
+
depth: depth
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (depth == 2) {
|
|
189
|
+
// SWAP 2: swap top two items
|
|
190
|
+
handleRotTwo.call(this);
|
|
191
|
+
} else if (depth == 3) {
|
|
192
|
+
// SWAP 3: swap TOS with third item
|
|
193
|
+
let one = this.dataStack.pop();
|
|
194
|
+
let two = this.dataStack.pop();
|
|
195
|
+
let three = this.dataStack.pop();
|
|
196
|
+
|
|
197
|
+
this.dataStack.push(one);
|
|
198
|
+
this.dataStack.push(two);
|
|
199
|
+
this.dataStack.push(three);
|
|
200
|
+
} else {
|
|
201
|
+
// General case: swap TOS with i-th item
|
|
202
|
+
let tos = this.dataStack.pop();
|
|
203
|
+
let index = this.dataStack.length - (depth - 1);
|
|
204
|
+
if (index >= 0) {
|
|
205
|
+
let temp = this.dataStack[index];
|
|
206
|
+
this.dataStack[index] = tos;
|
|
207
|
+
this.dataStack.push(temp);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function handleRotTwo() {
|
|
213
|
+
let one = this.dataStack.pop();
|
|
214
|
+
if (this.dataStack.top() instanceof AST.ASTChainStore) {
|
|
215
|
+
this.dataStack.pop();
|
|
216
|
+
}
|
|
217
|
+
let two = this.dataStack.pop();
|
|
218
|
+
|
|
219
|
+
this.dataStack.push(one);
|
|
220
|
+
this.dataStack.push(two);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function handleRotThree() {
|
|
224
|
+
let one = this.dataStack.pop();
|
|
225
|
+
let two = this.dataStack.pop();
|
|
226
|
+
if (this.dataStack.top() instanceof AST.ASTChainStore) {
|
|
227
|
+
this.dataStack.pop();
|
|
228
|
+
}
|
|
229
|
+
let three = this.dataStack.pop();
|
|
230
|
+
this.dataStack.push(one);
|
|
231
|
+
this.dataStack.push(three);
|
|
232
|
+
this.dataStack.push(two);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function handleRotFour() {
|
|
236
|
+
let one = this.dataStack.pop();
|
|
237
|
+
let two = this.dataStack.pop();
|
|
238
|
+
let three = this.dataStack.pop();
|
|
239
|
+
if (this.dataStack.top() instanceof AST.ASTChainStore) {
|
|
240
|
+
this.dataStack.pop();
|
|
241
|
+
}
|
|
242
|
+
let four = this.dataStack.pop();
|
|
243
|
+
this.dataStack.push(one);
|
|
244
|
+
this.dataStack.push(four);
|
|
245
|
+
this.dataStack.push(three);
|
|
246
|
+
this.dataStack.push(two);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function handlePushNull() {
|
|
250
|
+
this.dataStack.push(null);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function handleCache() {
|
|
254
|
+
/* These "fake" opcodes are used as placeholders for optimizing
|
|
255
|
+
certain opcodes in Python 3.11+. Since we have no need for
|
|
256
|
+
that during disassembly/decompilation, we can just treat these
|
|
257
|
+
as no-ops. */
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function handleNop() {}
|
|
261
|
+
|
|
262
|
+
function handleStopCode() {
|
|
263
|
+
// Early Python STOP_CODE opcode: terminate processing of instruction stream.
|
|
264
|
+
this.code.CurrentInstructionIndex = this.code.Instructions.length;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = {
|
|
268
|
+
handleDupTop,
|
|
269
|
+
handleCopyA,
|
|
270
|
+
handleDupTopTwo,
|
|
271
|
+
handleDupTopxA,
|
|
272
|
+
handleSwapA,
|
|
273
|
+
handleRotTwo,
|
|
274
|
+
handleRotThree,
|
|
275
|
+
handleRotFour,
|
|
276
|
+
handlePushNull,
|
|
277
|
+
handleCache,
|
|
278
|
+
handleNop,
|
|
279
|
+
handleStopCode
|
|
280
|
+
};
|