summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Davis <mrd@redhat.com>2022-02-01 12:43:18 -0800
committerMatt Davis <mrd@redhat.com>2022-02-01 12:43:18 -0800
commitc5f8e99eb87818859f93ae7dab9ca3fe36378436 (patch)
treef728e95f6556feecf9154b8f339f134b0335347b
parentdc406efc754c723991ec93f71cdf5d4d5d968f7b (diff)
parent6508642d5869998c72578e54dad3694dfe4cdb45 (diff)
downloadcffi-c5f8e99eb87818859f93ae7dab9ca3fe36378436.tar.gz
update from default
-rw-r--r--c/test_c.py47
-rw-r--r--cffi/_embedding.h3
-rw-r--r--cffi/recompiler.py5
-rw-r--r--doc/source/overview.rst5
-rw-r--r--doc/source/ref.rst25
-rw-r--r--doc/source/using.rst12
-rw-r--r--doc/source/whatsnew.rst6
-rw-r--r--testing/embedding/empty-test.c11
-rw-r--r--testing/embedding/empty.py9
-rw-r--r--testing/embedding/test_basic.py3
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')