import py, sys, platform import pytest from testing.cffi0 import backend_tests, test_function, test_ownlib from testing.support import u from cffi import FFI import _cffi_backend class TestFFI(backend_tests.BackendTests, test_function.TestFunction, test_ownlib.TestOwnLib): TypeRepr = "" @staticmethod def Backend(): return _cffi_backend def test_not_supported_bitfield_in_result(self): ffi = FFI(backend=self.Backend()) ffi.cdef("struct foo_s { int a,b,c,d,e; int x:1; };") e = py.test.raises(NotImplementedError, ffi.callback, "struct foo_s foo(void)", lambda: 42) assert str(e.value) == ("struct foo_s(*)(): " "callback with unsupported argument or return type or with '...'") def test_inspecttype(self): ffi = FFI(backend=self.Backend()) assert ffi.typeof("long").kind == "primitive" assert ffi.typeof("long(*)(long, long**, ...)").cname == ( "long(*)(long, long * *, ...)") assert ffi.typeof("long(*)(long, long**, ...)").ellipsis is True def test_new_handle(self): ffi = FFI(backend=self.Backend()) o = [2, 3, 4] p = ffi.new_handle(o) assert ffi.typeof(p) == ffi.typeof("void *") assert ffi.from_handle(p) is o assert ffi.from_handle(ffi.cast("char *", p)) is o py.test.raises(RuntimeError, ffi.from_handle, ffi.NULL) def test_callback_onerror(self): ffi = FFI(backend=self.Backend()) seen = [] def oops(*args): seen.append(args) def otherfunc(): raise LookupError def cb(n): otherfunc() a = ffi.callback("int(*)(int)", cb, error=42, onerror=oops) res = a(234) assert res == 42 assert len(seen) == 1 exc, val, tb = seen[0] assert exc is LookupError assert isinstance(val, LookupError) assert tb.tb_frame.f_code.co_name == 'cb' assert tb.tb_frame.f_locals['n'] == 234 def test_ffi_new_allocator_2(self): ffi = FFI(backend=self.Backend()) seen = [] def myalloc(size): seen.append(size) return ffi.new("char[]", b"X" * size) def myfree(raw): seen.append(raw) alloc1 = ffi.new_allocator(myalloc, myfree) alloc2 = ffi.new_allocator(alloc=myalloc, free=myfree, should_clear_after_alloc=False) p1 = alloc1("int[10]") p2 = alloc2("int[]", 10) assert seen == [40, 40] assert ffi.typeof(p1) == ffi.typeof("int[10]") assert ffi.sizeof(p1) == 40 assert ffi.typeof(p2) == ffi.typeof("int[]") assert ffi.sizeof(p2) == 40 assert p1[5] == 0 assert p2[6] == ord('X') * 0x01010101 raw1 = ffi.cast("char *", p1) raw2 = ffi.cast("char *", p2) del p1, p2 retries = 0 while len(seen) != 4: retries += 1 assert retries <= 5 import gc; gc.collect() assert seen == [40, 40, raw1, raw2] assert repr(seen[2]) == "" assert repr(seen[3]) == "" def test_ffi_new_allocator_3(self): ffi = FFI(backend=self.Backend()) seen = [] def myalloc(size): seen.append(size) return ffi.new("char[]", b"X" * size) alloc1 = ffi.new_allocator(myalloc) # no 'free' p1 = alloc1("int[10]") assert seen == [40] assert ffi.typeof(p1) == ffi.typeof("int[10]") assert ffi.sizeof(p1) == 40 assert p1[5] == 0 def test_ffi_new_allocator_4(self): ffi = FFI(backend=self.Backend()) py.test.raises(TypeError, ffi.new_allocator, free=lambda x: None) # def myalloc2(size): raise LookupError alloc2 = ffi.new_allocator(myalloc2) py.test.raises(LookupError, alloc2, "int[5]") # def myalloc3(size): return 42 alloc3 = ffi.new_allocator(myalloc3) e = py.test.raises(TypeError, alloc3, "int[5]") assert str(e.value) == "alloc() must return a cdata object (got int)" # def myalloc4(size): return ffi.cast("int", 42) alloc4 = ffi.new_allocator(myalloc4) e = py.test.raises(TypeError, alloc4, "int[5]") assert str(e.value) == "alloc() must return a cdata pointer, not 'int'" # def myalloc5(size): return ffi.NULL alloc5 = ffi.new_allocator(myalloc5) py.test.raises(MemoryError, alloc5, "int[5]") def test_new_struct_containing_struct_containing_array_varsize(self): ffi = FFI(backend=self.Backend()) ffi.cdef(""" struct foo_s { int len[100]; short data[]; }; struct bar_s { int abc[100]; struct foo_s tail; }; """) # loop to try to detect heap overwrites, if the size allocated # is too small for i in range(1, 501, 100): p = ffi.new("struct bar_s *", [[10], [[20], [3,4,5,6,7,8,9] * i]]) assert p.abc[0] == 10 assert p.tail.len[0] == 20 assert p.tail.data[0] == 3 assert p.tail.data[6] == 9 assert p.tail.data[7 * i - 1] == 9 def test_bogus_struct_containing_struct_containing_array_varsize(self): ffi = FFI(backend=self.Backend()) ffi.cdef(""" struct foo_s { signed char len; signed char data[]; }; struct bar_s { struct foo_s foo; int bcd; }; """) p = ffi.new("struct bar_s *", [[123, [45, 56, 67, 78]], 9999999]) assert p.foo.len == 123 assert p.foo.data[0] == 45 assert p.foo.data[1] == 56 assert p.foo.data[2] == 67 assert p.bcd == 9999999 assert p.foo.data[3] != 78 # has been overwritten with 9999999 class TestBitfield: def check(self, source, expected_ofs_y, expected_align, expected_size): # NOTE: 'expected_*' is the numbers expected from GCC. # The numbers expected from MSVC are not explicitly written # in this file, and will just be taken from the compiler. ffi = FFI() ffi.cdef("struct s1 { %s };" % source) ctype = ffi.typeof("struct s1") # verify the information with gcc ffi1 = FFI() ffi1.cdef(""" static const int Gofs_y, Galign, Gsize; struct s1 *try_with_value(int fieldnum, long long value); """) fnames = [name for name, cfield in ctype.fields if name and cfield.bitsize > 0] setters = ['case %d: s.%s = value; break;' % iname for iname in enumerate(fnames)] lib = ffi1.verify(""" #include struct s1 { %s }; struct sa { char a; struct s1 b; }; #define Gofs_y offsetof(struct s1, y) #define Galign offsetof(struct sa, b) #define Gsize sizeof(struct s1) struct s1 *try_with_value(int fieldnum, long long value) { static struct s1 s; memset(&s, 0, sizeof(s)); switch (fieldnum) { %s } return &s; } """ % (source, ' '.join(setters))) if sys.platform == 'win32': expected_ofs_y = lib.Gofs_y expected_align = lib.Galign expected_size = lib.Gsize else: assert (lib.Gofs_y, lib.Galign, lib.Gsize) == ( expected_ofs_y, expected_align, expected_size) # the real test follows assert ffi.offsetof("struct s1", "y") == expected_ofs_y assert ffi.alignof("struct s1") == expected_align assert ffi.sizeof("struct s1") == expected_size # compare the actual storage of the two for name, cfield in ctype.fields: if cfield.bitsize < 0 or not name: continue if int(ffi.cast(cfield.type, -1)) == -1: # signed min_value = -(1 << (cfield.bitsize-1)) max_value = (1 << (cfield.bitsize-1)) - 1 else: min_value = 0 max_value = (1 << cfield.bitsize) - 1 for t in [1, 2, 4, 8, 16, 128, 2813, 89728, 981729, -1,-2,-4,-8,-16,-128,-2813,-89728,-981729]: if min_value <= t <= max_value: self._fieldcheck(ffi, lib, fnames, name, t) def _fieldcheck(self, ffi, lib, fnames, name, value): s = ffi.new("struct s1 *") setattr(s, name, value) assert getattr(s, name) == value raw1 = ffi.buffer(s)[:] buff1 = ffi.buffer(s) t = lib.try_with_value(fnames.index(name), value) raw2 = ffi.buffer(t, len(raw1))[:] assert raw1 == raw2 buff2 = ffi.buffer(t, len(buff1)) assert buff1 == buff2 def test_bitfield_basic(self): self.check("int a; int b:9; int c:20; int y;", 8, 4, 12) self.check("int a; short b:9; short c:7; int y;", 8, 4, 12) self.check("int a; short b:9; short c:9; int y;", 8, 4, 12) def test_bitfield_reuse_if_enough_space(self): self.check("int a:2; char y;", 1, 4, 4) self.check("int a:1; char b ; int c:1; char y;", 3, 4, 4) self.check("int a:1; char b:8; int c:1; char y;", 3, 4, 4) self.check("char a; int b:9; char y;", 3, 4, 4) self.check("char a; short b:9; char y;", 4, 2, 6) self.check("int a:2; char b:6; char y;", 1, 4, 4) self.check("int a:2; char b:7; char y;", 2, 4, 4) self.check("int a:2; short b:15; char c:2; char y;", 5, 4, 8) self.check("int a:2; char b:1; char c:1; char y;", 1, 4, 4) @pytest.mark.skipif( "not (sys.platform == 'darwin' and platform.machine() == 'arm64')" " and " "platform.machine().startswith(('arm', 'aarch64'))") def test_bitfield_anonymous_no_align(self): L = FFI().alignof("long long") self.check("char y; int :1;", 0, 1, 2) self.check("char x; int z:1; char y;", 2, 4, 4) self.check("char x; int :1; char y;", 2, 1, 3) self.check("char x; long long z:48; char y;", 7, L, 8) self.check("char x; long long :48; char y;", 7, 1, 8) self.check("char x; long long z:56; char y;", 8, L, 8 + L) self.check("char x; long long :56; char y;", 8, 1, 9) self.check("char x; long long z:57; char y;", L + 8, L, L + 8 + L) self.check("char x; long long :57; char y;", L + 8, 1, L + 9) @pytest.mark.skipif( "(sys.platform == 'darwin' and platform.machine() == 'arm64')" " or " "not platform.machine().startswith(('arm', 'aarch64'))") def test_bitfield_anonymous_align_arm(self): L = FFI().alignof("long long") self.check("char y; int :1;", 0, 4, 4) self.check("char x; int z:1; char y;", 2, 4, 4) self.check("char x; int :1; char y;", 2, 4, 4) self.check("char x; long long z:48; char y;", 7, L, 8) self.check("char x; long long :48; char y;", 7, 8, 8) self.check("char x; long long z:56; char y;", 8, L, 8 + L) self.check("char x; long long :56; char y;", 8, L, 8 + L) self.check("char x; long long z:57; char y;", L + 8, L, L + 8 + L) self.check("char x; long long :57; char y;", L + 8, L, L + 8 + L) @pytest.mark.skipif( "not (sys.platform == 'darwin' and platform.machine() == 'arm64')" " and " "platform.machine().startswith(('arm', 'aarch64'))") def test_bitfield_zero(self): L = FFI().alignof("long long") self.check("char y; int :0;", 0, 1, 4) self.check("char x; int :0; char y;", 4, 1, 5) self.check("char x; int :0; int :0; char y;", 4, 1, 5) self.check("char x; long long :0; char y;", L, 1, L + 1) self.check("short x, y; int :0; int :0;", 2, 2, 4) self.check("char x; int :0; short b:1; char y;", 5, 2, 6) self.check("int a:1; int :0; int b:1; char y;", 5, 4, 8) @pytest.mark.skipif( "(sys.platform == 'darwin' and platform.machine() == 'arm64')" " or " "not platform.machine().startswith(('arm', 'aarch64'))") def test_bitfield_zero_arm(self): L = FFI().alignof("long long") self.check("char y; int :0;", 0, 4, 4) self.check("char x; int :0; char y;", 4, 4, 8) self.check("char x; int :0; int :0; char y;", 4, 4, 8) self.check("char x; long long :0; char y;", L, 8, L + 8) self.check("short x, y; int :0; int :0;", 2, 4, 4) self.check("char x; int :0; short b:1; char y;", 5, 4, 8) self.check("int a:1; int :0; int b:1; char y;", 5, 4, 8) def test_error_cases(self): ffi = FFI() ffi.cdef("struct s1 { float x:1; };") with pytest.raises(TypeError): ffi.new("struct s1 *") ffi.cdef("struct s2 { char x:0; };") with pytest.raises(TypeError): ffi.new("struct s2 *") ffi.cdef("struct s3 { char x:9; };") with pytest.raises(TypeError): ffi.new("struct s3 *") def test_struct_with_typedef(self): ffi = FFI() ffi.cdef("typedef struct { float x; } foo_t;") p = ffi.new("foo_t *", [5.2]) assert repr(p).startswith("