23 Commits

Author SHA1 Message Date
Michael Hansen
a05ddec0d8 Merge pull request #564 from whoami730/exceptions
Parse and display exception table
2025-08-30 10:02:14 -07:00
Sahil Jain
d8c6fdf711 Address comments 2025-08-30 20:01:32 +05:30
Michael Hansen
577720302e Add basic protection aginst circular references in pycdas and pycdc.
This fixes the last case of fuzzer errors detected by #572.
2025-08-28 16:42:03 -07:00
Michael Hansen
38799f5cfb Also check EOF in getBuffer() 2025-08-28 15:58:28 -07:00
Michael Hansen
0e7be40367 Add some extra guards against null dereference and empty std::stack pops
Fixes segfault cases of #572
2025-08-28 15:36:35 -07:00
Michael Hansen
ff0c1450b4 Abort immediately when attempting to read past end of stream.
No consumers of readByte() were actually checking for EOF, so they would
all keep re-reading the same byte over and over again, potentially until the
process runs out of memory (ref #572).
2025-08-28 15:14:55 -07:00
Sahil Jain
e8e10f1419 Parse exception table 2025-07-15 22:47:02 +05:30
Michael Hansen
a267bfb47f Merge pull request #561 from whoami730/bug-fix
Fix bug in RAISE_VARARGS
2025-07-04 12:41:02 -07:00
Sahil Jain
8b0ea9450e Fix RAISE_VARARGS bug 2025-07-04 19:16:04 +05:30
Michael Hansen
7d2039d24e Merge pull request #557 from whoami730/new-opcodes
Support py 3.12 opcodes: part 2
2025-07-02 07:58:23 -07:00
Michael Hansen
e64ea4bdec Merge branch 'master' into new-opcodes 2025-07-02 07:56:28 -07:00
Michael Hansen
4badfa6321 Merge pull request #555 from whoami730/master
Support py 3.12 opcodes: part 1
2025-07-02 07:51:36 -07:00
Sahil Jain
6e0089e01c Update 2025-07-01 22:52:59 +05:30
Sahil Jain
3afcfbc6a7 Fix compilation error 2025-07-01 14:01:05 +05:30
Sahil Jain
aa292c7682 Add newlines 2025-07-01 09:27:07 +05:30
Sahil Jain
5fe61462a2 Support COPY opcde 2025-07-01 09:25:49 +05:30
Sahil Jain
ad5f39db56 Support SLICE opcodes 2025-07-01 09:24:53 +05:30
Sahil Jain
97ec04789d Add JUMP_BACKWARD + CACHE comments 2025-07-01 09:22:57 +05:30
Sahil Jain
6dae4e801f Remove COPY opcode 2025-07-01 09:22:57 +05:30
Sahil Jain
040732920b Add comment 2025-07-01 09:22:57 +05:30
Sahil Jain
a93fd14672 Add new loop tests 2025-07-01 09:22:57 +05:30
Sahil Jain
a4a6a24f3e Support END_FOR opcode 2025-07-01 09:22:56 +05:30
Michael Hansen
307e0b8fd7 Update to upload-artifact/v4 to fix CI 2025-06-30 19:21:38 -07:00
23 changed files with 404 additions and 35 deletions

View File

@@ -23,7 +23,7 @@ jobs:
cmake --build build --config Release --target check
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: pycdc-release
path: build\Release\*.exe

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@
/.kdev4
__pycache__
tests-out
build/
.vscode/

View File

@@ -1,6 +1,7 @@
#include <cstring>
#include <cstdint>
#include <stdexcept>
#include <unordered_set>
#include "ASTree.h"
#include "FastStack.h"
#include "pyc_numeric.h"
@@ -897,7 +898,10 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
case Pyc::INSTRUMENTED_FOR_ITER_A:
{
PycRef<ASTNode> iter = stack.top(); // Iterable
stack.pop();
if (mod->verCompare(3, 12) < 0) {
// Do not pop the iterator for py 3.12+
stack.pop();
}
/* Pop it? Don't pop it? */
int end;
@@ -1163,6 +1167,9 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
}
break;
case Pyc::JUMP_ABSOLUTE_A:
// bpo-47120: Replaced JUMP_ABSOLUTE by the relative jump JUMP_BACKWARD.
case Pyc::JUMP_BACKWARD_A:
case Pyc::JUMP_BACKWARD_NO_INTERRUPT_A:
{
int offs = operand;
if (mod->verCompare(3, 10) >= 0)
@@ -1225,8 +1232,12 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
break;
}
stack = stack_hist.top();
stack_hist.pop();
if (!stack_hist.empty()) {
stack = stack_hist.top();
stack_hist.pop();
} else {
fprintf(stderr, "Warning: Stack history is empty, something wrong might have happened\n");
}
PycRef<ASTBlock> prev = curblock;
PycRef<ASTBlock> nil;
@@ -1383,10 +1394,10 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
} while (prev != nil);
curblock = blocks.top();
if (curblock->blktype() == ASTBlock::BLK_EXCEPT) {
curblock->setEnd(pos+offs);
if (!blocks.empty()) {
curblock = blocks.top();
if (curblock->blktype() == ASTBlock::BLK_EXCEPT)
curblock->setEnd(pos+offs);
}
}
break;
@@ -1678,10 +1689,37 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
case Pyc::POP_EXCEPT:
/* Do nothing. */
break;
case Pyc::END_FOR:
{
stack.pop();
if ((opcode == Pyc::END_FOR) && (mod->majorVer() == 3) && (mod->minorVer() == 12)) {
// one additional pop for python 3.12
stack.pop();
}
// end for loop here
/* TODO : Ensure that FOR loop ends here.
Due to CACHE instructions at play, the end indicated in
the for loop by pycdas is not correct, it is off by
some small amount. */
if (curblock->blktype() == ASTBlock::BLK_FOR) {
PycRef<ASTBlock> prev = blocks.top();
blocks.pop();
curblock = blocks.top();
curblock->append(prev.cast<ASTNode>());
}
else {
fprintf(stderr, "Wrong block type %i for END_FOR\n", curblock->blktype());
}
}
break;
case Pyc::POP_TOP:
{
PycRef<ASTNode> value = stack.top();
stack.pop();
if (!curblock->inited()) {
if (curblock->blktype() == ASTBlock::BLK_WITH) {
curblock.cast<ASTWithBlock>()->setExpr(value);
@@ -1736,7 +1774,8 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
else
curblock->append(new ASTPrint(stack.top(), stream));
stack.pop();
stream->setProcessed();
if (stream)
stream->setProcessed();
}
break;
case Pyc::PRINT_NEWLINE:
@@ -1764,7 +1803,8 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
else
curblock->append(new ASTPrint(nullptr, stream));
stack.pop();
stream->setProcessed();
if (stream)
stream->setProcessed();
}
break;
case Pyc::RAISE_VARARGS_A:
@@ -1787,8 +1827,6 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
blocks.pop();
curblock = blocks.top();
curblock->append(prev.cast<ASTNode>());
bc_next(source, mod, opcode, operand, pos);
}
}
break;
@@ -2473,6 +2511,79 @@ PycRef<ASTNode> BuildFromCode(PycRef<PycCode> code, PycModule* mod)
stack.push(next_tup);
}
break;
case Pyc::BINARY_SLICE:
{
PycRef<ASTNode> end = stack.top();
stack.pop();
PycRef<ASTNode> start = stack.top();
stack.pop();
PycRef<ASTNode> dest = stack.top();
stack.pop();
if (start.type() == ASTNode::NODE_OBJECT
&& start.cast<ASTObject>()->object() == Pyc_None) {
start = NULL;
}
if (end.type() == ASTNode::NODE_OBJECT
&& end.cast<ASTObject>()->object() == Pyc_None) {
end = NULL;
}
PycRef<ASTNode> slice;
if (start == NULL && end == NULL) {
slice = new ASTSlice(ASTSlice::SLICE0);
} else if (start == NULL) {
slice = new ASTSlice(ASTSlice::SLICE2, start, end);
} else if (end == NULL) {
slice = new ASTSlice(ASTSlice::SLICE1, start, end);
} else {
slice = new ASTSlice(ASTSlice::SLICE3, start, end);
}
stack.push(new ASTSubscr(dest, slice));
}
break;
case Pyc::STORE_SLICE:
{
PycRef<ASTNode> end = stack.top();
stack.pop();
PycRef<ASTNode> start = stack.top();
stack.pop();
PycRef<ASTNode> dest = stack.top();
stack.pop();
PycRef<ASTNode> values = stack.top();
stack.pop();
if (start.type() == ASTNode::NODE_OBJECT
&& start.cast<ASTObject>()->object() == Pyc_None) {
start = NULL;
}
if (end.type() == ASTNode::NODE_OBJECT
&& end.cast<ASTObject>()->object() == Pyc_None) {
end = NULL;
}
PycRef<ASTNode> slice;
if (start == NULL && end == NULL) {
slice = new ASTSlice(ASTSlice::SLICE0);
} else if (start == NULL) {
slice = new ASTSlice(ASTSlice::SLICE2, start, end);
} else if (end == NULL) {
slice = new ASTSlice(ASTSlice::SLICE1, start, end);
} else {
slice = new ASTSlice(ASTSlice::SLICE3, start, end);
}
curblock->append(new ASTStore(values, new ASTSubscr(dest, slice)));
}
break;
case Pyc::COPY_A:
{
PycRef<ASTNode> value = stack.top(operand);
stack.push(value);
}
break;
default:
fprintf(stderr, "Unsupported opcode: %s (%d)\n", Pyc::OpcodeName(opcode), opcode);
cleanBuild = false;
@@ -2669,6 +2780,8 @@ void print_formatted_value(PycRef<ASTFormattedValue> formatted_value, PycModule*
pyc_output << "}";
}
static std::unordered_set<ASTNode *> node_seen;
void print_src(PycRef<ASTNode> node, PycModule* mod, std::ostream& pyc_output)
{
if (node == NULL) {
@@ -2677,6 +2790,12 @@ void print_src(PycRef<ASTNode> node, PycModule* mod, std::ostream& pyc_output)
return;
}
if (node_seen.find((ASTNode *)node) != node_seen.end()) {
fputs("WARNING: Circular reference detected\n", stderr);
return;
}
node_seen.insert((ASTNode *)node);
switch (node->type()) {
case ASTNode::NODE_BINARY:
case ASTNode::NODE_COMPARE:
@@ -3332,10 +3451,12 @@ void print_src(PycRef<ASTNode> node, PycModule* mod, std::ostream& pyc_output)
pyc_output << "<NODE:" << node->type() << ">";
fprintf(stderr, "Unsupported Node type: %d\n", node->type());
cleanBuild = false;
node_seen.erase((ASTNode *)node);
return;
}
cleanBuild = true;
node_seen.erase((ASTNode *)node);
}
bool print_docstring(PycRef<PycObject> obj, int indent, PycModule* mod,
@@ -3352,8 +3473,16 @@ bool print_docstring(PycRef<PycObject> obj, int indent, PycModule* mod,
return false;
}
static std::unordered_set<PycCode *> code_seen;
void decompyle(PycRef<PycCode> code, PycModule* mod, std::ostream& pyc_output)
{
if (code_seen.find((PycCode *)code) != code_seen.end()) {
fputs("WARNING: Circular reference detected\n", stderr);
return;
}
code_seen.insert((PycCode *)code);
PycRef<ASTNode> source = BuildFromCode(code, mod);
PycRef<ASTNodeList> clean = source.cast<ASTNodeList>();
@@ -3447,4 +3576,6 @@ void decompyle(PycRef<PycCode> code, PycModule* mod, std::ostream& pyc_output)
start_line(cur_indent, pyc_output);
pyc_output << "# WARNING: Decompyle incomplete\n";
}
code_seen.erase((PycCode *)code);
}

View File

@@ -30,14 +30,30 @@ public:
{
if (m_ptr > -1)
m_stack[m_ptr--] = nullptr;
else {
#ifdef BLOCK_DEBUG
fprintf(stderr, "pop from empty stack\n");
#endif
}
}
PycRef<ASTNode> top() const
PycRef<ASTNode> top(int i = 1) const
{
if (m_ptr > -1)
return m_stack[m_ptr];
else
if (i > 0) {
int idx = m_ptr + 1 - i;
if ((m_ptr > -1) && (idx >= 0))
return m_stack[idx];
else {
#ifdef BLOCK_DEBUG
fprintf(stderr, "insufficient values on stack\n");
#endif
return nullptr;
}
}
else {
fprintf(stderr, "incorrect operand %i\n", i);
return nullptr;
}
}
bool empty() const

View File

@@ -472,6 +472,10 @@ void bc_disasm(std::ostream& pyc_output, PycRef<PycCode> code, PycModule* mod,
case Pyc::INSTRUMENTED_POP_JUMP_IF_FALSE_A:
case Pyc::INSTRUMENTED_POP_JUMP_IF_TRUE_A:
{
/* TODO: Fix offset based on CACHE instructions.
Offset is relative to next non-CACHE instruction
and thus will be printed lower than actual value.
See TODO @ END_FOR ASTree.cpp */
int offs = operand;
if (mod->verCompare(3, 10) >= 0)
offs *= sizeof(uint16_t); // BPO-27129
@@ -596,3 +600,18 @@ void bc_disasm(std::ostream& pyc_output, PycRef<PycCode> code, PycModule* mod,
pyc_output << "\n";
}
}
void bc_exceptiontable(std::ostream& pyc_output, PycRef<PycCode> code,
int indent)
{
for (const auto& entry : code->exceptionTableEntries()) {
for (int i=0; i<indent; i++)
pyc_output << " ";
pyc_output << entry.start_offset << " to " << entry.end_offset
<< " -> " << entry.target << " [" << entry.stack_depth
<< "] " << (entry.push_lasti ? "lasti": "")
<< "\n";
}
}

View File

@@ -32,3 +32,5 @@ void print_const(std::ostream& pyc_output, PycRef<PycObject> obj, PycModule* mod
void bc_next(PycBuffer& source, PycModule* mod, int& opcode, int& operand, int& pos);
void bc_disasm(std::ostream& pyc_output, PycRef<PycCode> code, PycModule* mod,
int indent, unsigned flags);
void bc_exceptiontable(std::ostream& pyc_output, PycRef<PycCode> code,
int indent);

View File

@@ -53,35 +53,43 @@ bool PycFile::atEof() const
int PycFile::getByte()
{
int ch = fgetc(m_stream);
if (ch == EOF)
ungetc(ch, m_stream);
if (ch == EOF) {
fputs("PycFile::getByte(): Unexpected end of stream\n", stderr);
std::exit(1);
}
return ch;
}
int PycFile::getBuffer(int bytes, void* buffer)
void PycFile::getBuffer(int bytes, void* buffer)
{
return (int)fread(buffer, 1, bytes, m_stream);
if (fread(buffer, 1, bytes, m_stream) != (size_t)bytes) {
fputs("PycFile::getBuffer(): Unexpected end of stream\n", stderr);
std::exit(1);
}
}
/* PycBuffer */
int PycBuffer::getByte()
{
if (atEof())
return EOF;
if (atEof()) {
fputs("PycBuffer::getByte(): Unexpected end of stream\n", stderr);
std::exit(1);
}
int ch = (int)(*(m_buffer + m_pos));
++m_pos;
return ch & 0xFF; // Make sure it's just a byte!
}
int PycBuffer::getBuffer(int bytes, void* buffer)
void PycBuffer::getBuffer(int bytes, void* buffer)
{
if (m_pos + bytes > m_size)
bytes = m_size - m_pos;
if (m_pos + bytes > m_size) {
fputs("PycBuffer::getBuffer(): Unexpected end of stream\n", stderr);
std::exit(1);
}
if (bytes != 0)
memcpy(buffer, (m_buffer + m_pos), bytes);
m_pos += bytes;
return bytes;
}
int formatted_print(std::ostream& stream, const char* format, ...)

6
data.h
View File

@@ -19,7 +19,7 @@ public:
virtual bool atEof() const = 0;
virtual int getByte() = 0;
virtual int getBuffer(int bytes, void* buffer) = 0;
virtual void getBuffer(int bytes, void* buffer) = 0;
int get16();
int get32();
Pyc_INT64 get64();
@@ -34,7 +34,7 @@ public:
bool atEof() const override;
int getByte() override;
int getBuffer(int bytes, void* buffer) override;
void getBuffer(int bytes, void* buffer) override;
private:
FILE* m_stream;
@@ -50,7 +50,7 @@ public:
bool atEof() const override { return (m_pos == m_size); }
int getByte() override;
int getBuffer(int bytes, void* buffer) override;
void getBuffer(int bytes, void* buffer) override;
private:
const unsigned char* m_buffer;

View File

@@ -128,3 +128,44 @@ PycRef<PycString> PycCode::getCellVar(PycModule* mod, int idx) const
? m_freeVars->get(idx - m_cellVars->size()).cast<PycString>()
: m_cellVars->get(idx).cast<PycString>();
}
int _parse_varint(PycBuffer& data, int& pos) {
int b = data.getByte();
pos += 1;
int val = b & 0x3F;
while (b & 0x40) {
val <<= 6;
b = data.getByte();
pos += 1;
val |= (b & 0x3F);
}
return val;
}
std::vector<PycExceptionTableEntry> PycCode::exceptionTableEntries() const
{
PycBuffer data(m_exceptTable->value(), m_exceptTable->length());
std::vector<PycExceptionTableEntry> entries;
int pos = 0;
while (!data.atEof()) {
int start = _parse_varint(data, pos) * 2;
int length = _parse_varint(data, pos) * 2;
int end = start + length;
int target = _parse_varint(data, pos) * 2;
int dl = _parse_varint(data, pos);
int depth = dl >> 1;
bool lasti = bool(dl & 1);
entries.push_back(PycExceptionTableEntry(start, end, target, depth, lasti));
}
return entries;
}

View File

@@ -8,6 +8,18 @@
class PycData;
class PycModule;
class PycExceptionTableEntry {
public:
int start_offset; // inclusive
int end_offset; // exclusive
int target;
int stack_depth;
bool push_lasti;
PycExceptionTableEntry(int m_start_offset, int m_end_offset, int m_target, int m_stack_depth, bool m_push_lasti) :
start_offset(m_start_offset), end_offset(m_end_offset), target(m_target), stack_depth(m_stack_depth), push_lasti(m_push_lasti) {};
};
class PycCode : public PycObject {
public:
typedef std::vector<PycRef<PycString>> globals_t;
@@ -87,6 +99,8 @@ public:
m_globalsUsed.emplace_back(std::move(varname));
}
std::vector<PycExceptionTableEntry> exceptionTableEntries() const;
private:
int m_argCount, m_posOnlyArgCount, m_kwOnlyArgCount, m_numLocals;
int m_stackSize, m_flags;

View File

@@ -4,6 +4,7 @@
#include <string>
#include <iostream>
#include <fstream>
#include <unordered_set>
#include "pyc_module.h"
#include "pyc_numeric.h"
#include "bytecode.h"
@@ -73,6 +74,8 @@ static void iprintf(std::ostream& pyc_output, int indent, const char* fmt, ...)
va_end(varargs);
}
static std::unordered_set<PycObject *> out_seen;
void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
unsigned flags, std::ostream& pyc_output)
{
@@ -81,6 +84,12 @@ void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
return;
}
if (out_seen.find((PycObject *)obj) != out_seen.end()) {
fputs("WARNING: Circular reference detected\n", stderr);
return;
}
out_seen.insert((PycObject *)obj);
switch (obj->type()) {
case PycObject::TYPE_CODE:
case PycObject::TYPE_CODE2:
@@ -145,16 +154,16 @@ void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
iputs(pyc_output, indent + 1, "[Disassembly]\n");
bc_disasm(pyc_output, codeObj, mod, indent + 2, flags);
if (mod->verCompare(3, 11) >= 0) {
iputs(pyc_output, indent + 1, "[Exception Table]\n");
bc_exceptiontable(pyc_output, codeObj, indent+2);
}
if (mod->verCompare(1, 5) >= 0 && (flags & Pyc::DISASM_PYCODE_VERBOSE) != 0) {
iprintf(pyc_output, indent + 1, "First Line: %d\n", codeObj->firstLine());
iputs(pyc_output, indent + 1, "[Line Number Table]\n");
output_object(codeObj->lnTable().cast<PycObject>(), mod, indent + 2, flags, pyc_output);
}
if (mod->verCompare(3, 11) >= 0 && (flags & Pyc::DISASM_PYCODE_VERBOSE) != 0) {
iputs(pyc_output, indent + 1, "[Exception Table]\n");
output_object(codeObj->exceptTable().cast<PycObject>(), mod, indent + 2, flags, pyc_output);
}
}
break;
case PycObject::TYPE_STRING:
@@ -246,6 +255,8 @@ void output_object(PycRef<PycObject> obj, PycModule* mod, int indent,
default:
iprintf(pyc_output, indent, "<TYPE: %d>\n", obj->type());
}
out_seen.erase((PycObject *)obj);
}
int main(int argc, char* argv[])

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,7 @@
l = [1,2,3,4,5,6]
print(l[1:3])
print(l[:2])
print(l[3:])
print(l[-4:])
print(l[:-2])
print(l[:])

View File

@@ -0,0 +1,8 @@
a = [0] * 16
l = [1,2,3]
a[13:] = l
a[5] = 10
a[:2] = [1,2]
a[:] = range(16)
print(a)

View File

@@ -0,0 +1,34 @@
def loop1():
iterable = [1, 2, 3]
for item in iterable:
pass
loop1()
def loop2():
for i in range(2):
print(i)
loop2()
def loop3():
def loop():
x = (1,2,3)
l = []
for i in x:
l.append(i)
return l
return loop()
loop3()
def loop4():
for i in range(3):
for j in range(2):
print(i*j)
loop4()
for j in [1,2,3][::-1]:
print("hi", j)

View File

@@ -0,0 +1,7 @@
import struct
def bytes_to_words(b):
'''Convert a byte string (little-endian) to a list of 32-bit words.'''
if len(b) % 4 != 0:
raise ValueError('Input bytes length must be a multiple of 4 for word conversion.')
return struct.unpack('<' + 'I' * (len(b) // 4), b)

View File

@@ -0,0 +1,7 @@
l = [ 1 , 2 , 3 , 4 , 5 , 6 ] <EOL>
print ( l [ 1 : 3 ] ) <EOL>
print ( l [ : 2 ] ) <EOL>
print ( l [ 3 : ] ) <EOL>
print ( l [ - 4 : ] ) <EOL>
print ( l [ : - 2 ] ) <EOL>
print ( l [ : ] ) <EOL>

View File

@@ -0,0 +1,7 @@
a = [ 0 ] * 16 <EOL>
l = [ 1 , 2 , 3 ] <EOL>
a [ 13 : ] = l <EOL>
a [ 5 ] = 10 <EOL>
a [ : 2 ] = [ 1 , 2 ] <EOL>
a [ : ] = range ( 16 ) <EOL>
print ( a ) <EOL>

View File

@@ -0,0 +1,46 @@
def loop1 ( ) : <EOL>
<INDENT>
iterable = [ 1 , 2 , 3 ] <EOL>
for item in iterable : <EOL>
<INDENT>
pass <EOL>
<OUTDENT>
<OUTDENT>
loop1 ( ) <EOL>
def loop2 ( ) : <EOL>
<INDENT>
for i in range ( 2 ) : <EOL>
<INDENT>
print ( i ) <EOL>
<OUTDENT>
<OUTDENT>
loop2 ( ) <EOL>
def loop3 ( ) : <EOL>
<INDENT>
def loop ( ) : <EOL>
<INDENT>
x = ( 1 , 2 , 3 ) <EOL>
l = [ ] <EOL>
for i in x : <EOL>
<INDENT>
l . append ( i ) <EOL>
<OUTDENT>
return l <EOL>
<OUTDENT>
return loop ( ) <EOL>
<OUTDENT>
loop3 ( ) <EOL>
def loop4 ( ) : <EOL>
<INDENT>
for i in range ( 3 ) : <EOL>
<INDENT>
for j in range ( 2 ) : <EOL>
<INDENT>
print ( i * j ) <EOL>
<OUTDENT>
<OUTDENT>
<OUTDENT>
loop4 ( ) <EOL>
for j in [ 1 , 2 , 3 ] [ : : - 1 ] : <EOL>
<INDENT>
print ( 'hi' , j ) <EOL>

View File

@@ -0,0 +1,9 @@
import struct <EOL>
def bytes_to_words ( b ) : <EOL>
<INDENT>
'Convert a byte string (little-endian) to a list of 32-bit words.' <EOL>
if len ( b ) % 4 != 0 : <EOL>
<INDENT>
raise ValueError ( 'Input bytes length must be a multiple of 4 for word conversion.' ) <EOL>
<OUTDENT>
return struct . unpack ( '<' + 'I' * ( len ( b ) // 4 ) , b ) <EOL>