Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions test/scripts/alignment_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,55 @@ class s_t(struct):
s = s_t.from_bytes(memory)
self.assertEqual(s.a.value, 0x41)
self.assertEqual(s.flags.value, 5)


class AlignedStructTailPaddingInstanceTest(unittest.TestCase):
"""Instance size must include tail padding for aligned structs."""

def test_instance_size_matches_class_size(self):
"""size_of(instance) should equal size_of(class) for aligned structs."""
class aligned_t(struct):
_aligned_ = True
a: c_int
b: c_char

self.assertEqual(size_of(aligned_t), 8)

memory = pystruct.pack("<ib", 42, 0x41) + b"\x00" * 3
s = aligned_t.from_bytes(memory)

self.assertEqual(size_of(s), 8)

def test_to_bytes_includes_tail_padding(self):
"""to_bytes() should return 8 bytes (including tail padding), not 5."""
class aligned_t(struct):
_aligned_ = True
a: c_int
b: c_char

memory = pystruct.pack("<ib", 42, 0x41) + b"\x00" * 3
s = aligned_t.from_bytes(memory)

self.assertEqual(len(s.to_bytes()), 8)

def test_nested_aligned_struct_tail_padding(self):
"""Nested aligned structs should also have correct tail padding."""
class inner_t(struct):
_aligned_ = True
x: c_int
y: c_char

class outer_t(struct):
_aligned_ = True
a: inner_t
b: c_int

self.assertEqual(size_of(inner_t), 8)

memory = b"\x00" * 16
s = outer_t.from_bytes(memory)
self.assertEqual(size_of(s), size_of(outer_t))


if __name__ == "__main__":
unittest.main()
82 changes: 82 additions & 0 deletions test/scripts/array_integration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#
# This file is part of libdestruct (https://github.com/mrindeciso/libdestruct).
# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

import unittest

from libdebug import debugger
from libdestruct import array, array_of, inflater, c_int, c_long, struct

class ArrayTest(unittest.TestCase):
def test_linear_arrays_1(self):
d = debugger("binaries/array_test")

d.run()

bp = d.bp("do_nothing")

d.cont()

libdestruct = inflater(d.memory)

self.assertTrue(bp.hit_on(d))

test1 = libdestruct.inflate(array_of(c_int, 10), d.regs.rdi)

self.assertEqual(len(test1), 10)

for i in range(10):
self.assertEqual(test1[i].value, i ** 2)

self.assertEqual(bytes(test1), b"".join((i ** 2).to_bytes(4, "little") for i in range(10)))

self.assertIn(test1[0], test1)

d.cont()

class test2_t(struct):
a: c_int

test2 = libdestruct.inflate(array_of(test2_t, 10), d.regs.rdi)

for i in range(10):
self.assertEqual(test2[i].a.value, i ** 3)

d.cont()

class test3_t(struct):
a: c_int
b: c_long

test3 = libdestruct.inflate(array_of(test3_t, 10), d.regs.rdi)

for i in range(10):
self.assertEqual(test3[i].a.value, i * 100)
self.assertEqual(test3[i].b.value, i * 1000)

d.cont()

class test4_t(struct):
a: c_int
b: array = array_of(c_int, 10)

test4 = libdestruct.inflate(array_of(test4_t, 10), d.regs.rdi)

for i in range(10):
self.assertEqual(test4[i].a.value, i ** 4)
for j in range(10):
self.assertEqual(test4[i].b[j].value, (i + 1) * j)

with self.assertRaises(NotImplementedError):
test4[i] = 4

with self.assertRaises(NotImplementedError):
test4._set([1, 2, 3])

with self.assertRaises(NotImplementedError):
test4.set([1, 2, 3])

d.kill()
d.terminate()
204 changes: 156 additions & 48 deletions test/scripts/array_test.py
Original file line number Diff line number Diff line change
@@ -1,82 +1,190 @@
#
# This file is part of libdestruct (https://github.com/mrindeciso/libdestruct).
# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved.
# Copyright (c) 2026 Roberto Alessandro Bertolini. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

import unittest
from enum import IntEnum

from libdebug import debugger
from libdestruct import array, array_of, inflater, c_int, c_long, struct
from libdestruct import array, c_int, inflater, struct, array_of, enum, enum_of, size_of

class ArrayTest(unittest.TestCase):
def test_linear_arrays_1(self):
d = debugger("binaries/array_test")

d.run()
class ArrayUnitTest(unittest.TestCase):
"""Array operations without debugger."""

bp = d.bp("do_nothing")
def test_array_value_property(self):
""".value calls self.get() without args - should not raise TypeError."""
memory = b"".join((i).to_bytes(4, "little") for i in range(5))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 5), 0)

d.cont()
val = arr.value
self.assertIsNotNone(val)

def test_array_repr(self):
memory = b"".join((i).to_bytes(4, "little") for i in range(3))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 3), 0)

libdestruct = inflater(d.memory)
r = repr(arr)
self.assertIsInstance(r, str)

self.assertTrue(bp.hit_on(d))
def test_array_indexing(self):
memory = b"".join((i).to_bytes(4, "little") for i in range(5))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 5), 0)

test1 = libdestruct.inflate(array_of(c_int, 10), d.regs.rdi)
for i in range(5):
self.assertEqual(arr[i].value, i)

def test_array_iteration(self):
memory = b"".join((i * 10).to_bytes(4, "little") for i in range(3))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 3), 0)

self.assertEqual(len(test1), 10)
values = [x.value for x in arr]
self.assertEqual(values, [0, 10, 20])

for i in range(10):
self.assertEqual(test1[i].value, i ** 2)
def test_array_len(self):
memory = b"".join((0).to_bytes(4, "little") for _ in range(7))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 7), 0)

self.assertEqual(bytes(test1), b"".join((i ** 2).to_bytes(4, "little") for i in range(10)))
self.assertEqual(len(arr), 7)

self.assertIn(test1[0], test1)
def test_array_contains(self):
memory = b"".join((i).to_bytes(4, "little") for i in range(5))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 5), 0)

d.cont()
elem = arr[2]
self.assertIn(elem, arr)

class test2_t(struct):
a: c_int
def test_array_to_bytes(self):
memory = b"".join((i).to_bytes(4, "little") for i in range(3))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 3), 0)

test2 = libdestruct.inflate(array_of(test2_t, 10), d.regs.rdi)
result = arr.to_bytes()
self.assertIsInstance(result, bytes)
self.assertEqual(result, memory)

for i in range(10):
self.assertEqual(test2[i].a.value, i ** 3)
def test_array_to_str(self):
memory = b"".join((i).to_bytes(4, "little") for i in range(3))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 3), 0)

d.cont()
result = arr.to_str()
self.assertIn("0", result)
self.assertIn("1", result)
self.assertIn("2", result)

class test3_t(struct):
a: c_int
b: c_long
def test_array_value_returns_all_elements(self):
memory = b"".join((i).to_bytes(4, "little") for i in range(4))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 4), 0)

test3 = libdestruct.inflate(array_of(test3_t, 10), d.regs.rdi)
val = arr.value
self.assertIsInstance(val, list)
self.assertEqual(len(val), 4)
self.assertEqual([x.value for x in val], [0, 1, 2, 3])

for i in range(10):
self.assertEqual(test3[i].a.value, i * 100)
self.assertEqual(test3[i].b.value, i * 1000)
def test_bytes_on_bytearray_backed_array(self):
memory = bytearray(b"".join((i).to_bytes(4, "little") for i in range(3)))
lib = inflater(memory)
arr = lib.inflate(array_of(c_int, 3), 0)

d.cont()
result = bytes(arr)
self.assertIsInstance(result, bytes)
self.assertEqual(len(result), 12)

class test4_t(struct):
a: c_int
b: array = array_of(c_int, 10)

test4 = libdestruct.inflate(array_of(test4_t, 10), d.regs.rdi)
class NegativeArrayCountTest(unittest.TestCase):
"""array[T, N] must reject non-positive counts at handler time."""

for i in range(10):
self.assertEqual(test4[i].a.value, i ** 4)
for j in range(10):
self.assertEqual(test4[i].b[j].value, (i + 1) * j)
def test_negative_count_raises(self):
"""array[c_int, -5] must raise ValueError."""
with self.assertRaises(ValueError):
class s_t(struct):
data: array[c_int, -5]
size_of(s_t)

with self.assertRaises(NotImplementedError):
test4[i] = 4
def test_zero_count_raises(self):
"""array[c_int, 0] must raise ValueError."""
with self.assertRaises(ValueError):
class s_t(struct):
data: array[c_int, 0]
size_of(s_t)

with self.assertRaises(NotImplementedError):
test4._set([1, 2, 3])
def test_positive_count_works(self):
"""array[c_int, 3] must work fine."""
class s_t(struct):
data: array[c_int, 3]
self.assertEqual(size_of(s_t), 12)

with self.assertRaises(NotImplementedError):
test4.set([1, 2, 3])

d.kill()
d.terminate()
class ArrayFreezeElementsTest(unittest.TestCase):
"""Freezing a struct with an array must freeze the array elements too."""

def test_array_element_access_returns_frozen_value(self):
"""s.data[0].value should return the frozen value, not live memory."""
class arr_struct_t(struct):
count: c_int
data: array_of(c_int, 3)

memory = bytearray(16)
memory[0:4] = (3).to_bytes(4, "little")
memory[4:8] = (10).to_bytes(4, "little")
memory[8:12] = (20).to_bytes(4, "little")
memory[12:16] = (30).to_bytes(4, "little")

lib = inflater(memory)
s = lib.inflate(arr_struct_t, 0)
s.freeze()

memory[4:8] = (99).to_bytes(4, "little")

self.assertEqual(s.count.value, 3)
self.assertEqual(s.data[0].value, 10)

def test_array_value_property_returns_frozen_elements(self):
"""s.data.value should contain frozen element values after memory mutation."""
class arr_struct_t(struct):
data: array_of(c_int, 2)

memory = bytearray(8)
memory[0:4] = (10).to_bytes(4, "little")
memory[4:8] = (20).to_bytes(4, "little")

lib = inflater(memory)
s = lib.inflate(arr_struct_t, 0)
s.freeze()

memory[0:4] = (99).to_bytes(4, "little")

frozen_vals = [elem.value for elem in s.data.value]
self.assertEqual(frozen_vals, [10, 20])

def test_array_to_bytes_returns_frozen_bytes(self):
"""s.data.to_bytes() should return frozen bytes, not live memory."""
class arr_struct_t(struct):
data: array_of(c_int, 2)

memory = bytearray(8)
memory[0:4] = (10).to_bytes(4, "little")
memory[4:8] = (20).to_bytes(4, "little")

lib = inflater(memory)
s = lib.inflate(arr_struct_t, 0)
s.freeze()

expected_bytes = bytes(memory)

memory[0:4] = (99).to_bytes(4, "little")

self.assertEqual(s.data.to_bytes(), expected_bytes)


if __name__ == "__main__":
unittest.main()
Loading
Loading