diff options
author | Matt Davis <mrd@redhat.com> | 2022-02-01 12:43:18 -0800 |
---|---|---|
committer | Matt Davis <mrd@redhat.com> | 2022-02-01 12:43:18 -0800 |
commit | c5f8e99eb87818859f93ae7dab9ca3fe36378436 (patch) | |
tree | f728e95f6556feecf9154b8f339f134b0335347b | |
parent | dc406efc754c723991ec93f71cdf5d4d5d968f7b (diff) | |
parent | 6508642d5869998c72578e54dad3694dfe4cdb45 (diff) | |
download | cffi-c5f8e99eb87818859f93ae7dab9ca3fe36378436.tar.gz |
update from default
-rw-r--r-- | c/test_c.py | 47 | ||||
-rw-r--r-- | cffi/_embedding.h | 3 | ||||
-rw-r--r-- | cffi/recompiler.py | 5 | ||||
-rw-r--r-- | doc/source/overview.rst | 5 | ||||
-rw-r--r-- | doc/source/ref.rst | 25 | ||||
-rw-r--r-- | doc/source/using.rst | 12 | ||||
-rw-r--r-- | doc/source/whatsnew.rst | 6 | ||||
-rw-r--r-- | testing/embedding/empty-test.c | 11 | ||||
-rw-r--r-- | testing/embedding/empty.py | 9 | ||||
-rw-r--r-- | testing/embedding/test_basic.py | 3 |
10 files changed, 115 insertions, 11 deletions
diff --git a/c/test_c.py b/c/test_c.py index 654584d..906eb07 100644 --- a/c/test_c.py +++ b/c/test_c.py @@ -1331,9 +1331,11 @@ def test_callback_exception(): except ImportError: import io as cStringIO # Python 3 import linecache - def matches(istr, ipattern, ipattern38): + def matches(istr, ipattern, ipattern38, ipattern311): if sys.version_info >= (3, 8): ipattern = ipattern38 + if sys.version_info >= (3, 11): + ipattern = ipattern311 str, pattern = istr, ipattern while '$' in pattern: i = pattern.index('$') @@ -1387,6 +1389,16 @@ Traceback (most recent call last): File "$", line $, in check_value $ ValueError: 42 +""", """\ +Exception ignored from cffi callback <function$Zcb1 at 0x$>: +Traceback (most recent call last): + File "$", line $, in Zcb1 + $ + $ + File "$", line $, in check_value + $ + $ +ValueError: 42 """) sys.stderr = cStringIO.StringIO() bigvalue = 20000 @@ -1401,6 +1413,13 @@ Traceback (most recent call last): File "$", line $, in test_callback_exception $ OverflowError: integer 60000 does not fit 'short' +""", """\ +Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C: +Traceback (most recent call last): + File "$", line $, in test_callback_exception + $ + $ +OverflowError: integer 60000 does not fit 'short' """) sys.stderr = cStringIO.StringIO() bigvalue = 20000 @@ -1449,6 +1468,19 @@ Traceback (most recent call last): File "$", line $, in test_callback_exception $ TypeError: $integer$ +""", """\ +Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C: +Traceback (most recent call last): + File "$", line $, in test_callback_exception + $ + $ +OverflowError: integer 60000 does not fit 'short' +Exception ignored during handling of the above exception by 'onerror': +Traceback (most recent call last): + File "$", line $, in test_callback_exception + $ + $ +TypeError: $integer$ """) # sys.stderr = cStringIO.StringIO() @@ -1478,6 +1510,19 @@ Traceback (most recent call last): File "$", line $, in oops $ AttributeError: 'str' object has no attribute 'append$ +""", """\ +Exception ignored from cffi callback <function$Zcb1 at 0x$>, trying to convert the result back to C: +Traceback (most recent call last): + File "$", line $, in test_callback_exception + $ + $ +OverflowError: integer 60000 does not fit 'short' +Exception ignored during handling of the above exception by 'onerror': +Traceback (most recent call last): + File "$", line $, in oops + $ + $ +AttributeError: 'str' object has no attribute 'append$ """) finally: sys.stderr = orig_stderr diff --git a/cffi/_embedding.h b/cffi/_embedding.h index e863d85..4a42cbb 100644 --- a/cffi/_embedding.h +++ b/cffi/_embedding.h @@ -22,7 +22,8 @@ extern "C" { * _cffi_call_python_org, which on CPython is actually part of the _cffi_exports[] array, is the function pointer copied from - _cffi_backend. + _cffi_backend. If _cffi_start_python() fails, then this is set + to NULL; otherwise, it should never be NULL. After initialization is complete, both are equal. However, the first one remains equal to &_cffi_start_and_call_python until the diff --git a/cffi/recompiler.py b/cffi/recompiler.py index 86b37d7..16a7078 100644 --- a/cffi/recompiler.py +++ b/cffi/recompiler.py @@ -406,9 +406,8 @@ class Recompiler: else: prnt(' NULL, /* no includes */') prnt(' %d, /* num_types */' % (len(self.cffi_types),)) - flags = 0 - if self._num_externpy: - flags |= 1 # set to mean that we use extern "Python" + # set 'flags' to 1 in embedding mode, 0 otherwise + flags = int(self.ffi._embedding is not None) prnt(' %d, /* flags */' % flags) prnt('};') prnt() diff --git a/doc/source/overview.rst b/doc/source/overview.rst index dbc3540..3de8a3f 100644 --- a/doc/source/overview.rst +++ b/doc/source/overview.rst @@ -15,7 +15,12 @@ two sections delve deeper in the CFFI library. Make sure you have `cffi installed`__. +You can find these and some other complete demos in the demo__ directory +of the repository__. + .. __: installation.html +.. __: https://foss.heptapod.net/pypy/cffi/-/tree/branch/default/demo +.. __: https://foss.heptapod.net/pypy/cffi .. _out-of-line-api-level: .. _real-example: diff --git a/doc/source/ref.rst b/doc/source/ref.rst index 05c0f7c..946e48c 100644 --- a/doc/source/ref.rst +++ b/doc/source/ref.rst @@ -846,20 +846,35 @@ allowed. argument is identical to a ``item[]`` argument (and ``ffi.cdef()`` doesn't record the difference). So when you call such a function, you can pass an argument that is accepted by either C type, like - for example passing a Python string to a ``char *`` argument + for example passing a Python byte string to a ``char *`` argument (because it works for ``char[]`` arguments) or a list of integers to a ``int *`` argument (it works for ``int[]`` arguments). Note that even if you want to pass a single ``item``, you need to specify it in a list of length 1; for example, a ``struct point_s *`` argument might be passed as ``[[x, y]]`` or ``[{'x': 5, 'y': - 10}]``. + 10}]``. In all these cases (including passing a byte string to + a ``char *`` argument), the required C data structure is created + just before the call is done, and freed afterwards. As an optimization, CFFI assumes that a - function with a ``char *`` argument to which you pass a Python + function with a ``char *`` argument to which you pass a Python byte string will not actually modify the array of characters passed in, - and so passes directly a pointer inside the Python string object. + and so it attempts to pass directly a pointer inside the Python + byte string object. This still doesn't mean that the ``char *`` + argument can be stored by the C function and inspected later. + The ``char *`` is only valid for the duration of the call, even if + the Python object is kept alive for longer. (On PyPy, this optimization is only available since PyPy 5.4 - with CFFI 1.8.) + with CFFI 1.8. It may fail in rare cases and fall back to making + a copy anyway, but only for short strings so it shouldn't be + noticeable.) + + If you need to pass a ``char *`` that must be valid for longer than + just the call, you need to build it explicitly, either with ``p = + ffi.new("char[]", mystring)`` (which makes a copy) or by not using a + byte string in the first place but something else like a buffer object, + or a bytearray and ``ffi.from_buffer()``; or just use + ``ffi.new("char[]", length)`` directly if possible. `[2]` C function calls are done with the GIL released. diff --git a/doc/source/using.rst b/doc/source/using.rst index 38c96ba..ccaa4db 100644 --- a/doc/source/using.rst +++ b/doc/source/using.rst @@ -383,6 +383,18 @@ argument and may mutate it!): assert lib.strlen("hello") == 5 +(Note that there is no guarantee that the ``char *`` passed to the +function remains valid after the call is done. Similarly, if you write +``lib.f(x); lib.f(x)`` where ``x`` is a variable containing a byte string, +the two calls to ``f()`` could sometimes receive different ``char *`` +pointers, with each of them only valid during the corresponding call. This is +important notably for PyPy which uses many optimizations tweaking the data +underlying a byte string object. CFFI will not make and free a copy of +the whole string at *every* call---it usually won't---but you *cannot* +write code that relies on it: there are cases were that would break. +If you need a pointer to remain valid, you need to make one explicitly, +for example with ``ptr = ffi.new("char[]", x)``.) + You can also pass unicode strings as ``wchar_t *`` or ``char16_t *`` or ``char32_t *`` arguments. Note that the C language makes no difference between argument declarations that diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst index aa7f2fe..2f32935 100644 --- a/doc/source/whatsnew.rst +++ b/doc/source/whatsnew.rst @@ -2,6 +2,12 @@ What's New ====================== +v1.15.1 +======= + +* If you call `ffi.embedding_api()` but don't write any `extern "Python"` + function there, then the resulting C code would fail an assert. Fixed. + v1.15.0 ======= diff --git a/testing/embedding/empty-test.c b/testing/embedding/empty-test.c new file mode 100644 index 0000000..b00dd50 --- /dev/null +++ b/testing/embedding/empty-test.c @@ -0,0 +1,11 @@ +#include <stdio.h> + +void initialize_my_empty_cffi(void); + +int main(void) +{ + initialize_my_empty_cffi(); + printf("OK\n"); + return 0; +} + diff --git a/testing/embedding/empty.py b/testing/embedding/empty.py index aa8d830..1093505 100644 --- a/testing/embedding/empty.py +++ b/testing/embedding/empty.py @@ -4,7 +4,14 @@ ffi = cffi.FFI() ffi.embedding_api("") -ffi.set_source("_empty_cffi", "") +ffi.set_source("_empty_cffi", """ +void initialize_my_empty_cffi(void) { + if (cffi_start_python() != 0) { + printf("oops, cffi_start_python() returned non-0\\n"); + abort(); + } +} +""") fn = ffi.compile(verbose=True) print('FILENAME: %s' % (fn,)) diff --git a/testing/embedding/test_basic.py b/testing/embedding/test_basic.py index 8d2e776..b29afd2 100644 --- a/testing/embedding/test_basic.py +++ b/testing/embedding/test_basic.py @@ -180,6 +180,9 @@ if sys.platform == 'win32': class TestBasic(EmbeddingTests): def test_empty(self): empty_cffi = self.prepare_module('empty') + self.compile('empty-test', [empty_cffi]) + output = self.execute('empty-test') + assert output == 'OK\n' def test_basic(self): add1_cffi = self.prepare_module('add1') |