[pypy-commit] cffi default: Detect packed structs. Improve error messages and test them.
arigo
pypy.commits at gmail.com
Thu Dec 22 11:53:54 EST 2016
Author: Armin Rigo <arigo at 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()
More information about the pypy-commit
mailing list