Author: Armin Rigo <ar...@tunes.org> Branch: Changeset: r2839:b88f27223be8 Date: 2016-12-22 15:04 +0100 http://bitbucket.org/cffi/cffi/changeset/b88f27223be8/
Log: Detect packed structs. Improve error messages and test them. diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c --- a/c/_cffi_backend.c +++ b/c/_cffi_backend.c @@ -141,6 +141,7 @@ #define CT_WITH_VAR_ARRAY 1048576 #define CT_IS_UNSIZED_CHAR_A 2097152 #define CT_LAZY_FIELD_LIST 4194304 +#define CT_WITH_PACKED_CHANGE 8388608 #define CT_PRIMITIVE_ANY (CT_PRIMITIVE_SIGNED | \ CT_PRIMITIVE_UNSIGNED | \ CT_PRIMITIVE_CHAR | \ @@ -4280,7 +4281,7 @@ CTypeDescrObject *ct; PyObject *fields, *interned_fields, *ignored; int is_union, alignment; - Py_ssize_t boffset, i, nb_fields, boffsetmax, alignedsize; + Py_ssize_t boffset, i, nb_fields, boffsetmax, alignedsize, boffsetorg; Py_ssize_t totalsize = -1; int totalalignment = -1; CFieldObject **previous; @@ -4308,7 +4309,7 @@ "first arg must be a non-initialized struct or union ctype"); return NULL; } - ct->ct_flags &= ~CT_CUSTOM_FIELD_POS; + ct->ct_flags &= ~(CT_CUSTOM_FIELD_POS | CT_WITH_PACKED_CHANGE); alignment = 1; boffset = 0; /* this number is in *bits*, not bytes! */ @@ -4325,7 +4326,7 @@ for (i=0; i<nb_fields; i++) { PyObject *fname; CTypeDescrObject *ftype; - int fbitsize = -1, falign, do_align; + int fbitsize = -1, falign, falignorg, do_align; Py_ssize_t foffset = -1; if (!PyArg_ParseTuple(PyList_GET_ITEM(fields, i), "O!O!|in:list item", @@ -4353,7 +4354,8 @@ /* update the total alignment requirement, but skip it if the field is an anonymous bitfield or if SF_PACKED */ - falign = (sflags & SF_PACKED) ? 1 : get_alignment(ftype); + falignorg = get_alignment(ftype); + falign = (sflags & SF_PACKED) ? 1 : falignorg; if (falign < 0) goto error; @@ -4383,7 +4385,11 @@ bs_flag = BS_REGULAR; /* align this field to its own 'falign' by inserting padding */ + boffsetorg = (boffset + falignorg*8-1) & ~(falignorg*8-1); /*bits!*/ boffset = (boffset + falign*8-1) & ~(falign*8-1); /* bits! */ + if (boffsetorg != boffset) { + ct->ct_flags |= CT_WITH_PACKED_CHANGE; + } if (foffset >= 0) { /* a forced field position: ignore the offset just computed, @@ -4398,6 +4404,7 @@ if (PyText_GetSize(fname) == 0 && ftype->ct_flags & (CT_STRUCT|CT_UNION)) { /* a nested anonymous struct or union */ + /* note: it seems we only get here with ffi.verify() */ CFieldObject *cfsrc = (CFieldObject *)ftype->ct_extra; for (; cfsrc != NULL; cfsrc = cfsrc->cf_next) { /* broken complexity in the call to get_field_name(), @@ -4684,7 +4691,8 @@ Another reason for CT_CUSTOM_FIELD_POS would be anonymous nested structures: we lost the information about having it here, so better safe (and forbid it) than sorry (and maybe - crash). + crash). Note: it seems we only get in this case with + ffi.verify(). */ if (force_lazy_struct(ct) < 0) return NULL; @@ -4692,9 +4700,16 @@ /* these NotImplementedErrors may be caught and ignored until a real call is made to a function of this type */ return fb_unsupported(ct, place, - "It can be a struct declared with \"...;\": then the C " - "calling convention may depend on the missing fields. " - "Or, it can be a struct with nested anonymous structs/unions"); + "It is a struct declared with \"...;\", but the C " + "calling convention may depend on the missing fields; " + "or, it contains anonymous struct/unions"); + } + /* Another reason: __attribute__((packed)) is not supported by libffi. + */ + if (ct->ct_flags & CT_WITH_PACKED_CHANGE) { + return fb_unsupported(ct, place, + "It is a 'packed' structure, with a different layout than " + "expected by libffi"); } n = PyDict_Size(ct->ct_stuff); @@ -4709,7 +4724,8 @@ assert(cf != NULL); if (cf->cf_bitshift >= 0) { return fb_unsupported(ct, place, - "It is a struct with bit fields"); + "It is a struct with bit fields, which libffi does not " + "support"); } flat = 1; ct1 = cf->cf_type; @@ -4719,7 +4735,8 @@ } if (flat <= 0) { return fb_unsupported(ct, place, - "It is a struct with a zero-length array"); + "It is a struct with a zero-length array, which libffi " + "does not support"); } nflat += flat; cf = cf->cf_next; @@ -4760,7 +4777,7 @@ } else if (ct->ct_flags & CT_UNION) { PyErr_Format(PyExc_NotImplementedError, - "ctype '%s' not supported as %s. " + "ctype '%s' not supported as %s by libffi. " "Unions" SUPPORTED_IN_API_MODE, ct->ct_name, place, place); return NULL; diff --git a/c/test_c.py b/c/test_c.py --- a/c/test_c.py +++ b/c/test_c.py @@ -1095,9 +1095,13 @@ BFunc = new_function_type((BStruct,), BDouble) # internally not callable dummy_func = cast(BFunc, 42) e = py.test.raises(NotImplementedError, dummy_func, "?") - msg = ("ctype \'struct foo\' not supported as argument (it is a struct " - 'declared with "...;", but the C calling convention may depend on ' - 'the missing fields)') + msg = ("ctype 'struct foo' not supported as argument. It is a struct " + 'declared with "...;", but the C calling convention may depend ' + "on the missing fields; or, it contains anonymous struct/unions. " + "Such structs are only supported as argument if the function is " + "'API mode' and non-variadic (i.e. declared inside ffibuilder." + "cdef()+ffibuilder.set_source() and not taking a final '...' " + "argument)") assert str(e.value) == msg def test_new_charp(): diff --git a/testing/cffi0/test_verify.py b/testing/cffi0/test_verify.py --- a/testing/cffi0/test_verify.py +++ b/testing/cffi0/test_verify.py @@ -1075,9 +1075,13 @@ int (*foo)(struct foo_s s) = &foo1; """) e = py.test.raises(NotImplementedError, lib.foo, "?") - msg = ("ctype 'struct foo_s' not supported as argument (it is a struct " - 'declared with "...;", but the C calling convention may depend ' - 'on the missing fields)') + msg = ("ctype 'struct foo_s' not supported as argument. It is a struct " + 'declared with "...;", but the C calling convention may depend on ' + "the missing fields; or, it contains anonymous struct/unions. " + "Such structs are only supported as argument " + "if the function is 'API mode' and non-variadic (i.e. declared " + "inside ffibuilder.cdef()+ffibuilder.set_source() and not taking " + "a final '...' argument)") assert str(e.value) == msg def test_func_returns_struct(): @@ -2146,14 +2150,23 @@ # assert did not crash so far e = py.test.raises(NotImplementedError, fooptr, ffi.new("Data *")) assert str(e.value) == ( - "ctype 'Data' (size 4) not supported as argument") + "ctype 'Data' not supported as argument by libffi. Unions are only " + "supported as argument if the function is 'API mode' and " + "non-variadic (i.e. declared inside ffibuilder.cdef()+" + "ffibuilder.set_source() and not taking a final '...' argument)") e = py.test.raises(NotImplementedError, bazptr) assert str(e.value) == ( - "ctype 'Data' (size 4) not supported as return value") + "ctype 'Data' not supported as return value by libffi. Unions are " + "only supported as return value if the function is 'API mode' and " + "non-variadic (i.e. declared inside ffibuilder.cdef()+" + "ffibuilder.set_source() and not taking a final '...' argument)") e = py.test.raises(NotImplementedError, barptr) assert str(e.value) == ( - "ctype 'MyStr' not supported as return value " - "(it is a struct with bit fields)") + "ctype 'MyStr' not supported as return value. It is a struct with " + "bit fields, which libffi does not support. Such structs are only " + "supported as return value if the function is 'API mode' and non-" + "variadic (i.e. declared inside ffibuilder.cdef()+ffibuilder." + "set_source() and not taking a final '...' argument)") def test_verify_extra_arguments(): ffi = FFI() diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py --- a/testing/cffi1/test_recompiler.py +++ b/testing/cffi1/test_recompiler.py @@ -2030,7 +2030,43 @@ """) assert lib.f().x == 200 e = py.test.raises(NotImplementedError, lib.g, 0) - print str(e.value) + assert str(e.value) == ( + 'ctype \'struct foo\' not supported as return value. It is a ' + 'struct declared with "...;", but the C calling convention may ' + 'depend on the missing fields; or, it contains anonymous ' + 'struct/unions. Such structs are only supported ' + 'as return value if the function is \'API mode\' and non-variadic ' + '(i.e. declared inside ffibuilder.cdef()+ffibuilder.set_source() ' + 'and not taking a final \'...\' argument)') + +def test_call_with_nested_anonymous_struct(): + if sys.platform == 'win32': + py.test.skip("needs a GCC extension") + ffi = FFI() + ffi.cdef(""" + struct foo { int a; union { int b, c; }; }; + struct foo f(void); + struct foo g(int, ...); + """) + lib = verify(ffi, "test_call_with_nested_anonymous_struct", """ + struct foo { int a; union { int b, c; }; }; + struct foo f(void) { + struct foo s = { 40 }; + s.b = 200; + return s; + } + struct foo g(int a, ...) { } + """) + assert lib.f().b == 200 + e = py.test.raises(NotImplementedError, lib.g, 0) + assert str(e.value) == ( + 'ctype \'struct foo\' not supported as return value. It is a ' + 'struct declared with "...;", but the C calling convention may ' + 'depend on the missing fields; or, it contains anonymous ' + 'struct/unions. Such structs are only supported ' + 'as return value if the function is \'API mode\' and non-variadic ' + '(i.e. declared inside ffibuilder.cdef()+ffibuilder.set_source() ' + 'and not taking a final \'...\' argument)') def test_call_with_bitfield(): ffi = FFI() @@ -2049,7 +2085,12 @@ """) assert lib.f().x == 11 e = py.test.raises(NotImplementedError, lib.g, 0) - print str(e.value) + assert str(e.value) == ( + "ctype 'struct foo' not supported as return value. It is a struct " + "with bit fields, which libffi does not support. Such structs are " + "only supported as return value if the function is 'API mode' and " + "non-variadic (i.e. declared inside ffibuilder.cdef()+ffibuilder." + "set_source() and not taking a final '...' argument)") def test_call_with_zero_length_field(): ffi = FFI() @@ -2068,7 +2109,12 @@ """) assert lib.f().a == 42 e = py.test.raises(NotImplementedError, lib.g, 0) - print str(e.value) + assert str(e.value) == ( + "ctype 'struct foo' not supported as return value. It is a " + "struct with a zero-length array, which libffi does not support." + " Such structs are only supported as return value if the function is " + "'API mode' and non-variadic (i.e. declared inside ffibuilder.cdef()" + "+ffibuilder.set_source() and not taking a final '...' argument)") def test_call_with_union(): ffi = FFI() @@ -2087,7 +2133,11 @@ """) assert lib.f().a == 42 e = py.test.raises(NotImplementedError, lib.g, 0) - print str(e.value) + assert str(e.value) == ( + "ctype 'union foo' not supported as return value by libffi. " + "Unions are only supported as return value if the function is " + "'API mode' and non-variadic (i.e. declared inside ffibuilder.cdef()" + "+ffibuilder.set_source() and not taking a final '...' argument)") def test_call_with_packed_struct(): if sys.platform == 'win32': @@ -2104,9 +2154,17 @@ struct foo s = { 40, 200 }; return s; } - struct foo g(int a, ...) { } + struct foo g(int a, ...) { + struct foo s = { 41, 201 }; + return s; + } """) assert lib.f().y == chr(40) assert lib.f().x == 200 e = py.test.raises(NotImplementedError, lib.g, 0) - print str(e.value) + assert str(e.value) == ( + "ctype 'struct foo' not supported as return value. It is a " + "'packed' structure, with a different layout than expected by libffi." + " Such structs are only supported as return value if the function is " + "'API mode' and non-variadic (i.e. declared inside ffibuilder.cdef()" + "+ffibuilder.set_source() and not taking a final '...' argument)") diff --git a/testing/cffi1/test_verify1.py b/testing/cffi1/test_verify1.py --- a/testing/cffi1/test_verify1.py +++ b/testing/cffi1/test_verify1.py @@ -1042,9 +1042,13 @@ int (*foo)(struct foo_s s) = &foo1; """) e = py.test.raises(NotImplementedError, lib.foo, "?") - msg = ("ctype 'struct foo_s' not supported as argument (it is a struct " - 'declared with "...;", but the C calling convention may depend ' - 'on the missing fields)') + msg = ("ctype 'struct foo_s' not supported as argument. It is a struct " + 'declared with "...;", but the C calling convention may depend on ' + "the missing fields; or, it contains anonymous struct/unions. " + "Such structs are only supported as argument " + "if the function is 'API mode' and non-variadic (i.e. declared " + "inside ffibuilder.cdef()+ffibuilder.set_source() and not taking " + "a final '...' argument)") assert str(e.value) == msg def test_func_returns_struct(): @@ -2114,14 +2118,23 @@ # assert did not crash so far e = py.test.raises(NotImplementedError, fooptr, ffi.new("Data *")) assert str(e.value) == ( - "ctype 'Data' (size 4) not supported as argument") + "ctype 'Data' not supported as argument by libffi. Unions are only " + "supported as argument if the function is 'API mode' and " + "non-variadic (i.e. declared inside ffibuilder.cdef()+" + "ffibuilder.set_source() and not taking a final '...' argument)") e = py.test.raises(NotImplementedError, bazptr) assert str(e.value) == ( - "ctype 'Data' (size 4) not supported as return value") + "ctype 'Data' not supported as return value by libffi. Unions are " + "only supported as return value if the function is 'API mode' and " + "non-variadic (i.e. declared inside ffibuilder.cdef()+" + "ffibuilder.set_source() and not taking a final '...' argument)") e = py.test.raises(NotImplementedError, barptr) assert str(e.value) == ( - "ctype 'MyStr' not supported as return value " - "(it is a struct with bit fields)") + "ctype 'MyStr' not supported as return value. It is a struct with " + "bit fields, which libffi does not support. Such structs are only " + "supported as return value if the function is 'API mode' and non-" + "variadic (i.e. declared inside ffibuilder.cdef()+ffibuilder." + "set_source() and not taking a final '...' argument)") def test_verify_extra_arguments(): ffi = FFI() _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit